Showing preview only (1,793K chars total). Download the full file or copy to clipboard to get everything.
Repository: fgmacedo/python-statemachine
Branch: develop
Commit: ee3607a7b9b5
Files: 518
Total size: 1.6 MB
Directory structure:
gitextract_3onmcp2r/
├── .git-blame-ignore-revs
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ └── workflows/
│ ├── python-package.yml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── AGENTS.md
├── LICENSE
├── README.md
├── conftest.py
├── contributing.md
├── docs/
│ ├── _static/
│ │ └── custom_machine.css
│ ├── actions.md
│ ├── api.md
│ ├── async.md
│ ├── authors.md
│ ├── behaviour.md
│ ├── concepts.md
│ ├── conf.py
│ ├── contributing.md
│ ├── diagram.md
│ ├── error_handling.md
│ ├── events.md
│ ├── guards.md
│ ├── how-to/
│ │ ├── coming_from_state_pattern.md
│ │ └── coming_from_transitions.md
│ ├── index.md
│ ├── installation.md
│ ├── integrations.md
│ ├── invoke.md
│ ├── listeners.md
│ ├── models.md
│ ├── processing_model.md
│ ├── releases/
│ │ ├── 0.1.0.md
│ │ ├── 0.2.0.md
│ │ ├── 0.3.0.md
│ │ ├── 0.4.2.md
│ │ ├── 0.5.0.md
│ │ ├── 0.5.1.md
│ │ ├── 0.6.0.md
│ │ ├── 0.6.1.md
│ │ ├── 0.6.2.md
│ │ ├── 0.7.0.md
│ │ ├── 0.7.1.md
│ │ ├── 0.8.0.md
│ │ ├── 0.9.0.md
│ │ ├── 1.0.0.md
│ │ ├── 1.0.1.md
│ │ ├── 1.0.2.md
│ │ ├── 1.0.3.md
│ │ ├── 2.0.0.md
│ │ ├── 2.1.0.md
│ │ ├── 2.1.1.md
│ │ ├── 2.1.2.md
│ │ ├── 2.2.0.md
│ │ ├── 2.3.0.md
│ │ ├── 2.3.1.md
│ │ ├── 2.3.2.md
│ │ ├── 2.3.3.md
│ │ ├── 2.3.4.md
│ │ ├── 2.3.5.md
│ │ ├── 2.3.6.md
│ │ ├── 2.4.0.md
│ │ ├── 2.5.0.md
│ │ ├── 2.6.0.md
│ │ ├── 3.0.0.md
│ │ ├── 3.1.0.md
│ │ ├── index.md
│ │ └── upgrade_2x_to_3.md
│ ├── statechart.md
│ ├── states.md
│ ├── timeout.md
│ ├── transitions.md
│ ├── tutorial.md
│ ├── validations.md
│ └── weighted_transitions.md
├── pyproject.toml
├── statemachine/
│ ├── __init__.py
│ ├── callbacks.py
│ ├── configuration.py
│ ├── contrib/
│ │ ├── __init__.py
│ │ ├── diagram/
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── extract.py
│ │ │ ├── formatter.py
│ │ │ ├── model.py
│ │ │ ├── renderers/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── dot.py
│ │ │ │ ├── mermaid.py
│ │ │ │ └── table.py
│ │ │ └── sphinx_ext.py
│ │ ├── timeout.py
│ │ └── weighted.py
│ ├── dispatcher.py
│ ├── engines/
│ │ ├── __init__.py
│ │ ├── async_.py
│ │ ├── base.py
│ │ └── sync.py
│ ├── event.py
│ ├── event_data.py
│ ├── events.py
│ ├── exceptions.py
│ ├── factory.py
│ ├── graph.py
│ ├── i18n.py
│ ├── invoke.py
│ ├── io/
│ │ ├── __init__.py
│ │ └── scxml/
│ │ ├── __init__.py
│ │ ├── actions.py
│ │ ├── invoke.py
│ │ ├── parser.py
│ │ ├── processor.py
│ │ └── schema.py
│ ├── locale/
│ │ ├── en/
│ │ │ └── LC_MESSAGES/
│ │ │ └── statemachine.po
│ │ ├── hi_IN/
│ │ │ └── LC_MESSAGES/
│ │ │ └── statemachine.po
│ │ ├── pt_BR/
│ │ │ └── LC_MESSAGES/
│ │ │ └── statemachine.po
│ │ └── zh_CN/
│ │ └── LC_MESSAGES/
│ │ └── statemachine.po
│ ├── mixins.py
│ ├── model.py
│ ├── orderedset.py
│ ├── py.typed
│ ├── registry.py
│ ├── signature.py
│ ├── spec_parser.py
│ ├── state.py
│ ├── statemachine.py
│ ├── states.py
│ ├── transition.py
│ ├── transition_list.py
│ ├── transition_mixin.py
│ └── utils.py
└── tests/
├── __init__.py
├── conftest.py
├── django_project/
│ ├── app.py
│ ├── core/
│ │ ├── __init__,.py
│ │ ├── settings.py
│ │ └── wsgi.py
│ ├── manage.py
│ └── workflow/
│ ├── __init__.py
│ ├── apps.py
│ ├── models.py
│ ├── statemachines.py
│ └── tests.py
├── examples/
│ ├── README.rst
│ ├── __init__.py
│ ├── ai_shell_machine.py
│ ├── air_conditioner_machine.py
│ ├── all_actions_machine.py
│ ├── async_guess_the_number_machine.py
│ ├── async_without_loop_machine.py
│ ├── enum_campaign_machine.py
│ ├── guess_the_number_machine.py
│ ├── lor_machine.py
│ ├── order_control_machine.py
│ ├── order_control_rich_model_machine.py
│ ├── persistent_model_machine.py
│ ├── recursive_event_machine.py
│ ├── reusing_transitions_machine.py
│ ├── sqlite_persistent_model_machine.py
│ ├── statechart_cleanup_machine.py
│ ├── statechart_compound_machine.py
│ ├── statechart_delayed_machine.py
│ ├── statechart_error_handling_machine.py
│ ├── statechart_eventless_machine.py
│ ├── statechart_history_machine.py
│ ├── statechart_in_condition_machine.py
│ ├── statechart_parallel_machine.py
│ ├── traffic_light_machine.py
│ ├── user_machine.py
│ └── weighted_idle_machine.py
├── helpers.py
├── machines/
│ ├── __init__.py
│ ├── compound/
│ │ ├── __init__.py
│ │ ├── middle_earth_journey.py
│ │ ├── middle_earth_journey_two_compounds.py
│ │ ├── middle_earth_journey_with_finals.py
│ │ ├── moria_expedition.py
│ │ ├── moria_expedition_with_escape.py
│ │ ├── quest_for_erebor.py
│ │ └── shire_to_rivendell.py
│ ├── donedata/
│ │ ├── __init__.py
│ │ ├── destroy_the_ring.py
│ │ ├── destroy_the_ring_simple.py
│ │ ├── nested_quest_donedata.py
│ │ ├── quest_for_erebor_done_convention.py
│ │ ├── quest_for_erebor_explicit_id.py
│ │ ├── quest_for_erebor_multi_word.py
│ │ └── quest_for_erebor_with_event.py
│ ├── error/
│ │ ├── __init__.py
│ │ ├── error_convention_event.py
│ │ ├── error_convention_transition_list.py
│ │ ├── error_in_action_sc.py
│ │ ├── error_in_action_sm_with_flag.py
│ │ ├── error_in_after_sc.py
│ │ ├── error_in_error_handler_sc.py
│ │ ├── error_in_guard_sc.py
│ │ ├── error_in_guard_sm.py
│ │ └── error_in_on_enter_sc.py
│ ├── eventless/
│ │ ├── __init__.py
│ │ ├── auto_advance.py
│ │ ├── beacon_chain.py
│ │ ├── beacon_chain_lighting.py
│ │ ├── coordinated_advance.py
│ │ ├── ring_corruption.py
│ │ ├── ring_corruption_with_bear_ring.py
│ │ └── ring_corruption_with_tick.py
│ ├── history/
│ │ ├── __init__.py
│ │ ├── deep_memory_of_moria.py
│ │ ├── gollum_personality.py
│ │ ├── gollum_personality_default_gollum.py
│ │ ├── gollum_personality_with_default.py
│ │ └── shallow_moria.py
│ ├── in_condition/
│ │ ├── __init__.py
│ │ ├── combined_guard.py
│ │ ├── descendant_check.py
│ │ ├── eventless_in.py
│ │ ├── fellowship.py
│ │ ├── fellowship_coordination.py
│ │ └── gate_of_moria.py
│ ├── parallel/
│ │ ├── __init__.py
│ │ ├── session.py
│ │ ├── session_with_done_state.py
│ │ ├── two_towers.py
│ │ ├── war_of_the_ring.py
│ │ └── war_with_exit.py
│ ├── showcase_actions.py
│ ├── showcase_compound.py
│ ├── showcase_deep_history.py
│ ├── showcase_guards.py
│ ├── showcase_history.py
│ ├── showcase_internal.py
│ ├── showcase_parallel.py
│ ├── showcase_parallel_compound.py
│ ├── showcase_self_transition.py
│ ├── showcase_simple.py
│ ├── transition_from_any.py
│ ├── tutorial_coffee_order.py
│ ├── validators/
│ │ ├── __init__.py
│ │ ├── multi_validator.py
│ │ ├── order_validation.py
│ │ ├── order_validation_no_error_events.py
│ │ ├── validator_fallthrough.py
│ │ ├── validator_with_cond.py
│ │ └── validator_with_error_transition.py
│ └── workflow/
│ ├── __init__.py
│ ├── campaign_machine.py
│ ├── campaign_machine_with_validator.py
│ ├── campaign_machine_with_values.py
│ └── reverse_traffic_light.py
├── models.py
├── scrape_images.py
├── scxml/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_microwave.py
│ ├── test_scxml_cases.py
│ └── w3c/
│ ├── LICENSE
│ ├── mandatory/
│ │ ├── test144.scxml
│ │ ├── test145.scxml
│ │ ├── test147.scxml
│ │ ├── test148.scxml
│ │ ├── test149.scxml
│ │ ├── test150.scxml
│ │ ├── test151.scxml
│ │ ├── test152.scxml
│ │ ├── test153.scxml
│ │ ├── test155.scxml
│ │ ├── test156.scxml
│ │ ├── test158.scxml
│ │ ├── test159.scxml
│ │ ├── test172.scxml
│ │ ├── test173.scxml
│ │ ├── test174.scxml
│ │ ├── test175.scxml
│ │ ├── test176.scxml
│ │ ├── test179.scxml
│ │ ├── test183.scxml
│ │ ├── test185.scxml
│ │ ├── test186.scxml
│ │ ├── test187.scxml
│ │ ├── test189.scxml
│ │ ├── test190.scxml
│ │ ├── test191.scxml
│ │ ├── test192.scxml
│ │ ├── test194.scxml
│ │ ├── test198.scxml
│ │ ├── test199.scxml
│ │ ├── test200.scxml
│ │ ├── test205.scxml
│ │ ├── test207.scxml
│ │ ├── test208.scxml
│ │ ├── test210.scxml
│ │ ├── test215.scxml
│ │ ├── test216.scxml
│ │ ├── test216sub1.scxml
│ │ ├── test220.scxml
│ │ ├── test223.scxml
│ │ ├── test224.scxml
│ │ ├── test225.scxml
│ │ ├── test226.scxml
│ │ ├── test226sub1.scxml
│ │ ├── test228.scxml
│ │ ├── test229.scxml
│ │ ├── test232.scxml
│ │ ├── test233.scxml
│ │ ├── test234.scxml
│ │ ├── test235.scxml
│ │ ├── test236.scxml
│ │ ├── test237.scxml
│ │ ├── test239.scxml
│ │ ├── test239sub1.scxml
│ │ ├── test240.scxml
│ │ ├── test241.scxml
│ │ ├── test242.scxml
│ │ ├── test242sub1.scxml
│ │ ├── test243.scxml
│ │ ├── test244.scxml
│ │ ├── test245.scxml
│ │ ├── test247.scxml
│ │ ├── test252.scxml
│ │ ├── test253.scxml
│ │ ├── test276.scxml
│ │ ├── test276sub1.scxml
│ │ ├── test277.scxml
│ │ ├── test279.scxml
│ │ ├── test280.scxml
│ │ ├── test286.scxml
│ │ ├── test287.scxml
│ │ ├── test294.scxml
│ │ ├── test298.scxml
│ │ ├── test302.scxml
│ │ ├── test303.scxml
│ │ ├── test304.scxml
│ │ ├── test309.scxml
│ │ ├── test310.scxml
│ │ ├── test311.scxml
│ │ ├── test312.scxml
│ │ ├── test318.scxml
│ │ ├── test319.scxml
│ │ ├── test321.scxml
│ │ ├── test322.scxml
│ │ ├── test323.scxml
│ │ ├── test324.scxml
│ │ ├── test325.scxml
│ │ ├── test326.scxml
│ │ ├── test329.scxml
│ │ ├── test330.scxml
│ │ ├── test331.scxml
│ │ ├── test332.scxml
│ │ ├── test333.scxml
│ │ ├── test335.scxml
│ │ ├── test336.scxml
│ │ ├── test337.scxml
│ │ ├── test338.scxml
│ │ ├── test339.scxml
│ │ ├── test342.scxml
│ │ ├── test343.scxml
│ │ ├── test344.scxml
│ │ ├── test346.scxml
│ │ ├── test347.scxml
│ │ ├── test348.scxml
│ │ ├── test349.scxml
│ │ ├── test350.scxml
│ │ ├── test351.scxml
│ │ ├── test352.scxml
│ │ ├── test354.scxml
│ │ ├── test355.scxml
│ │ ├── test364.scxml
│ │ ├── test372.scxml
│ │ ├── test375.scxml
│ │ ├── test376.scxml
│ │ ├── test377.scxml
│ │ ├── test378.scxml
│ │ ├── test387.scxml
│ │ ├── test388.scxml
│ │ ├── test396.scxml
│ │ ├── test399.scxml
│ │ ├── test401.scxml
│ │ ├── test402.scxml
│ │ ├── test403a.scxml
│ │ ├── test403b.scxml
│ │ ├── test403c.scxml
│ │ ├── test404.scxml
│ │ ├── test405.scxml
│ │ ├── test406.scxml
│ │ ├── test407.scxml
│ │ ├── test409.scxml
│ │ ├── test411.scxml
│ │ ├── test412.scxml
│ │ ├── test413.scxml
│ │ ├── test416.scxml
│ │ ├── test417.scxml
│ │ ├── test419.scxml
│ │ ├── test421.scxml
│ │ ├── test422.scxml
│ │ ├── test423.scxml
│ │ ├── test487.scxml
│ │ ├── test488.scxml
│ │ ├── test495.scxml
│ │ ├── test496.scxml
│ │ ├── test500.scxml
│ │ ├── test501.scxml
│ │ ├── test503.scxml
│ │ ├── test504.scxml
│ │ ├── test505.scxml
│ │ ├── test506.scxml
│ │ ├── test521.scxml
│ │ ├── test525.scxml
│ │ ├── test527.scxml
│ │ ├── test528.scxml
│ │ ├── test529.scxml
│ │ ├── test530.scxml
│ │ ├── test533.scxml
│ │ ├── test550.scxml
│ │ ├── test551.scxml
│ │ ├── test552.scxml
│ │ ├── test552.txt
│ │ ├── test553.scxml
│ │ ├── test554.scxml
│ │ ├── test570.scxml
│ │ ├── test576.scxml
│ │ ├── test579.scxml
│ │ └── test580.scxml
│ └── optional/
│ ├── test193.scxml
│ ├── test201.scxml
│ ├── test278.scxml
│ ├── test444.scxml
│ ├── test445.scxml
│ ├── test446.scxml
│ ├── test446.txt
│ ├── test448.scxml
│ ├── test449.scxml
│ ├── test451.scxml
│ ├── test452.scxml
│ ├── test453.scxml
│ ├── test456.scxml
│ ├── test457.scxml
│ ├── test459.scxml
│ ├── test460.scxml
│ ├── test509.scxml
│ ├── test510.scxml
│ ├── test518.scxml
│ ├── test519.scxml
│ ├── test520.scxml
│ ├── test522.scxml
│ ├── test531.scxml
│ ├── test532.scxml
│ ├── test534.scxml
│ ├── test557.scxml
│ ├── test557.txt
│ ├── test558.scxml
│ ├── test558.txt
│ ├── test560.scxml
│ ├── test561.scxml
│ ├── test562.scxml
│ ├── test567.scxml
│ ├── test569.scxml
│ ├── test577.scxml
│ └── test578.scxml
├── test_actions.py
├── test_api_contract.py
├── test_async.py
├── test_async_futures.py
├── test_callbacks.py
├── test_callbacks_isolation.py
├── test_class_listeners.py
├── test_conditions_algebra.py
├── test_configuration.py
├── test_contrib_diagram.py
├── test_contrib_timeout.py
├── test_copy.py
├── test_dispatcher.py
├── test_error_execution.py
├── test_events.py
├── test_examples.py
├── test_fellowship_quest.py
├── test_invoke.py
├── test_io.py
├── test_listener.py
├── test_mermaid_renderer.py
├── test_mixins.py
├── test_mock_compatibility.py
├── test_multiple_destinations.py
├── test_profiling.py
├── test_registry.py
├── test_rtc.py
├── test_scxml_units.py
├── test_signature.py
├── test_signature_positional_only.py
├── test_spec_parser.py
├── test_state.py
├── test_state_callbacks.py
├── test_statechart_compound.py
├── test_statechart_delayed.py
├── test_statechart_donedata.py
├── test_statechart_error.py
├── test_statechart_eventless.py
├── test_statechart_history.py
├── test_statechart_in_condition.py
├── test_statechart_parallel.py
├── test_statemachine.py
├── test_statemachine_bounded_transitions.py
├── test_statemachine_compat.py
├── test_statemachine_inheritance.py
├── test_threading.py
├── test_transition_list.py
├── test_transition_table.py
├── test_transitions.py
├── test_validators.py
├── test_weighted_transitions.py
└── testcases/
├── __init__.py
├── issue308.md
├── issue384_multiple_observers.md
├── issue449.md
├── test_issue434.py
├── test_issue480.py
└── test_issue509.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .git-blame-ignore-revs
================================================
37fcf9818178587635fffe1bb67a9fd5024a0a45
345d82390af35d5d70ddd39c612faa4a64b11080
d7738e9ad0a3e50bc5c87d4a75c436fb771c96f6
5bf10afae2b214900aa58dd44b0a91e469c70631
================================================
FILE: .github/FUNDING.yml
================================================
github: fgmacedo
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
* Python State Machine version:
* Python version:
* Operating System:
### Description
Describe what you were trying to get done.
Tell us what happened, what went wrong, and what you expected to happen.
### What I Did
```
Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.
```
If you're reporting a bug, consider providing a complete example that can be used directly in the automated tests. We allways write tests to reproduce the issue in order to avoid future regressions.
================================================
FILE: .github/workflows/python-package.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python checks
on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- run: git fetch origin develop
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup Graphviz
uses: ts-graphviz/setup-graphviz@v2
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-suffix: "python${{ matrix.python-version }}"
- name: Install the project
run: uv sync --all-extras --dev
#----------------------------------------------
# run ruff
#----------------------------------------------
- name: Linter with ruff
if: matrix.python-version == 3.14
run: |
uv run ruff check .
uv run ruff format --check .
#----------------------------------------------
# run pytest
#----------------------------------------------
- name: Test with pytest
run: |
uv run pytest -n auto --cov --cov-report=xml:coverage.xml
uv run coverage xml
#----------------------------------------------
# upload coverage
#----------------------------------------------
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: matrix.python-version == 3.14
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
directory: .
env_vars: OS,PYTHON
fail_ci_if_error: true
flags: unittests
name: codecov-umbrella
verbose: true
================================================
FILE: .github/workflows/release.yml
================================================
on:
push:
tags: [ 'v?*.*.*' ]
name: release
jobs:
release-build:
name: Build release artifacts
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- run: git fetch origin develop
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.14'
- name: Setup Graphviz
uses: ts-graphviz/setup-graphviz@v2
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: Install the project
run: uv sync --all-extras --dev
- name: Test
run: |
uv run pytest -n auto --cov
- name: Build
run: |
uv build
- name: Upload dists
uses: actions/upload-artifact@v4
with:
name: release-dists
path: dist/
pypi-publish:
# by a dedicated job to publish we avoid the risk of
# running code with access to PyPI credentials
name: Upload release to PyPI
runs-on: ubuntu-latest
needs:
- release-build
environment: release
permissions:
id-token: write
steps:
- name: Retrieve release distributions
uses: actions/download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
.mypy_cache
# jupyter
.ipynb_checkpoints/
.jupyterlite.doit.db
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
prof/
.benchmarks/
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
docs/auto_examples/
# PyBuilder
target/
# pyenv python configuration file
.python-version
# IDEs and editors
*.sublime*
.idea/
.vscode/
# Sphinx-galery
docs/auto_examples/sg_execution_times.*
docs/auto_examples/*.pickle
docs/sg_execution_times.rst
# Temporary files
tmp/
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
exclude: docs/auto_examples
- id: trailing-whitespace
exclude: docs/auto_examples
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: v0.15.0
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format
- repo: local
hooks:
- id: mypy
name: Mypy
entry: uv run mypy --namespace-packages --explicit-package-bases statemachine/ tests/
types: [python]
language: system
pass_filenames: false
- id: pyright
name: Pyright
entry: uv run pyright statemachine/
types: [python]
language: system
pass_filenames: false
- id: generate-images
name: Generate README images
entry: >-
uv run python -m statemachine.contrib.diagram
tests.examples.traffic_light_machine.TrafficLightMachine
docs/images/readme_trafficlightmachine.png
--events cycle cycle cycle
language: system
pass_filenames: false
files: (statemachine/contrib/diagram/|tests/examples/traffic_light_machine\.py)
- id: pytest
name: Pytest
entry: uv run pytest -n auto --cov-fail-under=100
types: [python]
language: system
pass_filenames: false
================================================
FILE: .readthedocs.yaml
================================================
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
build:
os: "ubuntu-22.04"
tools:
python: "3.14"
apt_packages:
- graphviz
jobs:
post_create_environment:
- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --frozen
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
================================================
FILE: AGENTS.md
================================================
# python-statemachine
Python Finite State Machines made easy.
## Project overview
A library for building finite state machines in Python, with support for sync and async engines,
Django integration, diagram generation, and a flexible callback/listener system.
- **Source code:** `statemachine/`
- **Tests:** `tests/`
- **Documentation:** `docs/` (Sphinx + MyST Markdown, hosted on ReadTheDocs)
## Architecture
- `statemachine.py` — Core `StateMachine` and `StateChart` classes
- `factory.py` — `StateMachineMetaclass` handles class construction, state/transition validation
- `state.py` / `event.py` — Descriptor-based `State` and `Event` definitions
- `transition.py` / `transition_list.py` — Transition logic and composition (`|` operator)
- `callbacks.py` — Priority-based callback registry (`CallbackPriority`, `CallbackGroup`)
- `dispatcher.py` — Listener/observer pattern, `callable_method` wraps callables with signature adaptation
- `signature.py` — `SignatureAdapter` for dependency injection into callbacks
- `engines/base.py` — Shared engine logic (microstep, transition selection, error handling)
- `engines/sync.py`, `engines/async_.py` — Sync and async processing loops
- `registry.py` — Global state machine registry (used by `MachineMixin`)
- `mixins.py` — `MachineMixin` for domain model integration (e.g., Django models)
- `spec_parser.py` — Boolean expression parser for condition guards
- `contrib/diagram.py` — Diagram generation via pydot/Graphviz
## Processing model
The engine follows the SCXML run-to-completion (RTC) model with two processing levels:
- **Microstep**: atomic execution of one transition set (before → exit → on → enter → after).
- **Macrostep**: complete processing cycle for one external event; repeats microsteps until
the machine reaches a **stable configuration** (no eventless transitions enabled, internal
queue empty).
### Event queues
- `send()` → **external queue** (processed after current macrostep ends).
- `raise_()` → **internal queue** (processed within the current macrostep, before external events).
### Error handling (`catch_errors_as_events`)
- `StateChart` has `catch_errors_as_events=True` by default; `StateMachine` has `False`.
- Errors are caught at the **block level** (per onentry/onexit/transition `on` block), not per
microstep. This means `after` callbacks still run even when an action raises — making
`after_<event>()` a natural **finalize** hook (runs on both success and failure paths).
- `error.execution` is dispatched as an internal event; define transitions for it to handle
errors within the statechart.
- Error during `error.execution` handling → ignored to prevent infinite loops.
#### `on_error` asymmetry: transition `on` vs onentry/onexit
Transition `on` content uses `on_error` **only for non-`error.execution` events**. During
`error.execution` processing, `on_error` is disabled for transition `on` content — errors
propagate to `microstep()` where `_send_error_execution` ignores them. This prevents infinite
loops in self-transition error handlers (e.g., `error_execution = s1.to(s1, on="handler")`
where `handler` raises). `onentry`/`onexit` blocks always use `on_error` regardless of the
current event.
### Eventless transitions
- Bare transition statements (not assigned to a variable) are **eventless** — they fire
automatically when their guard condition is met.
- Assigned transitions (e.g., `go = s1.to(s2)`) create **named events**.
- `error_` prefix naming convention: `error_X` auto-registers both `error_X` and `error.X`
event names (explicit `id=` takes precedence).
### Callback conventions
- Generic callbacks (always available): `prepare_event()`, `before_transition()`,
`on_transition()`, `on_exit_state()`, `on_enter_state()`, `after_transition()`.
- Event-specific: `before_<event>()`, `on_<event>()`, `after_<event>()`.
- State-specific: `on_enter_<state>()`, `on_exit_<state>()`.
- `on_error_execution()` works via naming convention but **only** when a transition for
`error.execution` is declared — it is NOT a generic callback.
### Thread safety
- The sync engine is **thread-safe**: multiple threads can send events to the same SM instance
concurrently. The processing loop uses a `threading.Lock` so at most one thread executes
transitions at a time. Event queues use `PriorityQueue` (stdlib, thread-safe).
- **Do not replace `PriorityQueue`** with non-thread-safe alternatives (e.g., `collections.deque`,
plain `list`) — this would break concurrent access guarantees.
- Stress tests in `tests/test_threading.py::TestThreadSafety` exercise real contention with
barriers and multiple sender threads. Any change to queue or locking internals must pass these.
### Invoke (`<invoke>`)
- `invoke.py` — `InvokeManager` on the engine manages the lifecycle: `mark_for_invoke()`,
`cancel_for_state()`, `spawn_pending_sync/async()`, `send_to_child()`.
- `_cleanup_terminated()` only removes invocations that are both terminated **and** cancelled.
A terminated-but-not-cancelled invocation means the handler's `run()` returned but the owning
state is still active — it must stay in `_active` so `send_to_child()` can still route events.
- **Child machine constructor blocks** in the processing loop. Use a listener pattern (e.g.,
`_ChildRefSetter`) to capture the child reference during the first `on_enter_state`, before
the loop spins.
- `#_<invokeid>` send target: routed via `_send_to_invoke()` in `io/scxml/actions.py` →
`InvokeManager.send_to_child()` → handler's `on_event()`.
- **Tests with blocking threads**: use `threading.Event.wait(timeout=)` instead of
`time.sleep()` for interruptible waits — avoids thread leak errors in teardown.
## Environment setup
```bash
uv sync --all-extras --dev
pre-commit install
```
## Running tests
Always use `uv` to run commands. Also, use a timeout to avoid being stuck in the case of a leaked thread or infinite loop:
```bash
# Run all tests (parallel)
timeout 120 uv run pytest -n 4
# Run a specific test file
uv run pytest tests/test_signature.py
# Run a specific test
uv run pytest tests/test_signature.py::TestSignatureAdapter::test_wrap_fn_single_positional_parameter
# Skip slow tests
uv run pytest -m "not slow"
```
When trying to run all tests, prefer to use xdist (`-n`) as some SCXML tests uses timeout of 30s to verify fallback mechanism.
Don't specify the directory `tests/`, because this will exclude doctests from both source modules (`--doctest-modules`) and markdown docs
(`--doctest-glob=*.md`) (enabled by default):
```bash
timeout 120 uv run pytest -n 4
```
Testes normally run under 60s (~40s on average), so take a closer look if they take longer, it can be a regression.
### Debug logging
`log_cli_level` defaults to `WARNING` in `pyproject.toml`. The engine caches a no-op
for `logger.debug` at init time — running tests with `DEBUG` would bypass this
optimization and inflate benchmark numbers. To enable debug logs for a specific run:
```bash
uv run pytest -o log_cli_level=DEBUG tests/test_something.py
```
When analyzing warnings or extensive output, run the tests **once** saving the output to a file
(`> /tmp/pytest-output.txt 2>&1`), then analyze the file — instead of running the suite
repeatedly with different greps.
Coverage is enabled by default (`--cov` is in `pyproject.toml`'s `addopts`). To generate a
coverage report to a file, pass `--cov-report` **in addition to** `--cov`:
```bash
# JSON report (machine-readable, includes missing_lines per file)
timeout 120 uv run pytest -n auto --cov=statemachine --cov-report=json:cov.json
# Terminal report with missing lines
timeout 120 uv run pytest -n auto --cov=statemachine --cov-report=term-missing
```
Note: `--cov=statemachine` is required to activate coverage collection; `--cov-report`
alone only changes the output format.
### Testing both sync and async engines
Use the `sm_runner` fixture (from `tests/conftest.py`) when you need to test the same
statechart on both sync and async engines. It is parametrized with `["sync", "async"]`
and provides `start()` / `send()` helpers that handle engine selection automatically:
```python
async def test_something(self, sm_runner):
sm = await sm_runner.start(MyStateChart)
await sm_runner.send(sm, "some_event")
assert "expected_state" in sm.configuration_values
```
Do **not** manually add async no-op listeners or duplicate test classes — prefer `sm_runner`.
### TDD and coverage requirements
Follow a **test-driven development** approach: tests are not an afterthought — they are a
first-class requirement that must be part of every implementation plan.
- **Planning phase:** every plan must include test tasks as explicit steps, not a final
"add tests" bullet. Identify what needs to be tested (new branches, edge cases, error
paths) while designing the implementation.
- **100% branch coverage is mandatory.** The pre-commit hook enforces `--cov-fail-under=100`
with branch coverage enabled. Code that drops coverage will not pass CI.
- **Verify coverage before committing:** after writing tests, run coverage on the affected
modules and check for missing lines/branches:
```bash
timeout 120 uv run pytest tests/<test_file>.py --cov=statemachine.<module> --cov-report=term-missing --cov-branch
```
- **Use pytest fixtures** (`tmp_path`, `monkeypatch`, etc.) — never hardcode paths or
use mutable global state when a fixture exists.
- **Unreachable defensive branches** (e.g., `if` guards that can never be True given the
type system) may be marked with `pragma: no cover`, but prefer writing a test first.
## Linting and formatting
```bash
# Lint
uv run ruff check .
# Auto-fix lint issues
uv run ruff check --fix .
# Format
uv run ruff format .
# Type check
uv run mypy statemachine/ tests/
```
## Code style
- **Formatter/Linter:** ruff (line length 99, target Python 3.9)
- **Rules:** pycodestyle, pyflakes, isort, pyupgrade, flake8-comprehensions, flake8-bugbear, flake8-pytest-style
- **Imports:** single-line, sorted by isort. **Always prefer top-level imports** — only use
lazy (in-function) imports when strictly necessary to break circular dependencies
- **Docstrings:** Google convention
- **Naming:** PascalCase for classes, snake_case for functions/methods, UPPER_SNAKE_CASE for constants
- **Type hints:** used throughout; `TYPE_CHECKING` for circular imports
- Pre-commit hooks enforce ruff + mypy + pytest
## Design principles
- **Use GRASP/SOLID patterns to guide decisions.** When refactoring or designing, explicitly
apply patterns like Information Expert, Single Responsibility, and Law of Demeter to decide
where logic belongs — don't just pick a convenient location.
- **Information Expert (GRASP):** Place logic in the module/class that already has the
knowledge it needs. If a method computes a result, it should signal or return it rather
than forcing another method to recompute the same thing.
- **Law of Demeter:** Methods should depend only on the data they need, not on the
objects that contain it. Pass the specific value (e.g., a `Future`) rather than the
parent object (e.g., `TriggerData`) — this reduces coupling and removes the need for
null-checks on intermediate accessors.
- **Single Responsibility:** Each module, class, and function should have one clear reason
to change. Functions and types belong in the module that owns their domain (e.g.,
event-name helpers belong in `event.py`, not in `factory.py`).
- **Interface Segregation:** Depend on narrow interfaces. If a helper only needs one field
from a dataclass, accept that field directly.
- **Decouple infrastructure from domain:** Modules like `signature.py` and `dispatcher.py` are
general-purpose (signature adaptation, listener/observer pattern) and intentionally not coupled
to the state machine domain. Prefer this separation even for modules that are only used
internally — it keeps responsibilities clear and the code easier to reason about.
- **Favor small, focused modules:** When adding new functionality, consider whether it can live in
its own module with a well-defined boundary, rather than growing an existing one.
## Building documentation
```bash
# Build HTML docs
uv run sphinx-build docs docs/_build/html
# Live reload for development
uv run sphinx-autobuild docs docs/_build/html --re-ignore "auto_examples/.*"
```
### Documentation code examples
All code examples in `docs/*.md` **must** be testable doctests (using ```` ```py ```` with
`>>>` prompts), not plain ```` ```python ```` blocks. The test suite collects them via
`--doctest-glob=*.md`. If an example cannot be expressed as a doctest (e.g., it requires
real concurrency), write it as a unit test in `tests/` and reference it from the docs instead.
## Git workflow
- Main branch: `develop`
- PRs target `develop`
- Releases are tagged as `v*.*.*`
- Signed commits preferred (`git commit -s`)
- Use [Conventional Commits](https://www.conventionalcommits.org/) messages
(e.g., `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `perf:`)
## Security
- Do not commit secrets, credentials, or `.env` files
- Validate at system boundaries; trust internal code
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017, Fernando Macedo
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Python StateMachine
[](https://pypi.python.org/pypi/python-statemachine)
[](https://pepy.tech/project/python-statemachine)
[](https://pypi.python.org/pypi/python-statemachine)
[](https://codecov.io/gh/fgmacedo/python-statemachine)
[](https://python-statemachine.readthedocs.io/en/latest/?badge=latest)
[](https://github.com/fgmacedo/python-statemachine/compare/main...develop)
Expressive [statecharts](https://statecharts.dev/) and [FSMs](https://en.wikipedia.org/wiki/Finite-state_machine) for modern Python.
<div align="center">

</div>
Welcome to python-statemachine, an intuitive and powerful state machine library designed for a
great developer experience. Define flat state machines or full statecharts with compound states,
parallel regions, and history — all with a clean, _pythonic_, declarative API that works in both
sync and async Python codebases.
## Quick start
```py
>>> from statemachine import StateChart, State
>>> class TrafficLightMachine(StateChart):
... "A traffic light machine"
... green = State(initial=True)
... yellow = State()
... red = State()
...
... cycle = (
... green.to(yellow)
... | yellow.to(red)
... | red.to(green)
... )
...
... def before_cycle(self, event: str, source: State, target: State):
... return f"Running {event} from {source.id} to {target.id}"
...
... def on_enter_red(self):
... print("Don't move.")
...
... def on_exit_red(self):
... print("Go ahead!")
```
Create an instance and send events:
```py
>>> sm = TrafficLightMachine()
>>> sm.send("cycle")
'Running cycle from green to yellow'
>>> sm.send("cycle")
Don't move.
'Running cycle from yellow to red'
>>> sm.send("cycle")
Go ahead!
'Running cycle from red to green'
```
Check which states are active:
```py
>>> sm.configuration
OrderedSet([State('Green', id='green', value='green', initial=True, final=False, parallel=False)])
>>> sm.green.is_active
True
```
Generate a diagram or get a text representation with f-strings:
```py
>>> print(f"{sm:md}")
| State | Event | Guard | Target |
| ------ | ----- | ----- | ------ |
| Green | Cycle | | Yellow |
| Yellow | Cycle | | Red |
| Red | Cycle | | Green |
<BLANKLINE>
```
```python
sm._graph().write_png("traffic_light.png")
```

Parameters are injected into callbacks automatically — the library inspects the
signature and provides only the arguments each callback needs:
```py
>>> sm.send("cycle")
'Running cycle from green to yellow'
```
## Guards and conditional transitions
Use `cond=` and `unless=` to add guards. When multiple transitions share the same
event, declaration order determines priority:
```py
>>> from statemachine import StateChart, State
>>> class ApprovalWorkflow(StateChart):
... pending = State(initial=True)
... approved = State(final=True)
... rejected = State(final=True)
...
... review = (
... pending.to(approved, cond="is_valid")
... | pending.to(rejected)
... )
...
... def is_valid(self, score: int = 0):
... return score >= 70
>>> sm = ApprovalWorkflow()
>>> sm.send("review", score=50)
>>> sm.rejected.is_active
True
>>> sm = ApprovalWorkflow()
>>> sm.send("review", score=85)
>>> sm.approved.is_active
True
```
The first transition whose guard passes wins. When `score < 70`, `is_valid` returns
`False` so the second transition (no guard — always matches) fires instead.
## Compound states — hierarchy
Break complex behavior into hierarchical levels with `State.Compound`. Entering a
compound activates both the parent and its `initial` child. Exiting removes the
parent and all descendants:
```py
>>> from statemachine import StateChart, State
>>> class DocumentWorkflow(StateChart):
... class editing(State.Compound):
... draft = State(initial=True)
... review = State()
... submit = draft.to(review)
... revise = review.to(draft)
...
... published = State(final=True)
... approve = editing.to(published)
>>> sm = DocumentWorkflow()
>>> set(sm.configuration_values) == {"editing", "draft"}
True
>>> sm.send("submit")
>>> "review" in sm.configuration_values
True
>>> sm.send("approve")
>>> set(sm.configuration_values) == {"published"}
True
```
## Parallel states — concurrency
`State.Parallel` activates all child regions simultaneously. Events in one
region don't affect others. A `done.state` event fires only when **all**
regions reach a final state:
```py
>>> from statemachine import StateChart, State
>>> class DeployPipeline(StateChart):
... class deploy(State.Parallel):
... class build(State.Compound):
... compiling = State(initial=True)
... compiled = State(final=True)
... finish_build = compiling.to(compiled)
... class tests(State.Compound):
... running = State(initial=True)
... passed = State(final=True)
... finish_tests = running.to(passed)
... released = State(final=True)
... done_state_deploy = deploy.to(released)
>>> sm = DeployPipeline()
>>> "compiling" in sm.configuration_values and "running" in sm.configuration_values
True
>>> sm.send("finish_build")
>>> "compiled" in sm.configuration_values and "running" in sm.configuration_values
True
>>> sm.send("finish_tests")
>>> set(sm.configuration_values) == {"released"}
True
```
## History states
`HistoryState()` records which child was active when a compound is exited.
Re-entering via the history pseudo-state restores the previous child instead
of starting from the initial one:
```py
>>> from statemachine import HistoryState, StateChart, State
>>> class EditorWithHistory(StateChart):
... class editor(State.Compound):
... source = State(initial=True)
... visual = State()
... h = HistoryState()
... toggle = source.to(visual) | visual.to(source)
... settings = State()
... open_settings = editor.to(settings)
... back = settings.to(editor.h)
>>> sm = EditorWithHistory()
>>> sm.send("toggle")
>>> "visual" in sm.configuration_values
True
>>> sm.send("open_settings")
>>> sm.send("back")
>>> "visual" in sm.configuration_values
True
```
Use `HistoryState(type="deep")` for deep history that remembers the exact leaf
state across nested compounds.
## Eventless transitions
Transitions without an event trigger fire automatically. With a guard, they
fire after any event processing when the condition is met:
```py
>>> from statemachine import StateChart, State
>>> class AutoCounter(StateChart):
... counting = State(initial=True)
... done = State(final=True)
...
... counting.to(done, cond="limit_reached")
... increment = counting.to.itself(internal=True, on="do_increment")
...
... count = 0
...
... def do_increment(self):
... self.count += 1
... def limit_reached(self):
... return self.count >= 3
>>> sm = AutoCounter()
>>> sm.send("increment")
>>> sm.send("increment")
>>> "counting" in sm.configuration_values
True
>>> sm.send("increment")
>>> "done" in sm.configuration_values
True
```
## Error handling
When using `StateChart`, runtime exceptions in callbacks are caught and
turned into `error.execution` events. Define a transition for that event
to handle errors within the state machine itself:
```py
>>> from statemachine import StateChart, State
>>> class ResilientService(StateChart):
... running = State(initial=True)
... failed = State(final=True)
...
... process = running.to(running, on="do_work")
... error_execution = running.to(failed)
...
... def do_work(self):
... raise RuntimeError("something broke")
>>> sm = ResilientService()
>>> sm.send("process")
>>> sm.failed.is_active
True
```
## Async support
Async callbacks just work — same API, no changes needed. The engine
detects async callbacks and switches to the async engine automatically:
```py
>>> import asyncio
>>> from statemachine import StateChart, State
>>> class AsyncWorkflow(StateChart):
... idle = State(initial=True)
... done = State(final=True)
...
... finish = idle.to(done)
...
... async def on_finish(self):
... return 42
>>> async def run():
... sm = AsyncWorkflow()
... result = await sm.finish()
... print(f"Result: {result}")
... print(sm.done.is_active)
>>> asyncio.run(run())
Result: 42
True
```
## More features
There's a lot more to explore:
- **DoneData** on final states — pass structured data to `done.state` handlers
- **Delayed events** — schedule events with `sm.send("event", delay=500)`
- **`In(state)` conditions** — cross-region guards in parallel states
- **`prepare_event`** callback — inject custom data into all callbacks
- **Observer pattern** — register external listeners to watch events and state changes
- **Django integration** — auto-discover state machines in Django apps with `MachineMixin`
- **Diagram generation** — via f-strings (`f"{sm:mermaid}"`), CLI, Sphinx directive, or Jupyter
- **Dictionary-based definitions** — create state machines from data structures
- **Internationalization** — error messages in multiple languages
Full documentation: https://python-statemachine.readthedocs.io
## Installing
```
pip install python-statemachine
```
To generate diagrams, install with the `diagrams` extra (requires
[Graphviz](https://graphviz.org/)):
```
pip install python-statemachine[diagrams]
```
## Contributing
- If you found this project helpful, please consider giving it a star on GitHub.
- **Contribute code**: If you would like to contribute code, please submit a pull
request. For more information on how to contribute, please see our [contributing.md](contributing.md) file.
- **Report bugs**: If you find any bugs, please report them by opening an issue
on our GitHub issue tracker.
- **Suggest features**: If you have an idea for a new feature, or feel something is harder than it should be,
please let us know by opening an issue on our GitHub issue tracker.
- **Documentation**: Help improve documentation by submitting pull requests.
- **Promote the project**: Help spread the word by sharing on social media,
writing a blog post, or giving a talk about it. Tag me on Twitter
[@fgmacedo](https://twitter.com/fgmacedo) so I can share it too!
================================================
FILE: conftest.py
================================================
import shutil
import sys
import pytest
@pytest.fixture(autouse=True, scope="session")
def add_doctest_context(doctest_namespace): # noqa: PT004
from statemachine.utils import run_async_from_sync
from statemachine import State
from statemachine import StateChart
from statemachine import StateMachine
class ContribAsyncio:
"""
Using `run_async_from_sync` to be injected in the doctests to better integration with an
already running loop, as all of our examples are also automated executed as doctests.
On real life code you should use standard `import asyncio; asyncio.run(main())`.
"""
def __init__(self):
self.run = run_async_from_sync
doctest_namespace["State"] = State
doctest_namespace["StateChart"] = StateChart
doctest_namespace["StateMachine"] = StateMachine
doctest_namespace["asyncio"] = ContribAsyncio()
def pytest_ignore_collect(collection_path, config):
if sys.version_info >= (3, 10): # noqa: UP036
return None
if "django_project" in str(collection_path):
return True
@pytest.fixture(scope="session")
def has_dot_installed():
return bool(shutil.which("dot"))
@pytest.fixture()
def requires_dot_installed(request, has_dot_installed):
if not has_dot_installed:
pytest.skip(f"Test {request.node.nodeid} requires 'dot' that is not installed.")
================================================
FILE: contributing.md
================================================
Please see [docs/contributing.md](docs/contributing).
================================================
FILE: docs/_static/custom_machine.css
================================================
/* div.sphx-glr-download {
height: 0px;
visibility: hidden;
} */
@media only screen and (min-width: 650px) {
.sphx-glr-thumbnails {
grid-template-columns: repeat(auto-fill, minmax(600px, 1fr)) !important;
}
.sphx-glr-thumbcontainer {
min-height: 320px !important;
margin: 20px !important;
justify-content: center;
}
.sphx-glr-thumbcontainer .figure {
width: 600px !important;
}
.sphx-glr-thumbcontainer img {
max-height: 250px !important;
max-width: 600px !important;
width: 100% !important;
}
.sphx-glr-thumbcontainer a.internal {
padding: 20px 10px 0 !important;
}
}
/* Gallery Donwload buttons */
div.sphx-glr-download a {
color: #404040 !important;
background-color: #f3f6f6 !important;
background-image: none;
border-radius: 4px;
border: none;
display: inline-block;
font-weight: bold;
padding: 1ex;
text-align: center;
}
div.sphx-glr-download code.download {
display: inline-block;
white-space: normal;
word-break: normal;
overflow-wrap: break-word;
/* border and background are given by the enclosing 'a' */
border: none;
background: none;
}
div.sphx-glr-download a:hover {
box-shadow: none;
text-decoration: none;
background-image: none;
background-color: #e5ebeb !important;
}
================================================
FILE: docs/actions.md
================================================
(actions)=
# Actions
```{seealso}
New to statecharts? See [](concepts.md) for an overview of how states,
transitions, events, and actions fit together.
```
An **action** is a side-effect that runs during a state change — sending
notifications, updating a database, logging, or returning a value. Actions are
the main reason statecharts exist: they ensure the right code runs at the right
time, depending on the sequence of events and the current state.
## Execution order
A single {ref}`microstep <macrostep-microstep>` executes callbacks in a fixed
sequence of **groups**. Each group runs to completion before the next one starts:
```{list-table}
:header-rows: 1
* - Group
- Callbacks
- `state` is
- Description
* - Prepare
- `prepare_event()`
- `source`
- Enrich event kwargs before anything else runs. See {ref}`preparing-events`.
* - Validators
- `validators`
- `source`
- Raise an exception to block the transition.
* - Conditions
- `cond`, `unless`
- `source`
- Return a boolean to allow or prevent the transition.
* - Before
- `before_transition()`, `before_<event>()`
- `source`
- Runs before any state changes.
* - Exit
- `on_exit_state()`, `on_exit_<state>()`
- exiting state
- Runs once per state being exited, from child to ancestor.
* - On
- `on_transition()`, `on_<event>()`
- `source`
- Transition content — the main action.
* - Enter
- `on_enter_state()`, `on_enter_<state>()`
- entering state
- Runs once per state being entered, from ancestor to child.
* - Invoke
- `on_invoke_state()`, `on_invoke_<state>()`
- `target`
- Spawns background work. See {ref}`invoke`.
* - After
- `after_transition()`, `after_<event>()`
- `target`
- Runs after all state changes are complete.
```
The `state` column shows what the `state` parameter resolves to when
{ref}`injected <dependency-injection>` into that callback. The `source` and
`target` parameters are always available regardless of group.
```{tip}
`after` callbacks run even when an earlier group raises and
`catch_errors_as_events` is enabled — making them a natural **finalize** hook.
See {ref}`error-handling-cleanup-finalize` for the full pattern.
```
```{seealso}
See {ref}`validators and guards` for the `validators`, `cond`, and `unless`
groups. The rest of this page focuses on actions.
```
### Priority within a group
Each group can contain multiple callbacks. Within the same group, callbacks
execute in **priority order**:
1. **Generic** — built-in callbacks like `on_enter_state()` or `before_transition()`.
2. **Inline** — callbacks passed as constructor parameters (e.g., `on="do_work"`).
3. **Decorator** — callbacks added via decorators (e.g., `@state.enter`).
4. **Naming convention** — callbacks discovered by name (e.g., `on_enter_idle()`).
```{seealso}
See the example {ref}`sphx_glr_auto_examples_all_actions_machine.py` for a
complete demonstration of callback resolution order.
```
### Exit and enter in compound states
In a flat state machine, exit and enter each run exactly once — for the
single source and the single target. With {ref}`compound <compound-states>`
and {ref}`parallel <parallel-states>` states, a transition may cross
multiple levels of the hierarchy, and the engine exits and enters **each
level individually**, following the
[SCXML](https://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation)
specification:
- **Exit** runs from the **innermost** (deepest child) state up to the
ancestor being left — children exit before their parents.
- **Enter** runs from the **outermost** (highest ancestor) state down to
the target leaf — parents enter before their children.
```py
>>> from statemachine import State, StateChart
>>> class HierarchicalExample(StateChart):
... class parent_a(State.Compound):
... child_a = State(initial=True)
... class parent_b(State.Compound):
... child_b = State(initial=True, final=True)
... cross = parent_a.to(parent_b)
...
... def on_exit_child_a(self):
... print(" exit child_a")
... def on_exit_parent_a(self):
... print(" exit parent_a")
... def on_enter_parent_b(self):
... print(" enter parent_b")
... def on_enter_child_b(self):
... print(" enter child_b")
>>> sm = HierarchicalExample()
>>> sm.send("cross")
exit child_a
exit parent_a
enter parent_b
enter child_b
```
This means that **exit and enter callbacks fire multiple times per
microstep** — once for each state in the exit/entry set. Use state-specific
callbacks (`on_exit_<state>`, `on_enter_<state>`) to target individual
levels of the hierarchy.
```{note}
The generic `on_exit_state()` and `on_enter_state()` callbacks also fire
once per state in the set, but the `state` parameter is bound to the
transition's `source` or `target` — not the individual state being
exited/entered. Use `event_data` if you need the full context, or prefer
state-specific callbacks for clarity.
```
```{seealso}
See {ref}`macrostep-microstep` for how microsteps compose into macrosteps,
and {ref}`compound-states` for how state hierarchies work.
```
(dependency-injection)=
(dynamic-dispatch)=
(dynamic dispatch)=
## Dependency injection
All callbacks (actions, conditions, validators) support automatic dependency
injection. The library inspects your method signature and passes only the
parameters you declare — you never need to accept arguments you don't use.
```py
>>> class FlexibleSC(StateChart):
... idle = State(initial=True)
... done = State(final=True)
...
... go = idle.to(done)
...
... def on_go(self):
... """No params needed? That's fine."""
... return "minimal"
...
... def after_go(self, event, source, target):
... """Need context? Just declare what you want."""
... print(f"{event}: {source.id} → {target.id}")
>>> sm = FlexibleSC()
>>> sm.send("go")
go: idle → done
'minimal'
```
### Available parameters
These parameters are available for injection into any callback:
| Parameter | Type | Description |
|---|---|---|
| `event_data` | {class}`~statemachine.event_data.EventData` | The full event data object for this microstep. |
| `event` | {class}`~statemachine.event.Event` | The event that triggered the transition. |
| `source` | {class}`~statemachine.state.State` | The state the machine was in when the event started. |
| `target` | {class}`~statemachine.state.State` | The destination state of the transition. |
| `state` | {class}`~statemachine.state.State` | The *current* state — equals `source` for before/exit/on, `target` for enter/after. |
| `error` | `Exception` | The exception object. Only available in callbacks triggered by `error.execution` events. See {ref}`error-execution`. |
| `model` | {class}`~statemachine.model.Model` | The underlying model instance (see {ref}`models`). |
| `machine` | {class}`~statemachine.statemachine.StateChart` | The state machine instance itself. |
| `transition` | {class}`~statemachine.transition.Transition` | The transition being executed. |
The following parameters are available **only in `on` callbacks** (transition
content):
| Parameter | Type | Description |
|---|---|---|
| `previous_configuration` | `OrderedSet[`{class}`~statemachine.state.State``]` | States that were active before the microstep. |
| `new_configuration` | `OrderedSet[`{class}`~statemachine.state.State``]` | States that will be active after the microstep. |
#### Configuration during `on` callbacks
By the time the `on` group runs, exit callbacks have already fired and the
exiting states may have been removed from `sm.configuration`, but the entering
states have not been added yet. This means that reading `sm.configuration`
inside an `on` callback returns a **transitional** snapshot — neither the old
nor the new configuration.
Use `previous_configuration` and `new_configuration` instead to reliably
inspect which states were active before and which will be active after:
```py
>>> from statemachine import State, StateChart
>>> class InspectConfig(StateChart):
... a = State(initial=True)
... b = State(final=True)
...
... go = a.to(b)
...
... def on_go(self, previous_configuration, new_configuration):
... current = {s.id for s in self.configuration}
... prev = {s.id for s in previous_configuration}
... new = {s.id for s in new_configuration}
... print(f"previous: {sorted(prev)}")
... print(f"configuration: {sorted(current)}")
... print(f"new: {sorted(new)}")
>>> sm = InspectConfig()
>>> sm.send("go")
previous: ['a']
configuration: []
new: ['b']
```
Notice that `sm.configuration` is **empty** during the `on` callback — state
`a` has already exited, but state `b` has not entered yet.
```{tip}
If you need the old 2.x behavior where `sm.configuration` updates atomically
(all exits and entries applied at once after the `on` group), set
`atomic_configuration_update = True` on your class. See the
[behaviour reference](behaviour.md) for details.
```
In addition, any positional or keyword arguments you pass when triggering the
event are forwarded to all callbacks:
```py
>>> class Greeter(StateChart):
... idle = State(initial=True)
...
... greet = idle.to.itself()
...
... def on_greet(self, name, greeting="Hello"):
... return f"{greeting}, {name}!"
>>> sm = Greeter()
>>> sm.send("greet", "Alice")
'Hello, Alice!'
>>> sm.send("greet", "Bob", greeting="Hi")
'Hi, Bob!'
```
```{seealso}
All actions and {ref}`conditions <validators and guards>` support the same
dependency injection mechanism. See {ref}`validators and guards` for how it
applies to guards.
```
## Binding actions
There are three ways to attach an action to a state or transition: **naming
conventions**, **inline parameters**, and **decorators**. All three can be
combined — the priority rules above determine execution order.
(state-actions)=
### State actions
States support `enter` and `exit` callbacks.
**Naming convention** — define a method matching `on_enter_<state_id>()` or
`on_exit_<state_id>()`:
```py
>>> from statemachine import StateChart, State
>>> class LoginFlow(StateChart):
... idle = State(initial=True)
... logged_in = State(final=True)
...
... login = idle.to(logged_in)
...
... def on_enter_logged_in(self):
... print("session started")
>>> sm = LoginFlow()
>>> sm.send("login")
session started
```
**Inline parameter** — pass callback names to the `State` constructor:
```py
>>> class LoginFlow(StateChart):
... idle = State(initial=True)
... logged_in = State(final=True, enter="start_session")
...
... login = idle.to(logged_in)
...
... def start_session(self):
... print("session started")
>>> sm = LoginFlow()
>>> sm.send("login")
session started
```
**Decorator** — use `@state.enter` or `@state.exit`:
```py
>>> class LoginFlow(StateChart):
... idle = State(initial=True)
... logged_in = State(final=True)
...
... login = idle.to(logged_in)
...
... @logged_in.enter
... def start_session(self):
... print("session started")
>>> sm = LoginFlow()
>>> sm.send("login")
session started
```
States also support `invoke` callbacks — background work that is spawned when
the state is entered and automatically cancelled when the state is exited.
Invoke supports the same three binding patterns (naming convention, inline,
decorator) and has its own completion and cancellation lifecycle.
```{seealso}
See {ref}`invoke` for the full invoke reference: execution model, binding
patterns, `done.invoke` transitions, cancellation, error handling, grouped
invokes, the `IInvoke` protocol, and child state machines.
```
(transition-actions)=
### Transition actions
Transitions support `before`, `on`, and `after` callbacks.
**Naming convention** — define a method matching `before_<event>()`,
`on_<event>()`, or `after_<event>()`. The callback is called for every
transition triggered by that event:
```py
>>> from statemachine import StateChart, State
>>> class Turnstile(StateChart):
... locked = State(initial=True)
... unlocked = State()
...
... coin = locked.to(unlocked)
... push = unlocked.to(locked)
...
... def on_coin(self):
... return "accepted"
...
... def after_push(self):
... print("gate closed")
>>> sm = Turnstile()
>>> sm.send("coin")
'accepted'
>>> sm.send("push")
gate closed
```
**Inline parameter** — pass callback names to the transition constructor:
```py
>>> class Turnstile(StateChart):
... locked = State(initial=True)
... unlocked = State()
...
... coin = locked.to(unlocked, on="accept_coin")
... push = unlocked.to(locked, after="close_gate")
...
... def accept_coin(self):
... return "accepted"
...
... def close_gate(self):
... print("gate closed")
>>> sm = Turnstile()
>>> sm.send("coin")
'accepted'
>>> sm.send("push")
gate closed
```
**Decorator** — use `@event.before`, `@event.on`, or `@event.after`:
```py
>>> class Turnstile(StateChart):
... locked = State(initial=True)
... unlocked = State()
...
... coin = locked.to(unlocked)
... push = unlocked.to(locked)
...
... @coin.on
... def accept_coin(self):
... return "accepted"
...
... @push.after
... def close_gate(self):
... print("gate closed")
>>> sm = Turnstile()
>>> sm.send("coin")
'accepted'
>>> sm.send("push")
gate closed
```
#### Declaring an event with an inline action
You can declare an event and its `on` action in a single expression by using the
transition as a decorator:
```py
>>> class Turnstile(StateChart):
... locked = State(initial=True)
... unlocked = State()
...
... push = unlocked.to(locked)
...
... @locked.to(unlocked)
... def coin(self):
... return "accepted"
>>> sm = Turnstile()
>>> sm.send("coin")
'accepted'
```
The resulting `coin` attribute is an {ref}`Event <events>`, not a plain method —
it only executes when the machine is in a state where a matching transition
exists.
## Generic callbacks
Generic callbacks run on **every** transition, regardless of which event or
state is involved. They follow the same group ordering and are useful for
cross-cutting concerns like logging or auditing:
```py
>>> class Audited(StateChart):
... idle = State(initial=True)
... active = State(final=True)
...
... start = idle.to(active)
...
... def before_transition(self, event, source):
... print(f"about to transition from {source.id} on {event}")
...
... def on_enter_state(self, target, event):
... print(f"entered {target.id} on {event}")
...
... def after_transition(self, event, source, target):
... print(f"completed {source.id} → {target.id} on {event}")
>>> sm = Audited()
entered idle on __initial__
>>> sm.send("start")
about to transition from idle on start
entered active on start
completed idle → active on start
```
The full list of generic callbacks:
| Callback | Group | Description |
|---|---|---|
| `before_transition()` | Before | Runs before any state change. |
| `on_exit_state()` | Exit | Runs when leaving any state. |
| `on_transition()` | On | Runs during any transition. |
| `on_enter_state()` | Enter | Runs when entering any state. |
| `on_invoke_state()` | Invoke | Runs when spawning invoke handlers for any state. See {ref}`invoke`. |
| `after_transition()` | After | Runs after all state changes. |
```{note}
`prepare_event()` is also a generic callback, but it serves a special purpose —
see {ref}`preparing-events` below.
```
```{tip}
Generic callbacks are the building blocks for {ref}`listeners <listeners>` — an
external object that implements the same callback signatures can observe every
transition without modifying the state machine class.
```
(preparing-events)=
## Preparing events
The `prepare_event` callback runs **before validators and conditions** and has a
unique capability: its return value (a `dict`) is merged into the keyword
arguments available to all subsequent callbacks in the same microstep.
This is useful for enriching events with computed context — for example, looking
up a user record from an ID before the transition runs:
```py
>>> class OrderFlow(StateChart):
... pending = State(initial=True)
... confirmed = State(final=True)
...
... confirm = pending.to(confirmed)
...
... def prepare_event(self, order_id=None):
... if order_id is not None:
... return {"order_total": order_id * 10}
... return {}
...
... def on_confirm(self, order_total=0):
... return f"confirmed ${order_total}"
>>> sm = OrderFlow()
>>> sm.send("confirm", order_id=5)
'confirmed $50'
```
## Return values
The return values from `before` and `on` callbacks are collected into a list
and returned to the caller. Other groups (`exit`, `enter`, `after`) do not
contribute to the return value.
```py
>>> class ReturnExample(StateChart):
... a = State(initial=True)
... b = State(final=True)
...
... go = a.to(b)
...
... def before_go(self):
... return "before"
...
... def on_go(self):
... return "on"
...
... def on_enter_b(self):
... return "enter (ignored)"
...
... def after_go(self):
... return "after (ignored)"
>>> sm = ReturnExample()
>>> sm.send("go")
['before', 'on']
```
When only one callback returns a value, the result is unwrapped (not a list):
```py
>>> class SingleReturn(StateChart):
... a = State(initial=True)
... b = State(final=True)
...
... go = a.to(b, on="do_it")
...
... def do_it(self):
... return 42
>>> sm = SingleReturn()
>>> sm.send("go")
42
```
When no callback returns a value, the result is `None`:
```py
>>> class NoReturn(StateChart):
... a = State(initial=True)
... b = State(final=True)
...
... go = a.to(b)
>>> sm = NoReturn()
>>> sm.send("go") is None
True
```
```{note}
If a callback is defined but returns `None` explicitly, it is included in the
result list. Only callbacks that are not defined at all are excluded.
```
================================================
FILE: docs/api.md
================================================
# API
## StateChart
```{versionadded} 3.0.0
```
```{eval-rst}
.. autoclass:: statemachine.statemachine.StateChart
:members:
:undoc-members:
```
## StateMachine
```{eval-rst}
.. autoclass:: statemachine.statemachine.StateMachine
:members:
:undoc-members:
```
## State
```{seealso}
{ref}`States` reference.
```
```{eval-rst}
.. autoclass:: statemachine.state.State
:members:
```
## HistoryState
```{versionadded} 3.0.0
```
```{eval-rst}
.. autoclass:: statemachine.state.HistoryState
:members:
```
## States (class)
```{eval-rst}
.. autoclass:: statemachine.states.States
:noindex:
:members:
```
## Transition
```{seealso}
{ref}`Transitions` reference.
```
```{eval-rst}
.. autoclass:: statemachine.transition.Transition
:members:
```
## TransitionList
```{eval-rst}
.. autoclass:: statemachine.transition_list.TransitionList
:members:
```
## Model
```{seealso}
{ref}`Domain models` reference.
```
```{eval-rst}
.. autoclass:: statemachine.model.Model
:members:
```
## TriggerData
```{eval-rst}
.. autoclass:: statemachine.event_data.TriggerData
:members:
```
## Event
```{eval-rst}
.. autoclass:: statemachine.event.Event
:members: id, name, __call__
```
## EventData
```{eval-rst}
.. autoclass:: statemachine.event_data.EventData
:members:
```
## Callback conventions
These are convention-based callbacks that you can define on your state machine
subclass. They are not methods on the base class — define them in your subclass
to enable the behavior.
### `prepare_event`
Called before every event is processed. Returns a `dict` of keyword arguments
that will be merged into `**kwargs` for all subsequent callbacks (guards, actions,
entry/exit handlers) during that event's processing:
```python
class MyMachine(StateChart):
initial = State(initial=True)
loop = initial.to.itself()
def prepare_event(self):
return {"request_id": generate_id()}
def on_loop(self, request_id):
# request_id is available here
...
```
## MachineMixin
```{seealso}
{ref}`Integrations <machinemixin>` for usage examples.
```
```{eval-rst}
.. autoclass:: statemachine.mixins.MachineMixin
:members:
:undoc-members:
```
## create_machine_class_from_definition
```{versionadded} 3.0.0
```
```{eval-rst}
.. autofunction:: statemachine.io.create_machine_class_from_definition
```
## timeout
```{versionadded} 3.0.0
```
```{seealso}
{ref}`timeout` how-to guide.
```
```{eval-rst}
.. autofunction:: statemachine.contrib.timeout.timeout
```
================================================
FILE: docs/async.md
================================================
(async)=
# Async support
```{seealso}
New to statecharts? See [](concepts.md) for an overview of how states,
transitions, events, and actions fit together.
```
The public API is the same for synchronous and asynchronous code. If the
state machine has at least one `async` callback, the engine switches to
{ref}`AsyncEngine <asyncengine>` automatically — no configuration needed.
All statechart features — compound states, parallel states, history
pseudo-states, eventless transitions, `done.state` events — work
identically in both engines.
## Writing async callbacks
Declare any callback as `async def` and the engine handles the rest:
```py
>>> class AsyncStateMachine(StateChart):
... initial = State("Initial", initial=True)
... final = State("Final", final=True)
...
... keep = initial.to.itself(internal=True)
... advance = initial.to(final)
...
... async def on_advance(self):
... return 42
>>> async def run_sm():
... sm = AsyncStateMachine()
... result = await sm.advance()
... print(f"Result is {result}")
... print(list(sm.configuration_values))
>>> asyncio.run(run_sm())
Result is 42
['final']
```
### Using from synchronous code
The same state machine can be used from a synchronous context — even
without a running `asyncio` loop. The engine creates one internally
with `asyncio.new_event_loop()` and awaits callbacks using
`loop.run_until_complete()`:
```py
>>> sm = AsyncStateMachine()
>>> result = sm.advance()
>>> print(f"Result is {result}")
Result is 42
>>> print(list(sm.configuration_values))
['final']
```
(initial state activation)=
## Initial state activation
In async code, Python cannot `await` during `__init__`, so the initial
state is **not** activated at instantiation time. If you inspect
`configuration` immediately after creating the instance, it won't reflect
the initial state:
```py
>>> async def show_problem():
... sm = AsyncStateMachine()
... print(list(sm.configuration_values))
>>> asyncio.run(show_problem())
[]
```
To fix this, explicitly await
{func}`activate_initial_state() <statemachine.StateChart.activate_initial_state>`
before inspecting the configuration:
```py
>>> async def correct_init():
... sm = AsyncStateMachine()
... await sm.activate_initial_state()
... print(list(sm.configuration_values))
>>> asyncio.run(correct_init())
['initial']
```
```{tip}
If you don't inspect the configuration before sending the first event,
you can skip this step — the first `send()` activates the initial state
automatically.
```
```py
>>> async def auto_activate():
... sm = AsyncStateMachine()
... await sm.keep() # activates initial state before handling the event
... print(list(sm.configuration_values))
>>> asyncio.run(auto_activate())
['initial']
```
## Concurrent event sending
A benefit exclusive to the async engine: when multiple coroutines send
events concurrently (e.g., via `asyncio.gather`), each caller receives
its own event's result — even though only one coroutine runs the
processing loop at a time. The sync engine does not support this pattern.
```py
>>> class ConcurrentSC(StateChart):
... s1 = State(initial=True)
... s2 = State()
... s3 = State(final=True)
...
... step1 = s1.to(s2)
... step2 = s2.to(s3)
...
... async def on_step1(self):
... return "result_1"
...
... async def on_step2(self):
... return "result_2"
>>> async def run_concurrent():
... import asyncio as _asyncio
... sm = ConcurrentSC()
... await sm.activate_initial_state()
... r1, r2 = await _asyncio.gather(
... sm.send("step1"),
... sm.send("step2"),
... )
... return r1, r2
>>> asyncio.run(run_concurrent())
('result_1', 'result_2')
```
Under the hood, the async engine attaches an `asyncio.Future` to each
externally enqueued event. The coroutine that acquires the processing lock
resolves each event's future as it processes the queue. Callers that didn't
acquire the lock simply `await` their future.
```{note}
Futures are only created for **external** events sent from outside the
processing loop. Events triggered from within callbacks (via `send()` or
`raise_()`) follow the {ref}`run-to-completion <rtc-model>` model — they
are enqueued and processed within the current macrostep.
```
If an exception occurs during processing (with `catch_errors_as_events=False`),
the exception is routed to the caller whose event caused it. Other callers
whose events were still pending will also receive the exception, since the
processing loop clears the queue on failure.
(syncengine)=
(asyncengine)=
## Engine selection
The engine is selected automatically when the state machine is
instantiated, based on the registered callbacks:
| Outer scope | Async callbacks? | Engine | Event loop |
|---|---|---|---|
| Sync | No | SyncEngine | None |
| Sync | Yes | AsyncEngine | Creates internal loop |
| Async | No | SyncEngine | None |
| Async | Yes | AsyncEngine | Reuses running loop |
**Outer scope** is the context where the state machine instance is created.
**Async callbacks** means at least one `async def` callback or condition is
declared on the machine, its model, or its listeners.
```{note}
All callbacks run on the same thread they are called from. Mixing
synchronous and asynchronous code is supported but requires care —
avoid sharing a state machine instance across threads without external
synchronization.
```
```{seealso}
See {ref}`processing model <macrostep-microstep>` for how the engine
processes events, and {ref}`behaviour` for the behavioral attributes
that affect processing.
```
================================================
FILE: docs/authors.md
================================================
# Credits
## Development Lead
* [Fernando Macedo](mailto:fgmacedo@gmail.com)
## Contributors
* [Guilherme Nepomuceno](mailto:piercio@loggi.com)
* [Rafael Rêgo](mailto:crafards@gmail.com)
* [Raphael Schrader](mailto:raphael@schradercloud.de)
* [João S. O. Bueno](mailto:gwidion@gmail.com)
* [Rodrigo Nogueira](mailto:rodrigo.b.nogueira@gmail.com)
## Scaffolding
This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the
[audreyr/cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage) project template.
================================================
FILE: docs/behaviour.md
================================================
(behaviour)=
(statecharts)=
# Behaviour
```{seealso}
New to statecharts? See [](concepts.md) for an overview of how states,
transitions, events, and actions fit together.
```
The {class}`~statemachine.statemachine.StateChart` class follows the
[SCXML specification](https://www.w3.org/TR/scxml/) by default. The
{class}`~statemachine.statemachine.StateMachine` class extends `StateChart`
but overrides several defaults to preserve backward compatibility with
pre-3.0 code.
The behavioral differences are controlled by class-level attributes. This
design allows a gradual upgrade path: start from `StateMachine` and
selectively enable spec-compliant behaviors one at a time, or start from
`StateChart` and get full SCXML compliance out of the box.
```{tip}
We **strongly recommend** that new projects use `StateChart` directly.
Existing projects should consider migrating when possible, as the
SCXML-compliant behavior provides more predictable semantics.
```
## Comparison table
| Attribute | `StateChart` | `StateMachine` | Description |
|---|---|---|---|
| `allow_event_without_transition` | `True` | `False` | Tolerate events that don't match any transition |
| `enable_self_transition_entries` | `True` | `False` | Execute entry/exit actions on self-transitions |
| `atomic_configuration_update` | `False` | `True` | When to update {ref}`configuration <querying-configuration>` during a microstep |
| `catch_errors_as_events` | `True` | `False` | Catch action errors as `error.execution` events |
Each attribute is described below, with cross-references to the pages that
cover the topic in depth.
## `allow_event_without_transition`
When `True` (SCXML default), sending an event that does not match any enabled
transition is silently ignored. When `False` (legacy default), a
`TransitionNotAllowed` exception is raised, including for unknown event names.
The SCXML spec requires tolerance to unmatched events, as the event-driven
model expects that not every event is relevant in every state.
```{seealso}
See {ref}`conditions` for how the engine selects transitions, and
{ref}`checking enabled events` to query which events are currently valid.
```
## `enable_self_transition_entries`
When `True` (SCXML default), a {ref}`self-transition <self-transition>`
executes the state's exit and entry actions, just like any other transition.
When `False` (legacy default), self-transitions skip entry/exit actions.
The SCXML spec treats self-transitions as regular transitions that happen to
return to the same state, so entry/exit actions must fire. Use an
{ref}`internal transition <internal-transition>` if you need a transition that
stays in the same state **without** running exit/entry actions.
```{seealso}
See {ref}`transitions` for the full reference on self-transitions and
internal transitions.
```
## `atomic_configuration_update`
Controls **when** the {ref}`configuration <querying-configuration>` is
updated during a microstep.
When `False` (SCXML default), the configuration reflects each phase as it
happens: states are removed during exit and added during entry. This means
that during transition `on` callbacks, the configuration may be empty or
partial — the source states have already been exited but the target states
have not yet been entered.
When `True` (legacy default), the configuration is updated atomically
**after** the `on` callbacks complete, so `sm.configuration` and
`state.is_active` always reflect a consistent snapshot during the transition.
```py
>>> from statemachine import State, StateChart
>>> class AtomicDemo(StateChart):
... atomic_configuration_update = True
... off = State(initial=True)
... on = State(final=True)
...
... switch = off.to(on, on="check_config")
...
... def check_config(self):
... # With atomic update, configuration is unchanged during 'on'
... self.off_was_active = self.off.is_active
... self.on_was_active = self.on.is_active
>>> sm = AtomicDemo()
>>> sm.send("switch")
>>> sm.off_was_active # source still in configuration during 'on'
True
>>> sm.on_was_active # target not yet in configuration during 'on'
False
```
With `atomic_configuration_update = False` (the SCXML default), the result
is different — `off.is_active` is `False` because exit already removed it,
and `on.is_active` is also `False` because entry hasn't added it yet.
In this mode, use `previous_configuration` and `new_configuration` to
inspect the full picture:
```py
>>> class SCXMLDemo(StateChart):
... off = State(initial=True)
... on = State(final=True)
...
... switch = off.to(on, on="check_config")
...
... def check_config(self, previous_configuration, new_configuration):
... self.prev = {s.id for s in previous_configuration}
... self.new = {s.id for s in new_configuration}
>>> sm = SCXMLDemo()
>>> sm.send("switch")
>>> sm.prev
{'off'}
>>> sm.new
{'on'}
```
```{seealso}
See {ref}`dependency-injection` for the full list of parameters available
in callbacks.
```
## `catch_errors_as_events`
When `True` (SCXML default), runtime exceptions in action callbacks
(entry/exit, transition `on`) are caught by the engine and dispatched as
internal `error.execution` events. When `False` (legacy default), exceptions
propagate normally to the caller.
```{note}
{ref}`Validators <validators>` are **not** affected by this flag — they
always propagate exceptions to the caller, regardless of the
`catch_errors_as_events` setting. See {ref}`validators` for details.
```
```{seealso}
See {ref}`error-handling` for the full `error.execution` lifecycle,
block-level error catching, and the cleanup/finalize pattern.
```
## Gradual migration
All behavioral attributes can be overridden individually. This lets you
adopt SCXML semantics incrementally in an existing `StateMachine`:
```python
class MyMachine(StateMachine):
catch_errors_as_events = True
# ... everything else behaves as before ...
```
Or keep a specific legacy behavior while using `StateChart` for the rest:
```python
class MyChart(StateChart):
atomic_configuration_update = True
# ... SCXML-compliant otherwise ...
```
```{seealso}
See [](releases/upgrade_2x_to_3.md) for a complete migration guide from
`StateMachine` 2.x to `StateChart` 3.x.
```
================================================
FILE: docs/concepts.md
================================================
(concepts)=
# Core concepts
A statechart organizes behavior around **states**, **transitions**, and
**events**. Together they describe *when* the system can change, *what*
triggers the change, and *what happens* as a result.
```py
>>> from statemachine import StateChart, State
>>> class Turnstile(StateChart):
... locked = State(initial=True)
... unlocked = State()
...
... coin = locked.to(unlocked, on="thank_you")
... push = unlocked.to(locked)
...
... def thank_you(self):
... return "Welcome!"
>>> sm = Turnstile()
>>> sm.coin()
'Welcome!'
```
Even in this minimal example, the core concepts appear:
| Concept | What it is | Declared as |
|---|---|---|
| {ref}`StateChart <statechart>` | The container and runtime for the machine | `class MyChart(StateChart)` |
| {ref}`State <states>` | A mode or condition of the system | `State()`, `State.Compound`, `State.Parallel` |
| {ref}`Transition <transitions>` | A link from source state to target state | `source.to(target)`, `target.from_(source)` |
| {ref}`Event <events>` | A signal that triggers transitions | Class-level assignment or `Event(...)` |
| {ref}`Action <actions>` | A side-effect during state changes | `on`, `before`, `after`, `enter`, `exit` callbacks |
| {ref}`Condition <conditions>` | A guard that allows/blocks a transition | `cond`, `unless`, `validators` parameters |
| {ref}`Listener <listeners>` | An external observer of the lifecycle | `listeners = [...]` class attribute |
Each concept below introduces the idea briefly; follow the "See also" links
for the full reference. Listeners are covered in {ref}`their own page <listeners>`.
(concepts-statechart)=
## StateChart
A {ref}`StateChart <statechart>` is the container for states, transitions,
and events. It defines the topology (which states exist and how they
connect) and provides the runtime API — sending events, querying the
current configuration, and managing listeners.
In the turnstile example, `Turnstile` is the `StateChart`. After
instantiation, `sm` holds the runtime state and exposes methods like
`sm.send("coin")`, `sm.configuration`, and `sm.allowed_events`.
```{seealso}
See [](statechart.md) for the full reference: creating instances, sending
events, querying configuration, checking termination, and managing
listeners at runtime.
```
(concepts-states)=
## States
A **state** describes what the system is doing right now. At any point in
time, a statechart is "in" one or more states — the **configuration**. States
determine which transitions are available and which events are accepted.
In the turnstile example, `locked` and `unlocked` are the two possible
states. The machine starts in `locked` (its **initial state**) and can only
reach `unlocked` when the `coin` event fires.
```{seealso}
See [](states.md) for the full reference: initial and final states, compound
(nested) states, parallel regions, history pseudo-states, and more.
```
(concepts-transitions)=
## Transitions
A **transition** is a link between a **source** state and a **target** state.
When a transition fires, the system leaves the source and enters the target.
Transitions can carry {ref}`actions <actions>` (side-effects) and
{ref}`conditions <conditions>` (guards that must be satisfied).
In the turnstile, `locked.to(unlocked)` is a transition: it moves the system
from `locked` to `unlocked` and runs the `thank_you` action along the way.
```{seealso}
See [](transitions.md) for the full reference: declaring transitions,
self-transitions, internal transitions, eventless (automatic) transitions,
and more.
```
(concepts-events)=
## Events
An **event** is a signal that something has happened. Events trigger
transitions — without an event, a transition will not fire (unless it is
an {ref}`eventless <eventless>` transition with a guard condition).
In the turnstile, `coin` and `push` are events. When you call `sm.coin()` or
`sm.send("coin")`, the engine looks for a matching transition from the current
state and fires it. Events are processed following a **run-to-completion**
model — each event is fully handled before the next one starts.
```{seealso}
See [](events.md) for the full reference: declaring, triggering, scheduling,
and naming conventions. See [](processing_model.md) for how macrosteps and
microsteps work under the hood.
```
(concepts-actions)=
## Actions
An **action** is a side-effect that runs during a transition or on
entry/exit of a state. Actions are how the statechart interacts with the
outside world — sending notifications, updating a database, logging,
or returning a value.
In the turnstile, `thank_you` is an action attached to the `coin` transition
via the `on` parameter.
```{seealso}
See [](actions.md) for the full reference: callback naming conventions,
execution order, dependency injection, and all available hooks.
```
(concepts-conditions)=
## Conditions
A **condition** (also called a **guard**) is a predicate that must evaluate
to `True` for a transition to fire. A **validator** is similar but raises an
exception to block the transition instead of silently preventing it.
Conditions let you have multiple transitions for the same event, each with a
different guard — the first one that passes wins.
```{seealso}
See [](guards.md) for the full reference: `cond`, `unless`, `validators`,
boolean expressions, and checking enabled events.
```
================================================
FILE: docs/conf.py
================================================
#!/usr/bin/env python
#
# statemachine documentation build configuration file, created by
# sphinx-quickstart on Tue Jul 9 22:26:36 2013.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sys
from sphinx_gallery import gen_gallery
# If extensions (or modules to document with autodoc) are in another
# directory, add these directories to sys.path here. If the directory is
# relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# Get the project root dir, which is the parent dir of this
cwd = os.getcwd()
project_root = os.path.dirname(cwd)
# Insert the project root dir as the first element in the PYTHONPATH.
# This lets us ensure that the source package is imported, and that its
# version is used.
sys.path.insert(0, project_root)
from tests.scrape_images import MachineScraper # noqa: E402
import statemachine # noqa: E402
# -- General configuration ---------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"myst_parser",
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.napoleon",
"sphinx.ext.viewcode",
"sphinx.ext.autosectionlabel",
"sphinx_gallery.gen_gallery",
"sphinx_copybutton",
"statemachine.contrib.diagram.sphinx_ext",
"sphinxcontrib.mermaid",
]
autosectionlabel_prefix_document = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "Python State Machine"
copyright = "2024, Fernando Macedo"
# The version info for the project you're documenting, acts as replacement
# for |version| and |release|, also used in various other places throughout
# the built documents.
#
# The short X.Y version.
version = statemachine.__version__
# The full version, including alpha/beta/rc tags.
release = statemachine.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build", "examples/.ipynb_checkpoints", "*.ipynb"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built
# documents.
# keep_warnings = False
# -- Options for HTML output -------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "furo"
# https://pradyunsg.me/furo/
# Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as
# html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the
# top of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon
# of the docs. This file should be a Windows icon file (.ico) being
# 16x16 or 32x32 pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets)
# here, relative to this directory. They are copied after the builtin
# static files, so a file named "default.css" will overwrite the builtin
# "default.css".
html_static_path = ["_static"]
html_css_files = [
"custom_machine.css",
]
html_js_files = [
"https://buttons.github.io/buttons.js",
]
html_title = f"python-statemachine {release}"
html_logo = "images/python-statemachine.png"
html_copy_source = False
html_show_sourcelink = False
html_theme_options = {
"navigation_with_keys": True,
"top_of_page_buttons": ["view", "edit"],
"source_repository": "https://github.com/fgmacedo/python-statemachine/",
# "source_branch": "develop",
"source_directory": "docs/",
}
pygments_style = "monokai"
pygments_dark_style = "monokai"
# If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names
# to template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer.
# Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer.
# Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages
# will contain a <link> tag referring to it. The value of this option
# must be the base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = "statemachinedoc"
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
# Napoleon settings (Google format )
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = True
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = True
napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_preprocess_types = False
napoleon_type_aliases = None
napoleon_attr_annotations = True
# Markdown (MyST) configs
myst_heading_anchors = 3
myst_enable_extensions = ["deflist", "substitution"]
myst_substitutions = {
"state": "{ref}`state`",
"event": "{ref}`event`",
}
# Github
html_context = {
"display_github": True, # Integrate GitHub
"github_user": "fgmacedo", # Username
"github_repo": "python-statemachine", # Repo name
"github_version": "develop", # Version
"conf_py_path": "/docs/", # Path in the checkout to the docs root
}
# Sphinx Galery
sphinx_gallery_conf = {
"examples_dirs": [
"../tests/examples",
], # path to your example scripts
"gallery_dirs": "auto_examples", # path to where to save gallery generated output
"capture_repr": ("_repr_html_", "__repr__"),
"filename_pattern": r"/.*\_machine.py",
"download_all_examples": False,
"show_signature": False,
"min_reported_time": 9999,
"thumbnail_size": (400, 280),
"image_scrapers": (MachineScraper(project_root),),
"reset_modules": [],
}
copybutton_exclude = ".linenos, .gp, .go"
def dummy_write_computation_times(gallery_conf, target_dir, costs):
"patch gen_gallery to disable write_computation_times"
pass
gen_gallery.write_computation_times = dummy_write_computation_times
================================================
FILE: docs/contributing.md
================================================
# Contributing
* <a class="github-button" href="https://github.com/fgmacedo/python-statemachine" data-icon="octicon-star" aria-label="Star fgmacedo/python-statemachine on GitHub">Star this project</a>
* <a class="github-button" href="https://github.com/fgmacedo/python-statemachine/issues" data-icon="octicon-issue-opened" aria-label="Issue fgmacedo/python-statemachine on GitHub">Open an Issue</a>
* <a class="github-button" href="https://github.com/fgmacedo/python-statemachine/fork" data-icon="octicon-repo-forked" aria-label="Fork fgmacedo/python-statemachine on GitHub">Fork</a>
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit
will always be given.
You can contribute in many ways:
## Types of Contributions
### Report Bugs
Report bugs at [https://github.com/fgmacedo/python-statemachine/issues](https://github.com/fgmacedo/python-statemachine/issues).
If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.
### Fix Bugs
Look through the GitHub issues for bugs. Anything tagged with "bug"
and "help wanted" is open to whoever wants to implement it.
### Implement Features
Look through the GitHub issues for features. Anything tagged with "enhancement"
and "help wanted" is open to whoever wants to implement it.
### Write Documentation
Python State Machine could always use more documentation, whether as part of the
official Python State Machine docs, in docstrings, or even on the web in blog posts,
articles, and such.
### Add a translation
Extract a `Portable Object Template` (`POT`) file:
```shell
pybabel extract statemachine -o statemachine/locale/statemachine.pot
```
Then, copy the template as a `.po` file into the target locale folder. For example, if you're adding support for Brazilian Portuguese language, the code is `pt_BR`, and the file path should be `statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po`:
```shell
cp statemachine/locale/statemachine.pot statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po
```
Then open the `statemachine.po` and translate.
After translation, to get the new language working locally, you need to compile the `.po` files into `.mo` (binary format). Run:
```shell
pybabel compile -d statemachine/locale/ -D statemachine
```
On Linux (Debian based), you can test changing the `LANGUAGE` environment variable.
```shell
# If the last line is `Can't guess when in Won.` something went wrong.
LANGUAGE=pt_BR python tests/examples/guess_the_number_machine.py
```
Then open a [pull request](https://github.com/fgmacedo/python-statemachine/pulls) with your translation file.
### Submit Feedback
The best way to send feedback is to file an issue at https://github.com/fgmacedo/python-statemachine/issues.
If you are proposing a feature:
* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
are welcome :)
## Get Started!
Ready to contribute? Here's how to set up `python-statemachine` for local development.
1. Install dependencies.
1. [graphviz](https://graphviz.org/download/#linux)
1. [uv](https://docs.astral.sh/uv/getting-started/installation/)
1. Fork the `python-statemachine` repository on GitHub.
1. Clone the forked repository to your local machine by running::
git clone https://github.com/YOUR-USERNAME/python-statemachine.git.
1. Run `uv sync` once to install all the development dependencies and create a virtual environment::
uv sync --all-extras
2. Install the pre-commit validations:
pre-commit install
3. Create a branch for local development:
git checkout -b <name-of-your-bugfix-or-feature>
4. Make changes to the code.
5. Run tests to ensure they pass by running:
uv run pytest
6. Update the documentation as needed.
Build the documentation:
uv run sphinx-build docs docs/_build/html
Now you can serve the local documentation using a webserver, like the built-in included
with python:
python -m http.server --directory docs/_build/html
And access your browser at http://localhost:8000/
If you're specially writting documentation, I strongly recommend using `sphinx-autobuild`
as it improves the workflow watching for file changes and with live reloading:
uv run sphinx-autobuild docs docs/_build/html --re-ignore "auto_examples/.*"
Sometimes you need a full fresh of the files being build for docs, you can safely remove
all automatically generated files to get a clean state by running:
rm -rf docs/_build/ docs/auto_examples
1. Commit your changes and push them to your forked repository:
git add -A .
git commit -s -m "Your detailed description of your changes."
git push origin name-of-your-bugfix-or-feature
1. Create a pull request on the original repository for your changes to be reviewed and potentially
merged. Be sure to follow the project's code of conduct and contributing guidelines.
1. Use `exit` to leave the virtual environment.
## Pull Request Guidelines
Before you submit a pull request, check that it meets these guidelines:
1. The pull request should include tests.
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in the next release notes.
3. Consider adding yourself to the contributor's list.
4. The pull request should work for all supported Python versions.
## Releasing a New Version
This project uses [git-flow](https://github.com/nvie/gitflow) for release management and
publishes to PyPI automatically via GitHub Actions when a version tag is pushed.
### Prerequisites
- You must be on the `develop` branch with a clean working tree.
- `git-flow` must be installed and initialized:
```shell
brew install git-flow # macOS
git flow init # use main for production, develop for next release
```
- All changes intended for the release must already be merged into `develop`.
### Step-by-step release process
The following steps use version `X.Y.Z` as a placeholder. Replace it with the actual version
number (e.g., `2.6.0`).
#### 1. Start the release branch
```shell
git checkout develop
git pull origin develop
git flow release start X.Y.Z
```
This creates and switches to a `release/X.Y.Z` branch based on `develop`.
#### 2. Bump the version number
Update the version string in **both** files:
- `pyproject.toml` — the `version` field under `[project]`
- `statemachine/__init__.py` — the `__version__` variable
#### 3. Update translations
Extract new translatable strings, merge them into all existing `.po` files, translate the
new entries, and compile:
```shell
uv run pybabel extract statemachine -o statemachine/locale/statemachine.pot
uv run pybabel update -i statemachine/locale/statemachine.pot -d statemachine/locale/ -D statemachine
# Edit each .po file to translate new empty msgstr entries
uv run pybabel compile -d statemachine/locale/ -D statemachine
```
```{note}
The `.pot` and `.mo` files are git-ignored. Only the `.po` source files are committed.
The compiled `.mo` files may cause test failures if your system locale matches a translated
language (error messages will appear translated instead of in English). Delete them after
verifying translations work: `rm -f statemachine/locale/*/LC_MESSAGES/statemachine.mo`
```
#### 4. Write release notes
Create `docs/releases/X.Y.Z.md` documenting all changes since the previous release. Include
sections for new features, bugfixes, performance improvements, and miscellaneous changes.
Reference GitHub issues/PRs where applicable.
Add the new file to the toctree in `docs/releases/index.md` (at the top of the appropriate
major version section).
Update any related documentation pages (e.g., if a bugfix adds a new behavior that users
should know about).
#### 5. Run linters and tests
```shell
uv run ruff check .
uv run ruff format --check .
uv run mypy statemachine/
uv run pytest -n auto
```
All checks must pass before committing.
#### 6. Commit
Stage all changed files and commit. The pre-commit hooks will run ruff, mypy, and pytest
automatically.
```shell
git add <files>
git commit -m "chore: prepare release X.Y.Z"
```
#### 7. Finish the release
```shell
git flow release finish X.Y.Z -m "vX.Y.Z"
```
This will:
- Merge `release/X.Y.Z` into `main`
- Create an annotated tag `X.Y.Z` on `main`
- Merge `main` back into `develop`
- Delete the `release/X.Y.Z` branch
```{note}
If tagging fails (e.g., GPG or editor issues), create the tag manually and re-run:
`git tag -a X.Y.Z -m "vX.Y.Z"` then `git flow release finish X.Y.Z -m "vX.Y.Z"`.
```
#### 8. Update the `latest` tag and push
```shell
git tag latest -f
git push origin main develop --tags -f
```
Force-pushing tags is needed to move the `latest` tag.
#### 9. Verify the release
The tag push triggers the `release` GitHub Actions workflow (`.github/workflows/release.yml`),
which will:
1. Check out the tag
2. Run the full test suite
3. Build the sdist and wheel with `uv build`
4. Publish to PyPI using trusted publishing
Monitor the workflow run at `https://github.com/fgmacedo/python-statemachine/actions` to
confirm the release was published successfully.
================================================
FILE: docs/diagram.md
================================================
(diagram)=
(diagrams)=
# Diagrams
You can generate visual diagrams from any
{class}`~statemachine.statemachine.StateChart` — useful for documentation,
debugging, or sharing your machine's structure with teammates.
```{statemachine-diagram} tests.examples.order_control_machine.OrderControl
:target:
```
## Installation
Diagram generation requires [pydot](https://github.com/pydot/pydot) and
[Graphviz](https://graphviz.org/):
```bash
pip install python-statemachine[diagrams] # installs pydot
```
You also need the `dot` command-line tool from Graphviz. On Debian/Ubuntu:
```bash
sudo apt install graphviz
```
For other systems, see the [Graphviz downloads page](https://graphviz.org/download/).
## Generating diagrams
Every state machine instance exposes a `_graph()` method that returns a
[pydot.Dot](https://github.com/pydot/pydot) graph object:
```python
from tests.examples.order_control_machine import OrderControl
sm = OrderControl()
graph = sm._graph() # returns a pydot.Dot object
```
### Highlighting the current state
The diagram automatically highlights the current state of the instance.
Send events to advance the machine and see the active state change:
```python
from tests.examples.traffic_light_machine import TrafficLightMachine
sm = TrafficLightMachine()
sm.send("cycle")
sm._graph().write_png("traffic_light_yellow.png")
```
```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine
:events: cycle
:caption: TrafficLightMachine after one cycle
```
### Exporting to a file
The `pydot.Dot` object supports writing to many formats — use
`write_png()`, `write_svg()`, `write_pdf()`, etc.:
```python
sm = OrderControl()
sm._graph().write_png("order_control.png")
```
```{statemachine-diagram} tests.examples.order_control_machine.OrderControl
:caption: OrderControl
```
For higher resolution PNGs, set the DPI before exporting:
```python
graph = sm._graph()
graph.set_dpi(300).write_png("order_control_300dpi.png")
```
```{note}
Supported formats include `dia`, `dot`, `fig`, `gif`, `jpg`, `pdf`,
`png`, `ps`, `svg`, and many others. See
[Graphviz output formats](https://graphviz.org/docs/outputs/) for the
complete list.
```
## Text representations
State machines support multiple text-based output formats, all accessible
through Python's built-in `format()` protocol, the `formatter` API, or
the command line.
| Format | Aliases | Description | Dependencies |
|--------|---------|-------------|--------------|
| `mermaid` | | [Mermaid stateDiagram-v2](https://mermaid.js.org/syntax/stateDiagram.html) source | None [^mermaid] |
| `md` | `markdown` | Transition table (pipe-delimited Markdown) | None |
| `rst` | | Transition table (RST grid table) | None |
| `dot` | | [Graphviz DOT](https://graphviz.org/doc/info/lang.html) language source | pydot |
| `svg` | | SVG markup (generated via DOT) | pydot, Graphviz |
[^mermaid]: Mermaid has a known rendering bug
([mermaid-js/mermaid#4052](https://github.com/mermaid-js/mermaid/issues/4052))
where transitions targeting or originating from a compound state inside a
parallel region crash the renderer. As a workaround, the `MermaidRenderer`
redirects such transitions to the compound's initial child state. The
visual result is equivalent — Mermaid draws the arrow crossing into the
compound boundary — but the arrow points to the child rather than the
compound border. This workaround will be revisited when the upstream bug
is resolved.
### Using `format()`
Use f-strings or the built-in `format()` function — no diagram imports needed:
```py
>>> from tests.examples.traffic_light_machine import TrafficLightMachine
>>> sm = TrafficLightMachine()
>>> print(f"{sm:mermaid}")
stateDiagram-v2
direction LR
state "Green" as green
state "Yellow" as yellow
state "Red" as red
[*] --> green
green --> yellow : Cycle
yellow --> red : Cycle
red --> green : Cycle
<BLANKLINE>
classDef active fill:#40E0D0,stroke:#333
green:::active
<BLANKLINE>
>>> print(f"{sm:md}")
| State | Event | Guard | Target |
| ------ | ----- | ----- | ------ |
| Green | Cycle | | Yellow |
| Yellow | Cycle | | Red |
| Red | Cycle | | Green |
<BLANKLINE>
```
Works on **classes** too (no active-state highlighting):
```py
>>> print(f"{TrafficLightMachine:mermaid}")
stateDiagram-v2
direction LR
state "Green" as green
state "Yellow" as yellow
state "Red" as red
[*] --> green
green --> yellow : Cycle
yellow --> red : Cycle
red --> green : Cycle
<BLANKLINE>
```
The `dot` format returns the Graphviz DOT language source:
```py
>>> print(f"{sm:dot}") # doctest: +ELLIPSIS
digraph TrafficLightMachine {
...
}
```
An empty format spec (e.g., `f"{sm:}"`) falls back to `repr()`.
(formatter-api)=
### Using the `formatter` API
The `formatter` object is the programmatic entry point for rendering
state machines in any registered text format:
```py
>>> from statemachine.contrib.diagram import formatter
>>> from tests.examples.traffic_light_machine import TrafficLightMachine
>>> print(formatter.render(TrafficLightMachine, "mermaid"))
stateDiagram-v2
direction LR
state "Green" as green
state "Yellow" as yellow
state "Red" as red
[*] --> green
green --> yellow : Cycle
yellow --> red : Cycle
red --> green : Cycle
<BLANKLINE>
>>> formatter.supported_formats()
['dot', 'markdown', 'md', 'mermaid', 'rst', 'svg']
```
Both `format()` and the Sphinx directive delegate to this same `formatter`
under the hood.
#### Registering custom formats
The `formatter` is extensible — register your own format with a
decorator and it becomes available everywhere (`format()`, CLI,
Sphinx directive):
```python
from statemachine.contrib.diagram import formatter
@formatter.register_format("plantuml", "puml")
def _render_plantuml(machine_or_class):
# your PlantUML renderer here
...
```
After registration, `f"{sm:plantuml}"` and `--format plantuml` work
immediately.
### Command line
You can generate diagrams without writing Python code:
```bash
python -m statemachine.contrib.diagram <classpath> <output_file>
```
The output format is inferred from the file extension:
```bash
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png
```
To highlight the current state, use `--events` to instantiate the machine and
send events before rendering:
```bash
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png --events cycle cycle cycle
```
Use `--format` to produce a text format instead of a Graphviz image:
```bash
# Mermaid stateDiagram-v2
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.mmd --format mermaid
# DOT source
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.dot --format dot
# Markdown transition table
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.md --format md
# RST transition table
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.rst --format rst
```
Use `-` as the output file to write to stdout (handy for piping):
```bash
python -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine - --format mermaid
```
## Auto-expanding docstrings
Use `{statechart:FORMAT}` placeholders in your class docstring to embed
a live representation of the state machine. The placeholder is replaced
at class definition time, so the docstring always reflects the actual
states and transitions:
```py
>>> from statemachine.statemachine import StateChart
>>> from statemachine.state import State
>>> class TrafficLight(StateChart):
... """A traffic light.
...
... {statechart:md}
... """
... green = State(initial=True)
... yellow = State()
... red = State()
... cycle = green.to(yellow) | yellow.to(red) | red.to(green)
>>> print(TrafficLight.__doc__)
A traffic light.
<BLANKLINE>
| State | Event | Guard | Target |
| ------ | ----- | ----- | ------ |
| Green | Cycle | | Yellow |
| Yellow | Cycle | | Red |
| Red | Cycle | | Green |
<BLANKLINE>
<BLANKLINE>
```
Any registered format works: `{statechart:rst}`, `{statechart:mermaid}`,
`{statechart:dot}`, etc.
### Choosing the right format
| Context | Recommended format |
|---------|-------------------|
| Sphinx with RST (autodoc default) | `{statechart:rst}` |
| Sphinx with MyST Markdown | `{statechart:md}` |
| `help()` in terminal / IDE | Either works; `md` reads more cleanly |
### Sphinx autodoc integration
Since the placeholder is expanded at class definition time, Sphinx `autodoc`
sees the final rendered text — no extra configuration needed.
For example, this class uses `{statechart:rst}` in its docstring:
```{literalinclude} ../tests/machines/showcase_simple.py
:pyobject: SimpleSC
:language: python
```
And here is the rendered autodoc output:
```{eval-rst}
.. autoclass:: tests.machines.showcase_simple.SimpleSC
:noindex:
```
## Sphinx directive
If you use [Sphinx](https://www.sphinx-doc.org/) to build your documentation, the
`statemachine-diagram` directive renders diagrams inline — no need to generate
image files manually.
### Setup
Add the extension to your `conf.py`:
```python
extensions = [
...
"statemachine.contrib.diagram.sphinx_ext",
]
```
### Basic usage
Reference any importable {class}`~statemachine.statemachine.StateChart` class by
its fully qualified path:
````markdown
```{statemachine-diagram} myproject.machines.OrderControl
```
````
```{statemachine-diagram} tests.examples.order_control_machine.OrderControl
:alt: OrderControl state machine
:align: center
```
### Highlighting a specific state
Pass `:events:` to instantiate the machine and send events before rendering.
This highlights the current state after processing:
````markdown
```{statemachine-diagram} myproject.machines.TrafficLight
:events: cycle
:caption: Traffic light after one cycle
```
````
```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine
:events: cycle
:caption: Traffic light after one cycle
:align: center
```
### Enabling zoom
For complex diagrams, add `:target:` (without a value) to make the diagram
clickable — it opens the full SVG in a new browser tab where users can
zoom and pan freely:
````markdown
```{statemachine-diagram} myproject.machines.OrderControl
:target:
```
````
```{statemachine-diagram} tests.examples.order_control_machine.OrderControl
:caption: Click to open full-size SVG
:target:
:align: center
```
### Mermaid format
Use `:format: mermaid` to render via
[sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
instead of Graphviz SVG — useful when you don't want to install Graphviz
in your docs build environment:
````markdown
```{statemachine-diagram} myproject.machines.TrafficLight
:format: mermaid
:caption: Rendered as Mermaid
```
````
```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine
:format: mermaid
:caption: TrafficLightMachine (Mermaid)
:align: center
```
### Directive options
The directive supports the same layout options as the standard `image` and
`figure` directives, plus state-machine-specific ones.
**State-machine options:**
`:events:` *(comma-separated string)*
: Events to send in sequence. When present, the machine is instantiated and
each event is sent before rendering.
`:format:` *(string)*
: Output format. Use `mermaid` to render via sphinxcontrib-mermaid
instead of Graphviz SVG. Default: DOT/SVG.
**Image/figure options:**
`:caption:` *(string)*
: Caption text; wraps the image in a `figure` node.
`:alt:` *(string)*
: Alt text for the image. Defaults to the class name.
`:width:` *(CSS length, e.g. `400px`, `80%`)*
: Explicit width for the diagram.
`:height:` *(CSS length)*
: Explicit height for the diagram.
`:scale:` *(integer percentage, e.g. `50%`)*
: Uniform scaling relative to the intrinsic size.
`:align:` *(left | center | right)*
: Image alignment. Defaults to `center`.
`:target:` *(URL or empty)*
: Makes the diagram clickable. When set without a value, the raw SVG is
saved as a file and linked so users can open it in a new tab for
full-resolution zooming — useful for large or complex diagrams.
`:class:` *(space-separated strings)*
: Extra CSS classes for the wrapper element.
`:figclass:` *(space-separated strings)*
: Extra CSS classes for the `figure` element (only when `:caption:` is set).
`:name:` *(string)*
: Reference target name for cross-referencing with `{ref}`.
```{note}
The directive imports the state machine class at Sphinx parse time. Machines
defined inline in doctest blocks cannot be referenced — use the
`_graph()` method for those cases.
```
## Jupyter integration
State machine instances are automatically rendered as diagrams in
JupyterLab cells — no extra code needed:

## Online generation (QuickChart)
If you prefer not to install Graphviz locally, you can generate diagrams
using the [QuickChart](https://quickchart.io/) online service:
```{eval-rst}
.. autofunction:: statemachine.contrib.diagram.quickchart_write_svg
```
## Customizing the output
The `DotGraphMachine` class gives you control over the diagram's visual
properties. Subclass it and override the class attributes to customize
fonts, colors, and layout:
```python
from statemachine.contrib.diagram import DotGraphMachine
from tests.examples.order_control_machine import OrderControl
```
Available attributes:
| Attribute | Default | Description |
|-----------|---------|-------------|
| `graph_rankdir` | `"LR"` | Graph direction (`"LR"` left-to-right, `"TB"` top-to-bottom) |
| `font_name` | `"Helvetica"` | Font face for labels |
| `state_font_size` | `"10"` | State label font size |
| `state_active_penwidth` | `2` | Border width of the active state |
| `state_active_fillcolor` | `"turquoise"` | Fill color of the active state |
| `transition_font_size` | `"9"` | Transition label font size |
For example, to generate a top-to-bottom diagram with a custom active
state color:
```python
class CustomDiagram(DotGraphMachine):
graph_rankdir = "TB"
state_active_fillcolor = "lightyellow"
sm = OrderControl()
sm.receive_payment(10)
graph = CustomDiagram(sm)
dot = graph()
dot.write_svg("order_control_custom.svg")
```
`DotGraphMachine` also works with **classes** (not just instances) to
generate diagrams without an active state:
```python
dot = DotGraphMachine(OrderControl)()
dot.write_png("order_control_class.png")
```
## Visual showcase
This section shows how each state machine feature is rendered in diagrams.
Each example includes the class definition, diagrams in both **Graphviz**
and **Mermaid** formats, and **instance** diagrams with the current state
highlighted after sending events.
### Simple states
A minimal state machine with three atomic states and linear transitions.
```{literalinclude} ../tests/machines/showcase_simple.py
:pyobject: SimpleSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC
:caption: Class (Graphviz)
```
```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC
:events:
:caption: Initial
```
```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC
:events: start
:caption: Running
```
```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC
:events: start, finish
:caption: Done (final)
```
### Entry and exit actions
States can declare `entry` / `exit` callbacks, shown in the state label.
```{literalinclude} ../tests/machines/showcase_actions.py
:pyobject: ActionsSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_actions.ActionsSC
:caption: Class (Graphviz)
```
```{statemachine-diagram} tests.machines.showcase_actions.ActionsSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_actions.ActionsSC
:events: power_on
:caption: Active: On
```
### Guard conditions
Transitions can have `cond` guards, shown in brackets on the edge label.
```{literalinclude} ../tests/machines/showcase_guards.py
:pyobject: GuardSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_guards.GuardSC
:caption: Class (Graphviz)
```
```{statemachine-diagram} tests.machines.showcase_guards.GuardSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_guards.GuardSC
:events:
:caption: Active: Pending
```
### Self-transitions
A transition from a state back to itself.
```{literalinclude} ../tests/machines/showcase_self_transition.py
:pyobject: SelfTransitionSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_self_transition.SelfTransitionSC
:caption: Class (Graphviz)
```
```{statemachine-diagram} tests.machines.showcase_self_transition.SelfTransitionSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_self_transition.SelfTransitionSC
:events:
:caption: Active: Counting
```
### Internal transitions
Internal transitions execute actions without exiting/entering the state.
```{literalinclude} ../tests/machines/showcase_internal.py
:pyobject: InternalSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_internal.InternalSC
:caption: Class (Graphviz)
```
```{statemachine-diagram} tests.machines.showcase_internal.InternalSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_internal.InternalSC
:events:
:caption: Active: Monitoring
```
### Compound states
A compound state contains child states. Entering the compound activates
its initial child.
```{literalinclude} ../tests/machines/showcase_compound.py
:pyobject: CompoundSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC
:caption: Class (Graphviz)
:target:
```
```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC
:events:
:caption: Off
:target:
```
```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC
:events: turn_on
:caption: Active/Idle
:target:
```
```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC
:events: turn_on, begin
:caption: Active/Working
:target:
```
### Parallel states
A parallel state activates all its regions simultaneously.
```{literalinclude} ../tests/machines/showcase_parallel.py
:pyobject: ParallelSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC
:caption: Class (Graphviz)
:target:
```
```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC
:events: enter
:caption: Both active
:target:
```
```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC
:events: enter, go_l
:caption: Left done
:target:
```
### Parallel with cross-boundary transitions
A transition targeting a compound state **inside** a parallel region triggers a
rendering bug in Mermaid (`mermaid-js/mermaid#4052`). The Mermaid renderer works
around this by redirecting the arrow to the compound's initial child — compare the
``rebuild`` arrow in both diagrams below.
```{literalinclude} ../tests/machines/showcase_parallel_compound.py
:pyobject: ParallelCompoundSC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC
:caption: Class (Graphviz) — ``rebuild`` points to the Build compound border
:target:
```
```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC
:format: mermaid
:caption: Class (Mermaid) — ``rebuild`` is redirected to Compile (initial child of Build)
```
```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC
:events: start, do_build
:caption: Build done
:target:
```
```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC
:events: start, do_build, do_test
:caption: Pipeline done → Review
:target:
```
### History states (shallow)
A history pseudo-state remembers the last active child of a compound state.
```{literalinclude} ../tests/machines/showcase_history.py
:pyobject: HistorySC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_history.HistorySC
:caption: Class (Graphviz)
:target:
```
```{statemachine-diagram} tests.machines.showcase_history.HistorySC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_history.HistorySC
:events: begin, advance
:caption: Step2
:target:
```
```{statemachine-diagram} tests.machines.showcase_history.HistorySC
:events: begin, advance, pause
:caption: Paused
:target:
```
```{statemachine-diagram} tests.machines.showcase_history.HistorySC
:events: begin, advance, pause, resume
:caption: Resumed (→Step2)
:target:
```
### Deep history
Deep history remembers the exact leaf state across nested compounds.
```{literalinclude} ../tests/machines/showcase_deep_history.py
:pyobject: DeepHistorySC
:language: python
```
```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC
:caption: Class (Graphviz)
:target:
```
```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC
:format: mermaid
:caption: Class (Mermaid)
```
```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC
:events: dive, enter_inner, go
:caption: Inner/B
:target:
```
```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC
:events: dive, enter_inner, go, leave, restore
:caption: Restored (→Inner/B)
:target:
```
================================================
FILE: docs/error_handling.md
================================================
(error-handling)=
# Error handling
```{seealso}
New to statecharts? See [](concepts.md) for an overview of how states,
transitions, events, and actions fit together.
```
What happens when a callback raises an exception during a transition?
With `StateChart`, errors in actions are caught by the engine and dispatched
as `error.execution` internal events — so the machine itself can react to
failures by transitioning to an error state, retrying, or recovering. This
follows the [SCXML error handling specification](https://www.w3.org/TR/scxml/#errorsAndEvents).
```{tip}
`catch_errors_as_events` is a class attribute that controls this behavior.
`StateChart` uses `True` by default (SCXML-compliant); set it to `False`
to let exceptions propagate to the caller instead. See {ref}`behaviour`
for the full comparison of behavioral attributes and how to customize them.
```
(error-execution)=
## How errors are caught
When an action raises during a {ref}`microstep <macrostep-microstep>`, the
engine catches the exception at the **block level**. Each phase of the
microstep is an independent block:
| Block | Callbacks |
|---|---|
| Exit | `on_exit_state()`, `on_exit_<state>()` |
| On | `on_transition()`, `on_<event>()` |
| Enter | `on_enter_state()`, `on_enter_<state>()` |
An error in one block:
- **Stops remaining actions in that block** — per the SCXML spec, execution
MUST NOT continue within the same block after an error.
- **Does not affect other blocks** — subsequent phases of the microstep
still execute. In particular, **`after` callbacks always run** regardless
of errors in earlier blocks.
This means that even if a transition's `on` action raises, the target states
are still entered and `after_<event>()` callbacks still run. The error is
caught and queued as an `error.execution` internal event that fires within
the same {ref}`macrostep <macrostep-microstep>`.
```{note}
`before` callbacks run before any state changes, so an error in `before`
prevents the transition from executing — but `after` still runs because
it belongs to a separate block.
```
## The `error.execution` event
After catching an error, the engine places an `error.execution` event on the
internal queue. You can define transitions for this event to handle errors
within the state machine itself — transitioning to error states, logging, or
recovering.
### The `error_` naming convention
Since Python identifiers cannot contain dots, any event attribute starting
with `error_` automatically matches both the underscore and dot-notation
forms. For example, `error_execution` matches both `"error_execution"` and
`"error.execution"`:
```py
>>> from statemachine import State, StateChart
>>> class ResilientChart(StateChart):
... operational = State(initial=True)
... broken = State(final=True)
...
... do_work = operational.to(operational, on="risky_action")
... error_execution = operational.to(broken)
...
... def risky_action(self):
... raise RuntimeError("something went wrong")
>>> sm = ResilientChart()
>>> sm.send("do_work")
>>> "broken" in sm.configuration_values
True
```
```{note}
If you provide an explicit `id=` parameter on the `Event`, it takes
precedence and the naming convention is not applied.
```
### Accessing error data
The original exception is available as `error` in the keyword arguments
of callbacks on the `error.execution` transition. Use
{ref}`dependency injection <dependency-injection>` to receive it:
```py
>>> from statemachine import State, StateChart
>>> class ErrorLogger(StateChart):
... running = State(initial=True)
... failed = State(final=True)
...
... process = running.to(running, on="do_process")
... error_execution = running.to(failed, on="log_error")
...
... def do_process(self):
... raise ValueError("bad data")
...
... def log_error(self, error):
... self.last_error = error
>>> sm = ErrorLogger()
>>> sm.send("process")
>>> str(sm.last_error)
'bad data'
```
### Error in error handler
If the `error.execution` handler itself raises, the engine **ignores** the
second error (logging a warning) to prevent infinite loops. The machine
remains in whatever configuration it reached before the failed handler.
```{note}
During `error.execution` processing, errors in transition `on` content
are **not** caught at block level — they propagate to the microstep where
they are silently discarded. This prevents infinite loops when an error
handler's own action raises (e.g., a self-transition
`error_execution = s1.to(s1, on="handler")` where `handler` raises).
Entry/exit blocks still use block-level catching regardless of the
current event.
```
(error-handling-cleanup-finalize)=
## Cleanup / finalize pattern
A common need is to run cleanup code after a transition **regardless of
success or failure** — releasing a lock, closing a connection, or clearing
temporary state.
Because errors are caught at the block level, `after_<event>()` callbacks
always run — making them a natural **finalize** hook, similar to Python's
`try/finally`:
```py
>>> from statemachine import Event, State, StateChart
>>> class ResourceManager(StateChart):
... idle = State(initial=True)
... working = State()
... recovering = State()
...
... start = idle.to(working)
... done = working.to(idle)
... recover = recovering.to(idle)
... error_execution = Event(working.to(recovering), id="error.execution")
...
... def __init__(self, should_fail=False):
... self.should_fail = should_fail
... self.released = False
... super().__init__()
...
... def on_enter_working(self):
... if self.should_fail:
... raise RuntimeError("something went wrong")
... self.raise_("done")
...
... def after_start(self):
... self.released = True # always runs — finalize hook
...
... def on_enter_recovering(self, error):
... self.last_error = error
... self.raise_("recover")
```
On the **success** path, the machine transitions `idle → working → idle`
and `after_start` releases the resource:
```py
>>> sm = ResourceManager(should_fail=False)
>>> sm.send("start")
>>> "idle" in sm.configuration_values
True
>>> sm.released
True
```
On the **failure** path, the action raises, but `after_start` **still runs**.
Then `error.execution` fires, transitions to `recovering`, and auto-recovers
back to `idle`:
```py
>>> sm = ResourceManager(should_fail=True)
>>> sm.send("start")
>>> "idle" in sm.configuration_values
True
>>> sm.released # finalize ran despite the error
True
>>> str(sm.last_error)
'something went wrong'
```
```{seealso}
See {ref}`sphx_glr_auto_examples_statechart_cleanup_machine.py` for a
more detailed version of this pattern with annotated output.
```
## Validators do not trigger error events
{ref}`Validators <validators>` operate in the **transition-selection** phase,
before any state changes occur. Their exceptions **always propagate** to the
caller — they are never caught by the engine and never converted to
`error.execution` events, regardless of the `catch_errors_as_events` setting.
This is intentional: a validator rejection means the transition should not
happen at all. It is semantically equivalent to a condition returning
`False`, but communicates the reason via an exception.
```{seealso}
See {ref}`validators` for examples and the full semantics of validator
propagation.
```
```{seealso}
See {ref}`behaviour` for the full comparison of behavioral attributes
and how to customize `catch_errors_as_events` and other settings.
See {ref}`actions` for the callback execution order within each
microstep.
```
================================================
FILE: docs/events.md
================================================
(events)=
(event)=
# Events
```{seealso}
New to statecharts? See [](concepts.md) for an overview of how states,
transitions, events, and actions fit together.
```
An **event** is a named signal that drives the state machine forward. When you
assign a transition to a class-level name, that name becomes an event — the
library creates an `Event` object automatically. Events are the external
interface of your machine: callers send event names, and the machine decides
which transitions to take.
(declaring-events)=
## Declaring events
The simplest way to declare an event is by assigning a transition to a name:
```py
>>> from statemachine import Event, State, StateChart
>>> class SimpleSM(StateChart):
... initial = State(initial=True)
... final = State(final=True)
...
... start = initial.to(final)
>>> isinstance(SimpleSM.start, Event)
True
```
The name `start` is automatically converted to an `Event` with
`id="start"`. Multiple transitions can share the same event using
the `|` operator:
```py
>>> class TrafficLight(StateChart):
... green = State(initial=True)
... yellow = State()
... red = State()
...
... cycle = green.to(yellow) | yellow.to(red) | red.to(green)
>>> sm = TrafficLight()
>>> sm.send("cycle")
>>> sm.yellow.is_active
True
```
For better IDE support (autocompletion, type checking) or to set a
human-readable display name, use the `Event` class explicitly:
```py
>>> class SimpleSM(StateChart):
... initial = State(initial=True)
... final = State(final=True)
...
... start = Event(initial.to(final), name="Start the machine")
>>> SimpleSM.start.name
'Start the machine'
>>> SimpleSM.start.id
'start'
```
(event-identity)=
## Event identity: `id` vs `name`
Every event has two string properties:
- **`id`** — the programmatic identifier, derived from the class attribute name.
Use this in `send()`, guards, and comparisons.
- **`name`** — a human-readable label for display purposes. Auto-generated from
the `id` by replacing `_` and `.` with spaces and capitalizing the first word.
You can override the automatic name by passing `name=` explicitly when
declaring the event:
```py
>>> TrafficLight.cycle.id
'cycle'
>>> TrafficLight.cycle.name
'Cycle'
>>> class Example(StateChart):
... on = State(initial=True)
... off = State(final=True)
... shut_down = Event(on.to(off), name="Shut the system down")
>>> Example.shut_down.name
'Shut the system down'
```
```{tip}
Always use `event.id` for programmatic checks. The `name` property is intended
for UI display and may differ from the `id`.
```
(triggering-events)=
(triggering events)=
## Triggering events
Once declared, events are triggered on a {ref}`StateChart <statechart>` instance
in two ways:
- **As a method call:** `sm.cycle()` — when the event name is known at
development time.
- **Via `send()`:** `sm.send("cycle")` — when the event name is dynamic (e.g.,
from user input, a message queue, or a data file).
Both styles produce the same result. The machine evaluates
{ref}`guard conditions <validators and guards>`, executes {ref}`actions`, and
updates the {ref}`configuration <querying-configuration>`.
```{seealso}
See {ref}`sending-events` for the full runtime API — `send()`, `raise_()`,
delayed events, and cancellation.
```
(event-parameter)=
## The `event` parameter on transitions
Each transition accepts an optional `event` parameter that binds it to a
specific event, overriding the default (which is the class-level attribute
name). This lets individual transitions within a group respond to their own
event identifiers:
```py
>>> from statemachine import Event, State, StateChart
>>> class TrafficLightMachine(StateChart):
... green = State(initial=True)
... yellow = State()
... red = State()
...
... slowdown = Event(name="Slowing down")
...
... cycle = Event(
... green.to(yellow, event=slowdown)
... | yellow.to(red, event="stop")
... | red.to(green, event="go"),
... name="Loop",
... )
>>> sm = TrafficLightMachine()
>>> sm.send("cycle") # umbrella event — dispatches green→yellow
>>> sm.yellow.is_active
True
>>> sm.send("stop") # individual event — dispatches yellow→red
>>> sm.red.is_active
True
>>> sm.send("go") # individual event — dispatches red→green
>>> sm.green.is_active
True
```
The `event` parameter accepts a string, an `Event` instance, a reference
to a previously declared `Event` (like `slowdown` above), or a **list** of
any of these. A space-separated string is also accepted and split into
individual events automatically:
```py
>>> class MultiEvent(StateChart):
... a = State(initial=True)
... b = State(final=True)
...
... # Both forms are equivalent — the transition responds to "move", "go" and "start"
... move = a.to(b, event=["go", "start"])
>>> sm = MultiEvent()
>>> sm.send("move")
>>> sm.b.is_active
True
>>> sm = MultiEvent()
>>> sm.send("go")
>>> sm.b.is_active
True
>>> sm = MultiEvent()
>>> sm.send("start")
>>> sm.b.is_active
True
```
```{tip}
This is an advanced feature. Most state machines only need the simple
`name = source.to(target)` form. Use the `event` parameter when you need
fine-grained control over event routing within a composite transition group.
```
(done-state-events)=
## Automatic events
The engine generates certain events automatically in response to structural
changes. You don't send these yourself — you define transitions that react
to them.
### `done.state` events
```{versionadded} 3.0.0
```
When a {ref}`compound state's <compound-states>` final child is entered, the
engine queues a `done.state.{parent_id}` internal event. Define a transition
for this event to react when a compound's work is complete:
```py
>>> from statemachine import State, StateChart
>>> class QuestWithDone(StateChart):
... class quest(State.Compound):
... traveling = State(initial=True)
... arrived = State(final=True)
... finish = traveling.to(arrived)
... celebration = State(final=True)
... done_state_quest = quest.to(celebration)
>>> sm = QuestWithDone()
>>> sm.send("finish")
>>> set(sm.configuration_values) == {"celebration"}
True
```
For {ref}`parallel states <parallel-states>`, the `done.state` event fires
only when **all** regions have reached a final state:
```py
>>> from statemachine import State, StateChart
>>> class WarWithDone(StateChart):
... class war(State.Parallel):
... class quest(State.Compound):
... start_q = State(initial=True)
... end_q = State(final=True)
... finish_q = start_q.to(end_q)
... class battle(State.Compound):
... start_b = State(initial=True)
... end_b = State(final=True)
... finish_b = start_b.to(end_b)
... peace = State(final=True)
... done_state_war = war.to(peace)
>>> sm = WarWithDone()
>>> sm.send("finish_q")
>>> "war" in sm.configuration_values
True
>>> sm.send("finish_b")
>>> set(sm.configuration_values) == {"peace"}
True
```
(donedata)=
#### DoneData
Final states can carry data to their `done.state` handlers via the `donedata`
parameter. The value should be a callable (or method name string) that returns
a `dict`, which is forwarded as keyword arguments to the transition handler:
```py
>>> from statemachine import Event, State, StateChart
>>> class QuestCompletion(StateChart):
... class quest(State.Compound):
... traveling = State(initial=True)
... completed = State(final=True, donedata="get_result")
... finish = traveling.to(completed)
... def get_result(self):
... return {"hero": "frodo", "outcome": "victory"}
... epilogue = State(final=True)
... done_state_quest = Event(quest.to(epilogue, on="capture_result"))
... def capture_result(self, hero=None, outcome=None, **kwargs):
... self.result = f"{hero}: {outcome}"
>>> sm = QuestCompletion()
>>> sm.send("finish")
>>> sm.result
'frodo: victory'
```
```{note}
`donedata` can only be specified on `final=True` states. Attempting to use it
on a non-final state raises `InvalidDefinition`.
```
### `error.execution` events
When a callback raises during a macrostep and
{ref}`catch_errors_as_events <behaviour>` is enabled, the engine dispatches an
`error.execution` internal event. Define a transition for this event to
recover from errors within the statechart:
```py
>>> from statemachine import State, StateChart
>>> class ResilientChart(StateChart):
... working = State(initial=True)
... failed = State(final=True)
...
... go = working.to.itself(on="do_work")
... error_execution = working.to(failed)
...
... def do_work(self):
... raise RuntimeError("something went wrong")
>>> sm = ResilientChart()
>>> sm.send("go")
>>> "failed" in sm.configuration_values
True
```
```{seealso}
See {ref}`error-execution` for the full error handling reference: recovery
patterns, `after` as a finalize hook, and nested error scenarios.
```
(naming-conventions)=
## Dot-notation naming conventions
SCXML uses dot-separated event names (`done.state.quest`, `error.execution`),
but Python identifiers cannot contain dots. The library provides prefix-based
naming conventions that automatically register both forms:
(done-state-convention)=
### `done_state_` prefix
Any event attribute starting with `done_state_` matches both the underscore
form and the dot-notation form. Only the prefix is replaced — the suffix is
kept as-is, preserving multi-word state names:
| Attribute name | Matches event names |
|-------------------------------|---------------------|
| `done_state_quest` | `"done_state_quest"` and `"done.state.quest"` |
| `done_state_lonely_mountain` | `"done_state_lonely_mountain"` and `"done.state.lonely_mountain"` |
### `error_` prefix
Any event attribute starting with `error_` matches both the underscore form
and the dot-notation form. Unlike `done_state_`, **all** underscores after
the prefix are replaced with dots:
| Attribute name | Matches event names |
|----------------------|---------------------|
| `error_execution` | `"error_execution"` and `"error.execution"` |
```{note}
If you provide an explicit `id=` parameter on the `Event`, it takes precedence
and the naming convention is not applied.
```
================================================
FILE: docs/guards.md
================================================
(validators-and-guards)=
(validators and guards)=
# Conditions
```{seealso}
New to statecharts? See [](concepts.md) for an overview of how states,
transitions, events, and actions fit together.
```
Conditions and validators are checked **before** a transition starts — they
decide whether the transition is allowed to proceed.
The difference is in how they communicate rejection:
| Mechanism | Rejects by | Use when |
|---|---|---|
| {ref}`Conditions <conditions>` (`cond` / `unless`) | Returning a falsy value | You want the engine to silently skip the transition and try the next one. |
| {ref}`Validators <validators>` | Raising an exception | You want the caller to know *why* the transition was rejected. |
Both run in the **transition-selection** phase, before any state changes
occur. See the {ref}`execution order <actions>` table for where they fit in
the callback sequence.
(guards)=
(conditions)=
## Conditions
A **condition** (also known as a _guard_) is a boolean predicate attached to a
transition. When an event arrives, the engine checks each candidate transition
in {ref}`declaration order <transition-priority>` — the first transition whose
conditions are all satisfied is selected. If none match, the event is either
ignored or raises an exception (see `allow_event_without_transition` in the
{ref}`behaviour reference <behaviour>`).
```{important}
A condition must not have side effects. Side effects belong in
{ref}`actions`.
```
There are two guard clause variants:
`cond`
: A list of condition expressions. The transition is allowed only if **all**
evaluate to `True`.
- Single: `cond="is_valid"`
- Multiple: `cond=["is_valid", "has_stock"]`
`unless`
: Same as `cond`, but the transition is allowed only if **all** evaluate to
`False`.
- Single: `unless="is_blocked"`
- Multiple: `unless=["is_blocked", "is_expired"]`
```py
>>> from statemachine import State, StateChart
>>> class ApprovalFlow(StateChart):
... 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 = ApprovalFlow()
>>> sm.send("approve") # cond is False — no transition
>>> "pending" in sm.configuration_values
True
>>> sm.is_manager = True
>>> sm.send("approve")
>>> "approved" in sm.configuration_values
True
```
### Multiple conditional transitions
When multiple transitions share the same event, guards let the engine pick the
right one at runtime. Transitions are checked in **declaration order** (the
order of `.to()` calls), not the order they appear in the `|` composition:
```py
>>> class PriorityRouter(StateChart):
... inbox = State(initial=True)
... urgent = State(final=True)
... normal = State(final=True)
... low = State(final=True)
...
... # Declaration order = evaluation order
... route = (
... inbox.to(urgent, cond="is_urgent")
... | inbox.to(normal, cond="is_normal")
... | inbox.to(low) # fallback — no condition
... )
...
... def is_urgent(self, priority=0, **kwargs):
... return priority >= 9
...
... def is_normal(self, priority=0, **kwargs):
... return priority >= 5
>>> sm = PriorityRouter()
>>> sm.send("route", priority=2)
>>> "low" in sm.configuration_values # fallback
True
>>> sm = PriorityRouter()
>>> sm.send("route", priority=7)
>>> "normal" in sm.configuration_values
True
>>> sm = PriorityRouter()
>>> sm.send("route", priority=10)
>>> "urgent" in sm.configuration_values # checked first
True
```
Condition methods receive the same keyword arguments passed to `send()` via
{ref}`dependency injection <dependency-injection>` — declare only the
parameters you need.
```{seealso}
See {ref}`sphx_glr_auto_examples_air_conditioner_machine.py` for another
example combining multiple transitions on the same event.
```
(condition expressions)=
### Condition expressions
Conditions support a mini-language for boolean expressions, allowing guards
to be defined as strings that reference attributes on the state machine, its
model, or registered {ref}`listeners <listeners>`.
The mini-language is based on Python's built-in
[`ast`](https://docs.python.org/3/library/ast.html) parser, so the syntax
is familiar:
```py
>>> class AccessControl(StateChart):
... locked = State(initial=True)
... unlocked = State(final=True)
...
... unlock = locked.to(unlocked, cond="has_key and not is_locked_out")
...
... has_key = True
... is_locked_out = False
>>> sm = AccessControl()
>>> sm.send("unlock")
>>> "unlocked" in sm.configuration_values
True
```
```{tip}
All condition expressions are validated when the `StateChart` class is
created. Invalid attribute names raise `InvalidDefinition` immediately,
helping you catch typos early.
```
#### Syntax elements
**Names** refer to attributes on the state machine instance, its model, or
listeners. They can point to properties, attributes, or methods:
- `is_active` — evaluated as `self.is_active` (property/attribute)
- `check_stock` — if it's a method, it's called with
{ref}`dependency injection <dependency-injection>`
**Boolean operators** (highest to lowest precedence):
1. `not` / `!` — Logical negation
2. `and` / `^` — Logical conjunction
3. `or` / `v` — Logical disjunction
**Comparison operators:**
`>`, `>=`, `==`, `!=`, `<`, `<=`
**Parentheses** control evaluation order:
```python
cond="(is_admin or is_moderator) and not is_banned"
```
#### Expression examples
- `is_logged_in and has_permission`
- `not is_active or is_admin`
- `!(is_guest ^ has_access)`
- `(is_admin or is_moderator) and !is_banned`
- `count > 0 and count <= 10`
```{seealso}
See {ref}`sphx_glr_auto_examples_lor_machine.py` for a complete example
using boolean algebra in conditions.
```
(checking enabled events)=
### Checking enabled events
The {ref}`allowed_events <querying-events>` property returns events
reachable from the current state based on topology alone — it does
**not** evaluate guards. To check which events currently have their
conditions satisfied, use `enabled_events()`:
```py
>>> class TaskMachine(StateChart):
... idle = State(initial=True)
... running = State(final=True)
...
... start = idle.to(running, cond="has_enough_resources")
...
... def has_enough_resources(self, cpu=0, **kwargs):
... return cpu >= 4
>>> sm = TaskMachine()
>>> [e.id for e in sm.allowed_events]
['start']
>>> sm.enabled_events()
[]
>>> [e.id for e in sm.enabled_events(cpu=8)]
['start']
```
`enabled_events()` accepts `*args` / `**kwargs` that are forwarded to the
condition callbacks, just like when triggering an event. This makes it
useful for UI scenarios where you want to show or hide buttons based on
whether an event's conditions are currently satisfied.
```{note}
An event is considered **enabled** if at least one of its transitions from
the current state has all conditions satisfied. If a condition raises an
exception, the event is treated as enabled (permissive behavior).
```
(validators)=
## Validators
Validators are imperative guards that **raise an exception** to reject a
transition. While conditions silently skip a transition and let the engine
try the next candidate, validators communicate the rejection reason directly
to the caller.
- Single: `validators="check_stock"`
- Multiple: `validators=["check_stock", "check_credit"]`
```py
>>> class OrderMachine(StateChart):
... pending = State(initial=True)
... confirmed = State(final=True)
...
... confirm = pending.to(confirmed, validators="check_stock")
...
... def check_stock(self, quantity=0, **kwargs):
... if quantity <= 0:
... raise ValueError("Quantity must be positive")
>>> sm = OrderMachine()
>>> sm.send("confirm", quantity=0)
Traceback (most recent call last):
...
ValueError: Quantity must be positive
>>> "pending" in sm.configuration_values # state unchanged
True
>>> sm.send("confirm", quantity=5) # retry with valid data
>>> "confirmed" in sm.configuration_values
True
```
### Validators always propagate
Validator exceptions **always propagate** to the caller, regardless of the
`catch_errors_as_events` flag. This is intentional: validators operate in the
**transition-selection** phase, not the execution phase. A validator that
rejects is semantically equivalent to a condition that returns `False` —
the transition simply should not happen. The difference is that the
validator communicates the reason via an exception.
This means that even when `catch_errors_as_events = True` (the default for
`StateChart`):
- Validator exceptions are **not** converted to `error.execution` events.
- Validator exceptions do **not** trigger `error.execution` transitions.
- The caller receives the exception directly and can handle it with
`try`/`except`.
```py
>>> class GuardedWithErrorHandler(StateChart):
... idle = State(initial=True)
... active = State()
... error_state = State(final=True)
...
... start = idle.to(active, validators="check_input")
... do_work = active.to.itself(on="risky_action")
... error_execution = active.to(error_state)
...
... def check_input(self, value=None, **kwargs):
... if value is None:
... raise ValueError("Input required")
...
... def risky_action(self, **kwargs):
... raise RuntimeError("Boom")
>>> sm = GuardedWithErrorHandler()
>>> sm.send("start")
Traceback (most recent call last):
...
ValueError: Input required
>>> "idle" in sm.configuration_values # NOT in error_state
True
>>> sm.send("start", value="ok")
>>> "active" in sm.configuration_values
True
>>> sm.send("do_work") # action error → goes to error_state
>>> "error_state" in sm.configuration_values
True
```
The validator rejection propagates directly to the caller, while the action
error in `risky_action()` is caught by the engine and routed through the
`error.execution` transition.
### Combining validators and conditions
Validators and conditions can be used together on the same transition.
Validators run **first** — if a validator rejects, conditions are never
evaluated:
```py
>>> class CombinedGuards(StateChart):
... idle = State(initial=True)
... active = State(final=True)
...
... start = idle.to(active, validators="check_auth", cond="has_permission")
...
... has_permission = True
...
... def check_auth(self, token=None, **kwargs):
... if token != "valid":
... raise PermissionError("Invalid token")
>>> sm = CombinedGuards()
>>> sm.send("start", token="bad")
Traceback (most recent call last):
...
PermissionError: Invalid token
>>> sm.send("start", token="valid")
>>> "active" in sm.configuration_values
True
```
```{seealso}
See the example {ref}`sphx_glr_auto_examples_all_actions_machine.py` for
a complete demonstration of validator and condition resolution order.
```
```{hint}
In Python, specific values are considered **falsy** and evaluate as `False`
in a boolean context: `None`, `0`, `0.0`, empty strings, lists, tuples,
sets, and dictionaries, and instances of classes whose `__bool__()` or
`__len__()` returns `False` or `0`.
So `cond=lambda: []` evaluates as `False`.
```
================================================
FILE: docs/how-to/coming_from_state_pattern.md
================================================
(coming-from-state-pattern)=
# Coming from the State Pattern
This guide is for developers familiar with the classic **State Pattern** from the
Gang of Four book (*Design Patterns: Elements of Reusable Object-Oriented Software*).
It walks through a typical State Pattern implementation, discusses its trade-offs,
and shows how to express the same behavior declaratively with python-statemachine.
## The classic State Pattern
The GoF State Pattern models an object whose behavior changes based on its internal
state. The standard recipe has three ingredients:
1. A **Context** class that delegates behavior to a state object.
2. An **abstract State** base class (or protocol) defining the interface.
3. **Concrete State** classes implementing state-specific behavior.
Here is a complete example — an order workflow with four states
(draft, confirmed, shipped, delivered) and a guard condition
(orders can only be confirmed if they have at least one item):
```python
from abc import ABC, abstractmethod
class OrderState(ABC):
"""Abstract base for all order states."""
@abstractmethod
def confirm(self, order):
...
@abstractmethod
def ship(self, order):
...
@abstractmethod
def deliver(self, order):
...
class DraftState(OrderState):
def confirm(self, order):
if order.item_count <= 0:
raise ValueError("Cannot confirm an empty order")
order._state = ConfirmedState()
print("Order confirmed")
def ship(self, order):
raise RuntimeError("Cannot ship a draft order")
def deliver(self, order):
raise RuntimeError("Cannot deliver a draft order")
class ConfirmedState(OrderState):
def confirm(self, order):
raise RuntimeError("Order already confirmed")
def ship(self, order):
order._state = ShippedState()
print("Order shipped")
def deliver(self, order):
raise RuntimeError("Cannot deliver before shipping")
class ShippedState(OrderState):
def confirm(self, order):
raise RuntimeError("Cannot confirm a shipped order")
def ship(self, order):
raise RuntimeError("Order already shipped")
def deliver(self, order):
order._state = DeliveredState()
print("Order delivered")
class DeliveredState(OrderState):
def confirm(self, order):
raise RuntimeError("Order already delivered")
def ship(self, order):
raise RuntimeError("Order already delivered")
def deliver(self, order):
raise RuntimeError("Order already delivered")
class Order:
def __init__(self, item_count=0):
self._state = DraftState()
self.item_count = item_count
def confirm(self):
self._state.confirm(self)
def ship(self):
self._state.ship(self)
def deliver(self):
self._state.deliver(self)
```
This works — but notice how much code it takes for just four states and three events.
## Pros and cons of the classic pattern
**Pros:**
- Encapsulates state-specific behavior in dedicated classes, eliminating large
`if/elif` chains.
- Follows the Open/Closed Principle for adding new states — you create a new class
without modifying existing ones.
- Each state class is independently testable.
**Cons:**
- **Class explosion** — every state requires a full class, even if most methods just
raise "invalid operation" errors. The example above has 4 state classes and 12
method implementations, 9 of which only raise exceptions.
- **Transitions are scattered** — to understand the full workflow you must read every
concrete state class. There is no single place showing all transitions at a glance.
- **No structural validation** — orphaned states, unreachable states, or missing
transitions are only discovered at runtime.
- **Guards are manual** — conditions like "only confirm if items > 0" are embedded in
method bodies, mixed with transition logic.
- **No diagrams** — visualizing the state machine requires manual drawing.
- **No async support** — adding async behavior requires rewriting the entire interface.
- **Signature duplication** — every state class must implement every method, even the
ones that are not valid for that state.
## Porting to python-statemachine
The same order workflow expressed declaratively:
```py
>>> from statemachine import State, StateChart
>>> from statemachine.exceptions import TransitionNotAllowed
>>> class OrderMachine(StateChart):
... allow_event_without_transition = False
...
... # States
... draft = State(initial=True)
... confirmed = State()
... shipped = State()
... delivered = State(final=True)
...
... # Transitions (the complete workflow at a glance)
... confirm = draft.to(confirmed, cond="has_items")
... ship = confirmed.to(shipped)
... deliver = shipped.to(delivered)
...
... item_count = 0
...
... @property
... def has_items(self):
... return self.item_count > 0
>>> sm = OrderMachine()
>>> sm.item_count = 3
>>> sm.send("confirm")
>>> sm.confirmed.is_active
True
>>> sm.send("ship")
>>> sm.shipped.is_active
True
>>> sm.send("deliver")
>>> sm.delivered.is_active
True
```
That is the **entire** state machine — states, transitions, and the guard condition,
all in one place. Setting `allow_event_without_transition = False` gives strict
behavior equivalent to the GoF pattern — invalid events raise
`TransitionNotAllowed`:
```py
>>> sm = OrderMachine()
>>> sm.item_count = 3
>>> try:
... sm.send("ship") # can't ship from draft
... except TransitionNotAllowed:
... print("Blocked: can't ship from draft")
Blocked: can't ship from draft
```
Guards work the same way — when the condition is not met, the transition is
rejected:
```py
>>> sm = OrderMachine()
>>> try:
... sm.send("confirm") # item_count is 0
... except TransitionNotAllowed:
... print("Cannot confirm an empty order")
Cannot confirm an empty order
```
### Going reactive
The strict mode above is a direct equivalent of the GoF pattern. But `StateChart`'s
default (`allow_event_without_transition = True`) follows the SCXML specification:
events that have no valid transition are **skipped**. This makes the
machine reactive — it only responds to events that are meaningful in its current
state, without requiring the caller to know which events are valid:
```py
>>> class ReactiveOrderMachine(StateChart):
... draft = State(initial=True)
... confirmed = State()
... shipped = State()
... delivered = State(final=True)
...
... confirm = draft.to(confirmed, cond="has_items")
... ship = confirmed.to(shipped)
... deliver = shipped.to(delivered)
...
... item_count = 0
...
... @property
... def has_items(self):
... return self.item_count > 0
>>> sm = ReactiveOrderMachine()
>>> sm.item_count = 3
>>> sm.send("ship") # no transition for "ship" from draft — skipped
>>> sm.draft.is_active # still in draft
True
>>> sm.send("confirm") # this one is valid
>>> sm.confirmed.is_active
True
```
This is particularly useful when the machine receives events from external sources
(message queues, UI frameworks, network protocols) where the sender doesn't track
the machine's current state. See {ref}`behaviour` for a comparison of all
class-level defaults.
### Adding callbacks
State-specific behavior (e.g., sending notifications) uses naming conventions
or inline declarations — no need to scatter logic across state classes:
```py
>>> from statemachine import State, StateChart
>>> class OrderWithCallbacks(StateChart):
... draft = State(initial=True)
... confirmed = State()
... shipped = State()
... delivered = State(final=True)
...
... confirm = draft.to(confirmed, cond="has_items")
... ship = confirmed.to(shipped)
... deliver = shipped.to(delivered)
...
... item_count = 0
...
... def __init__(self, **kwargs):
... self.log = []
... super().__init__(**kwargs)
...
... @property
... def has_items(self):
... return self.item_count > 0
...
... def on_enter_confirmed(self):
... self.log.append("confirmed")
...
... def on_enter_shipped(self):
... self.log.append("shipped")
...
... def on_enter_delivered(self):
... self.log.append("delivered")
>>> sm = OrderWithCallbacks()
>>> sm.item_count = 2
>>> sm.send("confirm")
>>> sm.send("ship")
>>> sm.send("deliver")
>>> sm.log
['confirmed', 'shipped', 'delivered']
```
### Structural validation catches design errors
Imagine a new requirement: orders can be cancelled from `draft` or `confirmed`.
With the GoF pattern, a developer adds a `CancelledState` class — but forgets to
wire the transitions in `DraftState` and `ConfirmedState`. The code compiles and
runs fine; the bug only surfaces when someone tries to cancel an order and
discovers there is no way to reach `CancelledState`. In a large codebase with
dozens of states, this kind of mistake can go unnoticed for a long time.
python-statemachine catches this at **class definition time**:
```py
>>> from statemachine import State, StateChart
>>> from statemachine.exceptions import InvalidDefinition
>>> try:
... class BrokenOrderMachine(StateChart):
... draft = State(initial=True)
... confirmed = State()
... shipped = State()
... delivered = State(final=True)
... cancelled = State(final=True) # added but never connected
...
... confirm = draft.to(confirmed)
... ship = confirmed.to(shipped)
... deliver = shipped.to(delivered)
... except InvalidDefinition as e:
... print(e)
There are unreachable states. ...Disconnected states: ['cancelled']
```
The fix is to declare the missing transitions — and now the full workflow is
visible in a single glance:
```py
>>> class FixedOrderMachine(StateChart):
... draft = State(initial=True)
... confirmed = State()
... shipped = State()
... delivered = State(final=True)
... cancelled = State(final=True)
...
... confirm = draft.to(confirmed)
... ship = confirmed.to(shipped)
... deliver = shipped.to(delivered)
... cancel = draft.to(cancelled) | confirmed.to(cancelled)
>>> sm = FixedOrderMachine()
>>> sm.send("cancel")
>>> sm.cancelled.is_active
True
```
## Side-by-side comparison
| Concept | State Pattern (GoF) | python-statemachine |
|---|---|---|
| State definition | One class per state | `State()` class attribute |
| Transition | Method in source state class sets `_state` | `.to()` declaration |
| Guard / condition | `if` check inside method body | `cond=` / `unless=` parameter |
| Invalid transition | Manual `raise` in every method | `TransitionNotAllowed` or skipped ({ref}`configurable <behaviour>`) |
| All transitions | Scattered across state classes | Visible in the class body |
| Context / model | Separate `Order` class | `StateChart` itself (or `model=`) |
| Adding a new state | New class + update all interfaces | New `State()` attribute + transitions |
| Entry / exit actions | Manual in transition methods | `on_enter_<state>()` / `on_exit_<state>()` |
| Diagrams | Manual | Built-in `_graph()` |
| Validation | None (runtime errors only) | Definition-time structural checks |
| Async support | Rewrite entire interface | Auto-detected from `async def` |
| Dependency injection | Not available | Built-in via `SignatureAdapter` |
## What you gain
By moving from the State Pattern to python-statemachine, you get:
- **Declarative definition** — the entire workflow is visible in one class body.
- **Structural validation** — unreachable states, missing transitions, and unresolved
callbacks are caught before the machine ever runs
(see {ref}`validations`).
- **Automatic diagrams** — call `_graph()` on any instance to generate a Graphviz
diagram (see {ref}`diagrams`).
- **Guards and conditions** — use `cond=`, `unless=`, or
{ref}`expression strings <condition expressions>` instead of manual `if` checks.
- **Dependency injection** — callbacks receive only the parameters they declare
(see {ref}`actions`).
- **Async support** — define `async def` callbacks and the engine auto-switches to
async processing (see {ref}`async`).
- **Listeners** — attach cross-cutting concerns (logging, auditing) as separate
objects without modifying the state machine
(see {ref}`listeners`).
- **No class explosion** — four states and three events require one class with a few
attributes, not four classes with twelve methods.
================================================
FILE: docs/how-to/coming_from_transitions.md
================================================
(coming-from-transitions)=
# Coming from pytransitions
This guide helps users of the [*transitions*](https://github.com/pytransitions/transitions)
library migrate to python-statemachine (or evaluate the differences). Code examples are
shown side by side where possible. For a quick overview, jump to the
{ref}`feature matrix <feature-matrix>`.
## At a glance
| Aspect | *transitions* | python-statemachine |
|---|---|---|
| Definition style | Imperative (dicts/lists passed to `Machine`) | Declarative (class-level `State` and `.to()`) |
| State definition | Strings or `State` objects in a list | Class attributes (`State(...)`) |
| Transition definition | `add_transition()` / dicts | `.to()` chaining, `\|` composition |
| Event triggers | Auto-generated methods on the model | `sm.send("event")` or `sm.event()` |
| Callbacks | String names or callables, per-transition | Naming conventions + decorators, {ref}`dependency injection <dependency-injection>` |
| Conditions | `conditions=`, `unless=` | `cond=`, `unless=`, {ref}`expression strings <condition expressions>` |
| Nested states | `HierarchicalMachine` + separator strings | `State.Compound` / `State.Parallel` nested classes |
| Completion events | `on_final` callback only | `done.state` / `done.invoke` {ref}`automatic events <done-state-events>` with `donedata` |
| Invoke | No | {ref}`Background work <invoke>` tied to state lifecycle |
| Async | Separate `AsyncMachine` class | {ref}`Auto-detected <async>` from `async def` callbacks |
| API surface | [12 Machine classes](https://github.com/pytransitions/transitions#-extensions) to combine features | {ref}`Single StateChart class <unified-api>` — all features built in |
| Diagrams | `GraphMachine` (separate base class) | Built-in {ref}`_graph() <diagrams>` on every instance |
| Model binding | `Machine(model=obj)` | {ref}`MachineMixin <machinemixin>` or `model=` parameter |
| Listeners | Machine-level callbacks only | Full {ref}`observer pattern <listeners>` (class-level, constructor, runtime) |
| Error handling | Exceptions propagate | Optional {ref}`catch_errors_as_events <error-execution>` (`error.execution`) |
| Validations | None | {ref}`Structural + callback checks <validations>` at definition and creation time |
| SCXML compliance | [Not a goal](https://github.com/pytransitions/transitions/issues/446#issuecomment-646837282) | {ref}`W3C conformant <processing-model>` with automated test suite |
| Processing model | Immediate or queued | Always queued ({ref}`run-to-completion <rtc-model>`) |
## Defining states
In *transitions*, states are defined as strings or dicts passed to the `Machine` constructor.
States can exist without any transitions — the library does not validate structural
consistency:
```python
from transitions import Machine
states = ["draft", "producing", "closed"]
machine = Machine(states=states, initial="draft")
# No transitions defined — "producing" and "closed" are unreachable, but no error is raised
```
In python-statemachine, states are class-level descriptors and **transitions are
required**. The library validates structural integrity at class definition time —
states without transitions are rejected:
```py
>>> from statemachine import State, StateChart
>>> from statemachine.exceptions import InvalidDefinition
>>> try:
... class BadWorkflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
... except InvalidDefinition as e:
... print(e)
There are unreachable states. ...Disconnected states: [...]
```
A valid definition requires transitions connecting all states:
```py
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
... produce = draft.to(producing)
... deliver = producing.to(closed)
>>> sm = Workflow()
>>> sm.draft.is_active
True
```
States are first-class objects with properties like `is_active`, `value`, and `id`.
You can set a human-readable name and a persistence value directly on the state.
See {ref}`states` for the full reference.
```py
>>> producing = State("Being produced", value=2)
```
### Flat vs compound definitions
In *transitions*, flat and hierarchical machines are **separate classes**. To use
compound states you must switch from `Machine` to `HierarchicalMachine` and define
the hierarchy through nested dicts — states and their children are described far from
the transitions that connect them:
```python
from transitions.extensions import HierarchicalMachine
states = [
"idle",
{
"name": "active",
"children": [
{"name": "working", "on_enter": "start_work"},
{"name": "paused"},
],
"initial": "working",
},
"done",
]
transitions = [
{"trigger": "start", "source": "idle", "dest": "active"},
{"trigger": "pause", "source": "active_working", "dest": "active_paused"},
{"trigger": "resume", "source": "active_paused", "dest": "active_working"},
{"trigger": "finish", "source": "active", "dest": "done"},
]
machine = HierarchicalMachine(states=states, transitions=transitions, initial="idle")
```
Note how child states are referenced with separator-based names (`active_working`,
`active_paused`) and the structure is split across two separate data structures.
In python-statemachine, `StateChart` handles both flat and compound machines. Compound
states are nested Python classes that act as **namespaces** — children, transitions,
and callbacks are declared together in the class body, mirroring the state hierarchy
directly in code:
```py
>>> from statemachine import State, StateChart
>>> class TaskMachine(StateChart):
... idle = State(initial=True)
...
... class active(State.Compound):
... working = State(initial=True)
... paused = State()
... pause = working.to(paused)
... resume = paused.to(working)
...
... def on_enter_working(self):
... self.started = True
...
... done = State(final=True)
...
... start = idle.to(active)
... finish = active.to(done)
>>> sm = TaskMachine()
>>> sm.send("start")
>>> sm.started
True
>>> sm.send("pause")
>>> "paused" in sm.configuration_values
True
>>> sm.send("resume")
>>> sm.send("finish")
>>> sm.done.is_active
True
```
Each compound class is self-contained: its children, internal transitions, and callbacks
live inside the same block. This scales naturally to deeper hierarchies and parallel
regions without switching to a different API.
python-statemachine also supports hierarchical features not available in *transitions*:
- {ref}`History pseudo-states <history-states>` (`HistoryState`) — remember and restore previous child states
- {ref}`Eventless transitions <eventless>` — fire automatically when their guard condition is met
See {ref}`compound-states` and {ref}`parallel-states` for the full reference.
### Creating machines from dicts
If you prefer the dict-based definition style familiar from *transitions*, you can
use {func}`~statemachine.io.create_machine_class_from_definition` to build a
`StateChart` dynamically. It supports states, transitions, conditions, and
callbacks (`on`, `before`, `after`, `enter`, `exit`):
```py
>>> from statemachine.io import create_machine_class_from_definition
>>> TrafficLight = create_machine_class_from_definition(
... "TrafficLight",
... states={
... "green": {
... "initial": True,
... "on": {"change": [{"target": "yellow"}]},
... },
... "yellow": {
... "on": {"change": [{"target": "red"}]},
... },
... "red": {
... "on": {"change": [{"target": "green"}]},
... },
... },
... )
>>> sm = TrafficLight()
>>> sm.send("change")
>>> sm.yellow.is_active
True
>>> sm.send("change")
>>> sm.red.is_active
True
```
The result is a regular `StateChart` subclass — all features (validations, diagrams,
listeners, async) work exactly the same way. See
{func}`~statemachine.io.create_machine_class_from_definition` for the full API.
## Defining transitions
*transitions* uses dicts or `add_transition()`:
```python
transitions = [
{"trigger": "produce", "source": "draft", "dest": "producing"},
{"trigger": "deliver", "source": "producing", "dest": "closed"},
{"trigger": "cancel", "source": ["draft", "producing"], "dest": "cancelled"},
]
machine = Machine(states=states, transitions=transitions, initial="draft")
```
python-statemachine uses `.to()` with `|` for composing multiple origins:
```py
>>> from statemachine import State, StateChart
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
... cancelled = State(final=True)
...
... produce = draft.to(producing)
... deliver = producing.to(closed)
... cancel = draft.to(cancelled) | producing.to(cancelled)
>>> sm = Workflow()
>>> sm.send("produce")
>>> sm.producing.is_active
True
```
The `|` operator composes transitions from different sources into a single event.
You can also use `from_()` to express the same thing from the target's perspective.
See {ref}`transitions` for the full reference.
```py
>>> class Workflow2(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
... cancelled = State(final=True)
...
... produce = draft.to(producing)
... deliver = producing.to(closed)
... cancel = cancelled.from_(draft, producing)
>>> sm = Workflow2()
>>> sm.send("produce")
>>> sm.send("cancel")
>>> sm.cancelled.is_active
True
```
## Triggering events
In *transitions*, events are called as methods on the model:
```python
machine.produce() # triggers the "produce" event
machine.deliver() # triggers the "deliver" event
```
python-statemachine supports both styles:
```py
>>> sm = Workflow()
>>> sm.send("produce") # send by name (recommended for dynamic dispatch)
>>> sm.producing.is_active
True
>>> sm.deliver() # call as method (convenient for static usage)
>>> sm.closed.is_active
True
```
`send()` is preferred when the event name comes from external input (e.g., an API
endpoint or message queue). Direct method calls are convenient when you know the
event at coding time. See {ref}`events` for the full reference.
## Callbacks and actions
### *transitions* callback order
In *transitions*, callbacks execute in this order per transition:
`prepare` → `conditions` → `before` → `on_exit_<state>` → `on_enter_<state>` → `after`.
Callbacks are specified as strings (method names) or callables:
```python
machine = Machine(
states=states,
transitions=[{
"trigger": "produce",
"source": "draft",
"dest": "producing",
"before": "validate_job",
"after": "notify_team",
}],
initial="draft",
)
```
### python-statemachine callback order
python-statemachine has a similar but more granular order:
`prepare` → `validators` → `conditions` → `before` → `on_exit` → `on` → `on_enter` → `after`.
The `on` group (between exit and enter) is unique to python-statemachine — it runs the
transition's own action, separate from state entry/exit. See {ref}`actions` for the
full execution order table.
Callbacks are resolved by **naming convention** or by **inline declaration**:
```py
>>> from statemachine import State, StateChart
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
...
... produce = draft.to(producing)
... deliver = producing.to(closed)
...
... # naming convention: on_enter_<state>
... def on_enter_producing(self):
... self.entered = True
...
... # naming convention: after_<event>
... def after_produce(self):
... self.notified = True
>>> sm = Workflow()
>>> sm.send("produce")
>>> sm.entered
True
>>> sm.notified
True
```
Inline callbacks are also supported:
```py
>>> class Workflow2(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
...
... produce = draft.to(producing, on="do_produce")
... deliver = producing.to(closed)
...
... def do_produce(self):
... return "producing"
>>> sm = Workflow2()
>>> sm.send("produce")
'producing'
```
### Dependency injection
A key difference: python-statemachine callbacks use **dependency injection** via
`SignatureAdapter`. The engine inspects each callback's signature and passes only
the parameters it accepts. You never need `**kwargs` unless you want to capture extras:
```py
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
...
... produce = draft.to(producing)
... deliver = producing.to(closed)
...
... def on_produce(self, source, target):
... return f"{source.id} -> {target.id}"
>>> sm = Workflow()
>>> sm.send("produce")
'draft -> producing'
```
Available parameters include `source`, `target`, `event`, `state`, `error`, and
any custom kwargs passed to `send()`. See {ref}`actions` for the complete list of
available parameters.
In *transitions*, you must accept `**kwargs` or use `EventData`:
```python
def on_enter_producing(self, **kwargs):
event_data = kwargs.get("event_data")
```
## Conditions and guards
In *transitions*:
```python
machine.add_transition(
"produce", "draft", "producing",
conditions=["is_valid", "has_resources"],
unless=["is_locked"],
)
```
In python-statemachine, use `cond=` and `unless=`:
```py
>>> from statemachine import State, StateChart
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
...
... produce = draft.to(producing, cond="is_valid", unless="is_locked")
... deliver = producing.to(closed)
...
... is_valid = True
... is_locked = False
>>> sm = Workflow()
>>> sm.send("produce")
>>> sm.producing.is_active
True
```
python-statemachine also supports **condition expressions** — boolean strings
evaluated at runtime. See {ref}`validators and guards` for the full reference.
```py
>>> class Workflow2(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
...
... produce = draft.to(producing, cond="is_valid and not is_locked")
... deliver = producing.to(closed)
...
... is_valid = True
... is_locked = False
>>> sm = Workflow2()
>>> sm.send("produce")
>>> sm.producing.is_active
True
```
## Completion events (`done.state`)
In *transitions*, the `on_final` callback fires when a final state is entered (and
propagates upward when all children of a compound are final). However, it is just a
**callback** — it cannot trigger transitions automatically. You must wire separate
triggers manually.
In python-statemachine, when a compound state's final child is entered, the engine
automatically dispatches a `done.state.<parent_id>` **event**. You define transitions
for it using the `done_state_` naming convention, and the transition fires
automatically — no manual wiring needed:
```py
>>> from statemachine import State, StateChart
>>> class Pipeline(StateChart):
... class processing(State.Compound):
... step1 = State(initial=True)
... step2 = State()
... completed = State(final=True)
... advance = step1.to(step2)
... finish = step2.to(completed)
... done = State(final=True)
... done_state_processing = processing.to(done)
>>> sm = Pipeline()
>>> sm.send("advance")
>>> sm.send("finish")
>>> sm.done.is_active
True
```
For parallel states, `done.state` fires only when **all** regions have reached a
final state. Final states can also carry data via `donedata`, which is forwarded
as keyword arguments to the transition handler.
See {ref}`done.state events <done-state-events>` and {ref}`DoneData <donedata>` for
full details.
## Invoke
*transitions* does not have a built-in mechanism for spawning background work tied to
a state's lifecycle.
In python-statemachine, a state can **invoke** external work — API calls, file I/O,
child state machines — when it is entered, and automatically cancel that work when
the state is exited. Handlers run in a background thread (sync engine) or a thread
executor (async engine). When the work completes, a `done.invoke.<state>` event
is automatically dispatched:
```py
>>> import time
>>> from statemachine import State, StateChart
>>> class FetchMachine(StateChart):
... loading = State(initial=True, invoke=lambda: {"status": "ok"})
... ready = State(final=True)
... done_invoke_loading = loading.to(ready)
>>> sm = FetchMachine()
>>> time.sleep(0.1)
>>> sm.ready.is_active
True
```
Invoke supports multiple handlers (`invoke=[a, b]`), grouped invocations
(`invoke_group`), child state machines, and the full callback naming conventions
(`on_invoke_<state>`, `@state.invoke`).
See {ref}`invoke` for full documentation.
## Async support
*transitions* requires a separate class:
```python
from transitions.extensions import AsyncMachine
class AsyncModel:
async def on_enter_producing(self):
await some_async_operation()
machine = AsyncMachine(model=AsyncModel(), states=states, initial="draft")
await machine.produce()
```
python-statemachine auto-detects async callbacks — no special class needed:
```py
>>> import asyncio
>>> from statemachine import State, StateChart
>>> class AsyncWorkflow(StateChart):
... draft = State(initial=True)
... producing = State(final=True)
...
... produce = draft.to(producing)
...
... async def on_enter_producing(self):
... return "async entered"
>>> async def main():
... sm = AsyncWorkflow()
... await sm.send("produce")
... return sm.producing.is_active
>>> asyncio.run(main())
True
```
If any callback is `async def`, the engine automatically switches to the async
processing loop. Sync and async callbacks can be mixed freely.
See {ref}`async` for the full reference.
## Diagrams
In *transitions*, diagram support requires replacing `Machine` with `GraphMachine`
— a separate base class. If you also need nested states, you must use
`HierarchicalGraphMachine`; add async and it becomes
`HierarchicalAsyncGraphMachine`. This is part of the
{ref}`class composition problem <unified-api>` discussed below.
```python
from transitions.extensions import GraphMachine
machine = GraphMachine(model=model, states=states, transitions=transitions, initial="draft")
machine.get_graph().draw("diagram.png", prog="dot")
```
In python-statemachine, diagram generation is available on **every** state machine
with no class changes. Every instance has a `_graph()` method built in, and
`_repr_svg_()` renders directly in Jupyter notebooks:
```py
>>> from statemachine import State, StateChart
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State()
... closed = State(final=True)
... produce = draft.to(producing)
... deliver = producing.to(closed)
>>> sm = Workflow()
>>> graph = sm._graph()
>>> type(graph).__name__
'Dot'
```
For more control, use `DotGraphMachine` directly:
```python
from statemachine.contrib.diagram import DotGraphMachine
graph = DotGraphMachine(Workflow)
graph().write_png("diagram.png")
```
Diagrams automatically render compound and parallel state hierarchies.
See {ref}`diagrams` for the full reference.
(unified-api)=
## Unified API vs class composition
One of the most significant architectural differences between the two libraries
is how features are composed.
In *transitions*, each feature lives in a separate `Machine` subclass. Combining
features requires using pre-built combined classes — the number of variants grows
combinatorially:
| Class | Nested | Diagrams | Locked | Async |
|---|:---:|:---:|:---:|:---:|
| `Machine` | | | | |
| `HierarchicalMachine` | x | | | |
| `GraphMachine` | | x | | |
| `LockedMachine` | | | x | |
| `AsyncMachine` | | | | x |
| `HierarchicalGraphMachine` | x | x | | |
| `LockedGraphMachine` | | x | x | |
| `LockedHierarchicalMachine` | x | | x | |
| `LockedHierarchicalGraphMachine` | x | x | x | |
| `AsyncGraphMachine` | | x | | x |
| `HierarchicalAsyncMachine` | x | | | x |
| `HierarchicalAsyncGraphMachine` | x | x | | x |
That is **12 classes** to cover all combinations — and switching from a flat
machine to a hierarchical one requires changing the base class across your
codebase.
In python-statemachine, `StateChart` is the single base class. All features are
always available:
- **Nested states** — use `State.Compound` / `State.Parallel` in the class body
- **Async** — auto-detected from `async def` callbacks
- **Diagrams** — built-in `_graph()` on every instance
- **Thread safety** — handled by the engine's run-to-completion processing loop
```py
>>> import asyncio
>>> from statemachine import State, StateChart
>>> class FullFeatured(StateChart):
... """Nested + async + diagrams — same single base class."""
... class phase(State.Compound):
... step1 = State(initial=True)
... step2 = State(final=True)
... advance = step1.to(step2)
... done = State(final=True)
... done_state_phase = phase.to(done)
...
... async def on_enter_done(self):
... self.result = "async action completed"
>>> async def main():
... sm = FullFeatured()
... graph = sm._graph() # diagrams work
... await sm.send("advance") # async works
... return sm.result
>>> asyncio.run(main())
'async action completed'
```
No class swapping, no feature matrices to consult — just `StateChart`.
## Model integration
*transitions* binds directly to a model object:
```python
class MyModel:
pass
model = MyModel()
machine = Machine(model=model, states=states, transitions=transitions, initial="draft")
model.produce() # events are added to the model
```
python-statemachine offers two approaches. See {ref}`domain models` for the full
reference.
**1. Pass a model to the state machine:**
```py
>>> from statemachine import State, StateChart
>>> class MyModel:
... pass
>>> class Workflow(StateChart):
... draft = State(initial=True)
... producing = State(final=True)
... produce = draft.to(producing)
>>> model = MyModel()
>>> sm = Workflow(model=model)
>>> sm.model is model
True
```
**2. Use `MachineMixin` for ORM integration:**
```py
>>> from statemachine.mixins import MachineMixin
>>> class WorkflowModel(MachineMixin):
... state_machine_name = "__main__.Workflow"
... state_machine_attr = "sm"
... bind_events_as_methods = True
...
... state = 0 # persisted field
```
`MachineMixin` is particularly useful with Django models, where the state field
is a database column. See {ref}`integrations <machinemixin>` for details.
## Listeners
In *transitions*, cross-cutting concerns like logging or auditing are handled through
machine-level callbacks (`prepare_event`, `finalize_event`, `on_exception`). These are
callables passed to the `Machine` constructor — not separate objects. All callbacks
must live on the model or be passed as functions:
```python
machine = Machine(
model=model,
states=states,
transitions=transitions,
initial="draft",
prepare_event="log_event",
finalize_event="cleanup",
)
```
python-statemachine has a full **listener/observer pattern**. A listener is any object
with methods matching the callback naming conventions — no base class required. Listeners
are first-class: they receive the same callbacks as the state machine itself, with full
{ref}`dependency injection <dependency-injection>`:
```py
>>> from statemachine import State, StateChart
>>> class AuditListener:
... def __init__(self):
... self.log = []
... def after_transition(self, event, source, target):
... self.log.append(f"{event}: {source.id} -> {target.id}")
>>> class OrderMachine(StateChart):
... listeners = [AuditListener]
... draft = State(initial=True)
... confirmed = State(final=True)
... confirm = draft.to(confirmed)
>>> sm = OrderMachine()
>>> sm.send("confirm")
>>> sm.active_listeners[0].log
['confirm: draft -> confirmed']
```
Listeners can be declared at the class level (`listeners = [...]`), passed at
construction time (`OrderMachine(listeners=[...])`), or attached at runtime
(`sm.add_listener(...)`). Multiple independent listeners compose naturally — each
receives only the parameters it declares.
Class-level listeners support inheritance (child listeners append after parent),
a `setup()` protocol for receiving runtime dependencies (DB sessions, Redis
clients), and `functools.partial` for configuration.
See {ref}`listeners` for the full reference.
## Error handling
*transitions* lets exceptions propagate normally:
```python
try:
machine.produce()
except SomeError:
# handle error
pass
```
python-statemachine supports both styles. With `StateMachine` (the 2.x base class),
exceptions propagate as in *transitions*. With `StateChart`, you can opt into
structured error handling:
```py
>>> from statemachine import State, StateChart
>>> class RobustWorkflow(StateChart):
... draft = State(initial=True)
... error_state = State(final=True)
...
... go = draft.to(draft, on="bad_action")
... error_execution = draft.to(error_state)
...
... def bad_action(self):
... raise RuntimeError("something went wrong")
>>> sm = RobustWorkflow()
>>> sm.send("go")
>>> sm.error_state.is_active
True
```
When `catch_errors_as_events=True` (default in `StateChart`), runtime exceptions
are caught and dispatched as `error.execution` internal events. You can define
transitions that handle these errors, keeping the state machine in a consistent
state. The error object is available as `error` in callback kwargs.
See {ref}`error handling <error-execution>` for full details.
## Validations
*transitions* does not validate the consistency of your state machine definition.
You can define unreachable states, trap states (non-final states with no outgoing
transitions), or reference nonexistent callback names — and the library will not
warn you. Errors only surface at runtime, when an event fails to trigger or a
callback is not found.
python-statemachine validates the statechart structure at **two stages**:
1. **Class definition time** — structural checks run as soon as the class body is
evaluated. If any check fails, the class itself is not created:
```py
>>> from statemachine import State, StateChart
>>> from statemachine.exceptions import InvalidDefinition
>>> try:
... class Bad(StateChart):
... red = State(initial=True)
... green = State()
... hazard = State()
... cycle = red.to(green) | green.to(red)
... blink = hazard.to.itself()
... except InvalidDefinition as e:
... print(e)
There are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard']
```
2. **Instance creation time** — callback resolution, boolean expression parsing,
and other runtime checks:
```py
>>> class MyChart(StateChart):
... a = State(initial=True)
... b = State(final=True)
... go = a.to(b, on="nonexistent_method")
>>> try:
... MyChart()
... except InvalidDefinition as e:
... assert "Did not found name 'nonexistent_method'" in str(e)
```
Built-in validations include: exactly one initial state, no transitions from final
states, unreachable states, trap states, final state reachability, internal
transition targets, callback resolution, and boolean expression parsing.
See {ref}`validations` for the full list.
(feature-matrix)=
## Feature matrix
| Feature | *transitions* | python-statemachine |
|---|:---:|:---:|
| Flat state machines | Yes | Yes |
| {ref}`Compound (nested) states <compound-states>` | Yes | Yes |
| {ref}`Parallel states <parallel-states>` | Yes | Yes |
| {ref}`History pseudo-states <history-states>` | No | **Yes** |
| {ref}`Eventless transitions <eventless>` | No | **Yes** |
| {ref}`Final states <final-state>` | Yes | Yes |
| {ref}`Condition expressions <condition expressions>` | No | **Yes** |
| {ref}`In() state checks <condition expressions>` | No | **Yes** |
| {ref}`Dependency injection <dependency-injection>` | No | **Yes** |
| {ref}`Auto async detection <async>` | No | **Yes** |
| {ref}`error.execution handling <error-execution>` | No | **Yes** |
| {ref}`done.state / done.invoke events <done-state-events>` | Callback only | **Yes** |
| {ref}`Delayed events <delayed-events>` | No | **Yes** |
| {ref}`Internal events (raise_()) <sending-events>` | No | **Yes** |
| {ref}`Invoke (background work) <invoke>` | No | **Yes** |
| {ref}`Listener/observer pattern <listeners>` | No | **Yes** |
| {ref}`Definition-time validations <validations>` | No | **Yes** |
| {ref}`SCXML conformance <processing-model>` | No | **Yes** |
| {ref}`Diagrams <diagrams>` | Yes | Yes |
| {ref}`Django integration <machinemixin>` | Community | Built-in |
| {ref}`Model binding <models>` | Yes | Yes |
| {ref}`Wildcard transitions (*) <events>` | Yes | Yes |
| {ref}`Reflexive transitions <self-transition>` | Yes | Yes |
| Ordered transitions | Yes | Via explicit wiring |
| Tags on states | Yes | Via subclassing |
| {ref}`Machine nesting (children) <invoke>` | Yes | Yes (invoke) |
| {ref}`Timeout transitions <timeout>` | Yes | Yes |
================================================
FILE: docs/index.md
================================================
```{include} ../README.md
```
---
```{toctree}
:caption: Getting started
:maxdepth: 2
:hidden:
installation
tutorial
```
```{toctree}
:caption: Core Concepts
:maxdepth: 2
:hidden:
concepts
states
transitions
events
actions
guards
```
```{toctree}
:caption: Runtime
:maxdepth: 2
:hidden:
statechart
processing_model
error_handling
async
listeners
```
```{toctree}
:caption: Configuration
:maxdepth: 2
:hidden:
behaviour
validations
```
```{toctree}
:caption: Advanced
:maxdepth: 2
:hidden:
invoke
models
integrations
weighted_transitions
timeout
```
```{toctree}
:caption: How to
:maxdepth: 2
:hidden:
how-to/coming_from_transitions
how-to/coming_from_state_pattern
```
```{toctree}
:caption: Reference
:maxdepth: 2
:hidden:
api
diagram
auto_examples/index
contributing
authors
```
```{toctree}
:caption: Releases
:maxdepth: 2
:hidden:
releases/3.0.0
releases/upgrade_2x_to_3
releases/index
```
================================================
FILE: docs/installation.md
================================================
# Installation
## Latest release
To install using [uv](https://docs.astral.sh/uv):
```shell
uv add python-statemachine
```
To install using [poetry](https://python-poetry.org/):
```shell
poetry add python-statemachine
```
Alternatively, if you prefer using [pip](https://pip.pypa.io):
```shell
python3 -m pip install python-statemachine
```
For those looking to generate diagrams from your state machines, [pydot](https://github.com/pydot/pydot) and [Graphviz](https://graphviz.org/) are required.
Conveniently, you can install python-statemachine along with the `pydot` dependency using the extras option.
For more information, please refer to our documentation.
```shell
python3 -m pip install "python-statemachine[diagrams]"
```
## From sources
The sources for Python State Machine can be downloaded from the [Github repo](https://github.com/fgmacedo/python-statemachine).
You can either clone the public repository:
```shell
git clone git://github.com/fgmacedo/python-statemachine
```
Or download the `tarball`:
```shell
curl -OL https://github.com/fgmacedo/python-statemachine/tarball/main
```
Once you have a copy of the source, you can install it with:
```shell
python3 -m pip install -e .
```
================================================
FILE: docs/integrations.md
================================================
# Integrations
(machinemixin)=
## MachineMixin
{ref}`Domain models` can inherit from `MachineMixin` to automatically instantiate
and bind a {ref}`StateChart` to any Python class. This is the foundation for
integrating state machines with ORMs and other domain objects.
```{seealso}
See the [MachineMixin API reference](api.md#machinemixin) for the full list of attributes.
```
### Example
Given this state machine:
```py
>>> from statemachine import StateChart, State
>>> from statemachine.mixins import MachineMixin
>>> class CampaignMachine(StateChart):
... "A workflow machine"
... draft = State('Draft', initial=True, value=1)
... producing = State('Being produced', value=2)
... closed = State('Closed', value=3, final=True)
... cancelled = State('Cancelled', value=4, final=True)
...
... add_job = draft.to.itself() | producing.to.itself()
... produce = draft.to(producing)
... deliver = producing.to(closed)
... cancel = cancelled.from_(draft, producing)
```
You can attach it to a model by inheriting from `MachineMixin` and setting
`state_machine_name` to the fully qualified class name:
``` py
>>> from statemachine import registry
>>> registry.register(CampaignMachine) # register for lookup by qualname
<class '...CampaignMachine'>
>>> registry._initialized = True # skip Django autodiscovery in doctest
>>> class Workflow(MachineMixin):
... state_machine_name = '__main__.CampaignMachine'
... state_machine_attr = 'sm'
... state_field_name = 'workflow_step'
... bind_events_as_methods = True
...
... workflow_step = 1
>>> model = Workflow()
>>> isinstance(model.sm, CampaignMachine)
True
>>> model.workflow_step
1
>>> model.sm.draft in model.sm.configuration
True
```
With `bind_events_as_methods = True`, events become methods on the model itself:
``` py
>>> model = Workflow()
>>> model.produce()
>>> model.workflow_step
2
>>> model.sm.cancel() # you can still call the SM directly
>>> model.workflow_step
4
>>> model.sm.cancelled in model.sm.configuration
True
```
```{note}
In this example `state_machine_name` uses a `__main__` prefix because the class
is defined inline for doctest purposes. In your code, use the fully qualified
path (e.g., `'myapp.statemachines.CampaignMachine'`).
```
(django integration)=
## Django integration
When used in a Django App, this library implements an auto-discovery hook similar to how Django's
built-in **admin** [autodiscover](https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.autodiscover).
> This library attempts to import a **statemachine** or **statemachines** module in each installed
> application. Such modules are expected to register `StateChart` classes to be used with
> the {ref}`MachineMixin`.
```{hint}
We advise keeping {ref}`StateChart` definitions in their own modules to avoid circular
references. If you place state machines in modules named `statemachine` or `statemachines`
inside installed Django Apps, they will be automatically imported and registered.
That said, nothing stops you from declaring your state machine alongside your models.
```
### Django example
```py
# campaign/statemachines.py
from statemachine import StateChart
from statemachine import State
class CampaignMachine(StateChart):
"A workflow machine"
draft = State('Draft', initial=True, value=1)
producing = State('Being produced', value=2)
closed = State('Closed', value=3)
cancelled = State('Cancelled', value=4)
add_job = draft.to.itself() | producing.to.itself()
produce = draft.to(producing)
deliver = producing.to(closed)
cancel = cancelled.from_(draft, producing)
```
Integrate with your Django model using `MachineMixin`:
```py
# campaign/models.py
from django.db import models
from statemachine.mixins import MachineMixin
class Campaign(models.Model, MachineMixin):
state_machine_name = 'campaign.statemachines.CampaignMachine'
state_machine_attr = 'sm'
state_field_name = 'step'
name = models.CharField(max_length=30)
step = models.IntegerField()
```
### Data migrations
Django's `apps.get_model()` returns **historical model** classes that are dynamically created
and don't carry user-defined class attributes like `state_machine_name`. Since version 2.6.0,
`MachineMixin` detects these historical models and gracefully skips state machine
initialization, so data migrations that use `apps.get_model()` work without errors.
```{note}
The state machine instance will **not** be available on historical model objects.
If your data migration needs to interact with the state machine, set the attributes
manually on the historical model class:
def backfill_data(apps, schema_editor):
MyModel = apps.get_model("myapp", "MyModel")
MyModel.state_machine_name = "myapp.statemachines.MyStateMachine"
for obj in MyModel.objects.all():
obj.statemachine # now available
```
================================================
FILE: docs/invoke.md
================================================
(invoke)=
# Invoke
Invoke lets a state spawn external work — API calls, file I/O, child state machines —
when it is entered, and automatically cancel that work when the state is exited. This
follows the [SCXML `<invoke>` semantics](https://www.w3.org/TR/scxml/#invoke) and is
similar to the **do activity** (`do/`) concept in UML Statecharts — an ongoing behavior
that runs for the duration of a state and is cancelled when the state is exited.
## Execution model
Invoke handlers run **outside** the main state machine processing loop:
- **Sync engine**: each invoke handler runs in a **daemon thread**.
- **Async engine**: each invoke handler runs in a **thread executor**
(`loop.run_in_executor`), wrapped in an `asyncio.Task`. The executor is used because
invoke handlers are expected to perform blocking I/O (network calls, file access,
subprocess communication) that would freeze the event loop if run directly.
When a handler completes, a `done.invoke.<state>.<id>` event is automatically sent back
to the machine. If the handler raises an exception, an `error.execution` event is sent
instead. If the owning state is exited before the handler finishes, the invocation is
**cancelled** — `ctx.cancelled` is set and `on_cancel()` is called on `IInvoke` handlers.
## Callback group
Invoke is a first-class callback group, just like `enter` and `exit`. This means
convention naming (`on_invoke_<state>`), decorators (`@state.invoke`), inline callables,
and the full {ref}`SignatureAdapter <actions>` dependency injection all work out of the box.
See the {ref}`actions` page for how invoke fits into the overall
callback {ref}`Ordering` and the available
{ref}`dependency injection <dynamic-dispatch>` parameters.
## Quick start
The simplest invoke is a plain callable passed to the `invoke` parameter. Here we read a
config file in a background thread and transition to `ready` when the data is available:
```py
>>> import json
>>> import tempfile
>>> import time
>>> from pathlib import Path
>>> from statemachine import State, StateChart
>>> config_file = Path(tempfile.mktemp(suffix=".json"))
>>> _ = config_file.write_text('{"db_host": "localhost", "db_port": 5432}')
>>> def load_config():
... return json.loads(config_file.read_text())
gitextract_3onmcp2r/
├── .git-blame-ignore-revs
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ └── workflows/
│ ├── python-package.yml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── AGENTS.md
├── LICENSE
├── README.md
├── conftest.py
├── contributing.md
├── docs/
│ ├── _static/
│ │ └── custom_machine.css
│ ├── actions.md
│ ├── api.md
│ ├── async.md
│ ├── authors.md
│ ├── behaviour.md
│ ├── concepts.md
│ ├── conf.py
│ ├── contributing.md
│ ├── diagram.md
│ ├── error_handling.md
│ ├── events.md
│ ├── guards.md
│ ├── how-to/
│ │ ├── coming_from_state_pattern.md
│ │ └── coming_from_transitions.md
│ ├── index.md
│ ├── installation.md
│ ├── integrations.md
│ ├── invoke.md
│ ├── listeners.md
│ ├── models.md
│ ├── processing_model.md
│ ├── releases/
│ │ ├── 0.1.0.md
│ │ ├── 0.2.0.md
│ │ ├── 0.3.0.md
│ │ ├── 0.4.2.md
│ │ ├── 0.5.0.md
│ │ ├── 0.5.1.md
│ │ ├── 0.6.0.md
│ │ ├── 0.6.1.md
│ │ ├── 0.6.2.md
│ │ ├── 0.7.0.md
│ │ ├── 0.7.1.md
│ │ ├── 0.8.0.md
│ │ ├── 0.9.0.md
│ │ ├── 1.0.0.md
│ │ ├── 1.0.1.md
│ │ ├── 1.0.2.md
│ │ ├── 1.0.3.md
│ │ ├── 2.0.0.md
│ │ ├── 2.1.0.md
│ │ ├── 2.1.1.md
│ │ ├── 2.1.2.md
│ │ ├── 2.2.0.md
│ │ ├── 2.3.0.md
│ │ ├── 2.3.1.md
│ │ ├── 2.3.2.md
│ │ ├── 2.3.3.md
│ │ ├── 2.3.4.md
│ │ ├── 2.3.5.md
│ │ ├── 2.3.6.md
│ │ ├── 2.4.0.md
│ │ ├── 2.5.0.md
│ │ ├── 2.6.0.md
│ │ ├── 3.0.0.md
│ │ ├── 3.1.0.md
│ │ ├── index.md
│ │ └── upgrade_2x_to_3.md
│ ├── statechart.md
│ ├── states.md
│ ├── timeout.md
│ ├── transitions.md
│ ├── tutorial.md
│ ├── validations.md
│ └── weighted_transitions.md
├── pyproject.toml
├── statemachine/
│ ├── __init__.py
│ ├── callbacks.py
│ ├── configuration.py
│ ├── contrib/
│ │ ├── __init__.py
│ │ ├── diagram/
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── extract.py
│ │ │ ├── formatter.py
│ │ │ ├── model.py
│ │ │ ├── renderers/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── dot.py
│ │ │ │ ├── mermaid.py
│ │ │ │ └── table.py
│ │ │ └── sphinx_ext.py
│ │ ├── timeout.py
│ │ └── weighted.py
│ ├── dispatcher.py
│ ├── engines/
│ │ ├── __init__.py
│ │ ├── async_.py
│ │ ├── base.py
│ │ └── sync.py
│ ├── event.py
│ ├── event_data.py
│ ├── events.py
│ ├── exceptions.py
│ ├── factory.py
│ ├── graph.py
│ ├── i18n.py
│ ├── invoke.py
│ ├── io/
│ │ ├── __init__.py
│ │ └── scxml/
│ │ ├── __init__.py
│ │ ├── actions.py
│ │ ├── invoke.py
│ │ ├── parser.py
│ │ ├── processor.py
│ │ └── schema.py
│ ├── locale/
│ │ ├── en/
│ │ │ └── LC_MESSAGES/
│ │ │ └── statemachine.po
│ │ ├── hi_IN/
│ │ │ └── LC_MESSAGES/
│ │ │ └── statemachine.po
│ │ ├── pt_BR/
│ │ │ └── LC_MESSAGES/
│ │ │ └── statemachine.po
│ │ └── zh_CN/
│ │ └── LC_MESSAGES/
│ │ └── statemachine.po
│ ├── mixins.py
│ ├── model.py
│ ├── orderedset.py
│ ├── py.typed
│ ├── registry.py
│ ├── signature.py
│ ├── spec_parser.py
│ ├── state.py
│ ├── statemachine.py
│ ├── states.py
│ ├── transition.py
│ ├── transition_list.py
│ ├── transition_mixin.py
│ └── utils.py
└── tests/
├── __init__.py
├── conftest.py
├── django_project/
│ ├── app.py
│ ├── core/
│ │ ├── __init__,.py
│ │ ├── settings.py
│ │ └── wsgi.py
│ ├── manage.py
│ └── workflow/
│ ├── __init__.py
│ ├── apps.py
│ ├── models.py
│ ├── statemachines.py
│ └── tests.py
├── examples/
│ ├── README.rst
│ ├── __init__.py
│ ├── ai_shell_machine.py
│ ├── air_conditioner_machine.py
│ ├── all_actions_machine.py
│ ├── async_guess_the_number_machine.py
│ ├── async_without_loop_machine.py
│ ├── enum_campaign_machine.py
│ ├── guess_the_number_machine.py
│ ├── lor_machine.py
│ ├── order_control_machine.py
│ ├── order_control_rich_model_machine.py
│ ├── persistent_model_machine.py
│ ├── recursive_event_machine.py
│ ├── reusing_transitions_machine.py
│ ├── sqlite_persistent_model_machine.py
│ ├── statechart_cleanup_machine.py
│ ├── statechart_compound_machine.py
│ ├── statechart_delayed_machine.py
│ ├── statechart_error_handling_machine.py
│ ├── statechart_eventless_machine.py
│ ├── statechart_history_machine.py
│ ├── statechart_in_condition_machine.py
│ ├── statechart_parallel_machine.py
│ ├── traffic_light_machine.py
│ ├── user_machine.py
│ └── weighted_idle_machine.py
├── helpers.py
├── machines/
│ ├── __init__.py
│ ├── compound/
│ │ ├── __init__.py
│ │ ├── middle_earth_journey.py
│ │ ├── middle_earth_journey_two_compounds.py
│ │ ├── middle_earth_journey_with_finals.py
│ │ ├── moria_expedition.py
│ │ ├── moria_expedition_with_escape.py
│ │ ├── quest_for_erebor.py
│ │ └── shire_to_rivendell.py
│ ├── donedata/
│ │ ├── __init__.py
│ │ ├── destroy_the_ring.py
│ │ ├── destroy_the_ring_simple.py
│ │ ├── nested_quest_donedata.py
│ │ ├── quest_for_erebor_done_convention.py
│ │ ├── quest_for_erebor_explicit_id.py
│ │ ├── quest_for_erebor_multi_word.py
│ │ └── quest_for_erebor_with_event.py
│ ├── error/
│ │ ├── __init__.py
│ │ ├── error_convention_event.py
│ │ ├── error_convention_transition_list.py
│ │ ├── error_in_action_sc.py
│ │ ├── error_in_action_sm_with_flag.py
│ │ ├── error_in_after_sc.py
│ │ ├── error_in_error_handler_sc.py
│ │ ├── error_in_guard_sc.py
│ │ ├── error_in_guard_sm.py
│ │ └── error_in_on_enter_sc.py
│ ├── eventless/
│ │ ├── __init__.py
│ │ ├── auto_advance.py
│ │ ├── beacon_chain.py
│ │ ├── beacon_chain_lighting.py
│ │ ├── coordinated_advance.py
│ │ ├── ring_corruption.py
│ │ ├── ring_corruption_with_bear_ring.py
│ │ └── ring_corruption_with_tick.py
│ ├── history/
│ │ ├── __init__.py
│ │ ├── deep_memory_of_moria.py
│ │ ├── gollum_personality.py
│ │ ├── gollum_personality_default_gollum.py
│ │ ├── gollum_personality_with_default.py
│ │ └── shallow_moria.py
│ ├── in_condition/
│ │ ├── __init__.py
│ │ ├── combined_guard.py
│ │ ├── descendant_check.py
│ │ ├── eventless_in.py
│ │ ├── fellowship.py
│ │ ├── fellowship_coordination.py
│ │ └── gate_of_moria.py
│ ├── parallel/
│ │ ├── __init__.py
│ │ ├── session.py
│ │ ├── session_with_done_state.py
│ │ ├── two_towers.py
│ │ ├── war_of_the_ring.py
│ │ └── war_with_exit.py
│ ├── showcase_actions.py
│ ├── showcase_compound.py
│ ├── showcase_deep_history.py
│ ├── showcase_guards.py
│ ├── showcase_history.py
│ ├── showcase_internal.py
│ ├── showcase_parallel.py
│ ├── showcase_parallel_compound.py
│ ├── showcase_self_transition.py
│ ├── showcase_simple.py
│ ├── transition_from_any.py
│ ├── tutorial_coffee_order.py
│ ├── validators/
│ │ ├── __init__.py
│ │ ├── multi_validator.py
│ │ ├── order_validation.py
│ │ ├── order_validation_no_error_events.py
│ │ ├── validator_fallthrough.py
│ │ ├── validator_with_cond.py
│ │ └── validator_with_error_transition.py
│ └── workflow/
│ ├── __init__.py
│ ├── campaign_machine.py
│ ├── campaign_machine_with_validator.py
│ ├── campaign_machine_with_values.py
│ └── reverse_traffic_light.py
├── models.py
├── scrape_images.py
├── scxml/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_microwave.py
│ ├── test_scxml_cases.py
│ └── w3c/
│ ├── LICENSE
│ ├── mandatory/
│ │ ├── test144.scxml
│ │ ├── test145.scxml
│ │ ├── test147.scxml
│ │ ├── test148.scxml
│ │ ├── test149.scxml
│ │ ├── test150.scxml
│ │ ├── test151.scxml
│ │ ├── test152.scxml
│ │ ├── test153.scxml
│ │ ├── test155.scxml
│ │ ├── test156.scxml
│ │ ├── test158.scxml
│ │ ├── test159.scxml
│ │ ├── test172.scxml
│ │ ├── test173.scxml
│ │ ├── test174.scxml
│ │ ├── test175.scxml
│ │ ├── test176.scxml
│ │ ├── test179.scxml
│ │ ├── test183.scxml
│ │ ├── test185.scxml
│ │ ├── test186.scxml
│ │ ├── test187.scxml
│ │ ├── test189.scxml
│ │ ├── test190.scxml
│ │ ├── test191.scxml
│ │ ├── test192.scxml
│ │ ├── test194.scxml
│ │ ├── test198.scxml
│ │ ├── test199.scxml
│ │ ├── test200.scxml
│ │ ├── test205.scxml
│ │ ├── test207.scxml
│ │ ├── test208.scxml
│ │ ├── test210.scxml
│ │ ├── test215.scxml
│ │ ├── test216.scxml
│ │ ├── test216sub1.scxml
│ │ ├── test220.scxml
│ │ ├── test223.scxml
│ │ ├── test224.scxml
│ │ ├── test225.scxml
│ │ ├── test226.scxml
│ │ ├── test226sub1.scxml
│ │ ├── test228.scxml
│ │ ├── test229.scxml
│ │ ├── test232.scxml
│ │ ├── test233.scxml
│ │ ├── test234.scxml
│ │ ├── test235.scxml
│ │ ├── test236.scxml
│ │ ├── test237.scxml
│ │ ├── test239.scxml
│ │ ├── test239sub1.scxml
│ │ ├── test240.scxml
│ │ ├── test241.scxml
│ │ ├── test242.scxml
│ │ ├── test242sub1.scxml
│ │ ├── test243.scxml
│ │ ├── test244.scxml
│ │ ├── test245.scxml
│ │ ├── test247.scxml
│ │ ├── test252.scxml
│ │ ├── test253.scxml
│ │ ├── test276.scxml
│ │ ├── test276sub1.scxml
│ │ ├── test277.scxml
│ │ ├── test279.scxml
│ │ ├── test280.scxml
│ │ ├── test286.scxml
│ │ ├── test287.scxml
│ │ ├── test294.scxml
│ │ ├── test298.scxml
│ │ ├── test302.scxml
│ │ ├── test303.scxml
│ │ ├── test304.scxml
│ │ ├── test309.scxml
│ │ ├── test310.scxml
│ │ ├── test311.scxml
│ │ ├── test312.scxml
│ │ ├── test318.scxml
│ │ ├── test319.scxml
│ │ ├── test321.scxml
│ │ ├── test322.scxml
│ │ ├── test323.scxml
│ │ ├── test324.scxml
│ │ ├── test325.scxml
│ │ ├── test326.scxml
│ │ ├── test329.scxml
│ │ ├── test330.scxml
│ │ ├── test331.scxml
│ │ ├── test332.scxml
│ │ ├── test333.scxml
│ │ ├── test335.scxml
│ │ ├── test336.scxml
│ │ ├── test337.scxml
│ │ ├── test338.scxml
│ │ ├── test339.scxml
│ │ ├── test342.scxml
│ │ ├── test343.scxml
│ │ ├── test344.scxml
│ │ ├── test346.scxml
│ │ ├── test347.scxml
│ │ ├── test348.scxml
│ │ ├── test349.scxml
│ │ ├── test350.scxml
│ │ ├── test351.scxml
│ │ ├── test352.scxml
│ │ ├── test354.scxml
│ │ ├── test355.scxml
│ │ ├── test364.scxml
│ │ ├── test372.scxml
│ │ ├── test375.scxml
│ │ ├── test376.scxml
│ │ ├── test377.scxml
│ │ ├── test378.scxml
│ │ ├── test387.scxml
│ │ ├── test388.scxml
│ │ ├── test396.scxml
│ │ ├── test399.scxml
│ │ ├── test401.scxml
│ │ ├── test402.scxml
│ │ ├── test403a.scxml
│ │ ├── test403b.scxml
│ │ ├── test403c.scxml
│ │ ├── test404.scxml
│ │ ├── test405.scxml
│ │ ├── test406.scxml
│ │ ├── test407.scxml
│ │ ├── test409.scxml
│ │ ├── test411.scxml
│ │ ├── test412.scxml
│ │ ├── test413.scxml
│ │ ├── test416.scxml
│ │ ├── test417.scxml
│ │ ├── test419.scxml
│ │ ├── test421.scxml
│ │ ├── test422.scxml
│ │ ├── test423.scxml
│ │ ├── test487.scxml
│ │ ├── test488.scxml
│ │ ├── test495.scxml
│ │ ├── test496.scxml
│ │ ├── test500.scxml
│ │ ├── test501.scxml
│ │ ├── test503.scxml
│ │ ├── test504.scxml
│ │ ├── test505.scxml
│ │ ├── test506.scxml
│ │ ├── test521.scxml
│ │ ├── test525.scxml
│ │ ├── test527.scxml
│ │ ├── test528.scxml
│ │ ├── test529.scxml
│ │ ├── test530.scxml
│ │ ├── test533.scxml
│ │ ├── test550.scxml
│ │ ├── test551.scxml
│ │ ├── test552.scxml
│ │ ├── test552.txt
│ │ ├── test553.scxml
│ │ ├── test554.scxml
│ │ ├── test570.scxml
│ │ ├── test576.scxml
│ │ ├── test579.scxml
│ │ └── test580.scxml
│ └── optional/
│ ├── test193.scxml
│ ├── test201.scxml
│ ├── test278.scxml
│ ├── test444.scxml
│ ├── test445.scxml
│ ├── test446.scxml
│ ├── test446.txt
│ ├── test448.scxml
│ ├── test449.scxml
│ ├── test451.scxml
│ ├── test452.scxml
│ ├── test453.scxml
│ ├── test456.scxml
│ ├── test457.scxml
│ ├── test459.scxml
│ ├── test460.scxml
│ ├── test509.scxml
│ ├── test510.scxml
│ ├── test518.scxml
│ ├── test519.scxml
│ ├── test520.scxml
│ ├── test522.scxml
│ ├── test531.scxml
│ ├── test532.scxml
│ ├── test534.scxml
│ ├── test557.scxml
│ ├── test557.txt
│ ├── test558.scxml
│ ├── test558.txt
│ ├── test560.scxml
│ ├── test561.scxml
│ ├── test562.scxml
│ ├── test567.scxml
│ ├── test569.scxml
│ ├── test577.scxml
│ └── test578.scxml
├── test_actions.py
├── test_api_contract.py
├── test_async.py
├── test_async_futures.py
├── test_callbacks.py
├── test_callbacks_isolation.py
├── test_class_listeners.py
├── test_conditions_algebra.py
├── test_configuration.py
├── test_contrib_diagram.py
├── test_contrib_timeout.py
├── test_copy.py
├── test_dispatcher.py
├── test_error_execution.py
├── test_events.py
├── test_examples.py
├── test_fellowship_quest.py
├── test_invoke.py
├── test_io.py
├── test_listener.py
├── test_mermaid_renderer.py
├── test_mixins.py
├── test_mock_compatibility.py
├── test_multiple_destinations.py
├── test_profiling.py
├── test_registry.py
├── test_rtc.py
├── test_scxml_units.py
├── test_signature.py
├── test_signature_positional_only.py
├── test_spec_parser.py
├── test_state.py
├── test_state_callbacks.py
├── test_statechart_compound.py
├── test_statechart_delayed.py
├── test_statechart_donedata.py
├── test_statechart_error.py
├── test_statechart_eventless.py
├── test_statechart_history.py
├── test_statechart_in_condition.py
├── test_statechart_parallel.py
├── test_statemachine.py
├── test_statemachine_bounded_transitions.py
├── test_statemachine_compat.py
├── test_statemachine_inheritance.py
├── test_threading.py
├── test_transition_list.py
├── test_transition_table.py
├── test_transitions.py
├── test_validators.py
├── test_weighted_transitions.py
└── testcases/
├── __init__.py
├── issue308.md
├── issue384_multiple_observers.md
├── issue449.md
├── test_issue434.py
├── test_issue480.py
└── test_issue509.py
Showing preview only (217K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2415 symbols across 205 files)
FILE: conftest.py
function add_doctest_context (line 8) | def add_doctest_context(doctest_namespace): # noqa: PT004
function pytest_ignore_collect (line 32) | def pytest_ignore_collect(collection_path, config):
function has_dot_installed (line 41) | def has_dot_installed():
function requires_dot_installed (line 46) | def requires_dot_installed(request, has_dot_installed):
FILE: docs/conf.py
function dummy_write_computation_times (line 298) | def dummy_write_computation_times(gallery_conf, target_dir, costs):
FILE: statemachine/callbacks.py
function allways_true (line 23) | def allways_true(*args, **kwargs):
class CallbackPriority (line 27) | class CallbackPriority(IntEnum):
class SpecReference (line 35) | class SpecReference(IntFlag):
class CallbackGroup (line 45) | class CallbackGroup(IntEnum):
method build_key (line 56) | def build_key(self, specs: "CallbackSpecList") -> str:
class CallbackSpec (line 60) | class CallbackSpec:
method __init__ (line 72) | def __init__(
method __repr__ (line 112) | def __repr__(self):
method __str__ (line 115) | def __str__(self):
method __eq__ (line 121) | def __eq__(self, other):
method __hash__ (line 124) | def __hash__(self):
class SpecListGrouper (line 128) | class SpecListGrouper:
method __init__ (line 129) | def __init__(self, list: "CallbackSpecList", group: CallbackGroup) -> ...
method add (line 134) | def add(self, callbacks, **kwargs):
method __call__ (line 138) | def __call__(self, callback):
method _add_unbounded_callback (line 141) | def _add_unbounded_callback(self, func, is_event=False, transitions=No...
method __iter__ (line 150) | def __iter__(self):
class CallbackSpecList (line 154) | class CallbackSpecList:
method __init__ (line 157) | def __init__(self, factory=CallbackSpec):
method __repr__ (line 163) | def __repr__(self):
method _add_unbounded_callback (line 166) | def _add_unbounded_callback(self, func, transitions=None, **kwargs):
method __iter__ (line 194) | def __iter__(self):
method clear (line 197) | def clear(self):
method grouper (line 200) | def grouper(self, group: CallbackGroup) -> SpecListGrouper:
method _add (line 205) | def _add(self, func, group: CallbackGroup, **kwargs):
method add (line 219) | def add(self, callbacks, group: CallbackGroup, **kwargs):
class CallbackWrapper (line 230) | class CallbackWrapper:
method __init__ (line 231) | def __init__(
method __repr__ (line 245) | def __repr__(self):
method __str__ (line 248) | def __str__(self):
method __lt__ (line 251) | def __lt__(self, other):
method __call__ (line 254) | async def __call__(self, *args, **kwargs):
method call (line 263) | def call(self, *args, **kwargs):
class CallbacksExecutor (line 270) | class CallbacksExecutor:
method __init__ (line 273) | def __init__(self):
method __iter__ (line 277) | def __iter__(self):
method __repr__ (line 280) | def __repr__(self):
method __str__ (line 283) | def __str__(self):
method add (line 286) | def add(self, key: str, spec: CallbackSpec, builder: Callable[[], Call...
method async_call (line 302) | async def async_call(
method async_all (line 323) | async def async_all(
method call (line 337) | def call(self, *args, on_error: "Callable[[Exception], None] | None" =...
method all (line 354) | def all(self, *args, on_error: "Callable[[Exception], None] | None" = ...
method visit (line 366) | def visit(self, visitor_fn, *args, **kwargs):
method async_visit (line 372) | async def async_visit(self, visitor_fn, *args, **kwargs):
class CallbacksRegistry (line 381) | class CallbacksRegistry:
method __init__ (line 382) | def __init__(self) -> None:
method __getitem__ (line 386) | def __getitem__(self, key: str) -> CallbacksExecutor:
method __contains__ (line 389) | def __contains__(self, key: str) -> bool:
method check (line 392) | def check(self, specs: CallbackSpecList):
method async_or_sync (line 412) | def async_or_sync(self):
method call (line 417) | def call(
method async_call (line 428) | async def async_call(
method all (line 439) | def all(
method async_all (line 450) | async def async_all(
method visit (line 461) | def visit(self, key: str, visitor_fn, *args, **kwargs):
method async_visit (line 466) | async def async_visit(self, key: str, visitor_fn, *args, **kwargs):
method str (line 471) | def str(self, key: str) -> str:
FILE: statemachine/configuration.py
class Configuration (line 17) | class Configuration:
method __init__ (line 35) | def __init__(
method value (line 52) | def value(self) -> Any:
method value (line 57) | def value(self, val: Any):
method values (line 66) | def values(self) -> OrderedSet[Any]:
method states (line 73) | def states(self) -> "OrderedSet[State]":
method states (line 89) | def states(self, new_configuration: "OrderedSet[State]"):
method add (line 94) | def add(self, state: "State"):
method discard (line 100) | def discard(self, state: "State"):
method current_state (line 109) | def current_state(self) -> "State | OrderedSet[State]":
method _read_from_model (line 136) | def _read_from_model(self) -> OrderedSet:
method _write_to_model (line 147) | def _write_to_model(self, values: OrderedSet):
method _invalidate (line 160) | def _invalidate(self):
FILE: statemachine/contrib/diagram/__init__.py
class DotGraphMachine (line 13) | class DotGraphMachine:
method __init__ (line 40) | def __init__(self, machine):
method _build_config (line 43) | def _build_config(self) -> DotRendererConfig:
method get_graph (line 53) | def get_graph(self):
method __call__ (line 58) | def __call__(self):
class MermaidGraphMachine (line 62) | class MermaidGraphMachine:
method __init__ (line 69) | def __init__(self, machine):
method _build_config (line 72) | def _build_config(self) -> MermaidRendererConfig:
method get_mermaid (line 79) | def get_mermaid(self) -> str:
method __call__ (line 84) | def __call__(self) -> str:
function quickchart_write_svg (line 88) | def quickchart_write_svg(sm, path: str):
function _find_sm_class (line 128) | def _find_sm_class(module):
function import_sm (line 144) | def import_sm(qualname):
function write_image (line 167) | def write_image(qualname, out, events=None, fmt=None):
function main (line 210) | def main(argv=None):
FILE: statemachine/contrib/diagram/extract.py
function _determine_state_type (line 22) | def _determine_state_type(state: "State") -> StateType:
function _actions_getter (line 37) | def _actions_getter(machine: "MachineRef"):
function _extract_state_actions (line 53) | def _extract_state_actions(state: "State", getter) -> List[DiagramAction]:
function _extract_state (line 75) | def _extract_state(
function _format_event_names (line 105) | def _format_event_names(transition: "Transition") -> str:
function _extract_transitions_from_state (line 134) | def _extract_transitions_from_state(state: "State") -> List[DiagramTrans...
function _extract_all_transitions (line 155) | def _extract_all_transitions(states) -> List[DiagramTransition]:
function _collect_compound_ids (line 169) | def _collect_compound_ids(states: List[DiagramState]) -> Set[str]:
function _collect_bidirectional_compound_ids (line 179) | def _collect_bidirectional_compound_ids(
function _mark_initial_transitions (line 200) | def _mark_initial_transitions(
function _resolve_initial_states (line 210) | def _resolve_initial_states(states: List[DiagramState]) -> None:
function extract (line 242) | def extract(machine_or_class: "MachineRef") -> DiagramGraph:
FILE: statemachine/contrib/diagram/formatter.py
class Formatter (line 32) | class Formatter:
method __init__ (line 35) | def __init__(self) -> None:
method register_format (line 38) | def register_format(
method render (line 59) | def render(self, machine_or_class: "MachineRef", fmt: str) -> str:
method supported_formats (line 81) | def supported_formats(self) -> List[str]:
method _primary_name (line 85) | def _primary_name(self, fn: "Callable[[MachineRef], str]") -> str:
function _render_dot (line 103) | def _render_dot(machine_or_class: "MachineRef") -> str:
function _render_svg (line 110) | def _render_svg(machine_or_class: "MachineRef") -> str:
function _render_mermaid (line 118) | def _render_mermaid(machine_or_class: "MachineRef") -> str:
function _render_md (line 125) | def _render_md(machine_or_class: "MachineRef") -> str:
function _render_rst (line 133) | def _render_rst(machine_or_class: "MachineRef") -> str:
FILE: statemachine/contrib/diagram/model.py
class StateType (line 8) | class StateType(Enum):
class ActionType (line 22) | class ActionType(Enum):
class DiagramAction (line 29) | class DiagramAction:
class DiagramState (line 35) | class DiagramState:
class DiagramTransition (line 47) | class DiagramTransition:
class DiagramGraph (line 58) | class DiagramGraph:
FILE: statemachine/contrib/diagram/renderers/dot.py
function _escape_html (line 18) | def _escape_html(text: str) -> str:
class DotRendererConfig (line 24) | class DotRendererConfig:
class DotRenderer (line 38) | class DotRenderer:
method __init__ (line 47) | def __init__(self, config: Optional[DotRendererConfig] = None):
method render (line 52) | def render(self, graph: DiagramGraph) -> pydot.Dot:
method _create_graph (line 60) | def _create_graph(self, name: str) -> pydot.Dot:
method _state_node_id (line 103) | def _state_node_id(self, state_id: str) -> str:
method _compound_edge_anchor (line 109) | def _compound_edge_anchor(self, state_id: str, direction: str) -> str:
method _render_states (line 120) | def _render_states(
method _place_extra_nodes (line 178) | def _place_extra_nodes(
method _render_initial_arrow (line 199) | def _render_initial_arrow(
method _create_initial_node (line 248) | def _create_initial_node(self, node_id: str) -> pydot.Node:
method _create_atomic_node (line 262) | def _create_atomic_node(self, state: DiagramState) -> pydot.Node:
method _build_html_table_label (line 307) | def _build_html_table_label(
method _format_action (line 339) | def _format_action(action: DiagramAction) -> str:
method _create_history_node (line 344) | def _create_history_node(self, state: DiagramState) -> pydot.Node:
method _create_compound_anchor_nodes (line 359) | def _create_compound_anchor_nodes(self, state: DiagramState) -> List[p...
method _create_compound_subgraph (line 395) | def _create_compound_subgraph(self, state: DiagramState) -> pydot.Subg...
method _build_compound_label (line 414) | def _build_compound_label(self, state: DiagramState) -> str:
method _add_transitions_for_state (line 432) | def _add_transitions_for_state(
method _create_edges (line 448) | def _create_edges(self, transition: DiagramTransition) -> List[pydot.E...
method _create_single_edge (line 462) | def _create_single_edge(
method _resolve_edge_endpoints (line 482) | def _resolve_edge_endpoints(
method _build_edge_label (line 516) | def _build_edge_label(self, event: str, cond_html: str, index: int) ->...
FILE: statemachine/contrib/diagram/renderers/mermaid.py
class MermaidRendererConfig (line 16) | class MermaidRendererConfig:
class MermaidRenderer (line 24) | class MermaidRenderer:
method __init__ (line 37) | def __init__(self, config: Optional[MermaidRendererConfig] = None):
method render (line 46) | def render(self, graph: DiagramGraph) -> str:
method _build_initial_child_map (line 73) | def _build_initial_child_map(self, states: List[DiagramState]) -> Dict...
method _collect_parallel_descendants (line 85) | def _collect_parallel_descendants(
method _build_all_descendants_map (line 100) | def _build_all_descendants_map(self, states: List[DiagramState]) -> Di...
method _collect_recursive_descendants (line 110) | def _collect_recursive_descendants(states: List[DiagramState]) -> Set[...
method _resolve_endpoint (line 118) | def _resolve_endpoint(self, state_id: str) -> str:
method _render_states (line 133) | def _render_states(
method _render_atomic_state (line 167) | def _render_atomic_state(
method _render_compound_state (line 186) | def _render_compound_state(
method _collect_all_descendant_ids (line 230) | def _collect_all_descendant_ids(self, states: List[DiagramState]) -> S...
method _render_scope_transitions (line 237) | def _render_scope_transitions(
method _is_fully_internal (line 296) | def _is_fully_internal(
method _render_single_transition (line 307) | def _render_single_transition(
method _format_action (line 329) | def _format_action(action: DiagramAction) -> str:
method _render_initial_and_final (line 334) | def _render_initial_and_final(
FILE: statemachine/contrib/diagram/renderers/table.py
class TransitionTableRenderer (line 8) | class TransitionTableRenderer:
method render (line 11) | def render(self, graph: DiagramGraph, fmt: str = "md") -> str:
method _collect_rows (line 27) | def _collect_rows(
method _build_state_name_map (line 53) | def _build_state_name_map(self, states: List[DiagramState]) -> dict:
method _render_md (line 62) | def _render_md(self, rows: "List[tuple[str, str, str, str]]") -> str:
method _render_rst (line 82) | def _render_rst(self, rows: "List[tuple[str, str, str, str]]") -> str:
FILE: statemachine/contrib/diagram/sphinx_ext.py
function _align_spec (line 32) | def _align_spec(argument: str) -> str:
function _parse_events (line 36) | def _parse_events(value: str) -> list[str]:
class StateMachineDiagram (line 49) | class StateMachineDiagram(SphinxDirective):
method run (line 78) | def run(self) -> list[nodes.Node]:
method _run_mermaid (line 151) | def _run_mermaid(self, machine: object, formatter: Any, qualname: str)...
method _prepare_svg (line 190) | def _prepare_svg(self, svg_text: str) -> tuple[str, str, str]:
method _build_svg_styles (line 206) | def _build_svg_styles(self, intrinsic_width: str, intrinsic_height: st...
method _resolve_target (line 235) | def _resolve_target(self, svg_text: str) -> str:
method _build_wrapper_classes (line 263) | def _build_wrapper_classes(self) -> list[str]:
function _split_length (line 270) | def _split_length(value: str) -> tuple[float, str]:
function setup (line 278) | def setup(app: "Sphinx") -> dict[str, Any]:
FILE: statemachine/contrib/timeout.py
class _Timeout (line 24) | class _Timeout:
method __init__ (line 27) | def __init__(self, duration: float, on: "str | None" = None):
method run (line 31) | def run(self, ctx: "InvokeContext") -> Any:
method __repr__ (line 45) | def __repr__(self) -> str:
function timeout (line 52) | def timeout(duration: float, *, on: "str | None" = None) -> _Timeout:
FILE: statemachine/contrib/weighted.py
class _WeightedGroup (line 16) | class _WeightedGroup:
method __init__ (line 23) | def __init__(self, weights: List[float], seed: "int | float | str | No...
method select (line 29) | def select(self) -> int:
method selected (line 35) | def selected(self) -> "int | None":
function _make_weighted_cond (line 39) | def _make_weighted_cond(index: int, group: _WeightedGroup, weight: float...
function to (line 69) | def to(target: "State", weight: "int | float", **kwargs: Any) -> _Weight...
function _validate_dest (line 100) | def _validate_dest(i: int, item: Any) -> "Tuple[State, float, Dict[str, ...
function weighted_transitions (line 136) | def weighted_transitions(
FILE: statemachine/dispatcher.py
class Listener (line 31) | class Listener:
method from_obj (line 44) | def from_obj(cls, obj, skip_attrs=None) -> "Listener":
method build_key (line 53) | def build_key(self, attr_name) -> str:
class Listeners (line 58) | class Listeners:
method from_listeners (line 65) | def from_listeners(cls, listeners: Iterable["Listener"]) -> "Listeners":
method resolve (line 70) | def resolve(
method _take_callback (line 88) | def _take_callback(self, name: str, names_not_found_handler: Callable)...
method build (line 103) | def build(self, spec: "CallbackSpec"):
method search (line 134) | def search(self, spec: "CallbackSpec"):
method _search_property (line 144) | def _search_property(self, spec):
method _search_callable (line 159) | def _search_callable(self, spec):
method search_name (line 171) | def search_name(self, name):
function callable_method (line 189) | def callable_method(a_callable) -> Callable:
function attr_method (line 213) | def attr_method(attribute, obj) -> Callable:
function event_method (line 223) | def event_method(func) -> Callable:
function resolver_factory_from_objects (line 231) | def resolver_factory_from_objects(*objects: Tuple[Any, ...]):
FILE: statemachine/engines/async_.py
class AsyncEngine (line 31) | class AsyncEngine(BaseEngine):
method put (line 38) | def put(self, trigger_data: TriggerData, internal: bool = False, _dela...
method _resolve_future (line 57) | def _resolve_future(future: "asyncio.Future[object] | None", result):
method _reject_future (line 63) | def _reject_future(future: "asyncio.Future[object] | None", exc: Excep...
method _reject_pending_futures (line 68) | def _reject_pending_futures(self, exc: Exception):
method _get_args_kwargs (line 74) | async def _get_args_kwargs(
method _conditions_match (line 96) | async def _conditions_match(self, transition: "Transition", trigger_da...
method _first_transition_that_matches (line 107) | async def _first_transition_that_matches( # type: ignore[override]
method _select_transitions (line 124) | async def _select_transitions( # type: ignore[override]
method select_eventless_transitions (line 138) | async def select_eventless_transitions(self, trigger_data: TriggerData):
method select_transitions (line 141) | async def select_transitions(self, trigger_data: TriggerData) -> "Orde...
method _execute_transition_content (line 144) | async def _execute_transition_content(
method _exit_states (line 166) | async def _exit_states( # type: ignore[override]
method _enter_states (line 189) | async def _enter_states( # noqa: C901
method microstep (line 273) | async def microstep(self, transitions: "List[Transition]", trigger_dat...
method _run_microstep (line 321) | async def _run_microstep(self, enabled_transitions, trigger_data): # ...
method activate_initial_state (line 334) | async def activate_initial_state(self, **kwargs):
method processing_loop (line 346) | async def processing_loop( # noqa: C901
method enabled_events (line 502) | async def enabled_events(self, *args, **kwargs):
FILE: statemachine/engines/base.py
class StateTransition (line 33) | class StateTransition:
class EventQueue (line 38) | class EventQueue:
method __init__ (line 39) | def __init__(self):
method __repr__ (line 42) | def __repr__(self):
method is_empty (line 45) | def is_empty(self):
method put (line 48) | def put(self, trigger_data: TriggerData):
method pop (line 52) | def pop(self):
method clear (line 56) | def clear(self):
method reject_futures (line 60) | def reject_futures(self, exc: Exception):
method remove (line 72) | def remove(self, send_id: str):
class BaseEngine (line 86) | class BaseEngine:
method __init__ (line 87) | def __init__(self, sm: "StateChart"):
method empty (line 102) | def empty(self): # pragma: no cover
method clear_cache (line 105) | def clear_cache(self):
method put (line 109) | def put(self, trigger_data: TriggerData, internal: bool = False, _dela...
method pop (line 127) | def pop(self): # pragma: no cover
method clear (line 130) | def clear(self):
method cancel_event (line 133) | def cancel_event(self, send_id: str):
method _on_error_handler (line 137) | def _on_error_handler(self) -> "Callable[[Exception], None] | None":
method _handle_error (line 159) | def _handle_error(self, error: Exception, trigger_data: TriggerData):
method _send_error_execution (line 170) | def _send_error_execution(self, error: Exception, trigger_data: Trigge...
method start (line 186) | def start(self, **kwargs):
method _initial_transitions (line 192) | def _initial_transitions(self, trigger_data):
method _filter_conflicting_transitions (line 202) | def _filter_conflicting_transitions(
method _compute_exit_set (line 246) | def _compute_exit_set(self, transitions: List[Transition]) -> OrderedS...
method get_transition_domain (line 262) | def get_transition_domain(self, transition: Transition) -> "State | No...
method find_lcca (line 289) | def find_lcca(states: List[State]) -> "State | None":
method get_effective_target_states (line 312) | def get_effective_target_states(self, transition: Transition) -> Order...
method select_eventless_transitions (line 329) | def select_eventless_transitions(self, trigger_data: TriggerData):
method select_transitions (line 335) | def select_transitions(self, trigger_data: TriggerData) -> OrderedSet[...
method _first_transition_that_matches (line 341) | def _first_transition_that_matches(
method _select_transitions (line 358) | def _select_transitions(
method microstep (line 374) | def microstep(self, transitions: List[Transition], trigger_data: Trigg...
method _get_args_kwargs (line 423) | def _get_args_kwargs(
method _conditions_match (line 448) | def _conditions_match(self, transition: Transition, trigger_data: Trig...
method _prepare_exit_states (line 455) | def _prepare_exit_states(
method _remove_state_from_configuration (line 488) | def _remove_state_from_configuration(self, state: State):
method _exit_states (line 493) | def _exit_states(
method _execute_transition_content (line 516) | def _execute_transition_content(
method _prepare_entry_states (line 538) | def _prepare_entry_states(
method _add_state_to_configuration (line 573) | def _add_state_to_configuration(self, target: State):
method stop (line 578) | def stop(self):
method __del__ (line 587) | def __del__(self):
method _handle_final_state (line 593) | def _handle_final_state(self, target: State, on_entry_result: list):
method _enter_states (line 627) | def _enter_states( # noqa: C901
method compute_entry_set (line 714) | def compute_entry_set(
method add_descendant_states_to_enter (line 749) | def add_descendant_states_to_enter( # noqa: C901
method add_ancestor_states_to_enter (line 884) | def add_ancestor_states_to_enter(
method _check_root_final_state (line 922) | def _check_root_final_state(self):
method is_in_final_state (line 945) | def is_in_final_state(self, state: State) -> bool:
FILE: statemachine/engines/sync.py
class SyncEngine (line 17) | class SyncEngine(BaseEngine):
method _run_microstep (line 18) | def _run_microstep(self, enabled_transitions, trigger_data):
method start (line 31) | def start(self, **kwargs):
method activate_initial_state (line 37) | def activate_initial_state(self, **kwargs):
method processing_loop (line 59) | def processing_loop(self, caller_future=None): # noqa: C901
method enabled_events (line 178) | def enabled_events(self, *args, **kwargs):
FILE: statemachine/event.py
function _expand_event_id (line 20) | def _expand_event_id(key: str) -> str:
class Event (line 50) | class Event(AddCallbacksMixin, str):
method __new__ (line 79) | def __new__(
method __repr__ (line 120) | def __repr__(self):
method is_same_event (line 125) | def is_same_event(self, *_args, event: "str | None" = None, **_kwargs)...
method _add_callback (line 128) | def _add_callback(self, callback, grouper: CallbackGroup, is_event=Fal...
method __get__ (line 140) | def __get__(self, instance, owner):
method put (line 152) | def put(self, *args, send_id: "str | None" = None, **kwargs):
method build_trigger (line 162) | def build_trigger(self, *args, machine: "StateChart", send_id: "str | ...
method __call__ (line 177) | def __call__(self, *args, **kwargs) -> Any:
method split (line 190) | def split( # type: ignore[override]
method match (line 198) | def match(self, event: str) -> bool:
class BoundEvent (line 226) | class BoundEvent(Event):
FILE: statemachine/event_data.py
class TriggerData (line 15) | class TriggerData:
method __post_init__ (line 46) | def __post_init__(self):
class EventData (line 53) | class EventData:
method __post_init__ (line 69) | def __post_init__(self):
method event (line 76) | def event(self):
method args (line 80) | def args(self):
method extended_kwargs (line 84) | def extended_kwargs(self):
FILE: statemachine/events.py
class Events (line 5) | class Events:
method __init__ (line 8) | def __init__(self):
method __str__ (line 11) | def __str__(self):
method __repr__ (line 15) | def __repr__(self):
method __iter__ (line 18) | def __iter__(self):
method add (line 21) | def add(self, events):
method match (line 37) | def match(self, event: "str | None"):
method _replace (line 42) | def _replace(self, old, new):
method is_empty (line 47) | def is_empty(self):
FILE: statemachine/exceptions.py
class StateMachineError (line 11) | class StateMachineError(Exception):
class InvalidDefinition (line 15) | class InvalidDefinition(StateMachineError):
class InvalidStateValue (line 19) | class InvalidStateValue(InvalidDefinition):
method __init__ (line 22) | def __init__(self, value, msg=None):
class AttrNotFound (line 29) | class AttrNotFound(InvalidDefinition):
class TransitionNotAllowed (line 33) | class TransitionNotAllowed(StateMachineError):
method __init__ (line 36) | def __init__(self, event: "Event | None", configuration: MutableSet["S...
FILE: statemachine/factory.py
class StateMachineMetaclass (line 26) | class StateMachineMetaclass(type):
method __init__ (line 39) | def __init__(
method _expand_docstring (line 99) | def _expand_docstring(cls) -> None:
method __format__ (line 127) | def __format__(cls, fmt: str) -> str:
method _initials_by_document_order (line 132) | def _initials_by_document_order( # noqa: C901
method _unpack_builders_callbacks (line 170) | def _unpack_builders_callbacks(cls):
method _check (line 179) | def _check(cls):
method _check_initial_state (line 193) | def _check_initial_state(cls):
method _check_final_states (line 206) | def _check_final_states(cls):
method _check_trap_states (line 218) | def _check_trap_states(cls):
method _check_reachable_final_states (line 230) | def _check_reachable_final_states(cls):
method _check_disconnected_state (line 244) | def _check_disconnected_state(cls):
method _setup (line 258) | def _setup(cls):
method _collect_class_listeners (line 275) | def _collect_class_listeners(cls, attrs: Dict[str, Any], bases: Tuple[...
method add_inherited (line 296) | def add_inherited(cls, bases):
method add_from_attributes (line 305) | def add_from_attributes(cls, attrs): # noqa: C901
method _add_states_from_dict (line 331) | def _add_states_from_dict(cls, states):
method _add_unbounded_callback (line 335) | def _add_unbounded_callback(cls, attr_name, func):
method add_state (line 343) | def add_state(cls, id, state: State):
method add_event (line 358) | def add_event(
method _update_event_references (line 381) | def _update_event_references(cls):
method events (line 395) | def events(self):
FILE: statemachine/graph.py
function visit_connected_states (line 10) | def visit_connected_states(state: "State"):
function disconnected_states (line 33) | def disconnected_states(starting_state: "State", all_states: MutableSet[...
function iterate_states_and_transitions (line 38) | def iterate_states_and_transitions(states: Iterable["State"]):
function iterate_states (line 48) | def iterate_states(states: Iterable["State"]):
function states_without_path_to_final_states (line 57) | def states_without_path_to_final_states(states: Iterable["State"]):
FILE: statemachine/i18n.py
function setup_i18n (line 8) | def setup_i18n():
FILE: statemachine/invoke.py
class IInvoke (line 37) | class IInvoke(Protocol):
method run (line 44) | def run(self, ctx: "InvokeContext") -> Any: ... # pragma: no branch
function _stop_child_machine (line 47) | def _stop_child_machine(child: "StateChart | None") -> None:
class _InvokeCallableWrapper (line 54) | class _InvokeCallableWrapper:
method __init__ (line 66) | def __init__(self, handler: Any):
method __call__ (line 76) | def __call__(self, **kwargs):
method run (line 79) | def run(self, ctx: "InvokeContext") -> Any:
method on_cancel (line 87) | def on_cancel(self):
function normalize_invoke_callbacks (line 99) | def normalize_invoke_callbacks(invoke: Any) -> Any:
function _needs_wrapping (line 119) | def _needs_wrapping(item: Any) -> bool:
class InvokeContext (line 138) | class InvokeContext:
class Invocation (line 161) | class Invocation:
class StateChartInvoker (line 173) | class StateChartInvoker:
method __init__ (line 181) | def __init__(self, child_class: "type[StateChart]"):
method run (line 185) | def run(self, _ctx: "InvokeContext") -> Any:
method on_cancel (line 191) | def on_cancel(self):
class InvokeGroup (line 196) | class InvokeGroup:
method __init__ (line 208) | def __init__(self, callables: "List[Callable[..., Any]]"):
method run (line 213) | def run(self, ctx: "InvokeContext") -> "List[Any]":
method on_cancel (line 234) | def on_cancel(self):
method _cancel_remaining (line 242) | def _cancel_remaining(self):
function invoke_group (line 248) | def invoke_group(*callables: "Callable[..., Any]") -> InvokeGroup:
class InvokeManager (line 265) | class InvokeManager:
method __init__ (line 272) | def __init__(self, engine: "BaseEngine"):
method _debug (line 278) | def _debug(self):
method _log_id (line 282) | def _log_id(self):
method sm (line 286) | def sm(self) -> "StateChart":
method mark_for_invoke (line 291) | def mark_for_invoke(self, state: "State", event_kwargs: "dict | None" ...
method cancel_for_state (line 303) | def cancel_for_state(self, state: "State"):
method cancel_all (line 314) | def cancel_all(self):
method _cleanup_terminated (line 321) | def _cleanup_terminated(self):
method spawn_pending_sync (line 338) | def spawn_pending_sync(self):
method _spawn_one_sync (line 353) | def _spawn_one_sync(self, callback: "CallbackWrapper", **kwargs):
method _run_sync_handler (line 375) | def _run_sync_handler(
method spawn_pending_async (line 412) | async def spawn_pending_async(self):
method _spawn_one_async (line 427) | def _spawn_one_async(self, callback: "CallbackWrapper", **kwargs):
method _run_async_handler (line 443) | async def _run_async_handler(
method _cancel (line 484) | def _cancel(self, invokeid: str):
method send_to_child (line 514) | def send_to_child(self, invokeid: str, event: str, **data) -> bool:
method handle_external_event (line 531) | def handle_external_event(self, trigger_data) -> None:
method _make_context (line 578) | def _make_context(
method _resolve_handler (line 593) | def _resolve_handler(underlying: Any) -> "Any | None":
FILE: statemachine/io/__init__.py
class ActionProtocol (line 19) | class ActionProtocol(Protocol): # pragma: no cover
method __call__ (line 20) | def __call__(self, *args, **kwargs) -> Any: ...
class TransitionDict (line 23) | class TransitionDict(TypedDict, total=False):
class BaseStateKwargs (line 40) | class BaseStateKwargs(TypedDict, total=False):
class StateKwargs (line 51) | class StateKwargs(BaseStateKwargs, total=False):
class HistoryKwargs (line 56) | class HistoryKwargs(TypedDict, total=False):
class HistoryDefinition (line 62) | class HistoryDefinition(HistoryKwargs, total=False):
class StateDefinition (line 67) | class StateDefinition(BaseStateKwargs, total=False):
function _parse_history (line 74) | def _parse_history(
function _parse_states (line 95) | def _parse_states(
function create_machine_class_from_definition (line 146) | def create_machine_class_from_definition(
FILE: statemachine/io/scxml/actions.py
class ParseTime (line 35) | class ParseTime:
method parse_delay (line 39) | def parse_delay(cls, delay: "str | None", delayexpr: "str | None", **k...
method replace (line 49) | def replace(cls, expr: str) -> str:
method time_in_ms (line 56) | def time_in_ms(cls, expr: str) -> float:
class _Data (line 87) | class _Data:
method __getattr__ (line 90) | def __getattr__(self, name):
method get (line 93) | def get(self, name, default=None):
class OriginTypeSCXML (line 97) | class OriginTypeSCXML(str):
method __eq__ (line 100) | def __eq__(self, other):
class EventDataWrapper (line 104) | class EventDataWrapper:
method __init__ (line 114) | def __init__(self, event_data=None, *, trigger_data=None):
method from_trigger_data (line 136) | def from_trigger_data(cls, trigger_data):
method __getattr__ (line 140) | def __getattr__(self, name):
method __eq__ (line 145) | def __eq__(self, value):
method name (line 150) | def name(self):
method data (line 156) | def data(self):
function _eval (line 169) | def _eval(expr: str, **kwargs) -> Any:
class CallableAction (line 182) | class CallableAction:
method __init__ (line 185) | def __init__(self):
method __call__ (line 188) | def __call__(self, *args, **kwargs):
method __str__ (line 191) | def __str__(self):
method __repr__ (line 194) | def __repr__(self):
method __name__ (line 198) | def __name__(self):
method __code__ (line 202) | def __code__(self):
class Cond (line 206) | class Cond(CallableAction):
method create (line 210) | def create(cls, cond: "str | None", processor=None):
method __init__ (line 217) | def __init__(self, cond: str, processor=None):
method __call__ (line 222) | def __call__(self, *args, **kwargs):
method _normalize (line 228) | def _normalize(cond: "str | None") -> "str | None":
function create_action_callable (line 253) | def create_action_callable(action: Action) -> Callable:
class Assign (line 274) | class Assign(CallableAction):
method __init__ (line 275) | def __init__(self, action: AssignAction):
method __call__ (line 279) | def __call__(self, *args, **kwargs):
class Log (line 305) | class Log(CallableAction):
method __init__ (line 306) | def __init__(self, action: LogAction):
method __call__ (line 310) | def __call__(self, *args, **kwargs):
function create_if_action_callable (line 322) | def create_if_action_callable(action: IfAction) -> Callable:
function create_foreach_action_callable (line 352) | def create_foreach_action_callable(action: ForeachAction) -> Callable:
function create_raise_action_callable (line 381) | def create_raise_action_callable(action: RaiseAction) -> Callable:
function _send_to_parent (line 391) | def _send_to_parent(action: SendAction, **kwargs):
function _send_to_invoke (line 415) | def _send_to_invoke(action: SendAction, invokeid: str, **kwargs):
function create_send_action_callable (line 434) | def create_send_action_callable(action: SendAction) -> Callable: # noqa...
function create_cancel_action_callable (line 505) | def create_cancel_action_callable(action: CancelAction) -> Callable:
function create_script_action_callable (line 522) | def create_script_action_callable(action: ScriptAction) -> Callable:
function create_invoke_init_callable (line 538) | def create_invoke_init_callable() -> Callable:
function _create_dataitem_callable (line 562) | def _create_dataitem_callable(action: DataItem) -> Callable:
function create_datamodel_action_callable (line 592) | def create_datamodel_action_callable(action: DataModel) -> "Callable | N...
class ExecuteBlock (line 613) | class ExecuteBlock(CallableAction):
method __init__ (line 616) | def __init__(self, content: ExecutableContent):
method __call__ (line 621) | def __call__(self, *args, **kwargs):
class DoneDataCallable (line 626) | class DoneDataCallable(CallableAction):
method __init__ (line 629) | def __init__(self, donedata: DoneData):
method __call__ (line 634) | def __call__(self, *args, **kwargs):
FILE: statemachine/io/scxml/invoke.py
class SCXMLInvoker (line 33) | class SCXMLInvoker:
method __init__ (line 40) | def __init__(
method run (line 61) | def run(self, ctx: InvokeContext) -> Any:
method on_cancel (line 109) | def on_cancel(self):
method on_event (line 116) | def on_event(self, event_name: str, **data):
method on_finalize (line 124) | def on_finalize(self, trigger_data):
method _resolve_content (line 142) | def _resolve_content(self, machine) -> "str | None":
method _evaluate_params (line 175) | def _evaluate_params(self, machine) -> dict:
method _create_child_class (line 195) | def _create_child_class(self, scxml_content: str, invokeid: str):
class _ChildRefSetter (line 201) | class _ChildRefSetter:
method __init__ (line 210) | def __init__(self, invoker: "SCXMLInvoker"):
method on_enter_state (line 213) | def on_enter_state(self, machine=None, **kwargs):
class _InvokeSession (line 218) | class _InvokeSession:
method __init__ (line 221) | def __init__(self, parent, invokeid: str):
method send_to_parent (line 225) | def send_to_parent(self, event: str, **data):
FILE: statemachine/io/scxml/parser.py
function strip_namespaces (line 31) | def strip_namespaces(tree: ET.Element):
function _parse_initial (line 43) | def _parse_initial(initial_content: "str | None") -> List[str]:
function parse_scxml (line 49) | def parse_scxml(scxml_content: str) -> StateMachineDefinition: # noqa: ...
function _find_own_datamodel_elements (line 90) | def _find_own_datamodel_elements(root: ET.Element) -> List[ET.Element]:
function parse_datamodel (line 110) | def parse_datamodel(root: ET.Element) -> "DataModel | None":
function parse_history (line 141) | def parse_history(state_elem: ET.Element) -> HistoryState:
function parse_state (line 158) | def parse_state( # noqa: C901
function parse_donedata (line 235) | def parse_donedata(element: ET.Element) -> DoneData:
function parse_transition (line 252) | def parse_transition(trans_elem: ET.Element, initial: bool = False) -> T...
function parse_executable_content (line 271) | def parse_executable_content(element: ET.Element) -> ExecutableContent:
function parse_element (line 281) | def parse_element(element: ET.Element) -> Action:
function parse_raise (line 303) | def parse_raise(element: ET.Element) -> RaiseAction:
function parse_assign (line 308) | def parse_assign(element: ET.Element) -> AssignAction:
function parse_log (line 322) | def parse_log(element: ET.Element) -> LogAction:
function parse_if (line 328) | def parse_if(element: ET.Element) -> IfAction:
function parse_foreach (line 344) | def parse_foreach(element: ET.Element) -> ForeachAction:
function parse_send (line 352) | def parse_send(element: ET.Element) -> SendAction:
function parse_cancel (line 415) | def parse_cancel(element: ET.Element) -> CancelAction:
function parse_script (line 421) | def parse_script(element: ET.Element) -> ScriptAction:
function parse_invoke (line 426) | def parse_invoke(element: ET.Element) -> InvokeDefinition:
FILE: statemachine/io/scxml/processor.py
function temporary_directory (line 32) | def temporary_directory(new_current_dir):
class IOProcessor (line 41) | class IOProcessor:
method __init__ (line 42) | def __init__(self, processor: "SCXMLProcessor", machine: StateChart):
method __getitem__ (line 46) | def __getitem__(self, name: str):
method location (line 50) | def location(self):
method get (line 53) | def get(self, name: str):
class SessionData (line 58) | class SessionData:
method __post_init__ (line 63) | def __post_init__(self):
class SCXMLProcessor (line 67) | class SCXMLProcessor:
method __init__ (line 68) | def __init__(self):
method parse_scxml_file (line 76) | def parse_scxml_file(self, path: Path):
method parse_scxml (line 81) | def parse_scxml(self, sm_name: str, scxml_content: str):
method process_definition (line 85) | def process_definition(self, definition, location: str, is_invoked: bo...
method _prepare_event (line 126) | def _prepare_event(self, *args, event: Event, **kwargs):
method _get_session (line 144) | def _get_session(self, machine: StateChart):
method _process_history (line 151) | def _process_history(self, history: Dict[str, HistoryState]) -> Dict[s...
method _process_states (line 166) | def _process_states(self, states: Dict[str, State]) -> Dict[str, State...
method _process_state (line 172) | def _process_state(self, state: State) -> StateDefinition: # noqa: C901
method _process_invocation (line 212) | def _process_invocation(self, invoke_def: InvokeDefinition) -> SCXMLIn...
method _register_child (line 220) | def _register_child(self, scxml_content: str, child_name: str) -> type:
method _process_transitions (line 226) | def _process_transitions(self, transitions: List[Transition]):
method _add (line 250) | def _add(self, location: str, definition: Dict[str, Any]):
method start (line 260) | def start(self, **kwargs):
FILE: statemachine/io/scxml/schema.py
class Action (line 10) | class Action:
method __str__ (line 11) | def __str__(self):
class ExecutableContent (line 16) | class ExecutableContent:
method __str__ (line 19) | def __str__(self):
method is_empty (line 23) | def is_empty(self):
class RaiseAction (line 28) | class RaiseAction(Action):
class AssignAction (line 33) | class AssignAction(Action):
class LogAction (line 40) | class LogAction(Action):
class IfBranch (line 46) | class IfBranch(Action):
method __str__ (line 50) | def __str__(self):
method append (line 53) | def append(self, action: Action):
class IfAction (line 58) | class IfAction(Action):
class ForeachAction (line 63) | class ForeachAction(Action):
class Param (line 71) | class Param:
class SendAction (line 78) | class SendAction(Action):
class CancelAction (line 93) | class CancelAction(Action):
class ScriptAction (line 99) | class ScriptAction(Action):
class Transition (line 104) | class Transition:
class DoneData (line 114) | class DoneData:
class InvokeDefinition (line 120) | class InvokeDefinition:
class State (line 135) | class State:
class HistoryState (line 150) | class HistoryState:
class DataItem (line 157) | class DataItem:
class DataModel (line 165) | class DataModel:
class StateMachineDefinition (line 171) | class StateMachineDefinition:
FILE: statemachine/mixins.py
class MachineMixin (line 5) | class MachineMixin:
method __init__ (line 22) | def __init__(self, *args, **kwargs):
method _is_django_historical_model (line 41) | def _is_django_historical_model(cls) -> bool:
FILE: statemachine/model.py
class Model (line 1) | class Model:
method __init__ (line 2) | def __init__(self):
method __repr__ (line 6) | def __repr__(self):
FILE: statemachine/orderedset.py
class OrderedSet (line 10) | class OrderedSet(MutableSet[T]):
method __init__ (line 74) | def __init__(self, iterable: "Iterable[T] | None" = None):
method add (line 77) | def add(self, x: T) -> None:
method clear (line 80) | def clear(self) -> None:
method discard (line 83) | def discard(self, x: T) -> None:
method __getitem__ (line 86) | def __getitem__(self, index) -> T:
method __contains__ (line 92) | def __contains__(self, x: object) -> bool:
method __len__ (line 95) | def __len__(self) -> int:
method __iter__ (line 98) | def __iter__(self) -> Iterator[T]:
method __str__ (line 101) | def __str__(self):
method __repr__ (line 104) | def __repr__(self):
method union (line 107) | def union(self, *others: Iterable[T]) -> "OrderedSet[T]":
method update (line 114) | def update(self, *others: Iterable[T]) -> None:
FILE: statemachine/registry.py
function autodiscover_modules (line 10) | def autodiscover_modules(module_name: str):
function register (line 18) | def register(cls):
function get_machine_cls (line 23) | def get_machine_cls(name):
function init_registry (line 28) | def init_registry():
function load_modules (line 35) | def load_modules(modules: Optional[List[str]] = None) -> None:
FILE: statemachine/signature.py
function _make_key (line 19) | def _make_key(method):
function signature_cache (line 36) | def signature_cache(user_function):
class SignatureAdapter (line 55) | class SignatureAdapter(Signature):
method __init__ (line 59) | def __init__(self, *args, **kwargs):
method from_callable (line 65) | def from_callable(cls, method):
method bind_expected (line 78) | def bind_expected(self, *args: Any, **kwargs: Any) -> BoundArguments:
method _fast_bind (line 88) | def _fast_bind(
method _full_bind (line 122) | def _full_bind( # noqa: C901
FILE: statemachine/spec_parser.py
function _unique_key (line 23) | def _unique_key(left, right, operator) -> str:
function replace_operators (line 29) | def replace_operators(expr: str) -> str:
function custom_not (line 37) | def custom_not(predicate: Callable) -> Callable:
function custom_and (line 54) | def custom_and(left: Callable, right: Callable) -> Callable:
function custom_or (line 81) | def custom_or(left: Callable, right: Callable) -> Callable:
function build_constant (line 108) | def build_constant(constant) -> Callable:
class Functions (line 117) | class Functions:
method register (line 121) | def register(cls, id) -> Callable:
method get (line 129) | def get(cls, func_id):
class InState (line 136) | class InState:
method __init__ (line 137) | def __init__(self, machine):
method __call__ (line 140) | def __call__(self, *state_ids: str):
function build_in_call (line 145) | def build_in_call(*state_ids: str) -> Callable:
function build_custom_operator (line 157) | def build_custom_operator(operator) -> Callable:
function build_expression (line 181) | def build_expression(node, variable_hook, operator_mapping): # noqa: C901
function parse_boolean_expr (line 222) | def parse_boolean_expr(expr, variable_hook, operator_mapping):
FILE: statemachine/state.py
class _TransitionBuilder (line 24) | class _TransitionBuilder:
method __init__ (line 25) | def __init__(self, state: "State"):
method itself (line 28) | def itself(self, **kwargs):
method __call__ (line 31) | def __call__(self, *states: "State", **kwargs):
class _ToState (line 35) | class _ToState(_TransitionBuilder):
method __call__ (line 36) | def __call__(self, *states: "State | NestedStateFactory | None", **kwa...
class _FromState (line 44) | class _FromState(_TransitionBuilder):
method any (line 45) | def any(self, **kwargs):
method __call__ (line 49) | def __call__(self, *states: "State | NestedStateFactory", **kwargs):
class NestedStateFactory (line 59) | class NestedStateFactory(type):
method __new__ (line 60) | def __new__( # type: ignore [misc]
method to (line 94) | def to(cls, *args: "State | NestedStateFactory", **kwargs) -> "_ToStat...
method from_ (line 102) | def from_( # pragma: no cover
class State (line 112) | class State:
class Compound (line 197) | class Compound(metaclass=NestedStateFactory):
class Parallel (line 200) | class Parallel(metaclass=NestedStateFactory, parallel=True):
method __init__ (line 203) | def __init__(
method _init_states (line 249) | def _init_states(self):
method __eq__ (line 259) | def __eq__(self, other):
method __hash__ (line 267) | def __hash__(self):
method _setup (line 270) | def _setup(self):
method _on_event_defined (line 280) | def _on_event_defined(self, event: str, transition: Transition, states...
method __repr__ (line 286) | def __repr__(self):
method __str__ (line 292) | def __str__(self):
method id (line 296) | def id(self) -> str:
method _set_id (line 299) | def _set_id(self, id: str) -> "State":
method to (line 310) | def to(self) -> _ToState:
method from_ (line 315) | def from_(self) -> _FromState:
method initial (line 320) | def initial(self):
method final (line 324) | def final(self):
method parallel (line 328) | def parallel(self):
method is_compound (line 332) | def is_compound(self):
method is_history (line 336) | def is_history(self):
method ancestors (line 339) | def ancestors(self, parent: "State | None" = None) -> Generator["State...
method is_descendant (line 347) | def is_descendant(self, state: "State") -> bool:
class InstanceState (line 351) | class InstanceState(State):
method __init__ (line 359) | def __init__(
method __getattr__ (line 369) | def __getattr__(self, name: str):
method __eq__ (line 374) | def __eq__(self, other):
method __hash__ (line 377) | def __hash__(self):
method __repr__ (line 380) | def __repr__(self):
method id (line 384) | def id(self) -> str:
method initial (line 388) | def initial(self):
method final (line 392) | def final(self):
method parallel (line 396) | def parallel(self):
method is_active (line 400) | def is_active(self):
class AnyState (line 406) | class AnyState(State):
method _on_event_defined (line 413) | def _on_event_defined(self, event: str, transition: Transition, states...
class HistoryType (line 422) | class HistoryType(str, Enum):
method is_deep (line 434) | def is_deep(self) -> bool:
class HistoryState (line 438) | class HistoryState(State):
method __init__ (line 439) | def __init__(
FILE: statemachine/statemachine.py
class StateChart (line 46) | class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
method __init__ (line 139) | def __init__(
method _get_engine (line 173) | def _get_engine(self):
method _resolve_class_listeners (line 179) | def _resolve_class_listeners(self, **kwargs: Any) -> List[object]:
method _build_configuration (line 199) | def _build_configuration(self) -> Configuration:
method activate_initial_state (line 215) | def activate_initial_state(self) -> Any:
method _processing_loop (line 221) | def _processing_loop(self, caller_future: "Any | None" = None) -> Any:
method __setattr__ (line 227) | def __setattr__(self, name, value):
method __repr__ (line 235) | def __repr__(self):
method __format__ (line 242) | def __format__(self, fmt: str) -> str:
method __getstate__ (line 247) | def __getstate__(self):
method __setstate__ (line 254) | def __setstate__(self, state: Dict[str, Any]) -> None:
method _get_initial_configuration (line 269) | def _get_initial_configuration(self):
method bind_events_to (line 280) | def bind_events_to(self, *targets):
method _add_listener (line 295) | def _add_listener(self, listeners: "Listeners", allowed_references: Sp...
method _register_callbacks (line 307) | def _register_callbacks(self, listeners: List[object]):
method active_listeners (line 331) | def active_listeners(self) -> List[object]:
method add_listener (line 339) | def add_listener(self, *listeners):
method _repr_html_ (line 355) | def _repr_html_(self):
method _repr_svg_ (line 358) | def _repr_svg_(self):
method _graph (line 361) | def _graph(self):
method configuration_values (line 367) | def configuration_values(self) -> OrderedSet[Any]:
method configuration (line 373) | def configuration(self) -> OrderedSet["State"]:
method configuration (line 378) | def configuration(self, new_configuration: OrderedSet["State"]):
method current_state_value (line 382) | def current_state_value(self):
method current_state_value (line 391) | def current_state_value(self, value):
method current_state (line 395) | def current_state(self) -> "State | MutableSet[State]":
method current_state (line 409) | def current_state(self, value): # pragma: no cover
method events (line 413) | def events(self) -> "List[Event]":
method allowed_events (line 417) | def allowed_events(self) -> "List[Event]":
method enabled_events (line 425) | def enabled_events(self, *args, **kwargs) -> Any:
method _put_nonblocking (line 443) | def _put_nonblocking(self, trigger_data: TriggerData, internal: bool =...
method send (line 447) | def send(
method raise_ (line 482) | def raise_(
method cancel_event (line 497) | def cancel_event(self, send_id: str):
method is_terminated (line 502) | def is_terminated(self):
class StateMachine (line 512) | class StateMachine(StateChart):
FILE: statemachine/states.py
class States (line 11) | class States:
method __init__ (line 38) | def __init__(self, states: "Dict[str, State] | None" = None) -> None:
method __repr__ (line 50) | def __repr__(self):
method __eq__ (line 53) | def __eq__(self, other):
method __getattr__ (line 56) | def __getattr__(self, name: str) -> "State":
method __len__ (line 61) | def __len__(self):
method __getitem__ (line 64) | def __getitem__(self, index):
method __iter__ (line 67) | def __iter__(self):
method append (line 70) | def append(self, state):
method items (line 73) | def items(self):
method from_enum (line 86) | def from_enum(cls, enum_type: EnumType, initial, final=None, use_enum_...
FILE: statemachine/transition.py
class Transition (line 16) | class Transition:
method __init__ (line 43) | def __init__(
method target (line 102) | def target(self) -> "State | None":
method targets (line 107) | def targets(self) -> "List[State]":
method __repr__ (line 111) | def __repr__(self):
method __str__ (line 117) | def __str__(self):
method _setup (line 120) | def _setup(self):
method match (line 155) | def match(self, event: str):
method event (line 159) | def event(self):
method events (line 163) | def events(self):
method add_event (line 166) | def add_event(self, value):
method _copy_with_args (line 169) | def _copy_with_args(self, **kwargs):
method is_eventless (line 184) | def is_eventless(self):
FILE: statemachine/transition_list.py
class TransitionList (line 15) | class TransitionList(AddCallbacksMixin):
method __init__ (line 18) | def __init__(self, transitions: "Iterable[Transition] | None" = None):
method __repr__ (line 27) | def __repr__(self):
method __or__ (line 31) | def __or__(self, other: "TransitionList | Iterable"):
method _on_event_defined (line 45) | def _on_event_defined(self, event: str, states: List["State"]):
method add_transitions (line 51) | def add_transitions(self, transition: "Transition | TransitionList | I...
method __getitem__ (line 70) | def __getitem__(self, index: int) -> "Transition":
method __len__ (line 81) | def __len__(self):
method __iter__ (line 89) | def __iter__(self):
method _add_callback (line 92) | def _add_callback(self, callback, grouper: CallbackGroup, is_event=Fal...
method add_event (line 103) | def add_event(self, event: str):
method unique_events (line 114) | def unique_events(self) -> List["Event"]:
method has_eventless_transition (line 130) | def has_eventless_transition(self):
FILE: statemachine/transition_mixin.py
class AddCallbacksMixin (line 11) | class AddCallbacksMixin:
method _add_callback (line 12) | def _add_callback(self, callback: T, grouper: CallbackGroup, is_event=...
method __call__ (line 15) | def __call__(self, *args, **kwargs) -> Any:
method before (line 24) | def before(self, f: Callable):
method after (line 36) | def after(self, f: Callable):
method on (line 48) | def on(self, f: Callable):
method cond (line 60) | def cond(self, f: Callable):
method unless (line 72) | def unless(self, f: Callable):
method validators (line 84) | def validators(self, f: Callable):
FILE: statemachine/utils.py
function qualname (line 12) | def qualname(cls):
function ensure_iterable (line 19) | def ensure_iterable(obj):
function humanize_id (line 32) | def humanize_id(id: str) -> str:
function run_async_from_sync (line 47) | def run_async_from_sync(coroutine: "Any") -> "Any":
FILE: tests/conftest.py
function pytest_addoption (line 9) | def pytest_addoption(parser):
function current_time (line 19) | def current_time():
function campaign_machine (line 24) | def campaign_machine():
function campaign_machine_with_validator (line 31) | def campaign_machine_with_validator():
function campaign_machine_with_final_state (line 40) | def campaign_machine_with_final_state():
function campaign_machine_with_values (line 47) | def campaign_machine_with_values():
function traffic_light_machine (line 54) | def traffic_light_machine():
function OrderControl (line 61) | def OrderControl():
function AllActionsMachine (line 68) | def AllActionsMachine():
function classic_traffic_light_machine (line 75) | def classic_traffic_light_machine(engine):
function classic_traffic_light_machine_allow_event (line 95) | def classic_traffic_light_machine_allow_event(classic_traffic_light_mach...
function reverse_traffic_light_machine (line 101) | def reverse_traffic_light_machine():
function approval_machine (line 108) | def approval_machine(current_time): # noqa: C901
function engine (line 151) | def engine(request):
class _AsyncListener (line 161) | class _AsyncListener:
method on_enter_state (line 164) | async def on_enter_state(
class SMRunner (line 169) | class SMRunner:
method __init__ (line 180) | def __init__(self, is_async: bool):
method start (line 183) | async def start(self, cls, **kwargs):
method send (line 198) | async def send(self, sm, event, **kwargs):
method processing_loop (line 207) | async def processing_loop(self, sm):
method sleep (line 216) | async def sleep(self, seconds: float):
function sm_runner (line 225) | def sm_runner(request):
function _check_leaked_threads (line 231) | def _check_leaked_threads():
FILE: tests/django_project/app.py
function home (line 5) | def home(request):
FILE: tests/django_project/manage.py
function main (line 8) | def main():
FILE: tests/django_project/workflow/apps.py
class WorfklowConfig (line 4) | class WorfklowConfig(AppConfig):
FILE: tests/django_project/workflow/models.py
class WorkflowSteps (line 8) | class WorkflowSteps(models.TextChoices):
class Workflow (line 13) | class Workflow(models.Model, MachineMixin):
FILE: tests/django_project/workflow/statemachines.py
class WorfklowStateMachine (line 8) | class WorfklowStateMachine(StateChart):
method has_user (line 16) | def has_user(self):
FILE: tests/django_project/workflow/tests.py
function Workflow (line 13) | def Workflow():
function User (line 20) | def User():
function one (line 27) | def one(Workflow):
class TestWorkflow (line 31) | class TestWorkflow:
method test_one (line 32) | def test_one(self, one):
method test_two (line 36) | def test_two(self, one):
method test_async_with_db_operation (line 43) | def test_async_with_db_operation(self, one, User, Workflow):
method test_should_publish (line 59) | def test_should_publish(self, one):
FILE: tests/examples/ai_shell_machine.py
class Spinner (line 154) | class Spinner:
method __init__ (line 157) | def __init__(self):
method __enter__ (line 161) | def __enter__(self):
method __exit__ (line 167) | def __exit__(self, *args):
method _run (line 172) | def _run(self):
function _tool_read_file (line 194) | def _tool_read_file(input_data: dict) -> str:
function _tool_list_files (line 206) | def _tool_list_files(input_data: dict) -> str:
function _tool_run_command (line 215) | def _tool_run_command(input_data: dict) -> str:
function _tool_sm_configuration (line 251) | def _tool_sm_configuration(sm, input_data: dict) -> str:
function _tool_sm_enabled_events (line 256) | def _tool_sm_enabled_events(sm, input_data: dict) -> str:
function _tool_sm_macrostep_count (line 261) | def _tool_sm_macrostep_count(sm, input_data: dict) -> str:
function _tool_sm_states (line 265) | def _tool_sm_states(sm, input_data: dict) -> str:
function execute_tool (line 325) | def execute_tool(name: str, input_data: dict, sm=None) -> str:
class AIShell (line 342) | class AIShell(StateChart):
class session (line 374) | class session(State.Parallel):
class conversation (line 375) | class conversation(State.Compound):
class processing (line 378) | class processing(State.Compound):
class context_tracker (line 406) | class context_tracker(State.Compound):
method __init__ (line 423) | def __init__(self):
method is_goodbye (line 435) | def is_goodbye(self, text="", **kwargs) -> bool:
method is_not_goodbye (line 438) | def is_not_goodbye(self, text="", **kwargs) -> bool:
method can_retry (line 441) | def can_retry(self, **kwargs) -> bool:
method cannot_retry (line 444) | def cannot_retry(self, **kwargs) -> bool:
method is_active_context (line 447) | def is_active_context(self, **kwargs) -> bool:
method is_deep_context (line 450) | def is_deep_context(self, **kwargs) -> bool:
method on_user_message (line 455) | def on_user_message(self, text, **kwargs):
method has_tool_calls (line 459) | def has_tool_calls(self, data=None, **kwargs) -> bool:
method on_invoke_thinking (line 463) | def on_invoke_thinking(self, **kwargs):
method on_invoke_using_tool (line 480) | def on_invoke_using_tool(self, data, **kwargs):
method on_enter_responding (line 494) | def on_enter_responding(self, **kwargs):
method on_enter_idle (line 500) | def on_enter_idle(self, **kwargs):
method on_enter_recovering (line 505) | def on_enter_recovering(self, **kwargs):
method on_enter_deep (line 513) | def on_enter_deep(self, **kwargs):
method on_enter_conversation_ended (line 517) | def on_enter_conversation_ended(self, **kwargs):
function _check_openai (line 526) | def _check_openai():
function main (line 536) | def main():
FILE: tests/examples/air_conditioner_machine.py
function sensor_temperature_reader (line 17) | def sensor_temperature_reader(seed: int, lower: int = 15, higher: int = ...
class AirConditioner (line 24) | class AirConditioner(StateChart):
method is_hot (line 39) | async def is_hot(self, temperature: int):
method is_good (line 42) | async def is_good(self, temperature: int):
method is_cool (line 45) | async def is_cool(self, temperature: int):
method after_transition (line 48) | async def after_transition(self, event: str, source: State, target: St...
function main (line 56) | async def main():
FILE: tests/examples/all_actions_machine.py
class AllActionsMachine (line 15) | class AllActionsMachine(StateChart):
method __init__ (line 29) | def __init__(self, *args, **kwargs):
method validation_1 (line 35) | def validation_1(self):
method validation_2 (line 39) | def validation_2(self):
method condition_1 (line 43) | def condition_1(self):
method condition_2 (line 47) | def condition_2(self):
method unless_1 (line 51) | def unless_1(self):
method unless_2 (line 55) | def unless_2(self):
method on_enter_state (line 61) | def on_enter_state(self):
method on_exit_state (line 64) | def on_exit_state(self):
method before_transition (line 69) | def before_transition(self):
method on_transition (line 72) | def on_transition(self):
method after_transition (line 75) | def after_transition(self):
method before_go_decor (line 81) | def before_go_decor(self):
method before_go_inline_1 (line 84) | def before_go_inline_1(self):
method before_go_inline_2 (line 87) | def before_go_inline_2(self):
method before_go (line 90) | def before_go(self):
method go_on_decor (line 94) | def go_on_decor(self):
method on_inline_1 (line 97) | def on_inline_1(self):
method on_inline_2 (line 100) | def on_inline_2(self):
method on_go (line 103) | def on_go(self):
method after_go_decor (line 107) | def after_go_decor(self):
method after_go_inline_1 (line 110) | def after_go_inline_1(self):
method after_go_inline_2 (line 113) | def after_go_inline_2(self):
method after_go (line 116) | def after_go(self):
method enter_initial_decor (line 122) | def enter_initial_decor(self):
method on_enter_initial (line 125) | def on_enter_initial(self):
method exit_initial_decor (line 129) | def exit_initial_decor(self):
method on_exit_initial (line 132) | def on_exit_initial(self):
method on_enter_final (line 135) | def on_enter_final(self):
method on_exit_final (line 138) | def on_exit_final(self):
FILE: tests/examples/async_guess_the_number_machine.py
class GuessTheNumberMachine (line 29) | class GuessTheNumberMachine(StateChart):
method __init__ (line 68) | def __init__(self, writer, max_attempts=5, lower=1, higher=5, seed=42):
method max_guesses_reached (line 80) | async def max_guesses_reached(self):
method before_guess (line 83) | async def before_guess(self, number):
method guess_is_lower (line 87) | async def guess_is_lower(self, number):
method guess_is_higher (line 90) | async def guess_is_higher(self, number):
method guess_is_equal (line 93) | async def guess_is_equal(self, number):
method on_enter_start (line 96) | async def on_enter_start(self):
method on_enter_low (line 102) | async def on_enter_low(self):
method on_enter_high (line 105) | async def on_enter_high(self):
method on_enter_won (line 108) | async def on_enter_won(self):
method on_enter_lose (line 111) | async def on_enter_lose(self):
function connect_stdin_stdout (line 122) | async def connect_stdin_stdout():
function main_async (line 146) | async def main_async():
function main_sync (line 161) | def main_sync():
FILE: tests/examples/async_without_loop_machine.py
class AsyncStateMachine (line 14) | class AsyncStateMachine(StateChart):
method on_start (line 22) | async def on_start(self):
method on_finish (line 25) | async def on_finish(self):
function sync_main (line 34) | def sync_main():
FILE: tests/examples/enum_campaign_machine.py
class CampaignStatus (line 17) | class CampaignStatus(Enum):
class CampaignMachine (line 23) | class CampaignMachine(StateChart):
FILE: tests/examples/guess_the_number_machine.py
class GuessTheNumberMachine (line 17) | class GuessTheNumberMachine(StateChart):
method __init__ (line 32) | def __init__(self, max_attempts=5, lower=1, higher=5, seed=42):
method max_guesses_reached (line 43) | def max_guesses_reached(self):
method before_guess (line 46) | def before_guess(self, number):
method guess_is_lower (line 50) | def guess_is_lower(self, number):
method guess_is_higher (line 53) | def guess_is_higher(self, number):
method guess_is_equal (line 56) | def guess_is_equal(self, number):
method on_enter_start (line 59) | def on_enter_start(self):
method on_enter_low (line 66) | def on_enter_low(self):
method on_enter_high (line 69) | def on_enter_high(self):
method on_enter_won (line 72) | def on_enter_won(self):
method on_enter_lose (line 75) | def on_enter_lose(self):
FILE: tests/examples/lor_machine.py
class LordOfTheRingsQuestStateMachine (line 15) | class LordOfTheRingsQuestStateMachine(StateChart):
FILE: tests/examples/order_control_machine.py
class OrderControl (line 13) | class OrderControl(StateChart):
method __init__ (line 28) | def __init__(self):
method payments_enough (line 34) | def payments_enough(self, amount):
method before_add_to_order (line 37) | def before_add_to_order(self, amount):
method before_receive_payment (line 41) | def before_receive_payment(self, amount):
method after_receive_payment (line 45) | def after_receive_payment(self):
method on_enter_waiting_for_payment (line 48) | def on_enter_waiting_for_payment(self):
FILE: tests/examples/order_control_rich_model_machine.py
class Order (line 15) | class Order:
method __init__ (line 16) | def __init__(self):
method payments_enough (line 21) | def payments_enough(self, amount):
method before_add_to_order (line 24) | def before_add_to_order(self, amount):
method on_receive_payment (line 28) | def on_receive_payment(self, amount):
method after_receive_payment (line 32) | def after_receive_payment(self):
method wait_for_payment (line 35) | def wait_for_payment(self):
class OrderControl (line 39) | class OrderControl(StateChart):
FILE: tests/examples/persistent_model_machine.py
class ResourceManagement (line 28) | class ResourceManagement(StateChart):
class AbstractPersistentModel (line 47) | class AbstractPersistentModel(ABC):
method __init__ (line 56) | def __init__(self):
method __repr__ (line 59) | def __repr__(self):
method state (line 63) | def state(self):
method state (line 69) | def state(self, value):
method _read_state (line 74) | def _read_state(self): ...
method _write_state (line 77) | def _write_state(self, value): ...
class FilePersistentModel (line 88) | class FilePersistentModel(AbstractPersistentModel):
method __init__ (line 93) | def __init__(self, file):
method _read_state (line 97) | def _read_state(self):
method _write_state (line 102) | def _write_state(self, value):
FILE: tests/examples/recursive_event_machine.py
class MyStateMachine (line 15) | class MyStateMachine(StateChart):
method on_enter_state (line 24) | def on_enter_state(self, target, event):
FILE: tests/examples/reusing_transitions_machine.py
class TrafficLightMachine (line 29) | class TrafficLightMachine(StateChart):
method before_slowdown (line 42) | def before_slowdown(self):
method before_cycle (line 45) | def before_cycle(self, event: str, source: State, target: State, messa...
method on_enter_red (line 49) | def on_enter_red(self):
method on_exit_red (line 52) | def on_exit_red(self):
class TrafficLightIsolatedTransitions (line 66) | class TrafficLightIsolatedTransitions(StateChart):
method before_slowdown (line 79) | def before_slowdown(self):
method before_cycle (line 82) | def before_cycle(self, event: str, source: State, target: State, messa...
method on_enter_red (line 86) | def on_enter_red(self):
method on_exit_red (line 89) | def on_exit_red(self):
FILE: tests/examples/sqlite_persistent_model_machine.py
class WorkflowDB (line 50) | class WorkflowDB:
method __init__ (line 53) | def __init__(self):
method insert_document (line 73) | def insert_document(self, title, author):
method find_document (line 81) | def find_document(self, doc_id):
method get_state (line 87) | def get_state(self, doc_id):
method set_state (line 97) | def set_state(self, doc_id, value):
method all_documents (line 117) | def all_documents(self):
method history_for (line 123) | def history_for(self, doc_id):
method mutation_count (line 130) | def mutation_count(self):
method close (line 134) | def close(self):
class Document (line 154) | class Document:
method __init__ (line 157) | def __init__(self, store, doc_id, title, author):
method create (line 165) | def create(cls, store, workflow_cls, title, author):
method load (line 173) | def load(cls, store, workflow_cls, doc_id):
method state (line 181) | def state(self):
method state (line 185) | def state(self, value):
method __repr__ (line 188) | def __repr__(self):
class ApprovalWorkflow (line 204) | class ApprovalWorkflow(StateChart):
class review (line 209) | class review(State.Parallel):
class legal_track (line 210) | class legal_track(State.Compound):
class tech_track (line 216) | class tech_track(State.Compound):
function print_table (line 249) | def print_table(headers, rows):
class ApprovalWorkflowAtomic (line 356) | class ApprovalWorkflowAtomic(ApprovalWorkflow):
FILE: tests/examples/statechart_cleanup_machine.py
class ResourceManager (line 24) | class ResourceManager(StateChart):
method __init__ (line 47) | def __init__(self, job=None):
method on_enter_working (line 51) | def on_enter_working(self):
method after_start (line 58) | def after_start(self):
method on_enter_recovering (line 63) | def on_enter_recovering(self, error=None, **kwargs):
method on_enter_idle (line 67) | def on_enter_idle(self):
function good_job (line 79) | def good_job():
function bad_job (line 101) | def bad_job():
FILE: tests/examples/statechart_compound_machine.py
class QuestMachine (line 18) | class QuestMachine(StateChart):
class shire (line 25) | class shire(State.Compound):
class rivendell (line 31) | class rivendell(State.Compound):
FILE: tests/examples/statechart_delayed_machine.py
class BeaconsMachine (line 53) | class BeaconsMachine(StateChart):
class quest (line 72) | class quest(State.Parallel):
class beacons (line 73) | class beacons(State.Compound):
class siege (line 93) | class siege(State.Compound):
method on_enter_minas_tirith (line 111) | def on_enter_minas_tirith(self):
method after_light_next (line 116) | def after_light_next(self, target):
method on_enter_holding (line 124) | def on_enter_holding(self):
method on_enter_rohan_rides (line 128) | def on_enter_rohan_rides(self):
method on_enter_city_falls (line 132) | def on_enter_city_falls(self):
class FailedBeaconsMachine (line 175) | class FailedBeaconsMachine(BeaconsMachine):
method on_enter_minas_tirith (line 180) | def on_enter_minas_tirith(self):
FILE: tests/examples/statechart_error_handling_machine.py
class QuestRecoveryMachine (line 24) | class QuestRecoveryMachine(StateChart):
method on_enter_danger_zone (line 46) | def on_enter_danger_zone(self):
method on_enter_recovering (line 50) | def on_enter_recovering(self, error=None, **kwargs):
class QuestNoCatch (line 88) | class QuestNoCatch(StateChart):
method on_enter_danger_zone (line 96) | def on_enter_danger_zone(self):
FILE: tests/examples/statechart_eventless_machine.py
class RingCorruptionMachine (line 19) | class RingCorruptionMachine(StateChart):
method is_tempted (line 50) | def is_tempted(self):
method is_corrupted (line 53) | def is_corrupted(self):
method is_lost (line 56) | def is_lost(self):
FILE: tests/examples/statechart_history_machine.py
class PersonalityMachine (line 20) | class PersonalityMachine(StateChart):
class personality (line 28) | class personality(State.Compound):
class DeepPersonalityMachine (line 87) | class DeepPersonalityMachine(StateChart):
class realm (line 90) | class realm(State.Compound):
class inner (line 91) | class inner(State.Compound):
FILE: tests/examples/statechart_in_condition_machine.py
class FellowshipMachine (line 18) | class FellowshipMachine(StateChart):
class quest (line 26) | class quest(State.Parallel):
class frodo_path (line 27) | class frodo_path(State.Compound):
class sam_path (line 35) | class sam_path(State.Compound):
FILE: tests/examples/statechart_parallel_machine.py
class WarMachine (line 17) | class WarMachine(StateChart):
class war (line 25) | class war(State.Parallel):
class frodos_quest (line 26) | class frodos_quest(State.Compound):
class aragorns_path (line 34) | class aragorns_path(State.Compound):
class gandalfs_defense (line 40) | class gandalfs_defense(State.Compound):
FILE: tests/examples/traffic_light_machine.py
class TrafficLightMachine (line 21) | class TrafficLightMachine(StateChart):
method before_cycle (line 30) | def before_cycle(self, event: str, source: State, target: State):
class Supervisor (line 38) | class Supervisor:
method __init__ (line 39) | def __init__(self, sm: StateChart, sm_event: str):
method run (line 44) | def run(self):
method stop (line 49) | def stop(self):
function main (line 53) | def main():
FILE: tests/examples/user_machine.py
class UserStatus (line 23) | class UserStatus(str, Enum):
class UserExperience (line 32) | class UserExperience(str, Enum):
class User (line 38) | class User:
method __post_init__ (line 46) | def __post_init__(self):
class MachineChangeListenter (line 58) | class MachineChangeListenter:
method before_transition (line 59) | def before_transition(self, event: str, state: State):
method on_enter_state (line 62) | def on_enter_state(self, state: State, event: str):
class UserStatusMachine (line 66) | class UserStatusMachine(StateChart):
method on_signup (line 89) | def on_signup(self, token: str):
class UserExperienceMachine (line 95) | class UserExperienceMachine(StateChart):
function main (line 109) | def main(): # type: ignore[attr-defined]
FILE: tests/examples/weighted_idle_machine.py
class WeightedIdleMachine (line 19) | class WeightedIdleMachine(StateChart):
FILE: tests/helpers.py
function import_module_by_path (line 5) | def import_module_by_path(src_file: Path):
FILE: tests/machines/compound/middle_earth_journey.py
class MiddleEarthJourney (line 5) | class MiddleEarthJourney(StateChart):
class rivendell (line 6) | class rivendell(State.Compound):
class moria (line 12) | class moria(State.Compound):
class lothlorien (line 18) | class lothlorien(State.Compound):
FILE: tests/machines/compound/middle_earth_journey_two_compounds.py
class MiddleEarthJourneyTwoCompounds (line 5) | class MiddleEarthJourneyTwoCompounds(StateChart):
class rivendell (line 6) | class rivendell(State.Compound):
class moria (line 12) | class moria(State.Compound):
FILE: tests/machines/compound/middle_earth_journey_with_finals.py
class MiddleEarthJourneyWithFinals (line 5) | class MiddleEarthJourneyWithFinals(StateChart):
class rivendell (line 6) | class rivendell(State.Compound):
class moria (line 12) | class moria(State.Compound):
class lothlorien (line 18) | class lothlorien(State.Compound):
FILE: tests/machines/compound/moria_expedition.py
class MoriaExpedition (line 5) | class MoriaExpedition(StateChart):
class moria (line 6) | class moria(State.Compound):
class upper_halls (line 7) | class upper_halls(State.Compound):
FILE: tests/machines/compound/moria_expedition_with_escape.py
class MoriaExpeditionWithEscape (line 5) | class MoriaExpeditionWithEscape(StateChart):
class moria (line 6) | class moria(State.Compound):
class upper_halls (line 7) | class upper_halls(State.Compound):
FILE: tests/machines/compound/quest_for_erebor.py
class QuestForErebor (line 5) | class QuestForErebor(StateChart):
class lonely_mountain (line 6) | class lonely_mountain(State.Compound):
FILE: tests/machines/compound/shire_to_rivendell.py
class ShireToRivendell (line 5) | class ShireToRivendell(StateChart):
class shire (line 6) | class shire(State.Compound):
FILE: tests/machines/donedata/destroy_the_ring.py
class DestroyTheRing (line 6) | class DestroyTheRing(StateChart):
class quest (line 7) | class quest(State.Compound):
method get_quest_result (line 13) | def get_quest_result(self):
method capture_result (line 19) | def capture_result(self, ring_destroyed=None, hero=None, **kwargs):
FILE: tests/machines/donedata/destroy_the_ring_simple.py
class DestroyTheRingSimple (line 6) | class DestroyTheRingSimple(StateChart):
class quest (line 7) | class quest(State.Compound):
method get_result (line 13) | def get_result(self):
FILE: tests/machines/donedata/nested_quest_donedata.py
class NestedQuestDoneData (line 6) | class NestedQuestDoneData(StateChart):
class outer (line 7) | class outer(State.Compound):
class inner (line 8) | class inner(State.Compound):
method inner_result (line 14) | def inner_result(self):
FILE: tests/machines/donedata/quest_for_erebor_done_convention.py
class QuestForEreborDoneConvention (line 5) | class QuestForEreborDoneConvention(StateChart):
class quest (line 6) | class quest(State.Compound):
FILE: tests/machines/donedata/quest_for_erebor_explicit_id.py
class QuestForEreborExplicitId (line 6) | class QuestForEreborExplicitId(StateChart):
class quest (line 7) | class quest(State.Compound):
FILE: tests/machines/donedata/quest_for_erebor_multi_word.py
class QuestForEreborMultiWord (line 5) | class QuestForEreborMultiWord(StateChart):
class lonely_mountain (line 6) | class lonely_mountain(State.Compound):
FILE: tests/machines/donedata/quest_for_erebor_with_event.py
class QuestForEreborWithEvent (line 6) | class QuestForEreborWithEvent(StateChart):
class quest (line 7) | class quest(State.Compound):
FILE: tests/machines/error/error_convention_event.py
class ErrorConventionEventSC (line 6) | class ErrorConventionEventSC(StateChart):
method bad_action (line 15) | def bad_action(self):
FILE: tests/machines/error/error_convention_transition_list.py
class ErrorConventionTransitionListSC (line 5) | class ErrorConventionTransitionListSC(StateChart):
method bad_action (line 14) | def bad_action(self):
FILE: tests/machines/error/error_in_action_sc.py
class ErrorInActionSC (line 6) | class ErrorInActionSC(StateChart):
method bad_action (line 14) | def bad_action(self):
FILE: tests/machines/error/error_in_action_sm_with_flag.py
class ErrorInActionSMWithFlag (line 6) | class ErrorInActionSMWithFlag(StateChart):
method bad_action (line 16) | def bad_action(self):
FILE: tests/machines/error/error_in_after_sc.py
class ErrorInAfterSC (line 6) | class ErrorInAfterSC(StateChart):
method bad_after (line 14) | def bad_after(self):
FILE: tests/machines/error/error_in_error_handler_sc.py
class ErrorInErrorHandlerSC (line 6) | class ErrorInErrorHandlerSC(StateChart):
method bad_action (line 20) | def bad_action(self):
method bad_error_handler (line 23) | def bad_error_handler(self):
FILE: tests/machines/error/error_in_guard_sc.py
class ErrorInGuardSC (line 6) | class ErrorInGuardSC(StateChart):
method bad_guard (line 13) | def bad_guard(self):
FILE: tests/machines/error/error_in_guard_sm.py
class ErrorInGuardSM (line 5) | class ErrorInGuardSM(StateChart):
method bad_guard (line 14) | def bad_guard(self):
FILE: tests/machines/error/error_in_on_enter_sc.py
class ErrorInOnEnterSC (line 6) | class ErrorInOnEnterSC(StateChart):
method on_enter_s2 (line 14) | def on_enter_s2(self):
FILE: tests/machines/eventless/auto_advance.py
class AutoAdvance (line 5) | class AutoAdvance(StateChart):
class journey (line 6) | class journey(State.Compound):
FILE: tests/machines/eventless/beacon_chain.py
class BeaconChain (line 5) | class BeaconChain(StateChart):
class beacons (line 6) | class beacons(State.Compound):
FILE: tests/machines/eventless/beacon_chain_lighting.py
class BeaconChainLighting (line 5) | class BeaconChainLighting(StateChart):
class chain (line 6) | class chain(State.Compound):
FILE: tests/machines/eventless/coordinated_advance.py
class CoordinatedAdvance (line 5) | class CoordinatedAdvance(StateChart):
class forces (line 6) | class forces(State.Parallel):
class vanguard (line 7) | class vanguard(State.Compound):
class rearguard (line 13) | class rearguard(State.Compound):
FILE: tests/machines/eventless/ring_corruption.py
class RingCorruption (line 5) | class RingCorruption(StateChart):
method is_corrupted (line 14) | def is_corrupted(self):
method increase_power (line 17) | def increase_power(self):
FILE: tests/machines/eventless/ring_corruption_with_bear_ring.py
class RingCorruptionWithBearRing (line 5) | class RingCorruptionWithBearRing(StateChart):
method is_corrupted (line 14) | def is_corrupted(self):
method increase_power (line 17) | def increase_power(self):
FILE: tests/machines/eventless/ring_corruption_with_tick.py
class RingCorruptionWithTick (line 5) | class RingCorruptionWithTick(StateChart):
method is_corrupted (line 14) | def is_corrupted(self):
FILE: tests/machines/history/deep_memory_of_moria.py
class DeepMemoryOfMoria (line 6) | class DeepMemoryOfMoria(StateChart):
class moria (line 7) | class moria(State.Compound):
class halls (line 8) | class halls(State.Compound):
FILE: tests/machines/history/gollum_personality.py
class GollumPersonality (line 6) | class GollumPersonality(StateChart):
class personality (line 7) | class personality(State.Compound):
FILE: tests/machines/history/gollum_personality_default_gollum.py
class GollumPersonalityDefaultGollum (line 6) | class GollumPersonalityDefaultGollum(StateChart):
class personality (line 7) | class personality(State.Compound):
FILE: tests/machines/history/gollum_personality_with_default.py
class GollumPersonalityWithDefault (line 6) | class GollumPersonalityWithDefault(StateChart):
class personality (line 7) | class personality(State.Compound):
FILE: tests/machines/history/shallow_moria.py
class ShallowMoria (line 6) | class ShallowMoria(StateChart):
class moria (line 7) | class moria(State.Compound):
class halls (line 8) | class halls(State.Compound):
FILE: tests/machines/in_condition/combined_guard.py
class CombinedGuard (line 5) | class CombinedGuard(StateChart):
class positions (line 6) | class positions(State.Parallel):
class scout (line 7) | class scout(State.Compound):
class warrior (line 13) | class warrior(State.Compound):
FILE: tests/machines/in_condition/descendant_check.py
class DescendantCheck (line 5) | class DescendantCheck(StateChart):
class realm (line 6) | class realm(State.Compound):
FILE: tests/machines/in_condition/eventless_in.py
class EventlessIn (line 5) | class EventlessIn(StateChart):
class coordination (line 6) | class coordination(State.Parallel):
class leader (line 7) | class leader(State.Compound):
class follower (line 13) | class follower(State.Compound):
FILE: tests/machines/in_condition/fellowship.py
class Fellowship (line 5) | class Fellowship(StateChart):
class positions (line 6) | class positions(State.Parallel):
class frodo (line 7) | class frodo(State.Compound):
class sam (line 13) | class sam(State.Compound):
FILE: tests/machines/in_condition/fellowship_coordination.py
class FellowshipCoordination (line 5) | class FellowshipCoordination(StateChart):
class mission (line 6) | class mission(State.Parallel):
class scouts (line 7) | class scouts(State.Compound):
class army (line 13) | class army(State.Compound):
FILE: tests/machines/in_condition/gate_of_moria.py
class GateOfMoria (line 5) | class GateOfMoria(StateChart):
FILE: tests/machines/parallel/session.py
class Session (line 5) | class Session(StateChart):
class session (line 6) | class session(State.Parallel):
class ui (line 7) | class ui(State.Compound):
class backend (line 13) | class backend(State.Compound):
FILE: tests/machines/parallel/session_with_done_state.py
class SessionWithDoneState (line 5) | class SessionWithDoneState(StateChart):
class session (line 6) | class session(State.Parallel):
class ui (line 7) | class ui(State.Compound):
class backend (line 13) | class backend(State.Compound):
FILE: tests/machines/parallel/two_towers.py
class TwoTowers (line 5) | class TwoTowers(StateChart):
class battle (line 6) | class battle(State.Parallel):
class helms_deep (line 7) | class helms_deep(State.Compound):
class isengard (line 13) | class isengard(State.Compound):
FILE: tests/machines/parallel/war_of_the_ring.py
class WarOfTheRing (line 5) | class WarOfTheRing(StateChart):
class war (line 6) | class war(State.Parallel):
class frodos_quest (line 7) | class frodos_quest(State.Compound):
class aragorns_path (line 15) | class aragorns_path(State.Compound):
class gandalfs_defense (line 21) | class gandalfs_defense(State.Compound):
FILE: tests/machines/parallel/war_with_exit.py
class WarWithExit (line 5) | class WarWithExit(StateChart):
class war (line 6) | class war(State.Parallel):
class front_a (line 7) | class front_a(State.Compound):
class front_b (line 13) | class front_b(State.Compound):
FILE: tests/machines/showcase_actions.py
class ActionsSC (line 5) | class ActionsSC(StateChart):
method on_exit_off (line 13) | def on_exit_off(self): ...
method on_enter_on (line 14) | def on_enter_on(self): ...
method on_exit_on (line 15) | def on_exit_on(self): ...
method on_enter_done (line 16) | def on_enter_done(self): ...
FILE: tests/machines/showcase_compound.py
class CompoundSC (line 5) | class CompoundSC(StateChart):
class active (line 6) | class active(State.Compound, name="Active"):
FILE: tests/machines/showcase_deep_history.py
class DeepHistorySC (line 6) | class DeepHistorySC(StateChart):
class outer (line 7) | class outer(State.Compound, name="Outer"):
class inner (line 8) | class inner(State.Compound, name="Inner"):
FILE: tests/machines/showcase_guards.py
class GuardSC (line 5) | class GuardSC(StateChart):
method is_valid (line 10) | def is_valid(self):
method is_invalid (line 13) | def is_invalid(self):
FILE: tests/machines/showcase_history.py
class HistorySC (line 6) | class HistorySC(StateChart):
class process (line 7) | class process(State.Compound, name="Process"):
FILE: tests/machines/showcase_internal.py
class InternalSC (line 5) | class InternalSC(StateChart):
method log_status (line 9) | def log_status(self): ...
FILE: tests/machines/showcase_parallel.py
class ParallelSC (line 5) | class ParallelSC(StateChart):
class both (line 6) | class both(State.Parallel, name="Both"):
class left (line 7) | class left(State.Compound, name="Left"):
class right (line 12) | class right(State.Compound, name="Right"):
FILE: tests/machines/showcase_parallel_compound.py
class ParallelCompoundSC (line 5) | class ParallelCompoundSC(StateChart):
class pipeline (line 17) | class pipeline(State.Parallel, name="Pipeline"):
class build (line 18) | class build(State.Compound, name="Build"):
class test (line 23) | class test(State.Compound, name="Test"):
FILE: tests/machines/showcase_self_transition.py
class SelfTransitionSC (line 5) | class SelfTransitionSC(StateChart):
FILE: tests/machines/showcase_simple.py
class SimpleSC (line 5) | class SimpleSC(StateChart):
FILE: tests/machines/transition_from_any.py
class OrderWorkflow (line 5) | class OrderWorkflow(StateChart):
class OrderWorkflowCompound (line 18) | class OrderWorkflowCompound(StateChart):
class active (line 19) | class active(State.Compound):
FILE: tests/machines/tutorial_coffee_order.py
class CoffeeOrder (line 5) | class CoffeeOrder(StateChart):
FILE: tests/machines/validators/multi_validator.py
class MultiValidator (line 5) | class MultiValidator(StateChart):
method check_a (line 13) | def check_a(self, **kwargs):
method check_b (line 17) | def check_b(self, **kwargs):
FILE: tests/machines/validators/order_validation.py
class OrderValidation (line 5) | class OrderValidation(StateChart):
method check_stock (line 15) | def check_stock(self, quantity=0, **kwargs):
FILE: tests/machines/validators/order_validation_no_error_events.py
class OrderValidationNoErrorEvents (line 5) | class OrderValidationNoErrorEvents(StateChart):
method check_stock (line 17) | def check_stock(self, quantity=0, **kwargs):
FILE: tests/machines/validators/validator_fallthrough.py
class ValidatorFallthrough (line 5) | class ValidatorFallthrough(StateChart):
method must_be_premium (line 18) | def must_be_premium(self, **kwargs):
FILE: tests/machines/validators/validator_with_cond.py
class ValidatorWithCond (line 5) | class ValidatorWithCond(StateChart):
method check_auth (line 15) | def check_auth(self, token=None, **kwargs):
FILE: tests/machines/validators/validator_with_error_transition.py
class ValidatorWithErrorTransition (line 5) | class ValidatorWithErrorTransition(StateChart):
method check_input (line 20) | def check_input(self, value=None, **kwargs):
method risky_action (line 24) | def risky_action(self, **kwargs):
FILE: tests/machines/workflow/campaign_machine.py
class CampaignMachine (line 5) | class CampaignMachine(StateChart):
FILE: tests/machines/workflow/campaign_machine_with_validator.py
class CampaignMachineWithValidator (line 5) | class CampaignMachineWithValidator(StateChart):
method can_produce (line 16) | def can_produce(*args, **kwargs):
FILE: tests/machines/workflow/campaign_machine_with_values.py
class CampaignMachineWithValues (line 5) | class CampaignMachineWithValues(StateChart):
FILE: tests/machines/workflow/reverse_traffic_light.py
class ReverseTrafficLightMachine (line 5) | class ReverseTrafficLightMachine(StateChart):
FILE: tests/models.py
class MyModel (line 1) | class MyModel:
method __init__ (line 4) | def __init__(self, **kwargs):
method __repr__ (line 9) | def __repr__(self):
FILE: tests/scrape_images.py
class MachineScraper (line 10) | class MachineScraper:
method __init__ (line 15) | def __init__(self, project_root):
method __repr__ (line 21) | def __repr__(self):
method _get_module (line 24) | def _get_module(self, src_file):
method generate_image (line 31) | def generate_image(self, sm_class, original_path):
method __call__ (line 39) | def __call__(self, block, block_vars, gallery_conf):
FILE: tests/scxml/conftest.py
function should_generate_debug_diagram (line 44) | def should_generate_debug_diagram(request):
function compute_testcase_marks (line 48) | def compute_testcase_marks(testcase_path: Path, is_async: bool) -> list[...
function pytest_generate_tests (line 57) | def pytest_generate_tests(metafunc):
FILE: tests/scxml/test_microwave.py
function test_microwave_scxml (line 38) | def test_microwave_scxml():
class TestMicrowave (line 63) | class TestMicrowave:
method microwave_cls (line 65) | def microwave_cls(self):
method test_microwave (line 109) | def test_microwave(self, microwave_cls):
FILE: tests/scxml/test_scxml_cases.py
class AsyncListener (line 20) | class AsyncListener:
method on_enter_state (line 23) | async def on_enter_state(
function _run_scxml_testcase (line 28) | def _run_scxml_testcase(
function _assert_passed (line 56) | def _assert_passed(sm: StateChart):
function _wait_for_completion (line 61) | def _wait_for_completion(sm: StateChart, timeout_s: float = 5.0):
function test_scxml_usecase_sync (line 70) | def test_scxml_usecase_sync(testcase_path: Path, should_generate_debug_d...
function _async_wait_for_completion (line 80) | async def _async_wait_for_completion(sm: StateChart, timeout_s: float = ...
function test_scxml_usecase_async (line 91) | async def test_scxml_usecase_async(testcase_path: Path, should_generate_...
FILE: tests/test_actions.py
class TestActions (line 5) | class TestActions:
method test_should_return_all_before_results (line 6) | def test_should_return_all_before_results(self, AllActionsMachine):
method test_should_allow_actions_on_the_model (line 9) | def test_should_allow_actions_on_the_model(self):
method test_should_should_compute_callbacks_meta_list (line 13) | def test_should_should_compute_callbacks_meta_list(self, campaign_mach...
FILE: tests/test_api_contract.py
class Model (line 31) | class Model:
method __init__ (line 34) | def __init__(self):
class FlatSC (line 43) | class FlatSC(StateChart):
class CompoundSC (line 52) | class CompoundSC(StateChart):
class parent (line 53) | class parent(State.Compound):
class ParallelSC (line 62) | class ParallelSC(StateChart):
class regions (line 63) | class regions(State.Parallel):
class region_a (line 64) | class region_a(State.Compound):
class region_b (line 69) | class region_b(State.Compound):
class ComplexParallelSC (line 75) | class ComplexParallelSC(StateChart):
class top (line 76) | class top(State.Parallel):
class left (line 77) | class left(State.Compound):
class nested (line 78) | class nested(State.Compound):
class right (line 86) | class right(State.Compound):
function assert_contract (line 97) | def assert_contract(sm, model, expected_ids: set):
function test_configuration_contract (line 206) | async def test_configuration_contract(sm_runner, sc_class, events, expec...
function test_setter_contract (line 231) | async def test_setter_contract(sm_runner, sc_class, new_value, expected_...
function test_set_none_clears_configuration (line 238) | async def test_set_none_clears_configuration(sm_runner):
function test_uninitialized_then_activated (line 266) | async def test_uninitialized_then_activated(sc_class, expected_ids):
FILE: tests/test_async.py
function async_order_control_machine (line 12) | def async_order_control_machine(): # noqa: C901
function test_async_order_control_machine (line 54) | async def test_async_order_control_machine(async_order_control_machine):
function test_async_state_from_sync_context (line 77) | def test_async_state_from_sync_context(async_order_control_machine):
class AsyncConditionExpressionMachine (line 102) | class AsyncConditionExpressionMachine(StateChart):
method cond_true (line 117) | async def cond_true(self):
method cond_false (line 120) | async def cond_false(self):
method on_enter_state (line 123) | async def on_enter_state(self, target):
function test_async_condition_not (line 127) | async def test_async_condition_not(recwarn):
function test_async_condition_not_blocked (line 136) | async def test_async_condition_not_blocked():
function test_async_condition_and (line 144) | async def test_async_condition_and():
function test_async_condition_and_blocked (line 152) | async def test_async_condition_and_blocked():
function test_async_condition_or_false_first (line 160) | async def test_async_condition_or_false_first():
function test_async_condition_or_true_first (line 168) | async def test_async_condition_or_true_first():
function test_async_condition_or_both_false (line 176) | async def test_async_condition_or_both_false():
function test_async_state_should_be_initialized (line 184) | async def test_async_state_should_be_initialized(async_order_control_mac...
function test_async_catch_errors_as_events_in_condition (line 214) | async def test_async_catch_errors_as_events_in_condition():
function test_async_catch_errors_as_events_in_transition (line 234) | async def test_async_catch_errors_as_events_in_transition():
function test_async_catch_errors_as_events_in_after (line 257) | async def test_async_catch_errors_as_events_in_after():
function test_async_catch_errors_as_events_in_before (line 277) | async def test_async_catch_errors_as_events_in_before():
function test_async_invalid_definition_in_transition_propagates (line 300) | async def test_async_invalid_definition_in_transition_propagates():
function test_async_invalid_definition_in_after_propagates (line 318) | async def test_async_invalid_definition_in_after_propagates():
function test_async_runtime_error_in_after_without_catch_errors_as_events (line 336) | async def test_async_runtime_error_in_after_without_catch_errors_as_even...
function test_async_engine_invalid_definition_in_condition_propagates (line 362) | async def test_async_engine_invalid_definition_in_condition_propagates():
function test_async_engine_invalid_definition_in_transition_propagates (line 381) | async def test_async_engine_invalid_definition_in_transition_propagates():
function test_async_engine_invalid_definition_in_after_propagates (line 400) | async def test_async_engine_invalid_definition_in_after_propagates():
function test_async_engine_runtime_error_in_after_without_catch_errors_as_events_propagates (line 419) | async def test_async_engine_runtime_error_in_after_without_catch_errors_...
function test_async_engine_start_noop_when_already_initialized (line 440) | async def test_async_engine_start_noop_when_already_initialized():
class TestAsyncEnabledEvents (line 460) | class TestAsyncEnabledEvents:
method test_passing_async_condition (line 461) | async def test_passing_async_condition(self):
method test_failing_async_condition (line 475) | async def test_failing_async_condition(self):
method test_kwargs_forwarded_to_async_conditions (line 489) | async def test_kwargs_forwarded_to_async_conditions(self):
method test_async_condition_exception_treated_as_enabled (line 504) | async def test_async_condition_exception_treated_as_enabled(self):
method test_duplicate_event_across_transitions_deduplicated (line 518) | async def test_duplicate_event_across_transitions_deduplicated(self):
method test_mixed_enabled_and_disabled_async (line 540) | async def test_mixed_enabled_and_disabled_async(self):
FILE: tests/test_async_futures.py
class TrafficLight (line 24) | class TrafficLight(StateChart):
method on_slow_down (line 33) | async def on_slow_down(self):
method on_stop (line 36) | async def on_stop(self):
method on_go (line 39) | async def on_go(self):
class FailingMachine (line 43) | class FailingMachine(StateChart):
method on_ok (line 51) | async def on_ok(self):
method on_fail (line 54) | async def on_fail(self):
class TestConcurrentSendsGetCorrectResults (line 63) | class TestConcurrentSendsGetCorrectResults:
method test_sequential_sends (line 67) | async def test_sequential_sends(self):
method test_single_async_caller_gets_result (line 79) | async def test_single_async_caller_gets_result(self):
class TestExceptionRouting (line 88) | class TestExceptionRouting:
method test_exception_reaches_caller (line 92) | async def test_exception_reaches_caller(self):
class TestTransitionNotAllowedRouting (line 112) | class TestTransitionNotAllowedRouting:
method test_transition_not_allowed (line 116) | async def test_transition_not_allowed(self):
class TestFutureEdgeCases (line 138) | class TestFutureEdgeCases:
method test_initial_activation_no_future (line 142) | async def test_initial_activation_no_future(self):
method test_allow_event_without_transition_resolves_none (line 149) | async def test_allow_event_without_transition_resolves_none(self):
method test_concurrent_sends_via_gather (line 160) | async def test_concurrent_sends_via_gather(self):
method test_concurrent_sends_exception_with_catch_errors_as_events_off (line 195) | async def test_concurrent_sends_exception_with_catch_errors_as_events_...
method test_separate_tasks_with_slow_callback (line 240) | async def test_separate_tasks_with_slow_callback(self):
method test_separate_tasks_validator_exception_routing (line 284) | async def test_separate_tasks_validator_exception_routing(self):
class TestEventQueueRejectFutures (line 327) | class TestEventQueueRejectFutures:
method test_reject_futures_skips_items_without_future (line 330) | def test_reject_futures_skips_items_without_future(self):
FILE: tests/test_callbacks.py
function ObjectWithCallbacks (line 17) | def ObjectWithCallbacks():
class TestCallbacksMachinery (line 40) | class TestCallbacksMachinery:
method test_callback_meta_is_hashable (line 41) | def test_callback_meta_is_hashable(self):
method test_can_add_callback_that_is_a_string (line 45) | def test_can_add_callback_that_is_a_string(self):
method test_callbacks_are_iterable (line 76) | def test_callbacks_are_iterable(self):
method test_add_many_callbacks_at_once (line 84) | def test_add_many_callbacks_at_once(self):
method test_raise_error_if_didnt_found_attr (line 93) | def test_raise_error_if_didnt_found_attr(self, is_convention):
method test_collect_results (line 110) | def test_collect_results(self):
method test_callbacks_values_resolution (line 134) | def test_callbacks_values_resolution(self, ObjectWithCallbacks):
class TestCallbacksAsDecorator (line 143) | class TestCallbacksAsDecorator:
method test_decorate_unbounded_function (line 144) | def test_decorate_unbounded_function(self, ObjectWithCallbacks):
method test_decorate_unbounded_machine_methods (line 169) | def test_decorate_unbounded_machine_methods(self):
class TestIssue406 (line 215) | class TestIssue406:
method test_issue_406 (line 223) | def test_issue_406(self, mocker):
class TestIssue417 (line 249) | class TestIssue417:
method mock_calls (line 256) | def mock_calls(self, mocker):
method model_class (line 260) | def model_class(self):
method sm_class (line 280) | def sm_class(self, model_class, mock_calls):
method test_issue_417_cannot_start (line 320) | def test_issue_417_cannot_start(self, model_class, sm_class, mock_calls):
method test_issue_417_can_start (line 328) | def test_issue_417_can_start(self, model_class, sm_class, mock_calls, ...
method test_raise_exception_if_property_is_not_found (line 337) | def test_raise_exception_if_property_is_not_found(self):
class TestVisitConditionFalse (line 357) | class TestVisitConditionFalse:
method test_visit_skips_when_condition_is_false (line 360) | def test_visit_skips_when_condition_is_false(self):
method test_async_visit_skips_when_condition_is_false (line 374) | async def test_async_visit_skips_when_condition_is_false(self):
FILE: tests/test_callbacks_isolation.py
function simple_sm_cls (line 8) | def simple_sm_cls():
class TestCallbacksIsolation (line 33) | class TestCallbacksIsolation:
method test_should_conditions_be_isolated (line 34) | def test_should_conditions_be_isolated(self, simple_sm_cls):
method test_should_actions_be_isolated (line 48) | def test_should_actions_be_isolated(self, simple_sm_cls):
FILE: tests/test_class_listeners.py
class RecordingListener (line 11) | class RecordingListener:
method __init__ (line 14) | def __init__(self):
method after_transition (line 17) | def after_transition(self, event, source, target):
class SetupListener (line 21) | class SetupListener:
method __init__ (line 24) | def __init__(self):
method setup (line 28) | def setup(self, sm, session=None, **kwargs):
method after_transition (line 31) | def after_transition(self, event, source, target):
class TestClassLevelListeners (line 35) | class TestClassLevelListeners:
method test_class_level_listener_callable_creates_per_instance (line 36) | def test_class_level_listener_callable_creates_per_instance(self):
method test_class_level_listener_shared_instance (line 58) | def test_class_level_listener_shared_instance(self):
method test_class_level_listener_partial (line 79) | def test_class_level_listener_partial(self):
method test_class_level_listener_lambda (line 102) | def test_class_level_listener_lambda(self):
method test_runtime_listeners_merge_with_class_level (line 117) | def test_runtime_listeners_merge_with_class_level(self):
class TestClassListenerInheritance (line 140) | class TestClassListenerInheritance:
method test_child_extends_parent_listeners (line 141) | def test_child_extends_parent_listeners(self):
method test_child_replaces_parent_listeners (line 163) | def test_child_replaces_parent_listeners(self):
method test_grandchild_inherits_full_chain (line 185) | def test_grandchild_inherits_full_chain(self):
method test_no_listeners_declared_inherits_parent (line 214) | def test_no_listeners_declared_inherits_parent(self):
class TestListenerSetupProtocol (line 233) | class TestListenerSetupProtocol:
method test_setup_receives_kwargs (line 234) | def test_setup_receives_kwargs(self):
method test_setup_ignores_unknown_kwargs (line 246) | def test_setup_ignores_unknown_kwargs(self):
method test_setup_not_called_on_shared_instances (line 258) | def test_setup_not_called_on_shared_instances(self):
method test_multiple_listeners_with_different_deps (line 272) | def test_multiple_listeners_with_different_deps(self):
method test_setup_receives_sm_instance (line 299) | def test_setup_receives_sm_instance(self):
method test_setup_optional_kwargs_default_to_none (line 318) | def test_setup_optional_kwargs_default_to_none(self):
method test_setup_required_kwarg_missing_raises_error (line 330) | def test_setup_required_kwarg_missing_raises_error(self):
method test_setup_required_kwarg_provided (line 345) | def test_setup_required_kwarg_provided(self):
class TestListenerValidation (line 361) | class TestListenerValidation:
method test_rejects_none_in_listeners (line 362) | def test_rejects_none_in_listeners(self):
method test_rejects_string_in_listeners (line 372) | def test_rejects_string_in_listeners(self):
method test_rejects_number_in_listeners (line 382) | def test_rejects_number_in_listeners(self):
method test_rejects_bool_in_listeners (line 392) | def test_rejects_bool_in_listeners(self):
class _PickleChart (line 403) | class _PickleChart(StateChart):
class _PickleMultiStepChart (line 411) | class _PickleMultiStepChart(StateChart):
class TestListenerSerialization (line 421) | class TestListenerSerialization:
method test_pickle_with_class_listeners (line 422) | def test_pickle_with_class_listeners(self):
method test_pickle_does_not_duplicate_class_listeners (line 434) | def test_pickle_does_not_duplicate_class_listeners(self):
method test_pickle_with_runtime_listeners (line 444) | def test_pickle_with_runtime_listeners(self):
class TestEmptyClassListeners (line 458) | class TestEmptyClassListeners:
method test_no_listeners_attribute (line 459) | def test_no_listeners_attribute(self):
method test_empty_listeners_list (line 468) | def test_empty_listeners_list(self):
FILE: tests/test_conditions_algebra.py
class AnyConditionSM (line 8) | class AnyConditionSM(StateChart):
function test_conditions_algebra_any_false (line 21) | def test_conditions_algebra_any_false():
function test_conditions_algebra_any_left_true (line 29) | def test_conditions_algebra_any_left_true():
function test_conditions_algebra_any_right_true (line 36) | def test_conditions_algebra_any_right_true():
function test_should_raise_invalid_definition_if_cond_is_not_valid_sintax (line 43) | def test_should_raise_invalid_definition_if_cond_is_not_valid_sintax():
function test_should_raise_invalid_definition_if_cond_is_not_found (line 57) | def test_should_raise_invalid_definition_if_cond_is_not_found():
FILE: tests/test_configuration.py
class ParallelSM (line 15) | class ParallelSM(StateChart):
class TestConfigurationStatesSetter (line 26) | class TestConfigurationStatesSetter:
method test_set_empty_configuration (line 27) | def test_set_empty_configuration(self):
method test_set_multi_element_configuration (line 34) | def test_set_multi_element_configuration(self):
class TestConfigurationValueSetter (line 44) | class TestConfigurationValueSetter:
method test_set_value_none_writes_none_to_model (line 45) | def test_set_value_none_writes_none_to_model(self):
method test_set_value_plain_set_coerces_to_ordered_set (line 53) | def test_set_value_plain_set_coerces_to_ordered_set(self):
class TestReadFromModelNonOrderedSet (line 65) | class TestReadFromModelNonOrderedSet:
method test_read_from_model_coerces_plain_set (line 66) | def test_read_from_model_coerces_plain_set(self):
class TestConfigurationDiscard (line 80) | class TestConfigurationDiscard:
method test_discard_nonmatching_scalar (line 81) | def test_discard_nonmatching_scalar(self):
class TestConfigurationCurrentState (line 91) | class TestConfigurationCurrentState:
method test_current_state_with_multiple_active_states (line 92) | def test_current_state_with_multiple_active_states(self):
class SerializingModel (line 111) | class SerializingModel:
method __init__ (line 116) | def __init__(self):
method state (line 120) | def state(self):
method state (line 129) | def state(self, value):
class WarSC (line 138) | class WarSC(StateChart):
class war (line 141) | class war(State.Parallel):
class region_a (line 142) | class region_a(State.Compound):
class region_b (line 147) | class region_b(State.Compound):
class TestAddDiscard (line 153) | class TestAddDiscard:
method test_add_calls_setter_on_serializing_model (line 156) | def test_add_calls_setter_on_serializing_model(self):
method test_discard_calls_setter_on_serializing_model (line 164) | def test_discard_calls_setter_on_serializing_model(self):
method test_parallel_lifecycle_with_serializing_model (line 179) | def test_parallel_lifecycle_with_serializing_model(self):
method test_state_restoration_from_serialized_model (line 194) | def test_state_restoration_from_serialized_model(self):
method test_parallel_with_serializing_model_both_engines (line 209) | async def test_parallel_with_serializing_model_both_engines(self, sm_r...
FILE: tests/test_contrib_diagram.py
function _parse_svg (line 25) | def _parse_svg(graph):
function _find_state_node (line 31) | def _find_state_node(svg_root, state_id):
function _has_rectangular_fill (line 42) | def _has_rectangular_fill(node_g):
function _path_has_curves (line 58) | def _path_has_curves(d_attr):
function expected_reprs (line 79) | def expected_reprs(request):
function test_machine_repr_custom_ (line 90) | def test_machine_repr_custom_(request, machine_name, expected_reprs):
function test_machine_dot (line 99) | def test_machine_dot(OrderControl):
class TestDiagramCmdLine (line 109) | class TestDiagramCmdLine:
method test_generate_image (line 110) | def test_generate_image(self, tmp_path):
method test_generate_image_from_module_path (line 119) | def test_generate_image_from_module_path(self, tmp_path):
method test_generate_complain_about_bad_sm_path (line 129) | def test_generate_complain_about_bad_sm_path(self, capsys, tmp_path):
method test_generate_image_with_events (line 141) | def test_generate_image_with_events(self, tmp_path):
method test_generate_complain_about_module_without_sm (line 159) | def test_generate_complain_about_module_without_sm(self, tmp_path):
method test_format_mermaid (line 166) | def test_format_mermaid(self, tmp_path):
method test_format_md (line 182) | def test_format_md(self, tmp_path):
method test_format_rst (line 198) | def test_format_rst(self, tmp_path):
method test_format_mermaid_stdout (line 214) | def test_format_mermaid_stdout(self, capsys):
method test_format_md_stdout (line 227) | def test_format_md_stdout(self, capsys):
method test_stdout_default_svg (line 240) | def test_stdout_default_svg(self, capsys):
method test_format_mermaid_with_events (line 252) | def test_format_mermaid_with_events(self, tmp_path):
class TestQuickChart (line 270) | class TestQuickChart:
method mock_quickchart (line 272) | def mock_quickchart(self, origin_img_path):
method test_should_call_write_svg (line 280) | def test_should_call_write_svg(self, OrderControl):
function test_compound_state_diagram (line 286) | def test_compound_state_diagram():
function test_parallel_state_diagram (line 309) | def test_parallel_state_diagram():
function test_nested_compound_state_diagram (line 335) | def test_nested_compound_state_diagram():
function test_subgraph_dashed_style_for_parallel_parent (line 358) | def test_subgraph_dashed_style_for_parallel_parent():
function test_initial_edge_with_compound_state_has_lhead (line 374) | def test_initial_edge_with_compound_state_has_lhead():
function test_initial_edge_inside_compound_subgraph (line 390) | def test_initial_edge_inside_compound_subgraph():
function test_history_state_shallow_diagram (line 418) | def test_history_state_shallow_diagram():
function test_history_state_deep_diagram (line 430) | def test_history_state_deep_diagram():
function test_history_state_default_transition (line 442) | def test_history_state_default_transition():
function test_parallel_state_label_indicator (line 455) | def test_parallel_state_label_indicator():
function test_history_state_in_graph_states (line 475) | def test_history_state_in_graph_states():
function test_multi_target_transition_diagram (line 485) | def test_multi_target_transition_diagram():
function test_compound_and_parallel_mixed (line 501) | def test_compound_and_parallel_mixed():
class TestSVGShapeConsistency (line 537) | class TestSVGShapeConsistency:
method test_active_state_has_no_rectangular_fill (line 547) | def test_active_state_has_no_rectangular_fill(self):
method test_active_and_inactive_states_use_same_svg_element_type (line 562) | def test_active_and_inactive_states_use_same_svg_element_type(self):
method test_no_state_node_has_rectangular_colored_fill (line 592) | def test_no_state_node_has_rectangular_colored_fill(self):
class TestExtract (line 616) | class TestExtract:
method test_deep_history_state_type (line 619) | def test_deep_history_state_type(self):
method test_internal_transition_actions_extracted (line 631) | def test_internal_transition_actions_extracted(self):
method test_internal_transition_skipped_in_bidirectional (line 643) | def test_internal_transition_skipped_in_bidirectional(self):
method test_internal_transition_without_action (line 667) | def test_internal_transition_without_action(self):
method test_extract_invalid_type_raises (line 683) | def test_extract_invalid_type_raises(self):
method test_resolve_initial_fallback (line 690) | def test_resolve_initial_fallback(self):
class TestFormatEventNames (line 703) | class TestFormatEventNames:
method test_simple_event_uses_name (line 706) | def test_simple_event_uses_name(self):
method test_done_state_alias_filtered (line 717) | def test_done_state_alias_filtered(self):
method test_done_invoke_alias_filtered (line 734) | def test_done_invoke_alias_filtered(self):
method test_error_alias_filtered (line 747) | def test_error_alias_filtered(self):
method test_multiple_distinct_events_preserved (line 760) | def test_multiple_distinct_events_preserved(self):
method test_eventless_transition_returns_empty (line 776) | def test_eventless_transition_returns_empty(self):
method test_dot_only_event_preserved (line 791) | def test_dot_only_event_preserved(self):
method test_explicit_event_name_displayed (line 804) | def test_explicit_event_name_displayed(self):
class TestDotRendererEdgeCases (line 820) | class TestDotRendererEdgeCases:
method test_compound_state_with_actions_label (line 823) | def test_compound_state_with_actions_label(self):
method test_internal_action_format (line 839) | def test_internal_action_format(self):
method test_targetless_transition_self_loop (line 848) | def test_targetless_transition_self_loop(self):
method test_compound_edge_anchor_non_bidirectional (line 860) | def test_compound_edge_anchor_non_bidirectional(self):
class TestDiagramMainModule (line 868) | class TestDiagramMainModule:
method test_main_module_execution (line 871) | def test_main_module_execution(self, tmp_path):
class TestSphinxDirective (line 892) | class TestSphinxDirective:
method test_parse_events (line 895) | def test_parse_events(self):
method test_import_and_render_class (line 903) | def test_import_and_render_class(self, tmp_path):
method test_import_and_render_with_events (line 913) | def test_import_and_render_with_events(self, tmp_path):
method test_import_invalid_qualname (line 928) | def test_import_invalid_qualname(self):
class TestSplitLength (line 936) | class TestSplitLength:
method test_split_pt (line 939) | def test_split_pt(self):
method test_split_px (line 946) | def test_split_px(self):
method test_split_float (line 953) | def test_split_float(self):
method test_split_no_match (line 960) | def test_split_no_match(self):
class TestAlignSpec (line 968) | class TestAlignSpec:
method test_valid_values (line 971) | def test_valid_values(self):
method test_invalid_value (line 978) | def test_invalid_value(self):
class TestSetup (line 985) | class TestSetup:
method test_setup_returns_metadata (line 988) | def test_setup_returns_metadata(self):
class TestPrepareSvg (line 999) | class TestPrepareSvg:
method _make_directive (line 1002) | def _make_directive(self, options=None):
method test_strips_xml_prologue (line 1010) | def test_strips_xml_prologue(self):
method test_extracts_intrinsic_dimensions (line 1023) | def test_extracts_intrinsic_dimensions(self):
method test_removes_fixed_dimensions (line 1031) | def test_removes_fixed_dimensions(self):
method test_handles_no_dimensions (line 1040) | def test_handles_no_dimensions(self):
method test_handles_px_dimensions (line 1048) | def test_handles_px_dimensions(self):
class TestBuildSvgStyles (line 1057) | class TestBuildSvgStyles:
method _make_directive (line 1060) | def _make_directive(self, options=None):
method test_intrinsic_width_as_max_width (line 1067) | def test_intrinsic_width_as_max_width(self):
method test_explicit_width (line 1073) | def test_explicit_width(self):
method test_explicit_height (line 1079) | def test_explicit_height(self):
method test_scale (line 1085) | def test_scale(self):
method test_scale_without_intrinsic (line 1091) | def test_scale_without_intrinsic(self):
method test_no_dimensions (line 1098) | def test_no_dimensions(self):
method test_explicit_width_overrides_scale (line 1103) | def test_explicit_width_overrides_scale(self):
class TestBuildWrapperClasses (line 1110) | class TestBuildWrapperClasses:
method _make_directive (line 1113) | def _make_directive(self, options=None):
method test_default_center_align (line 1120) | def test_default_center_align(self):
method test_custom_align (line 1125) | def test_custom_align(self):
method test_extra_css_classes (line 1130) | def test_extra_css_classes(self):
class TestResolveTarget (line 1136) | class TestResolveTarget:
method _make_directive (line 1139) | def _make_directive(self, options=None, tmp_path=None):
method test_no_target_option (line 1150) | def test_no_target_option(self):
method test_explicit_target_url (line 1154) | def test_explicit_target_url(self):
method test_empty_target_generates_file (line 1158) | def test_empty_target_generates_file(self, tmp_path):
method test_empty_target_deterministic_filename (line 1172) | def test_empty_target_deterministic_filename(self, tmp_path):
method test_different_events_different_filename (line 1180) | def test_different_events_different_filename(self, tmp_path):
class TestDirectiveRun (line 1187) | class TestDirectiveRun:
method _make_directive (line 1192) | def _make_directive(self, tmp_path, options=None):
method _run (line 1204) | def _run(self, tmp_path, qualname=None, options=None):
method test_render_class_diagram (line 1209) | def test_render_class_diagram(self, tmp_path):
method test_render_with_events (line 1222) | def test_render_with_events(self, tmp_path):
method test_render_with_empty_events (line 1230) | def test_render_with_empty_events(self, tmp_path):
method test_render_with_caption (line 1237) | def test_render_with_caption(self, tmp_path):
method test_render_with_figclass (line 1245) | def test_render_with_figclass(self, tmp_path):
method test_render_with_alt (line 1251) | def test_render_with_alt(self, tmp_path):
method test_render_default_alt (line 1257) | def test_render_default_alt(self, tmp_path):
method test_render_with_explicit_target (line 1263) | def test_render_with_explicit_target(self, tmp_path):
method test_render_with_empty_target (line 1271) | def test_render_with_empty_target(self, tmp_path):
method test_render_with_align (line 1279) | def test_render_with_align(self, tmp_path):
method test_render_with_width (line 1285) | def test_render_with_width(self, tmp_path):
method test_render_with_name (line 1291) | def test_render_with_name(self, tmp_path):
method test_render_with_class (line 1299) | def test_render_with_class(self, tmp_path):
method test_invalid_qualname_returns_warning (line 1305) | def test_invalid_qualname_returns_warning(self, tmp_path):
method test_render_failure_returns_warning (line 1314) | def test_render_failure_returns_warning(self, tmp_path):
method test_render_without_caption_uses_div (line 1327) | def test_render_without_caption_uses_div(self, tmp_path):
class TestGraphMethod (line 1336) | class TestGraphMethod:
method test_graph_returns_pydot_dot (line 1339) | def test_graph_returns_pydot_dot(self):
method test_graph_reflects_active_state (line 1348) | def test_graph_reflects_active_state(self):
class TestFormat (line 1358) | class TestFormat:
method test_format_mermaid_instance (line 1361) | def test_format_mermaid_instance(self):
method test_format_mermaid_class (line 1369) | def test_format_mermaid_class(self):
method test_format_md_instance (line 1376) | def test_format_md_instance(self):
method test_format_md_class (line 1384) | def test_format_md_class(self):
method test_format_markdown_alias (line 1390) | def test_format_markdown_alias(self):
method test_format_rst_instance (line 1396) | def test_format_rst_instance(self):
method test_format_rst_class (line 1403) | def test_format_rst_class(self):
method test_format_dot_instance (line 1409) | def test_format_dot_instance(self):
method test_format_dot_class (line 1417) | def test_format_dot_class(self):
method test_format_empty_falls_back_to_repr (line 1423) | def test_format_empty_falls_back_to_repr(self):
method test_format_empty_class (line 1430) | def test_format_empty_class(self):
method test_format_invalid_raises (line 1436) | def test_format_invalid_raises(self):
method test_format_invalid_class_raises (line 1443) | def test_format_invalid_class_raises(self):
class TestDocstringExpansion (line 1450) | class TestDocstringExpansion:
method test_md_placeholder (line 1453) | def test_md_placeholder(self):
method test_rst_placeholder (line 1471) | def test_rst_placeholder(self):
method test_mermaid_placeholder (line 1489) | def test_mermaid_placeholder(self):
method test_no_placeholder_unchanged (line 1503) | def test_no_placeholder_unchanged(self):
method test_no_docstring (line 1517) | def test_no_docstring(self):
method test_indentation_preserved (line 1529) | def test_indentation_preserved(self):
method test_multiple_placeholders (line 1547) | def test_multiple_placeholders(self):
class TestFormatter (line 1566) | class TestFormatter:
method test_render_mermaid (line 1569) | def test_render_mermaid(self):
method test_render_dot (line 1577) | def test_render_dot(self):
method test_render_svg (line 1585) | def test_render_svg(self):
method test_render_svg_instance (line 1595) | def test_render_svg_instance(self):
method test_format_svg (line 1606) | def test_format_svg(self):
method test_render_md (line 1612) | def test_render_md(self):
method test_render_markdown_alias (line 1620) | def test_render_markdown_alias(self):
method test_render_rst (line 1629) | def test_render_rst(self):
method test_render_empty_repr_instance (line 1637) | def test_render_empty_repr_instance(self):
method test_render_empty_repr_class (line 1645) | def test_render_empty_repr_class(self):
method test_render_invalid_raises (line 1652) | def test_render_invalid_raises(self):
method test_supported_formats (line 1658) | def test_supported_formats(self):
method test_register_custom_format (line 1669) | def test_register_custom_format(self):
method test_register_format_with_aliases (line 1681) | def test_register_format_with_aliases(self):
method test_error_message_lists_primary_formats (line 1695) | def test_error_message_lists_primary_formats(self):
class TestDirectiveMermaidFormat (line 1709) | class TestDirectiveMermaidFormat:
method _make_directive (line 1714) | def _make_directive(self, tmp_path, options=None):
method _run (line 1726) | def _run(self, tmp_path, qualname=None, options=None):
method test_mermaid_format_with_sphinxcontrib (line 1731) | def test_mermaid_format_with_sphinxcontrib(self, tmp_path):
method test_mermaid_format_with_caption (line 1741) | def test_mermaid_format_with_caption(self, tmp_path):
method test_mermaid_format_with_caption_and_name (line 1756) | def test_mermaid_format_with_caption_and_name(self, tmp_path):
method test_mermaid_format_with_name_no_caption (line 1764) | def test_mermaid_format_with_name_no_caption(self, tmp_path):
method test_mermaid_format_fallback_no_sphinxcontrib (line 1772) | def test_mermaid_format_fallback_no_sphinxcontrib(self, tmp_path):
method test_mermaid_render_failure_returns_warning (line 1791) | def test_mermaid_render_failure_returns_warning(self, tmp_path):
FILE: tests/test_contrib_timeout.py
class TestTimeoutValidation (line 13) | class TestTimeoutValidation:
method test_positive_duration (line 14) | def test_positive_duration(self):
method test_zero_duration_raises (line 19) | def test_zero_duration_raises(self):
method test_negative_duration_raises (line 23) | def test_negative_duration_raises(self):
method test_repr_without_on (line 27) | def test_repr_without_on(self):
method test_repr_with_on (line 30) | def test_repr_with_on(self):
class TestTimeoutBasic (line 34) | class TestTimeoutBasic:
method test_timeout_fires_done_invoke (line 37) | async def test_timeout_fires_done_invoke(self, sm_runner):
method test_timeout_cancelled_on_early_exit (line 49) | async def test_timeout_cancelled_on_early_exit(self, sm_runner):
class TestTimeoutCustomEvent (line 65) | class TestTimeoutCustomEvent:
method test_custom_event_fires (line 68) | async def test_custom_event_fires(self, sm_runner):
method test_custom_event_cancelled_on_early_exit (line 80) | async def test_custom_event_cancelled_on_early_exit(self, sm_runner):
class TestTimeoutComposition (line 93) | class TestTimeoutComposition:
method test_invoke_completes_before_timeout (line 96) | async def test_invoke_completes_before_timeout(self, sm_runner):
method test_timeout_fires_before_slow_invoke (line 115) | async def test_timeout_fires_before_slow_invoke(self, sm_runner):
FILE: tests/test_copy.py
function copy_pickle (line 17) | def copy_pickle(obj):
function copy_method (line 22) | def copy_method(request):
class GameStates (line 26) | class GameStates(str, Enum):
class GameStateMachine (line 33) | class GameStateMachine(StateChart):
method game_is_over (line 41) | def game_is_over(self) -> bool:
class MyStateMachine (line 47) | class MyStateMachine(StateChart):
method __init__ (line 53) | def __init__(self):
class MySM (line 59) | class MySM(StateChart):
method let_me_be_visible (line 65) | def let_me_be_visible(self):
class MyModel (line 69) | class MyModel:
method __init__ (line 70) | def __init__(self, name: str) -> None:
method __repr__ (line 74) | def __repr__(self) -> str:
method let_me_be_visible (line 78) | def let_me_be_visible(self):
method let_me_be_visible (line 82) | def let_me_be_visible(self, value):
function test_copy (line 86) | def test_copy(copy_method):
function test_copy_with_listeners (line 99) | def test_copy_with_listeners(copy_method):
function test_copy_with_enum (line 126) | def test_copy_with_enum(copy_method):
function test_copy_with_custom_init_and_vars (line 135) | def test_copy_with_custom_init_and_vars(copy_method):
class AsyncTrafficLightMachine (line 145) | class AsyncTrafficLightMachine(StateChart):
method on_enter_state (line 152) | async def on_enter_state(self, target):
function test_copy_async_statemachine_before_activation (line 156) | def test_copy_async_statemachine_before_activation(copy_method):
function test_copy_async_statemachine_after_activation (line 174) | def test_copy_async_statemachine_after_activation(copy_method):
FILE: tests/test_dispatcher.py
function _take_first_callable (line 12) | def _take_first_callable(iterable):
class Person (line 17) | class Person:
method __init__ (line 18) | def __init__(self, first_name, last_name, legal_document=None):
method get_full_name (line 23) | def get_full_name(self):
class Organization (line 27) | class Organization:
method __init__ (line 28) | def __init__(self, name, legal_document):
method get_full_name (line 32) | def get_full_name(self):
class TestEnsureCallable (line 36) | class TestEnsureCallable:
method args (line 43) | def args(self, request):
method kwargs (line 52) | def kwargs(self, request):
method test_return_same_object_if_already_a_callable (line 55) | def test_return_same_object_if_already_a_callable(self):
method test_retrieve_a_method_from_its_name (line 66) | def test_retrieve_a_method_from_its_name(self, args, kwargs):
method test_retrieve_a_callable_from_a_property_name (line 79) | def test_retrieve_a_callable_from_a_property_name(self, args, kwargs):
method test_retrieve_callable_from_a_property_name_that_should_keep_reference (line 90) | def test_retrieve_callable_from_a_property_name_that_should_keep_refer...
class TestResolverFactory (line 104) | class TestResolverFactory:
method test_should_chain_resolutions (line 114) | def test_should_chain_resolutions(self, attr, expected_value):
method test_should_ignore_list_of_attrs (line 133) | def test_should_ignore_list_of_attrs(self, attr, expected_value):
class TestSearchProperty (line 146) | class TestSearchProperty:
method test_not_found_property_with_same_name (line 147) | def test_not_found_property_with_same_name(self):
FILE: tests/test_error_execution.py
function test_exception_in_guard_sends_error_execution (line 18) | def test_exception_in_guard_sends_error_execution():
function test_exception_in_on_enter_sends_error_execution (line 29) | def test_exception_in_on_enter_sends_error_execution():
function test_exception_in_action_sends_error_execution (line 40) | def test_exception_in_action_sends_error_execution():
function test_exception_in_after_sends_error_execution_no_rollback (line 52) | def test_exception_in_after_sends_error_execution_no_rollback():
function test_statemachine_exception_propagates (line 64) | def test_statemachine_exception_propagates():
function test_invalid_definition_always_propagates (line 74) | def test_invalid_definition_always_propagates():
function test_error_in_error_handler_no_infinite_loop (line 91) | def test_error_in_error_handler_no_infinite_loop():
function test_statemachine_with_catch_errors_as_events_true (line 107) | def test_statemachine_with_catch_errors_as_events_true():
function test_error_data_available_in_error_execution_handler (line 117) | def test_error_data_available_in_error_execution_handler():
function test_error_convention_with_transition_list (line 146) | def test_error_convention_with_transition_list():
function test_error_convention_with_event_no_explicit_id (line 156) | def test_error_convention_with_event_no_explicit_id():
function test_error_convention_preserves_explicit_id (line 166) | def test_error_convention_preserves_explicit_id():
function test_non_error_prefix_unchanged (line 184) | def test_non_error_prefix_unchanged():
class TestErrorConventionLOTR (line 203) | class TestErrorConventionLOTR:
method test_ring_corrupts_bearer_convention_transition_list (line 206) | def test_ring_corrupts_bearer_convention_transition_list(self):
method test_ring_corrupts_bearer_convention_event (line 224) | def test_ring_corrupts_bearer_convention_event(self):
method test_explicit_id_takes_precedence (line 241) | def test_explicit_id_takes_precedence(self):
method test_error_data_passed_to_handler (line 258) | def test_error_data_passed_to_handler(self):
method test_error_in_guard_with_convention (line 281) | def test_error_in_guard_with_convention(self):
method test_error_in_on_enter_with_convention (line 298) | def test_error_in_on_enter_with_convention(self):
method test_error_in_after_with_convention (line 316) | def test_error_in_after_with_convention(self):
method test_error_in_error_handler_no_loop_with_convention (line 336) | def test_error_in_error_handler_no_loop_with_convention(self):
method test_multiple_source_states_with_convention (line 360) | def test_multiple_source_states_with_convention(self):
method test_convention_with_self_transition_to_final (line 378) | def test_convention_with_self_transition_to_final(self):
method test_statemachine_with_convention_and_flag (line 395) | def test_statemachine_with_convention_and_flag(self):
method test_statemachine_without_flag_propagates (line 412) | def test_statemachine_without_flag_propagates(self):
method test_no_error_handler_defined (line 430) | def test_no_error_handler_defined(self):
method test_recovery_from_error_allows_further_transitions (line 447) | def test_recovery_from_error_allows_further_transitions(self):
method test_error_nested_dots_convention (line 470) | def test_error_nested_dots_convention(self):
method test_multiple_errors_sequential (line 492) | def test_multiple_errors_sequential(self):
method test_invalid_definition_propagates_despite_convention (line 522) | def test_invalid_definition_propagates_despite_convention(self):
class TestErrorHandlerBehaviorLOTR (line 541) | class TestErrorHandlerBehaviorLOTR:
method test_on_callback_executes_on_error_transition (line 548) | def test_on_callback_executes_on_error_transition(self):
method test_on_callback_receives_error_kwarg (line 570) | def test_on_callback_receives_error_kwarg(self):
method test_error_in_on_callback_of_error_handler_is_ignored (line 594) | def test_error_in_on_callback_of_error_handler_is_ignored(self):
method test_condition_on_error_transition_routes_to_different_states (line 623) | def test_condition_on_error_transition_routes_to_different_states(self):
method test_condition_inspects_error_type_to_route (line 656) | def test_condition_inspects_error_type_to_route(self):
method test_condition_inspects_error_message_to_route (line 679) | def test_condition_inspects_error_message_to_route(self):
method test_error_handler_can_set_machine_attributes (line 701) | def test_error_handler_can_set_machine_attributes(self):
method test_error_recovery_then_second_error_handled (line 731) | def test_error_recovery_then_second_error_handled(self):
method test_all_conditions_false_error_unhandled (line 764) | def test_all_conditions_false_error_unhandled(self):
method test_error_in_before_callback_with_convention (line 784) | def test_error_in_before_callback_with_convention(self):
method test_error_in_exit_callback_with_convention (line 801) | def test_error_in_exit_callback_with_convention(self):
class TestEngineErrorPropagation (line 821) | class TestEngineErrorPropagation:
method test_invalid_definition_in_enter_propagates (line 822) | def test_invalid_definition_in_enter_propagates(self):
method test_invalid_definition_in_after_propagates (line 838) | def test_invalid_definition_in_after_propagates(self):
method test_runtime_error_in_after_without_catch_errors_as_events_propagates (line 854) | def test_runtime_error_in_after_without_catch_errors_as_events_propaga...
method test_runtime_error_in_after_with_catch_errors_as_events_handled (line 872) | def test_runtime_error_in_after_with_catch_errors_as_events_handled(se...
method test_runtime_error_in_microstep_without_catch_errors_as_events (line 890) | def test_runtime_error_in_microstep_without_catch_errors_as_events(self):
function test_internal_queue_processes_raised_events (line 910) | def test_internal_queue_processes_raised_events():
function test_engine_start_when_already_started (line 932) | def test_engine_start_when_already_started():
function test_error_in_internal_event_transition_caught_by_microstep (line 949) | def test_error_in_internal_event_transition_caught_by_microstep():
function test_invalid_definition_in_internal_event_propagates (line 974) | def test_invalid_definition_in_internal_event_propagates():
function test_runtime_error_in_internal_event_propagates_without_catch_errors_as_events (line 999) | def test_runtime_error_in_internal_event_propagates_without_catch_errors...
FILE: tests/test_events.py
function test_assign_events_on_transitions (line 9) | def test_assign_events_on_transitions():
class TestExplicitEvent (line 35) | class TestExplicitEvent:
method test_accept_event_instance (line 36) | def test_accept_event_instance(self):
method test_accept_event_name (line 51) | def test_accept_event_name(self):
method test_derive_name_from_id (line 62) | def test_derive_name_from_id(self):
method test_not_derive_name_from_id_if_not_event_class (line 76) | def test_not_derive_name_from_id_if_not_event_class(self):
method test_raise_invalid_definition_if_event_name_cannot_be_derived (line 90) | def test_raise_invalid_definition_if_event_name_cannot_be_derived(self):
method test_derive_from_id (line 101) | def test_derive_from_id(self):
method test_of_passing_event_as_parameters (line 110) | def test_of_passing_event_as_parameters(self):
method test_mixing_event_and_parameters (line 144) | def test_mixing_event_and_parameters(self):
method test_name_derived_from_identifier (line 176) | def test_name_derived_from_identifier(self):
method test_multiple_ids_from_the_same_event_will_be_converted_to_multiple_events (line 207) | def test_multiple_ids_from_the_same_event_will_be_converted_to_multipl...
method test_allow_registering_callbacks_using_decorator (line 236) | def test_allow_registering_callbacks_using_decorator(self):
method test_raise_registering_callbacks_using_decorator_if_no_transitions (line 263) | def test_raise_registering_callbacks_using_decorator_if_no_transitions...
method test_allow_using_events_as_commands (line 287) | def test_allow_using_events_as_commands(self):
method test_event_commands_fail_when_unbound_to_instance (line 301) | def test_event_commands_fail_when_unbound_to_instance(self):
function test_event_match_trailing_dot (line 313) | def test_event_match_trailing_dot():
function test_event_build_trigger_with_none_machine (line 320) | def test_event_build_trigger_with_none_machine():
function test_events_match_none_with_empty (line 327) | def test_events_match_none_with_empty():
function test_event_raises_on_non_string_id (line 335) | def test_event_raises_on_non_string_id():
FILE: tests/test_examples.py
function pytest_generate_tests (line 9) | def pytest_generate_tests(metafunc):
function example_file_wrapper (line 21) | def example_file_wrapper(file_name):
function test_example (line 29) | async def test_example(example_file_wrapper):
FILE: tests/test_fellowship_quest.py
class Peril (line 24) | class Peril(Exception):
class RingTemptation (line 28) | class RingTemptation(Peril):
class OrcAmbush (line 32) | class OrcAmbush(Peril):
class DarkSorcery (line 36) | class DarkSorcery(Peril):
class TreacherousTerrain (line 40) | class TreacherousTerrain(Peril):
class BalrogFury (line 44) | class BalrogFury(Peril):
class Character (line 53) | class Character:
method can_counter_with_magic (line 67) | def can_counter_with_magic(self, error=None, **kwargs):
method can_resist_temptation (line 71) | def can_resist_temptation(self, error=None, **kwargs):
method can_endure (line 75) | def can_endure(self, error=None, **kwargs):
method __repr__ (line 81) | def __repr__(self):
class Gandalf (line 85) | class Gandalf(Character):
class Aragorn (line 93) | class Aragorn(Character):
class Frodo (line 99) | class Frodo(Character):
class Legolas (line 105) | class Legolas(Character):
class Boromir (line 111) | class Boromir(Character):
class Pippin (line 117) | class Pippin(Character):
class Samwise (line 121) | class Samwise(Character):
class FellowshipQuest (line 132) | class FellowshipQuest(StateChart):
method encounter_danger (line 170) | def encounter_danger(self, peril, **kwargs):
method is_ring_corruption (line 173) | def is_ring_corruption(self, error=None, **kwargs):
method take_hit (line 177) | def take_hit(self, error=None, **kwargs):
function _state_by_name (line 192) | def _state_by_name(sm, name):
function test_single_peril_outcome (line 307) | def test_single_peril_outcome(character, peril, expected):
function test_wound_description (line 338) | def test_wound_description(character, peril, expect_wound):
function test_multi_peril_saga (line 383) | def test_multi_peril_saga(character, perils_and_states):
function test_wounded_then_second_peril_is_fatal (line 420) | def test_wounded_then_second_peril_is_fatal(character, first_peril, seco...
function test_recovery_after_wound (line 445) | def test_recovery_after_wound(character, peril):
FILE: tests/test_invoke.py
class TestInvokeSimpleCallable (line 15) | class TestInvokeSimpleCallable:
method test_simple_callable_invoke (line 18) | async def test_simple_callable_invoke(self, sm_runner):
method test_invoke_return_value_in_done_event (line 36) | async def test_invoke_return_value_in_done_event(self, sm_runner):
class TestInvokeNamingConvention (line 55) | class TestInvokeNamingConvention:
method test_naming_convention (line 58) | async def test_naming_convention(self, sm_runner):
class TestInvokeDecorator (line 78) | class TestInvokeDecorator:
method test_decorator_invoke (line 81) | async def test_decorator_invoke(self, sm_runner):
class TestInvokeIInvokeProtocol (line 102) | class TestInvokeIInvokeProtocol:
method test_iinvoke_class (line 105) | async def test_iinvoke_class(self, sm_runner):
method test_each_sm_instance_gets_own_handler (line 135) | async def test_each_sm_instance_gets_own_handler(self, sm_runner):
class TestInvokeCancelOnExit (line 161) | class TestInvokeCancelOnExit:
method test_cancel_on_exit_sync (line 164) | async def test_cancel_on_exit_sync(self):
method test_cancel_on_exit_with_on_cancel (line 190) | async def test_cancel_on_exit_with_on_cancel(self, sm_runner):
class TestInvokeErrorHandling (line 217) | class TestInvokeErrorHandling:
method test_error_in_invoke (line 220) | async def test_error_in_invoke(self, sm_runner):
class TestInvokeMultiple (line 239) | class TestInvokeMultiple:
method test_multiple_invokes (line 242) | async def test_multiple_invokes(self, sm_runner):
class TestInvokeStateChartChild (line 268) | class TestInvokeStateChartChild:
method test_statechart_invoker (line 271) | async def test_statechart_invoker(self, sm_runner):
class TestDoneInvokeTransition (line 292) | class TestDoneInvokeTransition:
method test_done_invoke_transition (line 295) | async def test_done_invoke_transition(self, sm_runner):
class TestDoneInvokeEventFormat (line 308) | class TestDoneInvokeEventFormat:
method test_done_invoke_event_has_no_duplicate_state_id (line 311) | async def test_done_invoke_event_has_no_duplicate_state_id(self, sm_ru...
class TestInvokeGroup (line 336) | class TestInvokeGroup:
method test_group_returns_ordered_results (line 339) | async def test_group_returns_ordered_results(self, sm_runner):
method test_group_with_file_io (line 365) | async def test_group_with_file_io(self, sm_runner, tmp_path):
method test_group_error_cancels_remaining (line 395) | async def test_group_error_cancels_remaining(self, sm_runner):
method test_group_cancel_on_exit (line 421) | async def test_group_cancel_on_exit(self, sm_runner):
method test_group_single_callable (line 443) | async def test_group_single_callable(self, sm_runner):
method test_each_sm_instance_gets_own_group (line 462) | async def test_each_sm_instance_gets_own_group(self, sm_runner):
class TestInvokeEventKwargs (line 495) | class TestInvokeEventKwargs:
method test_plain_callable_receives_event_kwargs (line 498) | async def test_plain_callable_receives_event_kwargs(self, sm_runner):
method test_iinvoke_handler_receives_event_kwargs_via_ctx (line 524) | async def test_iinvoke_handler_receives_event_kwargs_via_ctx(self, sm_...
method test_initial_state_invoke_has_empty_kwargs (line 551) | async def test_initial_state_invoke_has_empty_kwargs(self, sm_runner):
class TestInvokeNotTriggeredOnNonInvokeState (line 565) | class TestInvokeNotTriggeredOnNonInvokeState:
method test_no_invoke_on_plain_state (line 568) | async def test_no_invoke_on_plain_state(self, sm_runner):
class TestInvokeManagerCancelAll (line 584) | class TestInvokeManagerCancelAll:
method test_cancel_all (line 587) | async def test_cancel_all(self, sm_runner):
class TestInvokeCancelAlreadyTerminated (line 607) | class TestInvokeCancelAlreadyTerminated:
method test_cancel_terminated_invocation (line 610) | async def test_cancel_terminated_invocation(self, sm_runner):
class TestInvokeOnCancelException (line 630) | class TestInvokeOnCancelException:
method test_on_cancel_exception_is_suppressed (line 633) | async def test_on_cancel_exception_is_suppressed(self, sm_runner):
class TestStateChartInvokerOnCancel (line 655) | class TestStateChartInvokerOnCancel:
method test_on_cancel_clears_child (line 658) | def test_on_cancel_clears_child(self):
class TestNormalizeInvokeCallbacks (line 677) | class TestNormalizeInvokeCallbacks:
method test_string_passes_through (line 680) | def test_string_passes_through(self):
method test_already_wrapped_passes_through (line 686) | def test_already_wrapped_passes_through(self):
method test_iinvoke_class_with_run_method (line 699) | def test_iinvoke_class_with_run_method(self):
method test_plain_callable_passes_through (line 714) | def test_plain_callable_passes_through(self):
method test_non_invoke_class_passes_through (line 726) | def test_non_invoke_class_passes_through(self):
class TestResolveHandler (line 740) | class TestResolveHandler:
method test_bare_iinvoke_instance (line 743) | def test_bare_iinvoke_instance(self):
method test_bare_statechart_class (line 755) | def test_bare_statechart_class(self):
method test_plain_callable_returns_none (line 765) | def test_plain_callable_returns_none(self):
class TestInvokeCallableWrapperOnCancel (line 774) | class TestInvokeCallableWrapperOnCancel:
method test_on_cancel_non_class_instance_with_on_cancel (line 777) | def test_on_cancel_non_class_instance_with_on_cancel(self):
method test_on_cancel_class_not_yet_instantiated (line 796) | def test_on_cancel_class_not_yet_instantiated(self):
method test_callable_wrapper_call_returns_handler (line 811) | def test_callable_wrapper_call_returns_handler(self):
class TestInvokeGroupOnCancelBeforeRun (line 823) | class TestInvokeGroupOnCancelBeforeRun:
method test_on_cancel_before_run (line 826) | def test_on_cancel_before_run(self):
class TestDoneInvokeEventFactory (line 832) | class TestDoneInvokeEventFactory:
method test_done_invoke_with_event_object (line 835) | async def test_done_invoke_with_event_object(self, sm_runner):
class TestVisitNoCallbacks (line 850) | class TestVisitNoCallbacks:
method test_visit_missing_key (line 853) | def test_visit_missing_key(self):
method test_async_visit_missing_key (line 860) | async def test_async_visit_missing_key(self):
class TestAsyncVisitAwaitable (line 867) | class TestAsyncVisitAwaitable:
method test_async_visitor_fn_is_awaited (line 870) | async def test_async_visitor_fn_is_awaited(self):
class TestIInvokeProtocolRun (line 888) | class TestIInvokeProtocolRun:
method test_protocol_run_is_callable (line 891) | def test_protocol_run_is_callable(self):
class TestSpawnPendingAsyncEmpty (line 903) | class TestSpawnPendingAsyncEmpty:
method test_spawn_pending_async_no_pending (line 906) | async def test_spawn_pending_async_no_pending(self, sm_runner):
class TestInvokeAsyncCancelledDuringExecution (line 917) | class TestInvokeAsyncCancelledDuringExecution:
method test_success_after_cancel (line 920) | async def test_success_after_cancel(self):
method test_error_after_cancel (line 944) | async def test_error_after_cancel(self):
class TestSyncInvokeErrorAfterCancel (line 969) | class TestSyncInvokeErrorAfterCancel:
method test_sync_error_after_cancel (line 972) | async def test_sync_error_after_cancel(self):
class TestInvokeManagerUnit (line 995) | class TestInvokeManagerUnit:
method test_send_to_child_not_found (line 998) | def test_send_to_child_not_found(self):
method test_send_to_child_handler_without_on_event (line 1009) | def test_send_to_child_handler_without_on_event(self):
method test_handle_external_event_none_event (line 1027) | def test_handle_external_event_none_event(self):
class TestStopChildMachine (line 1041) | class TestStopChildMachine:
method test_stop_child_machine_exception_swallowed (line 1044) | def test_stop_child_machine_exception_swallowed(self):
class TestEngineDelCleanup (line 1058) | class TestEngineDelCleanup:
method test_del_swallows_cancel_all_exception (line 1061) | def test_del_swallows_cancel_all_exception(self):
FILE: tests/test_io.py
class TestParseHistory (line 7) | class TestParseHistory:
method test_history_without_transitions (line 8) | def test_history_without_transitions(self):
method test_history_with_on_only (line 15) | def test_history_with_on_only(self):
class TestCreateMachineWithEventNameConcat (line 25) | class TestCreateMachineWithEventNameConcat:
method test_transition_with_both_parent_and_own_event_name (line 26) | def test_transition_with_both_parent_and_own_event_name(self):
FILE: tests/test_listener.py
class TestObserver (line 18) | class TestObserver:
method test_add_log_observer (line 19) | def test_add_log_observer(self, campaign_machine, capsys):
method test_log_observer_on_creation (line 40) | def test_log_observer_on_creation(self, campaign_machine, capsys):
function test_regression_456 (line 60) | def test_regression_456():
FILE: tests/test_mermaid_renderer.py
class TestMermaidRendererSimple (line 15) | class TestMermaidRendererSimple:
method test_simple_states (line 18) | def test_simple_states(self):
method test_initial_and_final (line 35) | def test_initial_and_final(self):
method test_custom_direction (line 50) | def test_custom_direction(self):
method test_state_name_differs_from_id (line 59) | def test_state_name_differs_from_id(self):
method test_state_name_equals_id_no_declaration (line 71) | def test_state_name_equals_id_no_declaration(self):
class TestMermaidRendererTransitions (line 83) | class TestMermaidRendererTransitions:
method test_transition_with_guards (line 86) | def test_transition_with_guards(self):
method test_eventless_transition (line 100) | def test_eventless_transition(self):
method test_self_transition (line 114) | def test_self_transition(self):
method test_targetless_transition (line 127) | def test_targetless_transition(self):
method test_multi_target_transition (line 140) | def test_multi_target_transition(self):
method test_internal_transitions_skipped (line 156) | def test_internal_transitions_skipped(self):
method test_initial_transitions_skipped (line 169) | def test_initial_transitions_skipped(self):
class TestMermaidRendererActiveState (line 185) | class TestMermaidRendererActiveState:
method test_active_state_class (line 188) | def test_active_state_class(self):
method test_no_active_state_no_classdef (line 206) | def test_no_active_state_no_classdef(self):
method test_active_fill_config (line 216) | def test_active_fill_config(self):
class TestMermaidRendererCompound (line 231) | class TestMermaidRendererCompound:
method test_compound_state (line 234) | def test_compound_state(self):
method test_compound_no_duplicate_transitions (line 255) | def test_compound_no_duplicate_transitions(self):
method test_parallel_state (line 271) | def test_parallel_state(self):
method test_parallel_redirects_compound_endpoints (line 291) | def test_parallel_redirects_compound_endpoints(self):
method test_compound_outside_parallel_not_redirected (line 316) | def test_compound_outside_parallel_not_redirected(self):
method test_nested_compound (line 332) | def test_nested_compound(self):
class TestMermaidRendererPseudoStates (line 351) | class TestMermaidRendererPseudoStates:
method test_history_shallow (line 354) | def test_history_shallow(self):
method test_history_deep (line 374) | def test_history_deep(self):
method test_choice_state (line 394) | def test_choice_state(self):
method test_fork_state (line 404) | def test_fork_state(self):
method test_join_state (line 414) | def test_join_state(self):
class TestMermaidRendererActions (line 425) | class TestMermaidRendererActions:
method test_entry_exit_actions (line 428) | def test_entry_exit_actions(self):
method test_internal_action (line 448) | def test_internal_action(self):
method test_empty_internal_action_skipped (line 466) | def test_empty_internal_action_skipped(self):
class TestMermaidGraphMachine (line 485) | class TestMermaidGraphMachine:
method test_facade_returns_string (line 488) | def test_facade_returns_string(self):
method test_facade_callable (line 495) | def test_facade_callable(self):
method test_facade_with_instance (line 501) | def test_facade_with_instance(self):
method test_facade_custom_config (line 508) | def test_facade_custom_config(self):
class TestMermaidRendererEdgeCases (line 521) | class TestMermaidRendererEdgeCases:
method test_compound_state_name_equals_id (line 524) | def test_compound_state_name_equals_id(self):
method test_active_compound_state (line 545) | def test_active_compound_state(self):
method test_cross_scope_transition_rendered_at_parent (line 566) | def test_cross_scope_transition_rendered_at_parent(self):
method test_cross_scope_to_history_state (line 598) | def test_cross_scope_to_history_state(self):
method test_no_initial_state (line 634) | def test_no_initial_state(self):
method test_duplicate_transition_rendered_once (line 645) | def test_duplicate_transition_rendered_once(self):
method test_compound_no_initial_child (line 661) | def test_compound_no_initial_child(self):
class TestMermaidRendererIntegration (line 685) | class TestMermaidRendererIntegration:
method test_traffic_light (line 688) | def test_traffic_light(self):
method test_traffic_light_with_events (line 696) | def test_traffic_light_with_events(self):
FILE: tests/test_mixins.py
class MyMixedModel (line 7) | class MyMixedModel(MyModel, MachineMixin):
function test_mixin_should_instantiate_a_machine (line 11) | def test_mixin_should_instantiate_a_machine(campaign_machine):
function test_mixin_should_raise_exception_if_machine_class_does_not_exist (line 18) | def test_mixin_should_raise_exception_if_machine_class_does_not_exist():
function test_mixin_should_skip_init_for_django_historical_models (line 26) | def test_mixin_should_skip_init_for_django_historical_models():
FILE: tests/test_mock_compatibility.py
function test_minimal (line 5) | def test_minimal(mocker):
FILE: tests/test_multiple_destinations.py
class Request (line 8) | class Request:
method __init__ (line 9) | def __init__(self, state="requested"):
method is_ok (line 13) | def is_ok(self):
function test_transition_should_choose_final_state_on_multiple_possibilities (line 17) | def test_transition_should_choose_final_state_on_multiple_possibilities(
function test_transition_to_first_that_executes_if_multiple_targets (line 51) | def test_transition_to_first_that_executes_if_multiple_targets():
function test_do_not_transition_if_multiple_targets_with_guard (line 67) | def test_do_not_transition_if_multiple_targets_with_guard():
function test_check_invalid_reference_to_conditions (line 101) | def test_check_invalid_reference_to_conditions():
function test_should_change_to_returned_state_on_multiple_target_with_combined_transitions (line 117) | def test_should_change_to_returned_state_on_multiple_target_with_combine...
function test_transition_on_execute_should_be_called_with_run_syntax (line 170) | def test_transition_on_execute_should_be_called_with_run_syntax(approval...
function test_multiple_values_returned_with_multiple_targets (line 184) | def test_multiple_values_returned_with_multiple_targets():
function test_multiple_targets_using_or_starting_from_same_origin (line 211) | def test_multiple_targets_using_or_starting_from_same_origin(payment_fai...
function test_order_control (line 229) | def test_order_control(OrderControl):
FILE: tests/test_profiling.py
class OrderControl (line 15) | class OrderControl(StateChart):
class Order (line 32) | class Order:
method __init__ (line 33) | def __init__(self):
method payments_enough (line 39) | def payments_enough(self, amount):
method before_add_to_order (line 42) | def before_add_to_order(self, amount):
method on_receive_payment (line 46) | def on_receive_payment(self, amount):
method after_receive_payment (line 50) | def after_receive_payment(self):
class CompoundSC (line 55) | class CompoundSC(StateChart):
class active (line 56) | class active(State.Compound, name="Active"):
class ParallelSC (line 69) | class ParallelSC(StateChart):
class both (line 70) | class both(State.Parallel, name="Both"):
class left (line 71) | class left(State.Compound, name="Left"):
class right (line 77) | class right(State.Compound, name="Right"):
class GuardedSC (line 88) | class GuardedSC(StateChart):
method check_a (line 93) | def check_a(self):
method check_b (line 96) | def check_b(self):
class HistoryShallowSC (line 104) | class HistoryShallowSC(StateChart):
class process (line 105) | class process(State.Compound, name="Process"):
class DeepHistorySC (line 119) | class DeepHistorySC(StateChart):
class outer (line 120) | class outer(State.Compound, name="Outer"):
class inner (line 121) | class inner(State.Compound, name="Inner"):
class ManyTransitionsSC (line 139) | class ManyTransitionsSC(StateChart):
function create_order (line 159) | def create_order():
function add_to_order (line 164) | def add_to_order(sm, amount):
class TestSetupPerformance (line 174) | class TestSetupPerformance:
method test_flat_machine (line 177) | def test_flat_machine(self, benchmark):
method test_compound_machine (line 180) | def test_compound_machine(self, benchmark):
method test_parallel_machine (line 183) | def test_parallel_machine(self, benchmark):
method test_guarded_machine (line 186) | def test_guarded_machine(self, benchmark):
method test_history_machine (line 189) | def test_history_machine(self, benchmark):
method test_deep_history_machine (line 192) | def test_deep_history_machine(self, benchmark):
class TestEventPerformance (line 202) | class TestEventPerformance:
method test_flat_self_transition (line 205) | def test_flat_self_transition(self, benchmark):
method test_compound_enter_exit (line 211) | def test_compound_enter_exit(self, benchmark):
method test_parallel_region_events (line 222) | def test_parallel_region_events(self, benchmark):
method test_guarded_transitions (line 235) | def test_guarded_transitions(self, benchmark):
method test_history_pause_resume (line 245) | def test_history_pause_resume(self, benchmark):
method test_deep_history_cycle (line 257) | def test_deep_history_cycle(self, benchmark):
method test_many_transitions_full_cycle (line 270) | def test_many_transitions_full_cycle(self, benchmark):
method test_many_transitions_reset (line 283) | def test_many_transitions_reset(self, benchmark):
FILE: tests/test_registry.py
function django_autodiscover_modules (line 7) | def django_autodiscover_modules():
function test_should_register_a_state_machine (line 14) | def test_should_register_a_state_machine(caplog, django_autodiscover_mod...
function test_load_modules_should_call_autodiscover_modules (line 32) | def test_load_modules_should_call_autodiscover_modules(django_autodiscov...
FILE: tests/test_rtc.py
function chained_after_sm_class (line 11) | def chained_after_sm_class(): # noqa: C901
function chained_on_sm_class (line 47) | def chained_on_sm_class(): # noqa: C901
class TestChainedTransition (line 87) | class TestChainedTransition:
method test_should_allow_chaining_transitions_using_actions (line 106) | def test_should_allow_chaining_transitions_using_actions(
method test_should_preserve_event_order (line 140) | def test_should_preserve_event_order(self, chained_on_sm_class, expect...
class TestAsyncEngineRTC (line 151) | class TestAsyncEngineRTC:
method test_should_preserve_event_order (line 176) | def test_should_preserve_event_order(self, expected): # noqa: C901
FILE: tests/test_scxml_units.py
class TestParseTimeErrors (line 27) | class TestParseTimeErrors:
method test_invalid_milliseconds_value (line 28) | def test_invalid_milliseconds_value(self):
method test_invalid_seconds_value (line 33) | def test_invalid_seconds_value(self):
method test_invalid_unit (line 38) | def test_invalid_unit(self):
class TestStripNamespaces (line 47) | class TestStripNamespaces:
method test_removes_namespace_from_attributes (line 48) | def test_removes_namespace_from_attributes(self):
class TestParseScxml (line 58) | class TestParseScxml:
method test_no_scxml_element_raises (line 59) | def test_no_scxml_element_raises(self):
class TestParseState (line 66) | class TestParseState:
method test_state_without_id_gets_auto_generated (line 67) | def test_state_without_id_gets_auto_generated(self):
class TestParseHistory (line 76) | class TestParseHistory:
method test_history_without_id_raises (line 77) | def test_history_without_id_raises(self):
class TestParseElement (line 88) | class TestParseElement:
method test_unknown_tag_raises (line 89) | def test_unknown_tag_raises(self):
class TestParseSendParam (line 96) | class TestParseSendParam:
method test_param_without_expr_or_location_raises (line 97) | def test_param_without_expr_or_location_raises(self):
class TestCreateActionCallable (line 115) | class TestCreateActionCallable:
method test_unknown_action_type_raises (line 116) | def test_unknown_action_type_raises(self):
class TestLogAction (line 124) | class TestLogAction:
method test_log_without_label (line 125) | def test_log_without_label(self, capsys):
class TestCancelActionCallable (line 134) | class TestCancelActionCallable:
method test_cancel_without_sendid_raises (line 135) | def test_cancel_without_sendid_raises(self):
class TestCreateDatamodelCallable (line 145) | class TestCreateDatamodelCallable:
method test_empty_datamodel_returns_none (line 146) | def test_empty_datamodel_returns_none(self):
class TestIfBranch (line 156) | class TestIfBranch:
method test_str_with_none_cond (line 157) | def test_str_with_none_cond(self):
method test_str_with_cond (line 162) | def test_str_with_cond(self):
class TestSCXMLIfConditionError (line 171) | class TestSCXMLIfConditionError:
method test_if_condition_error_sends_error_execution (line 174) | def test_if_condition_error_sends_error_execution(self):
class TestSCXMLForeachArrayError (line 197) | class TestSCXMLForeachArrayError:
method test_foreach_bad_array_raises (line 200) | def test_foreach_bad_array_raises(self):
class TestSCXMLParallelFinalState (line 227) | class TestSCXMLParallelFinalState:
method test_parallel_state_done_when_all_regions_final (line 230) | def test_parallel_state_done_when_all_regions_final(self):
class TestEventDataWrapperMultipleArgs (line 263) | class TestEventDataWrapperMultipleArgs:
method test_data_returns_tuple_for_multiple_args (line 266) | def test_data_returns_tuple_for_multiple_args(self):
class TestIfActionRaisesWithoutErrorOnExecution (line 286) | class TestIfActionRaisesWithoutErrorOnExecution:
method test_if_condition_error_propagates_without_catch_errors_as_events (line 289) | def test_if_condition_error_propagates_without_catch_errors_as_events(...
class TestSCXMLSendWithParamNoExpr (line 306) | class TestSCXMLSendWithParamNoExpr:
method test_send_param_with_location_only (line 309) | def test_send_param_with_location_only(self):
class TestSCXMLHistoryWithoutTransitions (line 335) | class TestSCXMLHistoryWithoutTransitions:
method test_history_without_transitions (line 338) | def test_history_without_transitions(self):
function _make_invoker (line 368) | def _make_invoker(definition=None, base_dir=None, register_child=None):
class TestSCXMLInvoker (line 383) | class TestSCXMLInvoker:
method test_invalid_invoke_type_raises (line 384) | def test_invalid_invoke_type_raises(self):
method test_no_content_resolved_raises (line 398) | def test_no_content_resolved_raises(self):
method test_resolve_content_inline_xml (line 409) | def test_resolve_content_inline_xml(self):
method test_resolve_content_from_file (line 418) | def test_resolve_content_from_file(self, tmp_path):
method test_evaluate_params_namelist_and_params (line 429) | def test_evaluate_params_namelist_and_params(self):
method test_on_cancel_clears_child (line 443) | def test_on_cancel_clears_child(self):
method test_on_event_skips_terminated_child (line 451) | def test_on_event_skips_terminated_child(self):
method test_on_finalize_without_block_is_noop (line 462) | def test_on_finalize_without_block_is_noop(self):
method test_send_to_parent_warns_without_session (line 471) | def test_send_to_parent_warns_without_session(self, caplog):
class TestSendToInvoke (line 489) | class TestSendToInvoke:
method _make_machine_with_invoke_manager (line 492) | def _make_machine_with_invoke_manager(self, send_to_child_return=True):
method test_routes_event_to_child (line 500) | def test_routes_event_to_child(self):
method test_sends_error_communication_when_child_not_found (line 515) | def test_sends_error_communication_when_child_not_found(self):
method test_evaluates_eventexpr (line 529) | def test_evaluates_eventexpr(self):
method test_forwards_params (line 543) | def test_forwards_params(self):
method test_forwards_namelist_variables (line 561) | def test_forwards_namelist_variables(self):
method test_namelist_missing_variable_raises (line 579) | def test_namelist_missing_variable_raises(self):
method test_send_action_callable_routes_invoke_target (line 591) | def test_send_action_callable_routes_invoke_target(self):
method test_send_action_callable_scxml_session_target (line 604) | def test_send_action_callable_scxml_session_target(self):
class TestEventDataWrapperEdgeCases (line 624) | class TestEventDataWrapperEdgeCases:
method test_no_event_data_no_trigger_data_raises (line 625) | def test_no_event_data_no_trigger_data_raises(self):
method test_getattr_with_event_data_delegates (line 630) | def test_getattr_with_event_data_delegates(self):
method test_getattr_without_event_data_raises (line 640) | def test_getattr_without_event_data_raises(self):
method test_name_via_trigger_data (line 648) | def test_name_via_trigger_data(self):
class TestSendToParentParams (line 659) | class TestSendToParentParams:
method test_send_to_parent_with_namelist_and_params (line 660) | def test_send_to_parent_with_namelist_and_params(self):
method test_send_to_parent_namelist_missing_raises (line 683) | def test_send_to_parent_namelist_missing_raises(self):
method test_send_to_parent_param_without_expr_skipped (line 697) | def test_send_to_parent_param_without_expr_skipped(self):
class TestSendToInvokeParamSkip (line 724) | class TestSendToInvokeParamSkip:
method test_param_without_expr_is_skipped (line 725) | def test_param_without_expr_is_skipped(self):
class TestInvokeInitCallback (line 754) | class TestInvokeInitCallback:
method test_invoke_init_idempotent (line 755) | def test_invoke_init_idempotent(self):
class TestSCXMLInvokerEdgeCases (line 775) | class TestSCXMLInvokerEdgeCases:
method test_on_event_exception_in_child_send (line 776) | def test_on_event_exception_in_child_send(self):
method test_resolve_content_expr_non_string (line 788) | def test_resolve_content_expr_non_string(self):
method test_evaluate_params_with_location (line 798) | def test_evaluate_params_with_location(self):
class TestParserAssignChildXml (line 817) | class TestParserAssignChildXml:
method test_assign_with_child_xml_content (line 818) | def test_assign_with_child_xml_content(self):
method test_assign_with_text_content (line 839) | def test_assign_with_text_content(self):
class TestParserInvokeContent (line 859) | class TestParserInvokeContent:
method test_invoke_with_text_content (line 860) | def test_invoke_with_text_content(self):
method test_invoke_with_content_expr (line 876) | def test_invoke_with_content_expr(self):
method test_invoke_with_inline_scxml_no_namespace (line 891) | def test_invoke_with_inline_scxml_no_namespace(self):
method test_invoke_with_unknown_child_element (line 906) | def test_invoke_with_unknown_child_element(self):
method test_invoke_with_empty_content (line 922) | def test_invoke_with_empty_content(self):
method test_invoke_with_finalize_block (line 937) | def test_invoke_with_finalize_block(self):
class TestParserAssignEdgeCases (line 957) | class TestParserAssignEdgeCases:
method test_assign_without_children_or_text (line 958) | def test_assign_without_children_or_text(self):
class TestSCXMLInvokerResolveContentAbsolutePath (line 978) | class TestSCXMLInvokerResolveContentAbsolutePath:
method test_resolve_content_absolute_path (line 979) | def test_resolve_content_absolute_path(self, tmp_path):
class TestSCXMLInvokerEvaluateParamsNoExprNoLocation (line 991) | class TestSCXMLInvokerEvaluateParamsNoExprNoLocation:
method test_param_without_expr_or_location_skipped (line 992) | def test_param_without_expr_or_location_skipped(self):
class TestInvokeInitMachineNone (line 1005) | class TestInvokeInitMachineNone:
method test_invoke_init_without_machine_is_noop (line 1006) | def test_invoke_init_without_machine_is_noop(self):
class TestInvokeCallableWrapperRunInstance (line 1015) | class TestInvokeCallableWrapperRunInstance:
method test_run_with_instance_not_class (line 1016) | def test_run_with_instance_not_class(self):
class TestOrderedSetStr (line 1034) | class TestOrderedSetStr:
method test_str_representation (line 1035) | def test_str_representation(self):
FILE: tests/test_signature.py
function single_positional_param (line 9) | def single_positional_param(a):
function single_default_keyword_param (line 13) | def single_default_keyword_param(a=42):
function args_param (line 17) | def args_param(*args):
function kwargs_param (line 21) | def kwargs_param(**kwargs):
function args_and_kwargs_param (line 25) | def args_and_kwargs_param(*args, **kwargs):
function positional_optional_catchall (line 29) | def positional_optional_catchall(a, b="ham", *args):
function ignored_param (line 33) | def ignored_param(a, b, *, c, d=10):
function positional_and_kw_arguments (line 37) | def positional_and_kw_arguments(source, target, event):
function default_kw_arguments (line 41) | def default_kw_arguments(source: str = "A", target: str = "B", event: st...
class MyObject (line 45) | class MyObject:
method __init__ (line 46) | def __init__(self, value=42):
method method_no_argument (line 49) | def method_no_argument(self):
class TestSignatureAdapter (line 53) | class TestSignatureAdapter:
method test_wrap_fn_single_positional_parameter (line 149) | def test_wrap_fn_single_positional_parameter(self, func, args, kwargs,...
method test_support_for_partial (line 159) | def test_support_for_partial(self):
function named_and_kwargs (line 167) | def named_and_kwargs(source, **kwargs):
class TestCachedBindExpected (line 171) | class TestCachedBindExpected:
method setup_method (line 175) | def setup_method(self):
method test_named_param_not_leaked_into_kwargs (line 178) | def test_named_param_not_leaked_into_kwargs(self):
method test_kwargs_only_receives_unmatched_keys_with_positional (line 190) | def test_kwargs_only_receives_unmatched_keys_with_positional(self):
method test_var_positional_collected_as_tuple (line 200) | def test_var_positional_collected_as_tuple(self):
method test_keyword_only_after_var_positional (line 214) | def test_keyword_only_after_var_positional(self):
method test_positional_or_keyword_prefers_kwargs_over_positional (line 228) | def test_positional_or_keyword_prefers_kwargs_over_positional(self):
method test_empty_var_positional (line 244) | def test_empty_var_positional(self):
method test_named_params_before_var_positional (line 264) | def test_named_params_before_var_positional(self):
method test_kwargs_wins_with_var_positional_present (line 278) | def test_kwargs_wins_with_var_positional_present(self):
FILE: tests/test_signature_positional_only.py
class TestSignatureAdapter (line 7) | class TestSignatureAdapter:
method test_positional_only (line 22) | def test_positional_only(self, args, kwargs, expected):
FILE: tests/test_spec_parser.py
function variable_hook (line 13) | def variable_hook(
function test_expressions (line 178) | def test_expressions(expression, expected, hooks_called):
function test_negating_compound_false_expression (line 189) | def test_negating_compound_false_expression():
function test_expression_name_uniqueness (line 196) | def test_expression_name_uniqueness():
function test_classical_operators_name (line 204) | def test_classical_operators_name():
function test_empty_expression (line 213) | def test_empty_expression():
function test_whitespace_expression (line 219) | def test_whitespace_expression():
function test_missing_operator_expression (line 225) | def test_missing_operator_expression():
function test_dict_usage_expression (line 231) | def test_dict_usage_expression():
function test_unsupported_operator (line 237) | def test_unsupported_operator():
function test_simple_variable_returns_the_original_callback (line 244) | def test_simple_variable_returns_the_original_callback():
function async_variable_hook (line 259) | def async_variable_hook(var_name):
function test_async_expressions (line 301) | def test_async_expressions(expression, expected):
function mixed_variable_hook (line 309) | def mixed_variable_hook(var_name):
function test_mixed_sync_async_expressions (line 344) | def test_mixed_sync_async_expressions(expression, expected):
function test_functions_get_unknown_raises (line 354) | def test_functions_get_unknown_raises():
FILE: tests/test_state.py
function sm_class (line 9) | def sm_class():
class TestState (line 21) | class TestState:
method test_name_derived_from_id (line 22) | def test_name_derived_from_id(self, sm_class):
method test_state_from_instance_is_hashable (line 27) | def test_state_from_instance_is_hashable(self, sm_class):
method test_state_knows_if_its_initial (line 32) | def test_state_knows_if_its_initial(self, sm_class):
method test_state_knows_if_its_final (line 38) | def test_state_knows_if_its_final(self, sm_class):
function test_ordered_set_clear (line 45) | def test_ordered_set_clear():
function test_ordered_set_getitem (line 52) | def test_ordered_set_getitem():
function test_ordered_set_getitem_out_of_range (line 59) | def test_ordered_set_getitem_out_of_range():
function test_ordered_set_union (line 66) | def test_ordered_set_union():
FILE: tests/test_state_callbacks.py
function event_mock (line 7) | def event_mock():
function traffic_light_machine (line 12) | def traffic_light_machine(event_mock): # noqa: C901
class TestStateCallbacks (line 52) | class TestStateCallbacks:
method test_should_call_on_enter_generic_state (line 53) | def test_should_call_on_enter_generic_state(self, event_mock, traffic_...
method test_should_call_on_exit_generic_state (line 61) | def test_should_call_on_exit_generic_state(self, event_mock, traffic_l...
method test_should_call_on_enter_of_specific_state (line 66) | def test_should_call_on_enter_of_specific_state(self, event_mock, traf...
method test_should_call_on_exit_of_specific_state (line 71) | def test_should_call_on_exit_of_specific_state(self, event_mock, traff...
method test_should_be_on_the_previous_state_when_exiting (line 76) | def test_should_be_on_the_previous_state_when_exiting(self, event_mock...
method test_should_be_on_the_next_state_when_entering (line 90) | def test_should_be_on_the_next_state_when_entering(self, event_mock, t...
FILE: tests/test_statechart_compound.py
class TestCompoundStates (line 26) | class TestCompoundStates:
method test_enter_compound_activates_initial_child (line 27) | async def test_enter_compound_activates_initial_child(self, sm_runner):
method test_transition_within_compound (line 32) | async def test_transition_within_compound(self, sm_runner):
method test_exit_compound_removes_all_descendants (line 40) | async def test_exit_compound_removes_all_descendants(self, sm_runner):
method test_nested_compound_two_levels (line 46) | async def test_nested_compound_two_levels(self, sm_runner):
method test_transition_from_inner_to_outer (line 51) | async def test_transition_from_inner_to_outer(self, sm_runner):
method test_cross_compound_transition (line 57) | async def test_cross_compound_transition(self, sm_runner):
method test_enter_compound_lands_on_initial (line 69) | async def test_enter_compound_lands_on_initial(self, sm_runner):
method test_final_child_fires_done_state (line 76) | async def test_final_child_fires_done_state(self, sm_runner):
method test_multiple_compound_sequential_traversal (line 84) | async def test_multiple_compound_sequential_traversal(self, sm_runner):
method test_entry_exit_action_ordering (line 95) | async def test_entry_exit_action_ordering(self, sm_runner):
method test_callbacks_inside_compound_class (line 122) | async def test_callbacks_inside_compound_class(self, sm_runner):
method test_done_state_inside_compound (line 143) | async def test_done_state_inside_compound(self, sm_runner):
method test_done_invoke_inside_compound (line 166) | async def test_done_invoke_inside_compound(self, sm_runner):
method test_error_execution_inside_compound (line 184) | async def test_error_execution_inside_compound(self, sm_runner):
method test_compound_state_name_attribute (line 207) | def test_compound_state_name_attribute(self):
FILE: tests/test_statechart_delayed.py
class TestDelayedEvents (line 21) | class TestDelayedEvents:
method test_delayed_event_fires_after_delay (line 22) | async def test_delayed_event_fires_after_delay(self, sm_runner):
method test_cancel_delayed_event (line 48) | async def test_cancel_delayed_event(self, sm_runner):
method test_zero_delay_fires_immediately (line 68) | async def test_zero_delay_fires_immediately(self, sm_runner):
method test_delayed_event_on_event_definition (line 81) | async def test_delayed_event_on_event_definition(self, sm_runner):
FILE: tests/test_statechart_donedata.py
class TestDoneData (line 26) | class TestDoneData:
method test_donedata_callable_returns_dict (line 27) | async def test_donedata_callable_returns_dict(self, sm_runner):
method test_donedata_fires_done_state_with_data (line 34) | async def test_donedata_fires_done_state_with_data(self, sm_runner):
method test_donedata_in_nested_compound (line 40) | async def test_donedata_in_nested_compound(self, sm_runner):
method test_donedata_only_on_final_state (line 48) | def test_donedata_only_on_final_state(self):
method test_donedata_with_listener (line 58) | async def test_donedata_with_listener(self, sm_runner):
class TestDoneStateConvention (line 86) | class TestDoneStateConvention:
method test_done_state_convention_with_transition_list (line 87) | async def test_done_state_convention_with_transition_list(self, sm_run...
method test_done_state_convention_with_event_no_explicit_id (line 93) | async def test_done_state_convention_with_event_no_explicit_id(self, s...
method test_done_state_convention_preserves_explicit_id (line 99) | async def test_done_state_convention_preserves_explicit_id(self, sm_ru...
method test_done_state_convention_with_multi_word_state (line 105) | async def test_done_state_convention_with_multi_word_state(self, sm_ru...
FILE: tests/test_statechart_error.py
class TestErrorExecutionStatechart (line 16) | class TestErrorExecutionStatechart:
method test_error_in_compound_child_onentry (line 17) | async def test_error_in_compound_child_onentry(self, sm_runner):
method test_error_in_parallel_region_isolation (line 37) | async def test_error_in_parallel_region_isolation(self, sm_runner):
method test_error_recovery_exits_compound (line 64) | async def test_error_recovery_exits_compound(self, sm_runner):
FILE: tests/test_statechart_eventless.py
class TestEventlessTransitions (line 22) | class TestEventlessTransitions:
method test_eventless_fires_when_condition_met (line 23) | async def test_eventless_fires_when_condition_met(self, sm_runner):
method test_eventless_does_not_fire_when_condition_false (line 33) | async def test_eventless_does_not_fire_when_condition_false(self, sm_r...
method test_eventless_chain_cascades (line 40) | async def test_eventless_chain_cascades(self, sm_runner):
method test_eventless_gradual_condition (line 46) | async def test_eventless_gradual_condition(self, sm_runner):
method test_eventless_in_compound_state (line 58) | async def test_eventless_in_compound_state(self, sm_runner):
method test_eventless_with_in_condition (line 64) | async def test_eventless_with_in_condition(self, sm_runner):
method test_eventless_chain_with_final_triggers_done (line 75) | async def test_eventless_chain_with_final_triggers_done(self, sm_runner):
FILE: tests/test_statechart_history.py
class TestHistoryStates (line 20) | class TestHistoryStates:
method test_shallow_history_remembers_last_child (line 21) | async def test_shallow_history_remembers_last_child(self, sm_runner):
method test_shallow_history_default_on_first_visit (line 34) | async def test_shallow_history_default_on_first_visit(self, sm_runner):
method test_deep_history_remembers_full_descendant (line 42) | async def test_deep_history_remembers_full_descendant(self, sm_runner):
method test_multiple_exits_and_reentries (line 56) | async def test_multiple_exits_and_reentries(self, sm_runner):
method test_history_after_state_change (line 73) | async def test_history_after_state_change(self, sm_runner):
method test_shallow_only_remembers_immediate_child (line 81) | async def test_shallow_only_remembers_immediate_child(self, sm_runner):
method test_history_values_dict_populated (line 94) | async def test_history_values_dict_populated(self, sm_runner):
method test_history_with_default_transition (line 104) | async def test_history_with_default_transition(self, sm_runner):
FILE: tests/test_statechart_in_condition.py
class TestInCondition (line 21) | class TestInCondition:
method test_in_condition_true_enables_transition (line 22) | async def test_in_condition_true_enables_transition(self, sm_runner):
method test_in_condition_false_blocks_transition (line 30) | async def test_in_condition_false_blocks_transition(self, sm_runner):
method test_in_with_parallel_regions (line 36) | async def test_in_with_parallel_regions(self, sm_runner):
method test_in_with_compound_descendant (line 48) | async def test_in_with_compound_descendant(self, sm_runner):
method test_in_combined_with_event (line 60) | async def test_in_combined_with_event(self, sm_runner):
method test_in_with_eventless_transition (line 70) | async def test_in_with_eventless_transition(self, sm_runner):
FILE: tests/test_statechart_parallel.py
class TestParallelStates (line 20) | class TestParallelStates:
method test_parallel_activates_all_regions (line 21) | async def test_parallel_activates_all_regions(self, sm_runner):
method test_independent_transitions_in_regions (line 33) | async def test_independent_transitions_in_regions(self, sm_runner):
method test_configuration_includes_all_active_states (line 42) | async def test_configuration_includes_all_active_states(self, sm_runner):
method test_exit_parallel_exits_all_regions (line 56) | async def test_exit_parallel_exits_all_regions(self, sm_runner):
method test_event_in_one_region_no_effect_on_others (line 63) | async def test_event_in_one_region_no_effect_on_others(self, sm_runner):
method test_parallel_with_compound_children (line 72) | async def test_parallel_with_compound_children(self, sm_runner):
method test_current_state_value_set_comparison (line 79) | async def test_current_state_value_set_comparison(self, sm_runner):
method test_parallel_done_when_all_regions_final (line 94) | async def test_parallel_done_when_all_regions_final(self, sm_runner):
method test_parallel_not_done_when_one_region_final (line 105) | async def test_parallel_not_done_when_one_region_final(self, sm_runner):
method test_transition_within_compound_inside_parallel (line 113) | async def test_transition_within_compound_inside_parallel(self, sm_run...
method test_top_level_parallel_terminates_when_all_children_final (line 122) | async def test_top_level_parallel_terminates_when_all_children_final(s...
method test_top_level_parallel_done_state_fires_before_termination (line 133) | async def test_top_level_parallel_done_state_fires_before_termination(...
method test_top_level_parallel_not_terminated_when_one_region_pending (line 142) | async def test_top_level_parallel_not_terminated_when_one_region_pendi...
FILE: tests/test_statemachine.py
function test_machine_repr (line 11) | def test_machine_repr(campaign_machine):
function test_machine_should_be_at_start_state (line 20) | def test_machine_should_be_at_start_state(campaign_machine):
function test_machine_should_only_allow_only_one_initial_state (line 39) | def test_machine_should_only_allow_only_one_initial_state():
function test_machine_should_activate_initial_state (line 56) | def test_machine_should_activate_initial_state(mocker):
function test_machine_should_not_allow_transitions_from_final_state (line 89) | def test_machine_should_not_allow_transitions_from_final_state():
function test_should_change_state (line 104) | def test_should_change_state(campaign_machine):
function test_should_run_a_transition_that_keeps_the_state (line 117) | def test_should_run_a_transition_that_keeps_the_state(campaign_machine):
function test_should_change_state_with_multiple_machine_instances (line 137) | def test_should_change_state_with_multiple_machine_instances(campaign_ma...
function test_machine_should_list_allowed_events_in_the_current_state (line 158) | def test_machine_should_list_allowed_events_in_the_current_state(campaig...
function test_machine_should_run_a_transition_by_his_key (line 176) | def test_machine_should_run_a_transition_by_his_key(campaign_machine):
function test_machine_should_use_and_model_attr_other_than_state (line 191) | def test_machine_should_use_and_model_attr_other_than_state(campaign_mac...
function test_cant_assign_an_invalid_state_directly (line 205) | def test_cant_assign_an_invalid_state_directly(campaign_machine):
function test_should_allow_validate_data_for_transition (line 211) | def test_should_allow_validate_data_for_transition(campaign_machine_with...
function test_should_check_if_is_in_status (line 223) | def test_should_check_if_is_in_status(campaign_machine):
function test_defined_value_must_be_assigned_to_models (line 244) | def test_defined_value_must_be_assigned_to_models(campaign_machine_with_...
function test_state_machine_without_model (line 255) | def test_state_machine_without_model(campaign_machine):
function test_state_machine_with_a_start_value (line 277) | def test_state_machine_with_a_start_value(request, model, machine_name, ...
function test_state_machine_with_a_invalid_start_value (line 294) | def test_state_machine_with_a_invalid_start_value(request, model, machin...
function test_state_machine_with_a_invalid_model_state_value (line 300) | def test_state_machine_with_a_invalid_model_state_value(request, campaig...
function test_should_not_create_instance_of_abstract_machine (line 309) | def test_should_not_create_instance_of_abstract_machine():
function test_should_not_create_instance_of_machine_without_states (line 319) | def test_should_not_create_instance_of_machine_without_states():
function test_should_not_create_instance_of_machine_without_transitions (line 329) | def test_should_not_create_instance_of_machine_without_transitions():
function test_should_not_create_disconnected_machine (line 338) | def test_should_not_create_disconnected_machine():
function test_should_not_create_big_disconnected_machine (line 355) | def test_should_not_create_big_disconnected_machine():
function test_disconnected_validation_bypassed_by_flag (line 377) | def test_disconnected_validation_bypassed_by_flag():
function test_parallel_states_reachable_without_disabling_flag (line 392) | def test_parallel_states_reachable_without_disabling_flag():
function test_compound_substates_reachable_without_disabling_flag (line 411) | def test_compound_substates_reachable_without_disabling_flag():
function test_history_state_reachable_without_disabling_flag (line 428) | def test_history_state_reachable_without_disabling_flag():
function test_state_value_is_correct (line 447) | def test_state_value_is_correct():
function test_final_states (line 462) | def test_final_states(campaign_machine_with_final_state):
function test_should_not_override_states_properties (line 470) | def test_should_not_override_states_properties(campaign_machine):
class TestWarnings (line 478) | class TestWarnings:
method test_should_warn_if_model_already_has_attribute_and_binding_is_enabled (line 479) | def test_should_warn_if_model_already_has_attribute_and_binding_is_ena...
method test_should_raise_if_thereis_a_trap_state (line 506) | def test_should_raise_if_thereis_a_trap_state(self):
method test_should_raise_if_no_path_to_a_final_state (line 518) | def test_should_raise_if_no_path_to_a_final_state(self):
function test_model_with_custom_bool_is_not_replaced (line 534) | def test_model_with_custom_bool_is_not_replaced(campaign_machine):
function test_abstract_sm_no_states (line 549) | def test_abstract_sm_no_states():
function test_raise_sends_internal_event (line 558) | def test_raise_sends_internal_event():
function test_configuration_values_returns_ordered_set (line 572) | def test_configuration_values_returns_ordered_set():
function test_states_getitem (line 586) | def test_states_getitem():
function test_multiple_initial_states_raises (line 599) | def test_multiple_initial_states_raises():
function test_configuration_values_returns_orderedset_when_compound_state (line 610) | def test_configuration_values_returns_orderedset_when_compound_state():
class TestEnabledEvents (line 633) | class TestEnabledEvents:
method test_no_conditions_same_as_allowed_events (line 634) | def test_no_conditions_same_as_allowed_events(self, campaign_machine):
method test_passing_condition_returns_event (line 639) | def test_passing_condition_returns_event(self):
method test_failing_condition_excludes_event (line 652) | def test_failing_condition_excludes_event(self):
method test_multiple_transitions_one_passes (line 665) | def test_multiple_transitions_one_passes(self):
method test_duplicate_event_across_transitions_deduplicated (line 684) | def test_duplicate_event_across_transitions_deduplicated(self):
method test_final_state_returns_empty (line 705) | def test_final_state_returns_empty(self, campaign_machine):
method test_kwargs_forwarded_to_conditions (line 711) | def test_kwargs_forwarded_to_conditions(self):
method test_condition_exception_treated_as_enabled (line 725) | def test_condition_exception_treated_as_enabled(self):
method test_mixed_enabled_and_disabled (line 740) | def test_mixed_enabled_and_disabled(self):
method test_unless_condition (line 758) | def test_unless_condition(self):
method test_unless_condition_passes (line 771) | def test_unless_condition_passes(self):
class TestInvalidStateValueNonNone (line 785) | class TestInvalidStateValueNonNone:
method test_invalid_non_none_state_value (line 788) | def test_invalid_non_none_state_value(self):
class TestInitKwargsPropagation (line 805) | class TestInitKwargsPropagation:
method test_kwargs_available_in_on_enter_initial (line 808) | async def test_kwargs_available_in_on_enter_initial(self, sm_runner):
method test_kwargs_flow_through_eventless_transitions (line 820) | async def test_kwargs_flow_through_eventless_transitions(self, sm_runn...
method test_no_kwargs_still_works (line 836) | async def test_no_kwargs_still_works(self, sm_runner):
method test_multiple_kwargs (line 848) | async def test_multiple_kwargs(self, sm_runner):
method test_kwargs_in_invoke_handler (line 862) | async def test_kwargs_in_invoke_handler(self, sm_runner):
FILE: tests/test_statemachine_bounded_transitions.py
function event_mock (line 12) | def event_mock():
function state_machine (line 17) | def state_machine(event_mock):
function test_run_transition_pass_arguments_to_sub_transitions (line 42) | def test_run_transition_pass_arguments_to_sub_transitions(
FILE: tests/test_statemachine_compat.py
class TestStateMachineDefaults (line 27) | class TestStateMachineDefaults:
method test_allow_event_without_transition (line 30) | def test_allow_event_without_transition(self):
method test_enable_self_transition_entries (line 33) | def test_enable_self_transition_entries(self):
method test_atomic_configuration_update (line 36) | def test_atomic_configuration_update(self):
method test_catch_errors_as_events (line 39) | def test_catch_errors_as_events(self):
class TestStateMachineSmoke (line 48) | class TestStateMachineSmoke:
method test_create_send_and_check_state (line 51) | def test_create_send_and_check_state(self):
method test_final_state_terminates (line 68) | def test_final_state_terminates(self):
class TestTransitionNotAllowed (line 85) | class TestTransitionNotAllowed:
method sm (line 89) | def sm(self):
method test_invalid_event_raises (line 98) | def test_invalid_event_raises(self, sm):
method test_event_not_available_in_current_state (line 102) | def test_event_not_available_in_current_state(self, sm):
method test_condition_blocks_transition (line 107) | def test
Condensed preview — 518 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,825K chars).
[
{
"path": ".git-blame-ignore-revs",
"chars": 164,
"preview": "37fcf9818178587635fffe1bb67a9fd5024a0a45\n345d82390af35d5d70ddd39c612faa4a64b11080\nd7738e9ad0a3e50bc5c87d4a75c436fb771c96"
},
{
"path": ".github/FUNDING.yml",
"chars": 17,
"preview": "github: fgmacedo\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 531,
"preview": "* Python State Machine version:\n* Python version:\n* Operating System:\n\n### Description\n\nDescribe what you were trying to"
},
{
"path": ".github/workflows/python-package.yml",
"chars": 2120,
"preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
},
{
"path": ".github/workflows/release.yml",
"chars": 1445,
"preview": "on:\n push:\n tags: [ 'v?*.*.*' ]\nname: release\n\njobs:\n release-build:\n name: Build release artifacts\n runs-on:"
},
{
"path": ".gitignore",
"chars": 1078,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".pre-commit-config.yaml",
"chars": 1355,
"preview": "repos:\n- repo: https://github.com/pre-commit/pre-commit-hooks\n rev: v4.6.0\n hooks:\n - id: check-yaml\n - "
},
{
"path": ".readthedocs.yaml",
"chars": 554,
"preview": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
},
{
"path": "AGENTS.md",
"chars": 13222,
"preview": "# python-statemachine\n\nPython Finite State Machines made easy.\n\n## Project overview\n\nA library for building finite state"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "\nMIT License\n\nCopyright (c) 2017, Fernando Macedo\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 11096,
"preview": "# Python StateMachine\n\n[](https://pypi.python.org/pypi/pyt"
},
{
"path": "conftest.py",
"chars": 1406,
"preview": "import shutil\nimport sys\n\nimport pytest\n\n\n@pytest.fixture(autouse=True, scope=\"session\")\ndef add_doctest_context(doctest"
},
{
"path": "contributing.md",
"chars": 54,
"preview": "Please see [docs/contributing.md](docs/contributing).\n"
},
{
"path": "docs/_static/custom_machine.css",
"chars": 1403,
"preview": "/* div.sphx-glr-download {\n height: 0px;\n visibility: hidden;\n} */\n\n@media only screen and (min-width: 650px) {\n\n "
},
{
"path": "docs/actions.md",
"chars": 18228,
"preview": "(actions)=\n\n# Actions\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, "
},
{
"path": "docs/api.md",
"chars": 2559,
"preview": "# API\n\n## StateChart\n\n```{versionadded} 3.0.0\n```\n\n```{eval-rst}\n.. autoclass:: statemachine.statemachine.StateChart\n "
},
{
"path": "docs/async.md",
"chars": 5644,
"preview": "(async)=\n# Async support\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransition"
},
{
"path": "docs/authors.md",
"chars": 564,
"preview": "# Credits\n\n## Development Lead\n\n* [Fernando Macedo](mailto:fgmacedo@gmail.com)\n\n## Contributors\n\n* [Guilherme Nepomuceno"
},
{
"path": "docs/behaviour.md",
"chars": 6288,
"preview": "(behaviour)=\n(statecharts)=\n\n# Behaviour\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how st"
},
{
"path": "docs/concepts.md",
"chars": 5397,
"preview": "(concepts)=\n\n# Core concepts\n\nA statechart organizes behavior around **states**, **transitions**, and\n**events**. Togeth"
},
{
"path": "docs/conf.py",
"chars": 9233,
"preview": "#!/usr/bin/env python\n#\n# statemachine documentation build configuration file, created by\n# sphinx-quickstart on Tue Jul"
},
{
"path": "docs/contributing.md",
"chars": 9498,
"preview": "# Contributing\n\n* <a class=\"github-button\" href=\"https://github.com/fgmacedo/python-statemachine\" data-icon=\"octicon-sta"
},
{
"path": "docs/diagram.md",
"chars": 22178,
"preview": "(diagram)=\n(diagrams)=\n# Diagrams\n\nYou can generate visual diagrams from any\n{class}`~statemachine.statemachine.StateCha"
},
{
"path": "docs/error_handling.md",
"chars": 7682,
"preview": "\n(error-handling)=\n# Error handling\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,"
},
{
"path": "docs/events.md",
"chars": 10397,
"preview": "(events)=\n(event)=\n# Events\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransit"
},
{
"path": "docs/guards.md",
"chars": 11315,
"preview": "(validators-and-guards)=\n(validators and guards)=\n\n# Conditions\n\n```{seealso}\nNew to statecharts? See [](concepts.md) fo"
},
{
"path": "docs/how-to/coming_from_state_pattern.md",
"chars": 12525,
"preview": "(coming-from-state-pattern)=\n\n# Coming from the State Pattern\n\nThis guide is for developers familiar with the classic **"
},
{
"path": "docs/how-to/coming_from_transitions.md",
"chars": 29603,
"preview": "(coming-from-transitions)=\n\n# Coming from pytransitions\n\nThis guide helps users of the [*transitions*](https://github.co"
},
{
"path": "docs/index.md",
"chars": 911,
"preview": "```{include} ../README.md\n```\n\n---\n\n```{toctree}\n:caption: Getting started\n:maxdepth: 2\n:hidden:\n\ninstallation\ntutorial\n"
},
{
"path": "docs/installation.md",
"chars": 1222,
"preview": "# Installation\n\n\n## Latest release\n\nTo install using [uv](https://docs.astral.sh/uv):\n\n```shell\nuv add python-statemachi"
},
{
"path": "docs/integrations.md",
"chars": 4956,
"preview": "\n# Integrations\n\n(machinemixin)=\n## MachineMixin\n\n{ref}`Domain models` can inherit from `MachineMixin` to automatically "
},
{
"path": "docs/invoke.md",
"chars": 14776,
"preview": "(invoke)=\n# Invoke\n\nInvoke lets a state spawn external work — API calls, file I/O, child state machines —\nwhen it is ent"
},
{
"path": "docs/listeners.md",
"chars": 7753,
"preview": "(observers)=\n(listeners)=\n\n# Listeners\n\nA **listener** is an external object that observes a state machine's lifecycle\nw"
},
{
"path": "docs/models.md",
"chars": 2199,
"preview": "(domain models)=\n(models)=\n# Domain models\n\nIf you need to use any other object to persist the current state, or you're "
},
{
"path": "docs/processing_model.md",
"chars": 12927,
"preview": "(processing-model)=\n(processing model)=\n\n# Processing model\n\nThe engine processes events following the\n[SCXML](https://w"
},
{
"path": "docs/releases/0.1.0.md",
"chars": 61,
"preview": "# StateMachine 0.1.0\n\n*2017-03-21*\n\n* First release on PyPI.\n"
},
{
"path": "docs/releases/0.2.0.md",
"chars": 165,
"preview": "# StateMachine 0.2.0\n\n*2017-03-22*\n\n\n- ``State`` can hold a value that will be assigned to the model as the state value."
},
{
"path": "docs/releases/0.3.0.md",
"chars": 111,
"preview": "# StateMachine 0.3.0\n\n*2017-03-22*\n\n\n- README getting started section.\n- Tests to state machine without model.\n"
},
{
"path": "docs/releases/0.4.2.md",
"chars": 269,
"preview": "# StateMachine 0.4.2\n\n*2017-07-10*\n\n## Python compatibility on 0.4.2\n\n- Python 3.6 support.\n- Drop official support for "
},
{
"path": "docs/releases/0.5.0.md",
"chars": 221,
"preview": "# StateMachine 0.5.0\n\n*2017-07-13*\n\n- Custom exceptions.\n- Duplicated definition of ``on_execute`` callback is not allow"
},
{
"path": "docs/releases/0.5.1.md",
"chars": 165,
"preview": "# StateMachine 0.5.1\n\n*2017-07-24*\n\n\n- Fix bug on ``CombinedTransition._can_run`` not allowing transitions to run if the"
},
{
"path": "docs/releases/0.6.0.md",
"chars": 170,
"preview": "# StateMachine 0.6.0\n\n*2017-08-25*\n\n\n- Auto-discovering `statemachine`/`statemachines` under a Django project when\n the"
},
{
"path": "docs/releases/0.6.1.md",
"chars": 58,
"preview": "# StateMachine 0.6.1\n\n*2017-08-25*\n\n\n- Fix deploy issues.\n"
},
{
"path": "docs/releases/0.6.2.md",
"chars": 51,
"preview": "# StateMachine 0.6.2\n\n*2017-08-25*\n\n\n- Fix README.\n"
},
{
"path": "docs/releases/0.7.0.md",
"chars": 102,
"preview": "# StateMachine 0.7.0\n\n*2018-04-01*\n\n\n- New event callbacks: `on_enter_<state>` and `on_exit_<state>`.\n"
},
{
"path": "docs/releases/0.7.1.md",
"chars": 119,
"preview": "# StateMachine 0.7.1\n\n*2019-01-18*\n\n\n- Fix Django integration for registry loading statemachine modules on Django1.7+.\n"
},
{
"path": "docs/releases/0.8.0.md",
"chars": 831,
"preview": "# StateMachine 0.8.0\n\n*2020-01-23*\n\n## Python compatibility on 0.8.0\n\n- Add support for Python 3.7 and 3.8 (adding to te"
},
{
"path": "docs/releases/0.9.0.md",
"chars": 1826,
"preview": "# StateMachine 0.9.0\n\n*2022-12-21*\n\n## Python compatibility 0.9.0\n\nStateMachine 0.9 supports Python 2.7, 3.5, 3.6, 3.7, "
},
{
"path": "docs/releases/1.0.0.md",
"chars": 149,
"preview": "# StateMachine 1.0.0\n\n*January 11, 2023*\n\nThis release tag was replaced by [1.0.1](1.0.1.md) due to an error on the meta"
},
{
"path": "docs/releases/1.0.1.md",
"chars": 7830,
"preview": "# StateMachine 1.0.1\n\n*January 11, 2023*\n\nWelcome to StateMachine 1.0.1!\n\nThis version is a huge refactoring adding a lo"
},
{
"path": "docs/releases/1.0.2.md",
"chars": 340,
"preview": "# StateMachine 1.0.2\n\n*January 12, 2023*\n\n\nStateMachine 1.0.2 fixes a regression bug blocking the library usage on\nPytho"
},
{
"path": "docs/releases/1.0.3.md",
"chars": 453,
"preview": "# StateMachine 1.0.3\n\n*January 27, 2023*\n\n\nStateMachine 1.0.3 fixes a bug between {ref}`State` and {ref}`transition` ins"
},
{
"path": "docs/releases/2.0.0.md",
"chars": 10793,
"preview": "# StateMachine 2.0.0\n\n*March 5, 2023*\n\nWelcome to StateMachine 2.0.0!\n\nThis version is the first to take advantage of th"
},
{
"path": "docs/releases/2.1.0.md",
"chars": 1029,
"preview": "# StateMachine 2.1.0\n\n*June 11, 2023*\n\n## What's new in 2.1.0\n\n### Added support for declaring states using Enum\n\nGiven "
},
{
"path": "docs/releases/2.1.1.md",
"chars": 339,
"preview": "# StateMachine 2.1.1\n\n*August 3, 2023*\n\n\n## Bugfixes in 2.1.1\n\n- Fixes [#391](https://github.com/fgmacedo/python-statema"
},
{
"path": "docs/releases/2.1.2.md",
"chars": 677,
"preview": "# StateMachine 2.1.2\n\n*October 6, 2023*\n\nThis release improves the setup performance of the library by a 10x factor, wi"
},
{
"path": "docs/releases/2.2.0.md",
"chars": 2729,
"preview": "# StateMachine 2.2.0\n\n*May 6, 2024*\n\n## What's new in 2.2.0\n\nIn this release, we conducted a general cleanup and refact"
},
{
"path": "docs/releases/2.3.0.md",
"chars": 1381,
"preview": "# StateMachine 2.3.0\n\n*June 7, 2024*\n\n## What's new in 2.3.0\n\nThis release has a high expected feature, we're adding [as"
},
{
"path": "docs/releases/2.3.1.md",
"chars": 247,
"preview": "# StateMachine 2.3.1\n\n*June 10, 2024*\n\n\n## Bugfixes in 2.3.1\n\n- Fixes [#443](https://github.com/fgmacedo/python-statemac"
},
{
"path": "docs/releases/2.3.2.md",
"chars": 3466,
"preview": "# StateMachine 2.3.2\n\n*July 01, 2024*\n\n## What's new in 2.3.2\n\nObservers are now rebranded to {ref}`listeners`. With exp"
},
{
"path": "docs/releases/2.3.3.md",
"chars": 497,
"preview": "# StateMachine 2.3.3\n\n*July 3, 2024*\n\n\n## Bugfixes in 2.3.3\n\n- Fixes [#457](https://github.com/fgmacedo/python-statemach"
},
{
"path": "docs/releases/2.3.4.md",
"chars": 224,
"preview": "# StateMachine 2.3.4\n\n*July 11, 2024*\n\n\n## Bugfixes in 2.3.4\n\n- Fixes [#465](https://github.com/fgmacedo/python-statemac"
},
{
"path": "docs/releases/2.3.5.md",
"chars": 432,
"preview": "# StateMachine 2.3.5\n\n*September 9, 2024*\n\n### Python compatibility 2.3.5\n\nAdded Python 3.13 on the test matrix. StateMa"
},
{
"path": "docs/releases/2.3.6.md",
"chars": 339,
"preview": "# StateMachine 2.3.6\n\n*September 11, 2024*\n\n\n## Bugfixes in 2.3.6\n\n- Fixes [#474](https://github.com/fgmacedo/python-sta"
},
{
"path": "docs/releases/2.4.0.md",
"chars": 3046,
"preview": "# StateMachine 2.4.0\n\n*November 5, 2024*\n\n## What's new in 2.4.0\n\nThis release introduces powerful new features for the "
},
{
"path": "docs/releases/2.5.0.md",
"chars": 4879,
"preview": "# StateMachine 2.5.0\n\n*December 3, 2024*\n\n## What's new in 2.5.0\n\nThis release improves {ref}`Condition expressions` and"
},
{
"path": "docs/releases/2.6.0.md",
"chars": 4572,
"preview": "# StateMachine 2.6.0\n\n*February 2026*\n\n## What's new in 2.6.0\n\nThis release adds the {ref}`StateMachine.enabled_events` "
},
{
"path": "docs/releases/3.0.0.md",
"chars": 22489,
"preview": "# StateMachine 3.0.0\n\n*February 24, 2026*\n\n```{seealso}\nUpgrading from 2.x? See [](upgrade_2x_to_3.md) for a step-by-ste"
},
{
"path": "docs/releases/3.1.0.md",
"chars": 5861,
"preview": "# StateChart 3.1.0\n\n*Not released yet*\n\n## What's new in 3.1.0\n\n### Text representations with `format()`\n\nState machines"
},
{
"path": "docs/releases/index.md",
"chars": 863,
"preview": "# Release notes\n\nVersions follow [Semantic Versioning](https://semver.org/) (`<major>.<minor>.<patch>`).\n\nBackward incom"
},
{
"path": "docs/releases/upgrade_2x_to_3.md",
"chars": 14454,
"preview": "# Upgrading from 2.x to 3.0\n\nThis guide covers all backward-incompatible changes in python-statemachine 3.0 and provides"
},
{
"path": "docs/statechart.md",
"chars": 7238,
"preview": "(statechart-instance)=\n(statechart)=\n(statemachine)=\n\n# StateChart\n\n```{seealso}\nNew to statecharts? See [](concepts.md)"
},
{
"path": "docs/states.md",
"chars": 5887,
"preview": "(states)=\n(state)=\n\n# States\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransi"
},
{
"path": "docs/timeout.md",
"chars": 2836,
"preview": "(timeout)=\n# State timeouts\n\nA common need is preventing a state machine from getting stuck — for example,\na \"waiting fo"
},
{
"path": "docs/transitions.md",
"chars": 10712,
"preview": "(transitions)=\n(transition)=\n\n# Transitions\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how"
},
{
"path": "docs/tutorial.md",
"chars": 21526,
"preview": "\n# Tutorial\n\nThis tutorial walks you through python-statemachine from your first flat state\nmachine all the way to full "
},
{
"path": "docs/validations.md",
"chars": 8094,
"preview": "(validations)=\n\n# Validations\n\nThe library validates your statechart structure at two stages: **class\ndefinition time** "
},
{
"path": "docs/weighted_transitions.md",
"chars": 6847,
"preview": "(weighted-transitions)=\n\n# Weighted transitions\n\n```{seealso}\nSee {ref}`conditions` for how the engine selects transitio"
},
{
"path": "pyproject.toml",
"chars": 6094,
"preview": "[project]\nname = \"python-statemachine\"\nversion = \"3.1.0\"\ndescription = \"Python Finite State Machines made easy.\"\nauthors"
},
{
"path": "statemachine/__init__.py",
"chars": 445,
"preview": "from .event import Event\nfrom .state import HistoryState\nfrom .state import HistoryType\nfrom .state import State\nfrom .s"
},
{
"path": "statemachine/callbacks.py",
"chars": 14644,
"preview": "import asyncio\nfrom bisect import insort\nfrom collections import defaultdict\nfrom collections import deque\nfrom enum imp"
},
{
"path": "statemachine/configuration.py",
"chars": 5447,
"preview": "from typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Dict\nfrom typing import Mapping\nfrom typing i"
},
{
"path": "statemachine/contrib/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "statemachine/contrib/diagram/__init__.py",
"chars": 7530,
"preview": "import importlib\nfrom urllib.parse import quote\nfrom urllib.request import urlopen\n\nfrom .extract import extract\nfrom .f"
},
{
"path": "statemachine/contrib/diagram/__main__.py",
"chars": 80,
"preview": "import sys\n\nfrom . import main\n\nif __name__ == \"__main__\":\n sys.exit(main())\n"
},
{
"path": "statemachine/contrib/diagram/extract.py",
"chars": 9883,
"preview": "from typing import TYPE_CHECKING\nfrom typing import List\nfrom typing import Set\nfrom typing import Union\n\nfrom .model im"
},
{
"path": "statemachine/contrib/diagram/formatter.py",
"chars": 4754,
"preview": "\"\"\"Unified facade for rendering state machines in multiple text formats.\n\nThe :class:`Formatter` class provides a decora"
},
{
"path": "statemachine/contrib/diagram/model.py",
"chars": 1498,
"preview": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom enum import Enum\nfrom typing import List\nfrom typin"
},
{
"path": "statemachine/contrib/diagram/renderers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "statemachine/contrib/diagram/renderers/dot.py",
"chars": 19870,
"preview": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom typing import Dict\nfrom typing import List\nfrom typ"
},
{
"path": "statemachine/contrib/diagram/renderers/mermaid.py",
"chars": 13518,
"preview": "from dataclasses import dataclass\nfrom typing import Dict\nfrom typing import List\nfrom typing import Optional\nfrom typin"
},
{
"path": "statemachine/contrib/diagram/renderers/table.py",
"chars": 3734,
"preview": "from typing import List\n\nfrom ..model import DiagramGraph\nfrom ..model import DiagramState\nfrom ..model import DiagramTr"
},
{
"path": "statemachine/contrib/diagram/sphinx_ext.py",
"chars": 10336,
"preview": "\"\"\"Sphinx extension providing the ``statemachine-diagram`` directive.\n\nUsage in MyST Markdown::\n\n ```{statemachine-di"
},
{
"path": "statemachine/contrib/timeout.py",
"chars": 2249,
"preview": "\"\"\"Timeout helper for state invocations.\n\nProvides a ``timeout()`` function that returns an :class:`~statemachine.invoke"
},
{
"path": "statemachine/contrib/weighted.py",
"chars": 6862,
"preview": "import random\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfr"
},
{
"path": "statemachine/dispatcher.py",
"chars": 7841,
"preview": "from dataclasses import dataclass\nfrom functools import partial\nfrom functools import reduce\nfrom operator import attrge"
},
{
"path": "statemachine/engines/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "statemachine/engines/async_.py",
"chars": 22544,
"preview": "import asyncio\nimport contextvars\nfrom itertools import chain\nfrom time import time\nfrom typing import TYPE_CHECKING\nfro"
},
{
"path": "statemachine/engines/base.py",
"chars": 37959,
"preview": "import logging\nfrom dataclasses import dataclass\nfrom dataclasses import field\nfrom itertools import chain\nfrom queue im"
},
{
"path": "statemachine/engines/sync.py",
"chars": 9153,
"preview": "from time import sleep\nfrom time import time\nfrom typing import TYPE_CHECKING\n\nfrom statemachine.event import BoundEvent"
},
{
"path": "statemachine/event.py",
"chars": 7665,
"preview": "from typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import List\nfrom typing import cast\nfrom uuid import"
},
{
"path": "statemachine/event_data.py",
"chars": 3111,
"preview": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom time import time\nfrom typing import TYPE_CHECKING\nf"
},
{
"path": "statemachine/events.py",
"chars": 1212,
"preview": "from .event import Event\nfrom .utils import ensure_iterable\n\n\nclass Events:\n \"\"\"A collection of event names.\"\"\"\n\n "
},
{
"path": "statemachine/exceptions.py",
"chars": 1305,
"preview": "from typing import TYPE_CHECKING\nfrom typing import MutableSet\n\nfrom .i18n import _\n\nif TYPE_CHECKING:\n from .event i"
},
{
"path": "statemachine/factory.py",
"chars": 14706,
"preview": "import re\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Optional\nfrom typing"
},
{
"path": "statemachine/graph.py",
"chars": 1981,
"preview": "from collections import deque\nfrom typing import TYPE_CHECKING\nfrom typing import Iterable\nfrom typing import MutableSet"
},
{
"path": "statemachine/i18n.py",
"chars": 362,
"preview": "import gettext\nfrom pathlib import Path\n\nscript_dir = Path(__file__).resolve().parent\nlocale_dir = script_dir / \"locale\""
},
{
"path": "statemachine/invoke.py",
"chars": 23494,
"preview": "\"\"\"Invoke support for StateCharts.\n\nInvoke lets a state spawn external work (API calls, file I/O, child state machines)\n"
},
{
"path": "statemachine/io/__init__.py",
"chars": 8739,
"preview": "from typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Mapping\nfrom typing import Pro"
},
{
"path": "statemachine/io/scxml/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "statemachine/io/scxml/actions.py",
"chars": 22475,
"preview": "import html\nimport logging\nimport re\nfrom dataclasses import dataclass\nfrom itertools import chain\nfrom typing import An"
},
{
"path": "statemachine/io/scxml/invoke.py",
"chars": 8437,
"preview": "\"\"\"SCXML-specific invoke handler.\n\nImplements the IInvoke protocol by resolving child SCXML content (inline or\nvia src/s"
},
{
"path": "statemachine/io/scxml/parser.py",
"chars": 16580,
"preview": "import re\nimport xml.etree.ElementTree as ET\nfrom typing import List\nfrom typing import Literal\nfrom typing import Set\nf"
},
{
"path": "statemachine/io/scxml/processor.py",
"chars": 9461,
"preview": "import os\nfrom contextlib import contextmanager\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing i"
},
{
"path": "statemachine/io/scxml/schema.py",
"chars": 3927,
"preview": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom typing import Dict\nfrom typing import List\nfrom typ"
},
{
"path": "statemachine/locale/en/LC_MESSAGES/statemachine.po",
"chars": 4581,
"preview": "# This file is distributed under the same license as the project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid"
},
{
"path": "statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po",
"chars": 4859,
"preview": "# This file is distributed under the same license as the project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid"
},
{
"path": "statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po",
"chars": 4972,
"preview": "# This file is distributed under the same license as the project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid"
},
{
"path": "statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po",
"chars": 3851,
"preview": "# This file is distributed under the same license as the project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid"
},
{
"path": "statemachine/mixins.py",
"chars": 1706,
"preview": "from . import registry\nfrom .i18n import _\n\n\nclass MachineMixin:\n \"\"\"This mixing allows a model to automatically inst"
},
{
"path": "statemachine/model.py",
"chars": 211,
"preview": "class Model:\n def __init__(self):\n self.state = None\n \"\"\"Holds the current :ref:`state` value of the :r"
},
{
"path": "statemachine/orderedset.py",
"chars": 2543,
"preview": "import itertools\nfrom typing import Iterable\nfrom typing import Iterator\nfrom typing import MutableSet\nfrom typing impor"
},
{
"path": "statemachine/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "statemachine/registry.py",
"chars": 756,
"preview": "from typing import List\nfrom typing import Optional\n\nfrom .utils import qualname\n\ntry:\n from django.utils.module_load"
},
{
"path": "statemachine/signature.py",
"chars": 9922,
"preview": "from __future__ import annotations\n\nfrom functools import partial\nfrom inspect import BoundArguments\nfrom inspect import"
},
{
"path": "statemachine/spec_parser.py",
"chars": 8307,
"preview": "import ast\nimport operator\nimport re\nfrom functools import reduce\nfrom inspect import isawaitable\nfrom typing import Cal"
},
{
"path": "statemachine/state.py",
"chars": 15138,
"preview": "from enum import Enum\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Generator\nfrom typing i"
},
{
"path": "statemachine/statemachine.py",
"chars": 19214,
"preview": "import warnings\nfrom inspect import isawaitable\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing impo"
},
{
"path": "statemachine/states.py",
"chars": 4934,
"preview": "from enum import Enum\nfrom typing import Dict # deprecated since 3.9: https://peps.python.org/pep-0585/\nfrom typing imp"
},
{
"path": "statemachine/transition.py",
"chars": 6936,
"preview": "from copy import deepcopy\nfrom typing import TYPE_CHECKING\nfrom typing import List\n\nfrom .callbacks import CallbackGroup"
},
{
"path": "statemachine/transition_list.py",
"chars": 4376,
"preview": "from typing import TYPE_CHECKING\nfrom typing import Iterable\nfrom typing import List\n\nfrom .callbacks import CallbackGro"
},
{
"path": "statemachine/transition_mixin.py",
"chars": 2993,
"preview": "from typing import Any\nfrom typing import Callable\nfrom typing import TypeVar\n\nfrom .callbacks import CallbackGroup\nfrom"
},
{
"path": "statemachine/utils.py",
"chars": 1527,
"preview": "import asyncio\nimport re\nimport threading\nfrom typing import Any\n\n_SEPARATOR_RE = re.compile(r\"[_.]\")\n\n_cached_loop = th"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/conftest.py",
"chars": 7225,
"preview": "import asyncio\nimport threading\nimport time\nfrom datetime import datetime\n\nimport pytest\n\n\ndef pytest_addoption(parser):"
},
{
"path": "tests/django_project/app.py",
"chars": 195,
"preview": "from django.http import HttpResponse\nfrom django.urls import re_path\n\n\ndef home(request):\n return HttpResponse(\"WE LO"
},
{
"path": "tests/django_project/core/__init__,.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/django_project/core/settings.py",
"chars": 548,
"preview": "from pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = True\n\nROOT_URLCONF = \"app\"\n\nINSTALL"
},
{
"path": "tests/django_project/core/wsgi.py",
"chars": 388,
"preview": "\"\"\"\nWSGI config for project project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFo"
},
{
"path": "tests/django_project/manage.py",
"chars": 661,
"preview": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\n\nimport os\nimport sys\n\n\ndef main():\n"
},
{
"path": "tests/django_project/workflow/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/django_project/workflow/apps.py",
"chars": 148,
"preview": "from django.apps import AppConfig\n\n\nclass WorfklowConfig(AppConfig):\n default_auto_field = \"django.db.models.BigAutoF"
},
{
"path": "tests/django_project/workflow/models.py",
"chars": 660,
"preview": "from django.contrib.auth import get_user_model\nfrom django.db import models\nfrom statemachine.mixins import MachineMixin"
},
{
"path": "tests/django_project/workflow/statemachines.py",
"chars": 484,
"preview": "from statemachine.states import States\n\nfrom statemachine import StateChart\n\nfrom .models import WorkflowSteps\n\n\nclass W"
},
{
"path": "tests/django_project/workflow/tests.py",
"chars": 1672,
"preview": "import pytest\nfrom statemachine.exceptions import TransitionNotAllowed\n\nfrom workflow.models import WorkflowSteps\nfrom w"
},
{
"path": "tests/examples/README.rst",
"chars": 207,
"preview": "Examples\n--------\n\nBelow is a gallery of ``StateMachine`` examples.\n\n.. only:: comment\n\n sphinx-gallery does not supp"
},
{
"path": "tests/examples/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/examples/ai_shell_machine.py",
"chars": 18515,
"preview": "\"\"\"\nAI Shell -- coding assistant\n=============================\n\nA feature-rich coding assistant powered by python-statem"
},
{
"path": "tests/examples/air_conditioner_machine.py",
"chars": 1867,
"preview": "\"\"\"\nAir Conditioner machine\n=======================\n\nA StateChart that exercises reading from a stream of events.\n\n\"\"\"\n\n"
},
{
"path": "tests/examples/all_actions_machine.py",
"chars": 4705,
"preview": "\"\"\"\nAll actions machine\n===================\n\nA StateChart that exercises all possible :ref:`Actions` and :ref:`Guards`.\n"
},
{
"path": "tests/examples/async_guess_the_number_machine.py",
"chars": 5195,
"preview": "\"\"\"\nAsync guess the number machine\n==============================\n\nAn async example of StateChart for the well known gam"
},
{
"path": "tests/examples/async_without_loop_machine.py",
"chars": 905,
"preview": "\"\"\"\nAsync without external loop\n===========================\n\nDemonstrates that the state machine can have async callback"
},
{
"path": "tests/examples/enum_campaign_machine.py",
"chars": 1387,
"preview": "\"\"\"\nEnum campaign machine\n=====================\n\nA :ref:`StateChart` that demonstrates declaring :ref:`States from Enum "
},
{
"path": "tests/examples/guess_the_number_machine.py",
"chars": 2499,
"preview": "\"\"\"\nGuess the number machine\n========================\n\nA StateChart for the well known game.\n\nWell leave the machine ima"
},
{
"path": "tests/examples/lor_machine.py",
"chars": 3793,
"preview": "\"\"\"\nLord of the Rings Quest - Boolean algebra\n=========================================\n\nExample that demonstrates the u"
},
{
"path": "tests/examples/order_control_machine.py",
"chars": 1424,
"preview": "\"\"\"\nOrder control machine\n---------------------\n\nA StateChart that demonstrates :ref:`Guards` being used to control the "
},
{
"path": "tests/examples/order_control_rich_model_machine.py",
"chars": 3068,
"preview": "\"\"\"\nOrder control machine (rich model)\n==================================\n\nA StateChart that demonstrates :ref:`Actions`"
},
{
"path": "tests/examples/persistent_model_machine.py",
"chars": 3488,
"preview": "\"\"\"\nPersistent domain model\n=======================\n\nAn example originated from a question: \"How to save state to disk?\""
},
{
"path": "tests/examples/recursive_event_machine.py",
"chars": 913,
"preview": "\"\"\"\nLooping state machine\n=====================\n\nThis example demonstrates that you can call an event as a side-effect o"
},
{
"path": "tests/examples/reusing_transitions_machine.py",
"chars": 2089,
"preview": "\"\"\"\n\n-------------------\nReusing transitions\n-------------------\n\nThis example helps to turn visual the different compos"
},
{
"path": "tests/examples/sqlite_persistent_model_machine.py",
"chars": 14297,
"preview": "\"\"\"\nSQLite-backed approval workflow\n================================\n\nReal-world state machines often need to survive pr"
},
{
"path": "tests/examples/statechart_cleanup_machine.py",
"chars": 3119,
"preview": "\"\"\"\nCleanup / finalize pattern\n===========================\n\nThis example demonstrates how to guarantee cleanup code runs"
},
{
"path": "tests/examples/statechart_compound_machine.py",
"chars": 2980,
"preview": "\"\"\"\nCompound states -- Quest through Middle-earth\n==============================================\n\nThis example demonstra"
},
{
"path": "tests/examples/statechart_delayed_machine.py",
"chars": 7188,
"preview": "\"\"\"\nSupervised task -- Beacons of Gondor\n=====================================\n\nThis example demonstrates a **self-drive"
},
{
"path": "tests/examples/statechart_error_handling_machine.py",
"chars": 3183,
"preview": "\"\"\"\nError handling -- Quest Recovery\n=================================\n\nThis example demonstrates **error.execution** ha"
},
{
"path": "tests/examples/statechart_eventless_machine.py",
"chars": 3179,
"preview": "\"\"\"\nEventless (automatic) transitions -- The One Ring's Corruption\n====================================================="
},
{
"path": "tests/examples/statechart_history_machine.py",
"chars": 3843,
"preview": "\"\"\"\nHistory states -- Gollum's dual personality\n============================================\n\nThis example demonstrates "
},
{
"path": "tests/examples/statechart_in_condition_machine.py",
"chars": 3634,
"preview": "\"\"\"\nIn() guard condition -- Fellowship Coordination\n=================================================\n\nThis example demo"
},
{
"path": "tests/examples/statechart_parallel_machine.py",
"chars": 2978,
"preview": "\"\"\"\nParallel states -- War of the Ring\n===================================\n\nThis example demonstrates parallel states us"
},
{
"path": "tests/examples/traffic_light_machine.py",
"chars": 1379,
"preview": "\"\"\"\n\n---------------------\nTraffic light machine\n---------------------\n\nThis example demonstrates how to create a traffi"
},
{
"path": "tests/examples/user_machine.py",
"chars": 3454,
"preview": "\"\"\"\nUser workflow machine\n=====================\n\nThis machine binds the events to the User model, the StateChart is wrap"
},
{
"path": "tests/examples/weighted_idle_machine.py",
"chars": 1121,
"preview": "\"\"\"\n\n------------------------------\nWeighted idle animation machine\n------------------------------\n\nThis example demonst"
},
{
"path": "tests/helpers.py",
"chars": 245,
"preview": "import importlib\nfrom pathlib import Path\n\n\ndef import_module_by_path(src_file: Path):\n module_name = str(src_file).r"
},
{
"path": "tests/machines/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/machines/compound/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/machines/compound/middle_earth_journey.py",
"chars": 629,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MiddleEarthJourney(StateChart):\n class riv"
},
{
"path": "tests/machines/compound/middle_earth_journey_two_compounds.py",
"chars": 446,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MiddleEarthJourneyTwoCompounds(StateChart):\n "
},
{
"path": "tests/machines/compound/middle_earth_journey_with_finals.py",
"chars": 649,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MiddleEarthJourneyWithFinals(StateChart):\n "
},
{
"path": "tests/machines/compound/moria_expedition.py",
"chars": 426,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MoriaExpedition(StateChart):\n class moria("
},
{
"path": "tests/machines/compound/moria_expedition_with_escape.py",
"chars": 492,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MoriaExpeditionWithEscape(StateChart):\n cl"
},
{
"path": "tests/machines/compound/quest_for_erebor.py",
"chars": 360,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForErebor(StateChart):\n class lonely_"
},
{
"path": "tests/machines/compound/shire_to_rivendell.py",
"chars": 311,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ShireToRivendell(StateChart):\n class shire"
},
{
"path": "tests/machines/donedata/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/machines/donedata/destroy_the_ring.py",
"chars": 690,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass DestroyTheRing"
},
{
"path": "tests/machines/donedata/destroy_the_ring_simple.py",
"chars": 504,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass DestroyTheRing"
},
{
"path": "tests/machines/donedata/nested_quest_donedata.py",
"chars": 687,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass NestedQuestDon"
},
{
"path": "tests/machines/donedata/quest_for_erebor_done_convention.py",
"chars": 348,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForEreborDoneConvention(StateChart):\n "
},
{
"path": "tests/machines/donedata/quest_for_erebor_explicit_id.py",
"chars": 431,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForErebor"
},
{
"path": "tests/machines/donedata/quest_for_erebor_multi_word.py",
"chars": 369,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForEreborMultiWord(StateChart):\n clas"
},
{
"path": "tests/machines/donedata/quest_for_erebor_with_event.py",
"chars": 407,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForErebor"
},
{
"path": "tests/machines/error/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/machines/error/error_convention_event.py",
"chars": 475,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorConventio"
},
{
"path": "tests/machines/error/error_convention_transition_list.py",
"chars": 440,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorConventionTransitionListSC(StateChart):\n"
},
{
"path": "tests/machines/error/error_in_action_sc.py",
"chars": 441,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInActionS"
},
{
"path": "tests/machines/error/error_in_action_sm_with_flag.py",
"chars": 524,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInActionS"
},
{
"path": "tests/machines/error/error_in_after_sc.py",
"chars": 419,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInAfterSC"
},
{
"path": "tests/machines/error/error_in_error_handler_sc.py",
"chars": 671,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInErrorHa"
},
{
"path": "tests/machines/error/error_in_guard_sc.py",
"chars": 444,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInGuardSC"
},
{
"path": "tests/machines/error/error_in_guard_sm.py",
"chars": 418,
"preview": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInGuardSM(StateChart):\n \"\"\"StateChart"
},
{
"path": "tests/machines/error/error_in_on_enter_sc.py",
"chars": 428,
"preview": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInOnEnter"
}
]
// ... and 318 more files (download for full content)
About this extraction
This page contains the full source code of the fgmacedo/python-statemachine GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 518 files (1.6 MB), approximately 416.6k tokens, and a symbol index with 2415 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.