[
  {
    "path": ".git-blame-ignore-revs",
    "content": "37fcf9818178587635fffe1bb67a9fd5024a0a45\n345d82390af35d5d70ddd39c612faa4a64b11080\nd7738e9ad0a3e50bc5c87d4a75c436fb771c96f6\n5bf10afae2b214900aa58dd44b0a91e469c70631\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: fgmacedo\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "* Python State Machine version:\n* Python version:\n* Operating System:\n\n### Description\n\nDescribe what you were trying to get done.\nTell us what happened, what went wrong, and what you expected to happen.\n\n### What I Did\n\n```\nPaste the command(s) you ran and the output.\nIf there was a crash, please include the traceback here.\n```\n\nIf 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.\n"
  },
  {
    "path": ".github/workflows/python-package.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python\n\nname: Python checks\n\non:\n  push:\n    branches: [ \"develop\" ]\n  pull_request:\n    branches: [ \"develop\" ]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n    - uses: actions/checkout@v4\n    - run: git fetch origin develop\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Setup Graphviz\n      uses: ts-graphviz/setup-graphviz@v2\n    - name: Install uv\n      uses: astral-sh/setup-uv@v3\n      with:\n        enable-cache: true\n        cache-suffix: \"python${{ matrix.python-version }}\"\n    - name: Install the project\n      run: uv sync --all-extras --dev\n      #----------------------------------------------\n      #              run ruff\n      #----------------------------------------------\n    - name: Linter with ruff\n      if: matrix.python-version == 3.14\n      run: |\n        uv run ruff check .\n        uv run ruff format --check .\n      #----------------------------------------------\n      #              run pytest\n      #----------------------------------------------\n    - name: Test with pytest\n      run: |\n        uv run pytest -n auto --cov --cov-report=xml:coverage.xml\n        uv run coverage xml\n      #----------------------------------------------\n      #          upload coverage\n      #----------------------------------------------\n    - name: Upload coverage to Codecov\n      uses: codecov/codecov-action@v4\n      if: matrix.python-version == 3.14\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos\n        directory: .\n        env_vars: OS,PYTHON\n        fail_ci_if_error: true\n        flags: unittests\n        name: codecov-umbrella\n        verbose: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "on:\n  push:\n    tags: [ 'v?*.*.*' ]\nname: release\n\njobs:\n  release-build:\n    name: Build release artifacts\n    runs-on: ubuntu-latest\n\n    permissions:\n      id-token: write\n    steps:\n      - uses: actions/checkout@v4\n\n      - run: git fetch origin develop\n\n      - name: Setup Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.14'\n\n      - name: Setup Graphviz\n        uses: ts-graphviz/setup-graphviz@v2\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n        with:\n          enable-cache: true\n\n      - name: Install the project\n        run: uv sync --all-extras --dev\n\n      - name: Test\n        run: |\n          uv run pytest -n auto --cov\n\n      - name: Build\n        run: |\n          uv build\n\n      - name: Upload  dists\n        uses: actions/upload-artifact@v4\n        with:\n          name: release-dists\n          path: dist/\n\n\n  pypi-publish:\n    # by a dedicated job to publish we avoid the risk of\n    # running code with access to PyPI credentials\n    name: Upload release to PyPI\n    runs-on: ubuntu-latest\n    needs:\n      - release-build\n    environment: release\n    permissions:\n      id-token: write\n\n    steps:\n      - name: Retrieve release distributions\n        uses: actions/download-artifact@v4\n        with:\n          name: release-dists\n          path: dist/\n\n      - name: Publish package distributions to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n.mypy_cache\n\n# jupyter\n.ipynb_checkpoints/\n.jupyterlite.doit.db\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nprof/\n.benchmarks/\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\n.pytest_cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\ndocs/auto_examples/\n\n# PyBuilder\ntarget/\n\n# pyenv python configuration file\n.python-version\n\n# IDEs and editors\n*.sublime*\n.idea/\n.vscode/\n\n# Sphinx-galery\ndocs/auto_examples/sg_execution_times.*\ndocs/auto_examples/*.pickle\ndocs/sg_execution_times.rst\n\n# Temporary files\ntmp/\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.6.0\n    hooks:\n    -   id: check-yaml\n    -   id: end-of-file-fixer\n        exclude: docs/auto_examples\n    -   id: trailing-whitespace\n        exclude: docs/auto_examples\n- repo: https://github.com/charliermarsh/ruff-pre-commit\n  # Ruff version.\n  rev: v0.15.0\n  hooks:\n    # Run the linter.\n    - id: ruff\n      args: [ --fix ]\n    # Run the formatter.\n    - id: ruff-format\n\n- repo: local\n  hooks:\n  - id: mypy\n    name: Mypy\n    entry: uv run mypy --namespace-packages --explicit-package-bases statemachine/ tests/\n    types: [python]\n    language: system\n    pass_filenames: false\n  - id: pyright\n    name: Pyright\n    entry: uv run pyright statemachine/\n    types: [python]\n    language: system\n    pass_filenames: false\n  - id: generate-images\n    name: Generate README images\n    entry: >-\n      uv run python -m statemachine.contrib.diagram\n      tests.examples.traffic_light_machine.TrafficLightMachine\n      docs/images/readme_trafficlightmachine.png\n      --events cycle cycle cycle\n    language: system\n    pass_filenames: false\n    files: (statemachine/contrib/diagram/|tests/examples/traffic_light_machine\\.py)\n  - id: pytest\n    name: Pytest\n    entry: uv run pytest -n auto --cov-fail-under=100\n    types: [python]\n    language: system\n    pass_filenames: false\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\nbuild:\n  os: \"ubuntu-22.04\"\n  tools:\n    python: \"3.14\"\n  apt_packages:\n    - graphviz\n  jobs:\n    post_create_environment:\n      - asdf plugin add uv\n      - asdf install uv latest\n      - asdf global uv latest\n      - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --frozen\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n   configuration: docs/conf.py\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# python-statemachine\n\nPython Finite State Machines made easy.\n\n## Project overview\n\nA library for building finite state machines in Python, with support for sync and async engines,\nDjango integration, diagram generation, and a flexible callback/listener system.\n\n- **Source code:** `statemachine/`\n- **Tests:** `tests/`\n- **Documentation:** `docs/` (Sphinx + MyST Markdown, hosted on ReadTheDocs)\n\n## Architecture\n\n- `statemachine.py` — Core `StateMachine` and `StateChart` classes\n- `factory.py` — `StateMachineMetaclass` handles class construction, state/transition validation\n- `state.py` / `event.py` — Descriptor-based `State` and `Event` definitions\n- `transition.py` / `transition_list.py` — Transition logic and composition (`|` operator)\n- `callbacks.py` — Priority-based callback registry (`CallbackPriority`, `CallbackGroup`)\n- `dispatcher.py` — Listener/observer pattern, `callable_method` wraps callables with signature adaptation\n- `signature.py` — `SignatureAdapter` for dependency injection into callbacks\n- `engines/base.py` — Shared engine logic (microstep, transition selection, error handling)\n- `engines/sync.py`, `engines/async_.py` — Sync and async processing loops\n- `registry.py` — Global state machine registry (used by `MachineMixin`)\n- `mixins.py` — `MachineMixin` for domain model integration (e.g., Django models)\n- `spec_parser.py` — Boolean expression parser for condition guards\n- `contrib/diagram.py` — Diagram generation via pydot/Graphviz\n\n## Processing model\n\nThe engine follows the SCXML run-to-completion (RTC) model with two processing levels:\n\n- **Microstep**: atomic execution of one transition set (before → exit → on → enter → after).\n- **Macrostep**: complete processing cycle for one external event; repeats microsteps until\n  the machine reaches a **stable configuration** (no eventless transitions enabled, internal\n  queue empty).\n\n### Event queues\n\n- `send()` → **external queue** (processed after current macrostep ends).\n- `raise_()` → **internal queue** (processed within the current macrostep, before external events).\n\n### Error handling (`catch_errors_as_events`)\n\n- `StateChart` has `catch_errors_as_events=True` by default; `StateMachine` has `False`.\n- Errors are caught at the **block level** (per onentry/onexit/transition `on` block), not per\n  microstep. This means `after` callbacks still run even when an action raises — making\n  `after_<event>()` a natural **finalize** hook (runs on both success and failure paths).\n- `error.execution` is dispatched as an internal event; define transitions for it to handle\n  errors within the statechart.\n- Error during `error.execution` handling → ignored to prevent infinite loops.\n\n#### `on_error` asymmetry: transition `on` vs onentry/onexit\n\nTransition `on` content uses `on_error` **only for non-`error.execution` events**. During\n`error.execution` processing, `on_error` is disabled for transition `on` content — errors\npropagate to `microstep()` where `_send_error_execution` ignores them. This prevents infinite\nloops in self-transition error handlers (e.g., `error_execution = s1.to(s1, on=\"handler\")`\nwhere `handler` raises). `onentry`/`onexit` blocks always use `on_error` regardless of the\ncurrent event.\n\n### Eventless transitions\n\n- Bare transition statements (not assigned to a variable) are **eventless** — they fire\n  automatically when their guard condition is met.\n- Assigned transitions (e.g., `go = s1.to(s2)`) create **named events**.\n- `error_` prefix naming convention: `error_X` auto-registers both `error_X` and `error.X`\n  event names (explicit `id=` takes precedence).\n\n### Callback conventions\n\n- Generic callbacks (always available): `prepare_event()`, `before_transition()`,\n  `on_transition()`, `on_exit_state()`, `on_enter_state()`, `after_transition()`.\n- Event-specific: `before_<event>()`, `on_<event>()`, `after_<event>()`.\n- State-specific: `on_enter_<state>()`, `on_exit_<state>()`.\n- `on_error_execution()` works via naming convention but **only** when a transition for\n  `error.execution` is declared — it is NOT a generic callback.\n\n### Thread safety\n\n- The sync engine is **thread-safe**: multiple threads can send events to the same SM instance\n  concurrently. The processing loop uses a `threading.Lock` so at most one thread executes\n  transitions at a time. Event queues use `PriorityQueue` (stdlib, thread-safe).\n- **Do not replace `PriorityQueue`** with non-thread-safe alternatives (e.g., `collections.deque`,\n  plain `list`) — this would break concurrent access guarantees.\n- Stress tests in `tests/test_threading.py::TestThreadSafety` exercise real contention with\n  barriers and multiple sender threads. Any change to queue or locking internals must pass these.\n\n### Invoke (`<invoke>`)\n\n- `invoke.py` — `InvokeManager` on the engine manages the lifecycle: `mark_for_invoke()`,\n  `cancel_for_state()`, `spawn_pending_sync/async()`, `send_to_child()`.\n- `_cleanup_terminated()` only removes invocations that are both terminated **and** cancelled.\n  A terminated-but-not-cancelled invocation means the handler's `run()` returned but the owning\n  state is still active — it must stay in `_active` so `send_to_child()` can still route events.\n- **Child machine constructor blocks** in the processing loop. Use a listener pattern (e.g.,\n  `_ChildRefSetter`) to capture the child reference during the first `on_enter_state`, before\n  the loop spins.\n- `#_<invokeid>` send target: routed via `_send_to_invoke()` in `io/scxml/actions.py` →\n  `InvokeManager.send_to_child()` → handler's `on_event()`.\n- **Tests with blocking threads**: use `threading.Event.wait(timeout=)` instead of\n  `time.sleep()` for interruptible waits — avoids thread leak errors in teardown.\n\n## Environment setup\n\n```bash\nuv sync --all-extras --dev\npre-commit install\n```\n\n## Running tests\n\nAlways use `uv` to run commands. Also, use a timeout to avoid being stuck in the case of a leaked thread or infinite loop:\n\n```bash\n# Run all tests (parallel)\ntimeout 120 uv run pytest -n 4\n\n# Run a specific test file\nuv run pytest tests/test_signature.py\n\n# Run a specific test\nuv run pytest tests/test_signature.py::TestSignatureAdapter::test_wrap_fn_single_positional_parameter\n\n# Skip slow tests\nuv run pytest -m \"not slow\"\n```\n\nWhen trying to run all tests, prefer to use xdist (`-n`) as some SCXML tests uses timeout of 30s to verify fallback mechanism.\nDon't specify the directory `tests/`, because this will exclude doctests from both source modules (`--doctest-modules`) and markdown docs\n(`--doctest-glob=*.md`) (enabled by default):\n\n```bash\ntimeout 120 uv run pytest -n 4\n```\n\nTestes normally run under 60s (~40s on average), so take a closer look if they take longer, it can be a regression.\n\n### Debug logging\n\n`log_cli_level` defaults to `WARNING` in `pyproject.toml`. The engine caches a no-op\nfor `logger.debug` at init time — running tests with `DEBUG` would bypass this\noptimization and inflate benchmark numbers. To enable debug logs for a specific run:\n\n```bash\nuv run pytest -o log_cli_level=DEBUG tests/test_something.py\n```\n\nWhen analyzing warnings or extensive output, run the tests **once** saving the output to a file\n(`> /tmp/pytest-output.txt 2>&1`), then analyze the file — instead of running the suite\nrepeatedly with different greps.\n\nCoverage is enabled by default (`--cov` is in `pyproject.toml`'s `addopts`). To generate a\ncoverage report to a file, pass `--cov-report` **in addition to** `--cov`:\n\n```bash\n# JSON report (machine-readable, includes missing_lines per file)\ntimeout 120 uv run pytest -n auto --cov=statemachine --cov-report=json:cov.json\n\n# Terminal report with missing lines\ntimeout 120 uv run pytest -n auto --cov=statemachine --cov-report=term-missing\n```\n\nNote: `--cov=statemachine` is required to activate coverage collection; `--cov-report`\nalone only changes the output format.\n\n### Testing both sync and async engines\n\nUse the `sm_runner` fixture (from `tests/conftest.py`) when you need to test the same\nstatechart on both sync and async engines. It is parametrized with `[\"sync\", \"async\"]`\nand provides `start()` / `send()` helpers that handle engine selection automatically:\n\n```python\nasync def test_something(self, sm_runner):\n    sm = await sm_runner.start(MyStateChart)\n    await sm_runner.send(sm, \"some_event\")\n    assert \"expected_state\" in sm.configuration_values\n```\n\nDo **not** manually add async no-op listeners or duplicate test classes — prefer `sm_runner`.\n\n### TDD and coverage requirements\n\nFollow a **test-driven development** approach: tests are not an afterthought — they are a\nfirst-class requirement that must be part of every implementation plan.\n\n- **Planning phase:** every plan must include test tasks as explicit steps, not a final\n  \"add tests\" bullet. Identify what needs to be tested (new branches, edge cases, error\n  paths) while designing the implementation.\n- **100% branch coverage is mandatory.** The pre-commit hook enforces `--cov-fail-under=100`\n  with branch coverage enabled. Code that drops coverage will not pass CI.\n- **Verify coverage before committing:** after writing tests, run coverage on the affected\n  modules and check for missing lines/branches:\n  ```bash\n  timeout 120 uv run pytest tests/<test_file>.py --cov=statemachine.<module> --cov-report=term-missing --cov-branch\n  ```\n- **Use pytest fixtures** (`tmp_path`, `monkeypatch`, etc.) — never hardcode paths or\n  use mutable global state when a fixture exists.\n- **Unreachable defensive branches** (e.g., `if` guards that can never be True given the\n  type system) may be marked with `pragma: no cover`, but prefer writing a test first.\n\n## Linting and formatting\n\n```bash\n# Lint\nuv run ruff check .\n\n# Auto-fix lint issues\nuv run ruff check --fix .\n\n# Format\nuv run ruff format .\n\n# Type check\nuv run mypy statemachine/ tests/\n```\n\n## Code style\n\n- **Formatter/Linter:** ruff (line length 99, target Python 3.9)\n- **Rules:** pycodestyle, pyflakes, isort, pyupgrade, flake8-comprehensions, flake8-bugbear, flake8-pytest-style\n- **Imports:** single-line, sorted by isort. **Always prefer top-level imports** — only use\n  lazy (in-function) imports when strictly necessary to break circular dependencies\n- **Docstrings:** Google convention\n- **Naming:** PascalCase for classes, snake_case for functions/methods, UPPER_SNAKE_CASE for constants\n- **Type hints:** used throughout; `TYPE_CHECKING` for circular imports\n- Pre-commit hooks enforce ruff + mypy + pytest\n\n## Design principles\n\n- **Use GRASP/SOLID patterns to guide decisions.** When refactoring or designing, explicitly\n  apply patterns like Information Expert, Single Responsibility, and Law of Demeter to decide\n  where logic belongs — don't just pick a convenient location.\n  - **Information Expert (GRASP):** Place logic in the module/class that already has the\n    knowledge it needs. If a method computes a result, it should signal or return it rather\n    than forcing another method to recompute the same thing.\n  - **Law of Demeter:** Methods should depend only on the data they need, not on the\n    objects that contain it. Pass the specific value (e.g., a `Future`) rather than the\n    parent object (e.g., `TriggerData`) — this reduces coupling and removes the need for\n    null-checks on intermediate accessors.\n  - **Single Responsibility:** Each module, class, and function should have one clear reason\n    to change. Functions and types belong in the module that owns their domain (e.g.,\n    event-name helpers belong in `event.py`, not in `factory.py`).\n  - **Interface Segregation:** Depend on narrow interfaces. If a helper only needs one field\n    from a dataclass, accept that field directly.\n- **Decouple infrastructure from domain:** Modules like `signature.py` and `dispatcher.py` are\n  general-purpose (signature adaptation, listener/observer pattern) and intentionally not coupled\n  to the state machine domain. Prefer this separation even for modules that are only used\n  internally — it keeps responsibilities clear and the code easier to reason about.\n- **Favor small, focused modules:** When adding new functionality, consider whether it can live in\n  its own module with a well-defined boundary, rather than growing an existing one.\n\n## Building documentation\n\n```bash\n# Build HTML docs\nuv run sphinx-build docs docs/_build/html\n\n# Live reload for development\nuv run sphinx-autobuild docs docs/_build/html --re-ignore \"auto_examples/.*\"\n```\n\n### Documentation code examples\n\nAll code examples in `docs/*.md` **must** be testable doctests (using ```` ```py ```` with\n`>>>` prompts), not plain ```` ```python ```` blocks. The test suite collects them via\n`--doctest-glob=*.md`. If an example cannot be expressed as a doctest (e.g., it requires\nreal concurrency), write it as a unit test in `tests/` and reference it from the docs instead.\n\n## Git workflow\n\n- Main branch: `develop`\n- PRs target `develop`\n- Releases are tagged as `v*.*.*`\n- Signed commits preferred (`git commit -s`)\n- Use [Conventional Commits](https://www.conventionalcommits.org/) messages\n  (e.g., `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `perf:`)\n\n## Security\n\n- Do not commit secrets, credentials, or `.env` files\n- Validate at system boundaries; trust internal code\n"
  },
  {
    "path": "LICENSE",
    "content": "\nMIT License\n\nCopyright (c) 2017, Fernando Macedo\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# Python StateMachine\n\n[![pypi](https://img.shields.io/pypi/v/python-statemachine.svg)](https://pypi.python.org/pypi/python-statemachine)\n[![downloads total](https://static.pepy.tech/badge/python-statemachine)](https://pepy.tech/project/python-statemachine)\n[![downloads](https://img.shields.io/pypi/dm/python-statemachine.svg)](https://pypi.python.org/pypi/python-statemachine)\n[![Coverage report](https://codecov.io/gh/fgmacedo/python-statemachine/branch/develop/graph/badge.svg)](https://codecov.io/gh/fgmacedo/python-statemachine)\n[![Documentation Status](https://readthedocs.org/projects/python-statemachine/badge/?version=latest)](https://python-statemachine.readthedocs.io/en/latest/?badge=latest)\n[![GitHub commits since last release (main)](https://img.shields.io/github/commits-since/fgmacedo/python-statemachine/main/develop)](https://github.com/fgmacedo/python-statemachine/compare/main...develop)\n\nExpressive [statecharts](https://statecharts.dev/) and [FSMs](https://en.wikipedia.org/wiki/Finite-state_machine) for modern Python.\n\n<div align=\"center\">\n\n![](https://github.com/fgmacedo/python-statemachine/blob/develop/docs/images/python-statemachine.png?raw=true)\n\n</div>\n\nWelcome to python-statemachine, an intuitive and powerful state machine library designed for a\ngreat developer experience. Define flat state machines or full statecharts with compound states,\nparallel regions, and history — all with a clean, _pythonic_, declarative API that works in both\nsync and async Python codebases.\n\n\n## Quick start\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class TrafficLightMachine(StateChart):\n...     \"A traffic light machine\"\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = (\n...         green.to(yellow)\n...         | yellow.to(red)\n...         | red.to(green)\n...     )\n...\n...     def before_cycle(self, event: str, source: State, target: State):\n...         return f\"Running {event} from {source.id} to {target.id}\"\n...\n...     def on_enter_red(self):\n...         print(\"Don't move.\")\n...\n...     def on_exit_red(self):\n...         print(\"Go ahead!\")\n\n```\n\nCreate an instance and send events:\n\n```py\n>>> sm = TrafficLightMachine()\n>>> sm.send(\"cycle\")\n'Running cycle from green to yellow'\n\n>>> sm.send(\"cycle\")\nDon't move.\n'Running cycle from yellow to red'\n\n>>> sm.send(\"cycle\")\nGo ahead!\n'Running cycle from red to green'\n\n```\n\nCheck which states are active:\n\n```py\n>>> sm.configuration\nOrderedSet([State('Green', id='green', value='green', initial=True, final=False, parallel=False)])\n\n>>> sm.green.is_active\nTrue\n\n```\n\nGenerate a diagram or get a text representation with f-strings:\n\n```py\n>>> print(f\"{sm:md}\")\n| State  | Event | Guard | Target |\n| ------ | ----- | ----- | ------ |\n| Green  | Cycle |       | Yellow |\n| Yellow | Cycle |       | Red    |\n| Red    | Cycle |       | Green  |\n<BLANKLINE>\n\n```\n\n```python\nsm._graph().write_png(\"traffic_light.png\")\n```\n\n![](https://raw.githubusercontent.com/fgmacedo/python-statemachine/develop/docs/images/readme_trafficlightmachine.png)\n\nParameters are injected into callbacks automatically — the library inspects the\nsignature and provides only the arguments each callback needs:\n\n```py\n>>> sm.send(\"cycle\")\n'Running cycle from green to yellow'\n\n```\n\n\n## Guards and conditional transitions\n\nUse `cond=` and `unless=` to add guards. When multiple transitions share the same\nevent, declaration order determines priority:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class ApprovalWorkflow(StateChart):\n...     pending = State(initial=True)\n...     approved = State(final=True)\n...     rejected = State(final=True)\n...\n...     review = (\n...         pending.to(approved, cond=\"is_valid\")\n...         | pending.to(rejected)\n...     )\n...\n...     def is_valid(self, score: int = 0):\n...         return score >= 70\n\n>>> sm = ApprovalWorkflow()\n>>> sm.send(\"review\", score=50)\n>>> sm.rejected.is_active\nTrue\n\n>>> sm = ApprovalWorkflow()\n>>> sm.send(\"review\", score=85)\n>>> sm.approved.is_active\nTrue\n\n```\n\nThe first transition whose guard passes wins. When `score < 70`, `is_valid` returns\n`False` so the second transition (no guard — always matches) fires instead.\n\n\n## Compound states — hierarchy\n\nBreak complex behavior into hierarchical levels with `State.Compound`. Entering a\ncompound activates both the parent and its `initial` child. Exiting removes the\nparent and all descendants:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class DocumentWorkflow(StateChart):\n...     class editing(State.Compound):\n...         draft = State(initial=True)\n...         review = State()\n...         submit = draft.to(review)\n...         revise = review.to(draft)\n...\n...     published = State(final=True)\n...     approve = editing.to(published)\n\n>>> sm = DocumentWorkflow()\n>>> set(sm.configuration_values) == {\"editing\", \"draft\"}\nTrue\n\n>>> sm.send(\"submit\")\n>>> \"review\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"approve\")\n>>> set(sm.configuration_values) == {\"published\"}\nTrue\n\n```\n\n\n## Parallel states — concurrency\n\n`State.Parallel` activates all child regions simultaneously. Events in one\nregion don't affect others. A `done.state` event fires only when **all**\nregions reach a final state:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class DeployPipeline(StateChart):\n...     class deploy(State.Parallel):\n...         class build(State.Compound):\n...             compiling = State(initial=True)\n...             compiled = State(final=True)\n...             finish_build = compiling.to(compiled)\n...         class tests(State.Compound):\n...             running = State(initial=True)\n...             passed = State(final=True)\n...             finish_tests = running.to(passed)\n...     released = State(final=True)\n...     done_state_deploy = deploy.to(released)\n\n>>> sm = DeployPipeline()\n>>> \"compiling\" in sm.configuration_values and \"running\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"finish_build\")\n>>> \"compiled\" in sm.configuration_values and \"running\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"finish_tests\")\n>>> set(sm.configuration_values) == {\"released\"}\nTrue\n\n```\n\n\n## History states\n\n`HistoryState()` records which child was active when a compound is exited.\nRe-entering via the history pseudo-state restores the previous child instead\nof starting from the initial one:\n\n```py\n>>> from statemachine import HistoryState, StateChart, State\n\n>>> class EditorWithHistory(StateChart):\n...     class editor(State.Compound):\n...         source = State(initial=True)\n...         visual = State()\n...         h = HistoryState()\n...         toggle = source.to(visual) | visual.to(source)\n...     settings = State()\n...     open_settings = editor.to(settings)\n...     back = settings.to(editor.h)\n\n>>> sm = EditorWithHistory()\n>>> sm.send(\"toggle\")\n>>> \"visual\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"open_settings\")\n>>> sm.send(\"back\")\n>>> \"visual\" in sm.configuration_values\nTrue\n\n```\n\nUse `HistoryState(type=\"deep\")` for deep history that remembers the exact leaf\nstate across nested compounds.\n\n\n## Eventless transitions\n\nTransitions without an event trigger fire automatically. With a guard, they\nfire after any event processing when the condition is met:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class AutoCounter(StateChart):\n...     counting = State(initial=True)\n...     done = State(final=True)\n...\n...     counting.to(done, cond=\"limit_reached\")\n...     increment = counting.to.itself(internal=True, on=\"do_increment\")\n...\n...     count = 0\n...\n...     def do_increment(self):\n...         self.count += 1\n...     def limit_reached(self):\n...         return self.count >= 3\n\n>>> sm = AutoCounter()\n>>> sm.send(\"increment\")\n>>> sm.send(\"increment\")\n>>> \"counting\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"increment\")\n>>> \"done\" in sm.configuration_values\nTrue\n\n```\n\n\n## Error handling\n\nWhen using `StateChart`, runtime exceptions in callbacks are caught and\nturned into `error.execution` events. Define a transition for that event\nto handle errors within the state machine itself:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class ResilientService(StateChart):\n...     running = State(initial=True)\n...     failed = State(final=True)\n...\n...     process = running.to(running, on=\"do_work\")\n...     error_execution = running.to(failed)\n...\n...     def do_work(self):\n...         raise RuntimeError(\"something broke\")\n\n>>> sm = ResilientService()\n>>> sm.send(\"process\")\n>>> sm.failed.is_active\nTrue\n\n```\n\n\n## Async support\n\nAsync callbacks just work — same API, no changes needed. The engine\ndetects async callbacks and switches to the async engine automatically:\n\n```py\n>>> import asyncio\n>>> from statemachine import StateChart, State\n\n>>> class AsyncWorkflow(StateChart):\n...     idle = State(initial=True)\n...     done = State(final=True)\n...\n...     finish = idle.to(done)\n...\n...     async def on_finish(self):\n...         return 42\n\n>>> async def run():\n...     sm = AsyncWorkflow()\n...     result = await sm.finish()\n...     print(f\"Result: {result}\")\n...     print(sm.done.is_active)\n\n>>> asyncio.run(run())\nResult: 42\nTrue\n\n```\n\n\n## More features\n\nThere's a lot more to explore:\n\n- **DoneData** on final states — pass structured data to `done.state` handlers\n- **Delayed events** — schedule events with `sm.send(\"event\", delay=500)`\n- **`In(state)` conditions** — cross-region guards in parallel states\n- **`prepare_event`** callback — inject custom data into all callbacks\n- **Observer pattern** — register external listeners to watch events and state changes\n- **Django integration** — auto-discover state machines in Django apps with `MachineMixin`\n- **Diagram generation** — via f-strings (`f\"{sm:mermaid}\"`), CLI, Sphinx directive, or Jupyter\n- **Dictionary-based definitions** — create state machines from data structures\n- **Internationalization** — error messages in multiple languages\n\nFull documentation: https://python-statemachine.readthedocs.io\n\n\n## Installing\n\n```\npip install python-statemachine\n```\n\nTo generate diagrams, install with the `diagrams` extra (requires\n[Graphviz](https://graphviz.org/)):\n\n```\npip install python-statemachine[diagrams]\n```\n\n\n## Contributing\n\n- If you found this project helpful, please consider giving it a star on GitHub.\n\n- **Contribute code**: If you would like to contribute code, please submit a pull\nrequest. For more information on how to contribute, please see our [contributing.md](contributing.md) file.\n\n- **Report bugs**: If you find any bugs, please report them by opening an issue\n  on our GitHub issue tracker.\n\n- **Suggest features**: If you have an idea for a new feature, or feel something is harder than it should be,\n  please let us know by opening an issue on our GitHub issue tracker.\n\n- **Documentation**: Help improve documentation by submitting pull requests.\n\n- **Promote the project**: Help spread the word by sharing on social media,\n  writing a blog post, or giving a talk about it. Tag me on Twitter\n  [@fgmacedo](https://twitter.com/fgmacedo) so I can share it too!\n"
  },
  {
    "path": "conftest.py",
    "content": "import shutil\nimport sys\n\nimport pytest\n\n\n@pytest.fixture(autouse=True, scope=\"session\")\ndef add_doctest_context(doctest_namespace):  # noqa: PT004\n    from statemachine.utils import run_async_from_sync\n\n    from statemachine import State\n    from statemachine import StateChart\n    from statemachine import StateMachine\n\n    class ContribAsyncio:\n        \"\"\"\n        Using `run_async_from_sync` to be injected in the doctests to better integration with an\n        already running loop, as all of our examples are also automated executed as doctests.\n\n        On real life code you should use standard `import asyncio; asyncio.run(main())`.\n        \"\"\"\n\n        def __init__(self):\n            self.run = run_async_from_sync\n\n    doctest_namespace[\"State\"] = State\n    doctest_namespace[\"StateChart\"] = StateChart\n    doctest_namespace[\"StateMachine\"] = StateMachine\n    doctest_namespace[\"asyncio\"] = ContribAsyncio()\n\n\ndef pytest_ignore_collect(collection_path, config):\n    if sys.version_info >= (3, 10):  # noqa: UP036\n        return None\n\n    if \"django_project\" in str(collection_path):\n        return True\n\n\n@pytest.fixture(scope=\"session\")\ndef has_dot_installed():\n    return bool(shutil.which(\"dot\"))\n\n\n@pytest.fixture()\ndef requires_dot_installed(request, has_dot_installed):\n    if not has_dot_installed:\n        pytest.skip(f\"Test {request.node.nodeid} requires 'dot' that is not installed.\")\n"
  },
  {
    "path": "contributing.md",
    "content": "Please see [docs/contributing.md](docs/contributing).\n"
  },
  {
    "path": "docs/_static/custom_machine.css",
    "content": "/* div.sphx-glr-download {\n    height: 0px;\n    visibility: hidden;\n} */\n\n@media only screen and (min-width: 650px) {\n\n    .sphx-glr-thumbnails {\n        grid-template-columns: repeat(auto-fill, minmax(600px, 1fr)) !important;\n    }\n\n    .sphx-glr-thumbcontainer {\n        min-height: 320px !important;\n        margin: 20px !important;\n        justify-content: center;\n    }\n    .sphx-glr-thumbcontainer .figure {\n        width: 600px !important;\n    }\n    .sphx-glr-thumbcontainer img {\n        max-height: 250px !important;\n        max-width: 600px !important;\n        width: 100% !important;\n    }\n    .sphx-glr-thumbcontainer a.internal {\n        padding: 20px 10px 0 !important;\n    }\n\n}\n\n/* Gallery Donwload buttons */\ndiv.sphx-glr-download a {\n    color: #404040 !important;\n    background-color: #f3f6f6 !important;\n    background-image: none;\n    border-radius: 4px;\n    border: none;\n    display: inline-block;\n    font-weight: bold;\n    padding: 1ex;\n    text-align: center;\n  }\n\n  div.sphx-glr-download code.download {\n    display: inline-block;\n    white-space: normal;\n    word-break: normal;\n    overflow-wrap: break-word;\n    /* border and background are given by the enclosing 'a' */\n    border: none;\n    background: none;\n  }\n\n  div.sphx-glr-download a:hover {\n    box-shadow: none;\n    text-decoration: none;\n    background-image: none;\n    background-color: #e5ebeb !important;\n  }\n"
  },
  {
    "path": "docs/actions.md",
    "content": "(actions)=\n\n# Actions\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nAn **action** is a side-effect that runs during a state change — sending\nnotifications, updating a database, logging, or returning a value. Actions are\nthe main reason statecharts exist: they ensure the right code runs at the right\ntime, depending on the sequence of events and the current state.\n\n\n## Execution order\n\nA single {ref}`microstep <macrostep-microstep>` executes callbacks in a fixed\nsequence of **groups**. Each group runs to completion before the next one starts:\n\n```{list-table}\n:header-rows: 1\n\n*   - Group\n    - Callbacks\n    - `state` is\n    - Description\n*   - Prepare\n    - `prepare_event()`\n    - `source`\n    - Enrich event kwargs before anything else runs. See {ref}`preparing-events`.\n*   - Validators\n    - `validators`\n    - `source`\n    - Raise an exception to block the transition.\n*   - Conditions\n    - `cond`, `unless`\n    - `source`\n    - Return a boolean to allow or prevent the transition.\n*   - Before\n    - `before_transition()`, `before_<event>()`\n    - `source`\n    - Runs before any state changes.\n*   - Exit\n    - `on_exit_state()`, `on_exit_<state>()`\n    - exiting state\n    - Runs once per state being exited, from child to ancestor.\n*   - On\n    - `on_transition()`, `on_<event>()`\n    - `source`\n    - Transition content — the main action.\n*   - Enter\n    - `on_enter_state()`, `on_enter_<state>()`\n    - entering state\n    - Runs once per state being entered, from ancestor to child.\n*   - Invoke\n    - `on_invoke_state()`, `on_invoke_<state>()`\n    - `target`\n    - Spawns background work. See {ref}`invoke`.\n*   - After\n    - `after_transition()`, `after_<event>()`\n    - `target`\n    - Runs after all state changes are complete.\n```\n\nThe `state` column shows what the `state` parameter resolves to when\n{ref}`injected <dependency-injection>` into that callback. The `source` and\n`target` parameters are always available regardless of group.\n\n```{tip}\n`after` callbacks run even when an earlier group raises and\n`catch_errors_as_events` is enabled — making them a natural **finalize** hook.\nSee {ref}`error-handling-cleanup-finalize` for the full pattern.\n```\n\n```{seealso}\nSee {ref}`validators and guards` for the `validators`, `cond`, and `unless`\ngroups. The rest of this page focuses on actions.\n```\n\n\n### Priority within a group\n\nEach group can contain multiple callbacks. Within the same group, callbacks\nexecute in **priority order**:\n\n1. **Generic** — built-in callbacks like `on_enter_state()` or `before_transition()`.\n2. **Inline** — callbacks passed as constructor parameters (e.g., `on=\"do_work\"`).\n3. **Decorator** — callbacks added via decorators (e.g., `@state.enter`).\n4. **Naming convention** — callbacks discovered by name (e.g., `on_enter_idle()`).\n\n```{seealso}\nSee the example {ref}`sphx_glr_auto_examples_all_actions_machine.py` for a\ncomplete demonstration of callback resolution order.\n```\n\n\n### Exit and enter in compound states\n\nIn a flat state machine, exit and enter each run exactly once — for the\nsingle source and the single target. With {ref}`compound <compound-states>`\nand {ref}`parallel <parallel-states>` states, a transition may cross\nmultiple levels of the hierarchy, and the engine exits and enters **each\nlevel individually**, following the\n[SCXML](https://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation)\nspecification:\n\n- **Exit** runs from the **innermost** (deepest child) state up to the\n  ancestor being left — children exit before their parents.\n- **Enter** runs from the **outermost** (highest ancestor) state down to\n  the target leaf — parents enter before their children.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class HierarchicalExample(StateChart):\n...     class parent_a(State.Compound):\n...         child_a = State(initial=True)\n...     class parent_b(State.Compound):\n...         child_b = State(initial=True, final=True)\n...     cross = parent_a.to(parent_b)\n...\n...     def on_exit_child_a(self):\n...         print(\"  exit  child_a\")\n...     def on_exit_parent_a(self):\n...         print(\"  exit  parent_a\")\n...     def on_enter_parent_b(self):\n...         print(\"  enter parent_b\")\n...     def on_enter_child_b(self):\n...         print(\"  enter child_b\")\n\n>>> sm = HierarchicalExample()\n>>> sm.send(\"cross\")\n  exit  child_a\n  exit  parent_a\n  enter parent_b\n  enter child_b\n\n```\n\nThis means that **exit and enter callbacks fire multiple times per\nmicrostep** — once for each state in the exit/entry set. Use state-specific\ncallbacks (`on_exit_<state>`, `on_enter_<state>`) to target individual\nlevels of the hierarchy.\n\n```{note}\nThe generic `on_exit_state()` and `on_enter_state()` callbacks also fire\nonce per state in the set, but the `state` parameter is bound to the\ntransition's `source` or `target` — not the individual state being\nexited/entered. Use `event_data` if you need the full context, or prefer\nstate-specific callbacks for clarity.\n```\n\n```{seealso}\nSee {ref}`macrostep-microstep` for how microsteps compose into macrosteps,\nand {ref}`compound-states` for how state hierarchies work.\n```\n\n\n(dependency-injection)=\n(dynamic-dispatch)=\n(dynamic dispatch)=\n\n## Dependency injection\n\nAll callbacks (actions, conditions, validators) support automatic dependency\ninjection. The library inspects your method signature and passes only the\nparameters you declare — you never need to accept arguments you don't use.\n\n```py\n>>> class FlexibleSC(StateChart):\n...     idle = State(initial=True)\n...     done = State(final=True)\n...\n...     go = idle.to(done)\n...\n...     def on_go(self):\n...         \"\"\"No params needed? That's fine.\"\"\"\n...         return \"minimal\"\n...\n...     def after_go(self, event, source, target):\n...         \"\"\"Need context? Just declare what you want.\"\"\"\n...         print(f\"{event}: {source.id} → {target.id}\")\n\n>>> sm = FlexibleSC()\n>>> sm.send(\"go\")\ngo: idle → done\n'minimal'\n\n```\n\n### Available parameters\n\nThese parameters are available for injection into any callback:\n\n| Parameter | Type | Description |\n|---|---|---|\n| `event_data` | {class}`~statemachine.event_data.EventData` | The full event data object for this microstep. |\n| `event` | {class}`~statemachine.event.Event` | The event that triggered the transition. |\n| `source` | {class}`~statemachine.state.State` | The state the machine was in when the event started. |\n| `target` | {class}`~statemachine.state.State` | The destination state of the transition. |\n| `state` | {class}`~statemachine.state.State` | The *current* state — equals `source` for before/exit/on, `target` for enter/after. |\n| `error` | `Exception` | The exception object. Only available in callbacks triggered by `error.execution` events. See {ref}`error-execution`. |\n| `model` | {class}`~statemachine.model.Model` | The underlying model instance (see {ref}`models`). |\n| `machine` | {class}`~statemachine.statemachine.StateChart` | The state machine instance itself. |\n| `transition` | {class}`~statemachine.transition.Transition` | The transition being executed. |\n\nThe following parameters are available **only in `on` callbacks** (transition\ncontent):\n\n| Parameter | Type | Description |\n|---|---|---|\n| `previous_configuration` | `OrderedSet[`{class}`~statemachine.state.State``]` | States that were active before the microstep. |\n| `new_configuration` | `OrderedSet[`{class}`~statemachine.state.State``]` | States that will be active after the microstep. |\n\n#### Configuration during `on` callbacks\n\nBy the time the `on` group runs, exit callbacks have already fired and the\nexiting states may have been removed from `sm.configuration`, but the entering\nstates have not been added yet. This means that reading `sm.configuration`\ninside an `on` callback returns a **transitional** snapshot — neither the old\nnor the new configuration.\n\nUse `previous_configuration` and `new_configuration` instead to reliably\ninspect which states were active before and which will be active after:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class InspectConfig(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...\n...     go = a.to(b)\n...\n...     def on_go(self, previous_configuration, new_configuration):\n...         current = {s.id for s in self.configuration}\n...         prev = {s.id for s in previous_configuration}\n...         new = {s.id for s in new_configuration}\n...         print(f\"previous:      {sorted(prev)}\")\n...         print(f\"configuration: {sorted(current)}\")\n...         print(f\"new:           {sorted(new)}\")\n\n>>> sm = InspectConfig()\n>>> sm.send(\"go\")\nprevious:      ['a']\nconfiguration: []\nnew:           ['b']\n\n```\n\nNotice that `sm.configuration` is **empty** during the `on` callback — state\n`a` has already exited, but state `b` has not entered yet.\n\n```{tip}\nIf you need the old 2.x behavior where `sm.configuration` updates atomically\n(all exits and entries applied at once after the `on` group), set\n`atomic_configuration_update = True` on your class. See the\n[behaviour reference](behaviour.md) for details.\n```\n\nIn addition, any positional or keyword arguments you pass when triggering the\nevent are forwarded to all callbacks:\n\n```py\n>>> class Greeter(StateChart):\n...     idle = State(initial=True)\n...\n...     greet = idle.to.itself()\n...\n...     def on_greet(self, name, greeting=\"Hello\"):\n...         return f\"{greeting}, {name}!\"\n\n>>> sm = Greeter()\n>>> sm.send(\"greet\", \"Alice\")\n'Hello, Alice!'\n\n>>> sm.send(\"greet\", \"Bob\", greeting=\"Hi\")\n'Hi, Bob!'\n\n```\n\n```{seealso}\nAll actions and {ref}`conditions <validators and guards>` support the same\ndependency injection mechanism. See {ref}`validators and guards` for how it\napplies to guards.\n```\n\n\n## Binding actions\n\nThere are three ways to attach an action to a state or transition: **naming\nconventions**, **inline parameters**, and **decorators**. All three can be\ncombined — the priority rules above determine execution order.\n\n\n(state-actions)=\n\n### State actions\n\nStates support `enter` and `exit` callbacks.\n\n**Naming convention** — define a method matching `on_enter_<state_id>()` or\n`on_exit_<state_id>()`:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class LoginFlow(StateChart):\n...     idle = State(initial=True)\n...     logged_in = State(final=True)\n...\n...     login = idle.to(logged_in)\n...\n...     def on_enter_logged_in(self):\n...         print(\"session started\")\n\n>>> sm = LoginFlow()\n>>> sm.send(\"login\")\nsession started\n\n```\n\n**Inline parameter** — pass callback names to the `State` constructor:\n\n```py\n>>> class LoginFlow(StateChart):\n...     idle = State(initial=True)\n...     logged_in = State(final=True, enter=\"start_session\")\n...\n...     login = idle.to(logged_in)\n...\n...     def start_session(self):\n...         print(\"session started\")\n\n>>> sm = LoginFlow()\n>>> sm.send(\"login\")\nsession started\n\n```\n\n**Decorator** — use `@state.enter` or `@state.exit`:\n\n```py\n>>> class LoginFlow(StateChart):\n...     idle = State(initial=True)\n...     logged_in = State(final=True)\n...\n...     login = idle.to(logged_in)\n...\n...     @logged_in.enter\n...     def start_session(self):\n...         print(\"session started\")\n\n>>> sm = LoginFlow()\n>>> sm.send(\"login\")\nsession started\n\n```\n\nStates also support `invoke` callbacks — background work that is spawned when\nthe state is entered and automatically cancelled when the state is exited.\nInvoke supports the same three binding patterns (naming convention, inline,\ndecorator) and has its own completion and cancellation lifecycle.\n\n```{seealso}\nSee {ref}`invoke` for the full invoke reference: execution model, binding\npatterns, `done.invoke` transitions, cancellation, error handling, grouped\ninvokes, the `IInvoke` protocol, and child state machines.\n```\n\n\n(transition-actions)=\n\n### Transition actions\n\nTransitions support `before`, `on`, and `after` callbacks.\n\n**Naming convention** — define a method matching `before_<event>()`,\n`on_<event>()`, or `after_<event>()`. The callback is called for every\ntransition triggered by that event:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class Turnstile(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State()\n...\n...     coin = locked.to(unlocked)\n...     push = unlocked.to(locked)\n...\n...     def on_coin(self):\n...         return \"accepted\"\n...\n...     def after_push(self):\n...         print(\"gate closed\")\n\n>>> sm = Turnstile()\n>>> sm.send(\"coin\")\n'accepted'\n\n>>> sm.send(\"push\")\ngate closed\n\n```\n\n**Inline parameter** — pass callback names to the transition constructor:\n\n```py\n>>> class Turnstile(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State()\n...\n...     coin = locked.to(unlocked, on=\"accept_coin\")\n...     push = unlocked.to(locked, after=\"close_gate\")\n...\n...     def accept_coin(self):\n...         return \"accepted\"\n...\n...     def close_gate(self):\n...         print(\"gate closed\")\n\n>>> sm = Turnstile()\n>>> sm.send(\"coin\")\n'accepted'\n\n>>> sm.send(\"push\")\ngate closed\n\n```\n\n**Decorator** — use `@event.before`, `@event.on`, or `@event.after`:\n\n```py\n>>> class Turnstile(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State()\n...\n...     coin = locked.to(unlocked)\n...     push = unlocked.to(locked)\n...\n...     @coin.on\n...     def accept_coin(self):\n...         return \"accepted\"\n...\n...     @push.after\n...     def close_gate(self):\n...         print(\"gate closed\")\n\n>>> sm = Turnstile()\n>>> sm.send(\"coin\")\n'accepted'\n\n>>> sm.send(\"push\")\ngate closed\n\n```\n\n#### Declaring an event with an inline action\n\nYou can declare an event and its `on` action in a single expression by using the\ntransition as a decorator:\n\n```py\n>>> class Turnstile(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State()\n...\n...     push = unlocked.to(locked)\n...\n...     @locked.to(unlocked)\n...     def coin(self):\n...         return \"accepted\"\n\n>>> sm = Turnstile()\n>>> sm.send(\"coin\")\n'accepted'\n\n```\n\nThe resulting `coin` attribute is an {ref}`Event <events>`, not a plain method —\nit only executes when the machine is in a state where a matching transition\nexists.\n\n\n## Generic callbacks\n\nGeneric callbacks run on **every** transition, regardless of which event or\nstate is involved. They follow the same group ordering and are useful for\ncross-cutting concerns like logging or auditing:\n\n```py\n>>> class Audited(StateChart):\n...     idle = State(initial=True)\n...     active = State(final=True)\n...\n...     start = idle.to(active)\n...\n...     def before_transition(self, event, source):\n...         print(f\"about to transition from {source.id} on {event}\")\n...\n...     def on_enter_state(self, target, event):\n...         print(f\"entered {target.id} on {event}\")\n...\n...     def after_transition(self, event, source, target):\n...         print(f\"completed {source.id} → {target.id} on {event}\")\n\n>>> sm = Audited()\nentered idle on __initial__\n\n>>> sm.send(\"start\")\nabout to transition from idle on start\nentered active on start\ncompleted idle → active on start\n\n```\n\nThe full list of generic callbacks:\n\n| Callback | Group | Description |\n|---|---|---|\n| `before_transition()` | Before | Runs before any state change. |\n| `on_exit_state()` | Exit | Runs when leaving any state. |\n| `on_transition()` | On | Runs during any transition. |\n| `on_enter_state()` | Enter | Runs when entering any state. |\n| `on_invoke_state()` | Invoke | Runs when spawning invoke handlers for any state. See {ref}`invoke`. |\n| `after_transition()` | After | Runs after all state changes. |\n\n```{note}\n`prepare_event()` is also a generic callback, but it serves a special purpose —\nsee {ref}`preparing-events` below.\n```\n\n```{tip}\nGeneric callbacks are the building blocks for {ref}`listeners <listeners>` — an\nexternal object that implements the same callback signatures can observe every\ntransition without modifying the state machine class.\n```\n\n\n(preparing-events)=\n\n## Preparing events\n\nThe `prepare_event` callback runs **before validators and conditions** and has a\nunique capability: its return value (a `dict`) is merged into the keyword\narguments available to all subsequent callbacks in the same microstep.\n\nThis is useful for enriching events with computed context — for example, looking\nup a user record from an ID before the transition runs:\n\n```py\n>>> class OrderFlow(StateChart):\n...     pending = State(initial=True)\n...     confirmed = State(final=True)\n...\n...     confirm = pending.to(confirmed)\n...\n...     def prepare_event(self, order_id=None):\n...         if order_id is not None:\n...             return {\"order_total\": order_id * 10}\n...         return {}\n...\n...     def on_confirm(self, order_total=0):\n...         return f\"confirmed ${order_total}\"\n\n>>> sm = OrderFlow()\n>>> sm.send(\"confirm\", order_id=5)\n'confirmed $50'\n\n```\n\n\n## Return values\n\nThe return values from `before` and `on` callbacks are collected into a list\nand returned to the caller. Other groups (`exit`, `enter`, `after`) do not\ncontribute to the return value.\n\n```py\n>>> class ReturnExample(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...\n...     go = a.to(b)\n...\n...     def before_go(self):\n...         return \"before\"\n...\n...     def on_go(self):\n...         return \"on\"\n...\n...     def on_enter_b(self):\n...         return \"enter (ignored)\"\n...\n...     def after_go(self):\n...         return \"after (ignored)\"\n\n>>> sm = ReturnExample()\n>>> sm.send(\"go\")\n['before', 'on']\n\n```\n\nWhen only one callback returns a value, the result is unwrapped (not a list):\n\n```py\n>>> class SingleReturn(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...\n...     go = a.to(b, on=\"do_it\")\n...\n...     def do_it(self):\n...         return 42\n\n>>> sm = SingleReturn()\n>>> sm.send(\"go\")\n42\n\n```\n\nWhen no callback returns a value, the result is `None`:\n\n```py\n>>> class NoReturn(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...\n...     go = a.to(b)\n\n>>> sm = NoReturn()\n>>> sm.send(\"go\") is None\nTrue\n\n```\n\n```{note}\nIf a callback is defined but returns `None` explicitly, it is included in the\nresult list. Only callbacks that are not defined at all are excluded.\n```\n"
  },
  {
    "path": "docs/api.md",
    "content": "# API\n\n## StateChart\n\n```{versionadded} 3.0.0\n```\n\n```{eval-rst}\n.. autoclass:: statemachine.statemachine.StateChart\n    :members:\n    :undoc-members:\n```\n\n## StateMachine\n\n```{eval-rst}\n.. autoclass:: statemachine.statemachine.StateMachine\n    :members:\n    :undoc-members:\n```\n\n## State\n\n```{seealso}\n{ref}`States` reference.\n```\n\n\n```{eval-rst}\n.. autoclass:: statemachine.state.State\n    :members:\n```\n\n## HistoryState\n\n```{versionadded} 3.0.0\n```\n\n```{eval-rst}\n.. autoclass:: statemachine.state.HistoryState\n    :members:\n```\n\n## States (class)\n\n```{eval-rst}\n.. autoclass:: statemachine.states.States\n    :noindex:\n    :members:\n```\n\n## Transition\n\n```{seealso}\n{ref}`Transitions` reference.\n```\n\n```{eval-rst}\n.. autoclass:: statemachine.transition.Transition\n    :members:\n```\n\n## TransitionList\n\n```{eval-rst}\n.. autoclass:: statemachine.transition_list.TransitionList\n    :members:\n```\n\n## Model\n\n```{seealso}\n{ref}`Domain models` reference.\n```\n\n\n```{eval-rst}\n.. autoclass:: statemachine.model.Model\n    :members:\n```\n\n## TriggerData\n\n\n```{eval-rst}\n.. autoclass:: statemachine.event_data.TriggerData\n    :members:\n```\n\n## Event\n\n```{eval-rst}\n.. autoclass:: statemachine.event.Event\n    :members: id, name, __call__\n```\n\n## EventData\n\n```{eval-rst}\n.. autoclass:: statemachine.event_data.EventData\n    :members:\n```\n\n## Callback conventions\n\nThese are convention-based callbacks that you can define on your state machine\nsubclass. They are not methods on the base class — define them in your subclass\nto enable the behavior.\n\n### `prepare_event`\n\nCalled before every event is processed. Returns a `dict` of keyword arguments\nthat will be merged into `**kwargs` for all subsequent callbacks (guards, actions,\nentry/exit handlers) during that event's processing:\n\n```python\nclass MyMachine(StateChart):\n    initial = State(initial=True)\n    loop = initial.to.itself()\n\n    def prepare_event(self):\n        return {\"request_id\": generate_id()}\n\n    def on_loop(self, request_id):\n        # request_id is available here\n        ...\n```\n\n## MachineMixin\n\n```{seealso}\n{ref}`Integrations <machinemixin>` for usage examples.\n```\n\n```{eval-rst}\n.. autoclass:: statemachine.mixins.MachineMixin\n    :members:\n    :undoc-members:\n```\n\n## create_machine_class_from_definition\n\n```{versionadded} 3.0.0\n```\n\n```{eval-rst}\n.. autofunction:: statemachine.io.create_machine_class_from_definition\n```\n\n## timeout\n\n```{versionadded} 3.0.0\n```\n\n```{seealso}\n{ref}`timeout` how-to guide.\n```\n\n```{eval-rst}\n.. autofunction:: statemachine.contrib.timeout.timeout\n```\n"
  },
  {
    "path": "docs/async.md",
    "content": "(async)=\n# Async support\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nThe public API is the same for synchronous and asynchronous code. If the\nstate machine has at least one `async` callback, the engine switches to\n{ref}`AsyncEngine <asyncengine>` automatically — no configuration needed.\n\nAll statechart features — compound states, parallel states, history\npseudo-states, eventless transitions, `done.state` events — work\nidentically in both engines.\n\n\n## Writing async callbacks\n\nDeclare any callback as `async def` and the engine handles the rest:\n\n```py\n>>> class AsyncStateMachine(StateChart):\n...     initial = State(\"Initial\", initial=True)\n...     final = State(\"Final\", final=True)\n...\n...     keep = initial.to.itself(internal=True)\n...     advance = initial.to(final)\n...\n...     async def on_advance(self):\n...         return 42\n\n>>> async def run_sm():\n...     sm = AsyncStateMachine()\n...     result = await sm.advance()\n...     print(f\"Result is {result}\")\n...     print(list(sm.configuration_values))\n\n>>> asyncio.run(run_sm())\nResult is 42\n['final']\n\n```\n\n### Using from synchronous code\n\nThe same state machine can be used from a synchronous context — even\nwithout a running `asyncio` loop. The engine creates one internally\nwith `asyncio.new_event_loop()` and awaits callbacks using\n`loop.run_until_complete()`:\n\n```py\n>>> sm = AsyncStateMachine()\n>>> result = sm.advance()\n>>> print(f\"Result is {result}\")\nResult is 42\n>>> print(list(sm.configuration_values))\n['final']\n\n```\n\n\n(initial state activation)=\n\n## Initial state activation\n\nIn async code, Python cannot `await` during `__init__`, so the initial\nstate is **not** activated at instantiation time. If you inspect\n`configuration` immediately after creating the instance, it won't reflect\nthe initial state:\n\n```py\n>>> async def show_problem():\n...     sm = AsyncStateMachine()\n...     print(list(sm.configuration_values))\n\n>>> asyncio.run(show_problem())\n[]\n\n```\n\nTo fix this, explicitly await\n{func}`activate_initial_state() <statemachine.StateChart.activate_initial_state>`\nbefore inspecting the configuration:\n\n```py\n>>> async def correct_init():\n...     sm = AsyncStateMachine()\n...     await sm.activate_initial_state()\n...     print(list(sm.configuration_values))\n\n>>> asyncio.run(correct_init())\n['initial']\n\n```\n\n```{tip}\nIf you don't inspect the configuration before sending the first event,\nyou can skip this step — the first `send()` activates the initial state\nautomatically.\n```\n\n```py\n>>> async def auto_activate():\n...     sm = AsyncStateMachine()\n...     await sm.keep()  # activates initial state before handling the event\n...     print(list(sm.configuration_values))\n\n>>> asyncio.run(auto_activate())\n['initial']\n\n```\n\n\n## Concurrent event sending\n\nA benefit exclusive to the async engine: when multiple coroutines send\nevents concurrently (e.g., via `asyncio.gather`), each caller receives\nits own event's result — even though only one coroutine runs the\nprocessing loop at a time. The sync engine does not support this pattern.\n\n```py\n>>> class ConcurrentSC(StateChart):\n...     s1 = State(initial=True)\n...     s2 = State()\n...     s3 = State(final=True)\n...\n...     step1 = s1.to(s2)\n...     step2 = s2.to(s3)\n...\n...     async def on_step1(self):\n...         return \"result_1\"\n...\n...     async def on_step2(self):\n...         return \"result_2\"\n\n>>> async def run_concurrent():\n...     import asyncio as _asyncio\n...     sm = ConcurrentSC()\n...     await sm.activate_initial_state()\n...     r1, r2 = await _asyncio.gather(\n...         sm.send(\"step1\"),\n...         sm.send(\"step2\"),\n...     )\n...     return r1, r2\n\n>>> asyncio.run(run_concurrent())\n('result_1', 'result_2')\n\n```\n\nUnder the hood, the async engine attaches an `asyncio.Future` to each\nexternally enqueued event. The coroutine that acquires the processing lock\nresolves each event's future as it processes the queue. Callers that didn't\nacquire the lock simply `await` their future.\n\n```{note}\nFutures are only created for **external** events sent from outside the\nprocessing loop. Events triggered from within callbacks (via `send()` or\n`raise_()`) follow the {ref}`run-to-completion <rtc-model>` model — they\nare enqueued and processed within the current macrostep.\n```\n\nIf an exception occurs during processing (with `catch_errors_as_events=False`),\nthe exception is routed to the caller whose event caused it. Other callers\nwhose events were still pending will also receive the exception, since the\nprocessing loop clears the queue on failure.\n\n\n(syncengine)=\n(asyncengine)=\n\n## Engine selection\n\nThe engine is selected automatically when the state machine is\ninstantiated, based on the registered callbacks:\n\n| Outer scope | Async callbacks? | Engine | Event loop |\n|---|---|---|---|\n| Sync | No | SyncEngine | None |\n| Sync | Yes | AsyncEngine | Creates internal loop |\n| Async | No | SyncEngine | None |\n| Async | Yes | AsyncEngine | Reuses running loop |\n\n**Outer scope** is the context where the state machine instance is created.\n**Async callbacks** means at least one `async def` callback or condition is\ndeclared on the machine, its model, or its listeners.\n\n```{note}\nAll callbacks run on the same thread they are called from. Mixing\nsynchronous and asynchronous code is supported but requires care —\navoid sharing a state machine instance across threads without external\nsynchronization.\n```\n\n\n```{seealso}\nSee {ref}`processing model <macrostep-microstep>` for how the engine\nprocesses events, and {ref}`behaviour` for the behavioral attributes\nthat affect processing.\n```\n"
  },
  {
    "path": "docs/authors.md",
    "content": "# Credits\n\n## Development Lead\n\n* [Fernando Macedo](mailto:fgmacedo@gmail.com)\n\n## Contributors\n\n* [Guilherme Nepomuceno](mailto:piercio@loggi.com)\n* [Rafael Rêgo](mailto:crafards@gmail.com)\n* [Raphael Schrader](mailto:raphael@schradercloud.de)\n* [João S. O. Bueno](mailto:gwidion@gmail.com)\n* [Rodrigo Nogueira](mailto:rodrigo.b.nogueira@gmail.com)\n\n\n## Scaffolding\n\nThis package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the\n[audreyr/cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage) project template.\n"
  },
  {
    "path": "docs/behaviour.md",
    "content": "(behaviour)=\n(statecharts)=\n\n# Behaviour\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nThe {class}`~statemachine.statemachine.StateChart` class follows the\n[SCXML specification](https://www.w3.org/TR/scxml/) by default. The\n{class}`~statemachine.statemachine.StateMachine` class extends `StateChart`\nbut overrides several defaults to preserve backward compatibility with\npre-3.0 code.\n\nThe behavioral differences are controlled by class-level attributes. This\ndesign allows a gradual upgrade path: start from `StateMachine` and\nselectively enable spec-compliant behaviors one at a time, or start from\n`StateChart` and get full SCXML compliance out of the box.\n\n```{tip}\nWe **strongly recommend** that new projects use `StateChart` directly.\nExisting projects should consider migrating when possible, as the\nSCXML-compliant behavior provides more predictable semantics.\n```\n\n\n## Comparison table\n\n| Attribute | `StateChart` | `StateMachine` | Description |\n|---|---|---|---|\n| `allow_event_without_transition` | `True` | `False` | Tolerate events that don't match any transition |\n| `enable_self_transition_entries` | `True` | `False` | Execute entry/exit actions on self-transitions |\n| `atomic_configuration_update` | `False` | `True` | When to update {ref}`configuration <querying-configuration>` during a microstep |\n| `catch_errors_as_events` | `True` | `False` | Catch action errors as `error.execution` events |\n\nEach attribute is described below, with cross-references to the pages that\ncover the topic in depth.\n\n\n## `allow_event_without_transition`\n\nWhen `True` (SCXML default), sending an event that does not match any enabled\ntransition is silently ignored. When `False` (legacy default), a\n`TransitionNotAllowed` exception is raised, including for unknown event names.\n\nThe SCXML spec requires tolerance to unmatched events, as the event-driven\nmodel expects that not every event is relevant in every state.\n\n```{seealso}\nSee {ref}`conditions` for how the engine selects transitions, and\n{ref}`checking enabled events` to query which events are currently valid.\n```\n\n\n## `enable_self_transition_entries`\n\nWhen `True` (SCXML default), a {ref}`self-transition <self-transition>`\nexecutes the state's exit and entry actions, just like any other transition.\nWhen `False` (legacy default), self-transitions skip entry/exit actions.\n\nThe SCXML spec treats self-transitions as regular transitions that happen to\nreturn to the same state, so entry/exit actions must fire. Use an\n{ref}`internal transition <internal-transition>` if you need a transition that\nstays in the same state **without** running exit/entry actions.\n\n```{seealso}\nSee {ref}`transitions` for the full reference on self-transitions and\ninternal transitions.\n```\n\n\n## `atomic_configuration_update`\n\nControls **when** the {ref}`configuration <querying-configuration>` is\nupdated during a microstep.\n\nWhen `False` (SCXML default), the configuration reflects each phase as it\nhappens: states are removed during exit and added during entry. This means\nthat during transition `on` callbacks, the configuration may be empty or\npartial — the source states have already been exited but the target states\nhave not yet been entered.\n\nWhen `True` (legacy default), the configuration is updated atomically\n**after** the `on` callbacks complete, so `sm.configuration` and\n`state.is_active` always reflect a consistent snapshot during the transition.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class AtomicDemo(StateChart):\n...     atomic_configuration_update = True\n...     off = State(initial=True)\n...     on = State(final=True)\n...\n...     switch = off.to(on, on=\"check_config\")\n...\n...     def check_config(self):\n...         # With atomic update, configuration is unchanged during 'on'\n...         self.off_was_active = self.off.is_active\n...         self.on_was_active = self.on.is_active\n\n>>> sm = AtomicDemo()\n>>> sm.send(\"switch\")\n>>> sm.off_was_active  # source still in configuration during 'on'\nTrue\n>>> sm.on_was_active  # target not yet in configuration during 'on'\nFalse\n\n```\n\nWith `atomic_configuration_update = False` (the SCXML default), the result\nis different — `off.is_active` is `False` because exit already removed it,\nand `on.is_active` is also `False` because entry hasn't added it yet.\nIn this mode, use `previous_configuration` and `new_configuration` to\ninspect the full picture:\n\n```py\n>>> class SCXMLDemo(StateChart):\n...     off = State(initial=True)\n...     on = State(final=True)\n...\n...     switch = off.to(on, on=\"check_config\")\n...\n...     def check_config(self, previous_configuration, new_configuration):\n...         self.prev = {s.id for s in previous_configuration}\n...         self.new = {s.id for s in new_configuration}\n\n>>> sm = SCXMLDemo()\n>>> sm.send(\"switch\")\n>>> sm.prev\n{'off'}\n>>> sm.new\n{'on'}\n\n```\n\n```{seealso}\nSee {ref}`dependency-injection` for the full list of parameters available\nin callbacks.\n```\n\n\n## `catch_errors_as_events`\n\nWhen `True` (SCXML default), runtime exceptions in action callbacks\n(entry/exit, transition `on`) are caught by the engine and dispatched as\ninternal `error.execution` events. When `False` (legacy default), exceptions\npropagate normally to the caller.\n\n```{note}\n{ref}`Validators <validators>` are **not** affected by this flag — they\nalways propagate exceptions to the caller, regardless of the\n`catch_errors_as_events` setting. See {ref}`validators` for details.\n```\n\n```{seealso}\nSee {ref}`error-handling` for the full `error.execution` lifecycle,\nblock-level error catching, and the cleanup/finalize pattern.\n```\n\n\n## Gradual migration\n\nAll behavioral attributes can be overridden individually. This lets you\nadopt SCXML semantics incrementally in an existing `StateMachine`:\n\n```python\nclass MyMachine(StateMachine):\n    catch_errors_as_events = True\n    # ... everything else behaves as before ...\n```\n\nOr keep a specific legacy behavior while using `StateChart` for the rest:\n\n```python\nclass MyChart(StateChart):\n    atomic_configuration_update = True\n    # ... SCXML-compliant otherwise ...\n```\n\n```{seealso}\nSee [](releases/upgrade_2x_to_3.md) for a complete migration guide from\n`StateMachine` 2.x to `StateChart` 3.x.\n```\n"
  },
  {
    "path": "docs/concepts.md",
    "content": "(concepts)=\n\n# Core concepts\n\nA statechart organizes behavior around **states**, **transitions**, and\n**events**. Together they describe *when* the system can change, *what*\ntriggers the change, and *what happens* as a result.\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class Turnstile(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State()\n...\n...     coin = locked.to(unlocked, on=\"thank_you\")\n...     push = unlocked.to(locked)\n...\n...     def thank_you(self):\n...         return \"Welcome!\"\n\n>>> sm = Turnstile()\n>>> sm.coin()\n'Welcome!'\n\n```\n\nEven in this minimal example, the core concepts appear:\n\n| Concept | What it is | Declared as |\n|---|---|---|\n| {ref}`StateChart <statechart>` | The container and runtime for the machine | `class MyChart(StateChart)` |\n| {ref}`State <states>` | A mode or condition of the system | `State()`, `State.Compound`, `State.Parallel` |\n| {ref}`Transition <transitions>` | A link from source state to target state | `source.to(target)`, `target.from_(source)` |\n| {ref}`Event <events>` | A signal that triggers transitions | Class-level assignment or `Event(...)` |\n| {ref}`Action <actions>` | A side-effect during state changes | `on`, `before`, `after`, `enter`, `exit` callbacks |\n| {ref}`Condition <conditions>` | A guard that allows/blocks a transition | `cond`, `unless`, `validators` parameters |\n| {ref}`Listener <listeners>` | An external observer of the lifecycle | `listeners = [...]` class attribute |\n\nEach concept below introduces the idea briefly; follow the \"See also\" links\nfor the full reference. Listeners are covered in {ref}`their own page <listeners>`.\n\n(concepts-statechart)=\n\n## StateChart\n\nA {ref}`StateChart <statechart>` is the container for states, transitions,\nand events. It defines the topology (which states exist and how they\nconnect) and provides the runtime API — sending events, querying the\ncurrent configuration, and managing listeners.\n\nIn the turnstile example, `Turnstile` is the `StateChart`. After\ninstantiation, `sm` holds the runtime state and exposes methods like\n`sm.send(\"coin\")`, `sm.configuration`, and `sm.allowed_events`.\n\n```{seealso}\nSee [](statechart.md) for the full reference: creating instances, sending\nevents, querying configuration, checking termination, and managing\nlisteners at runtime.\n```\n\n\n(concepts-states)=\n\n## States\n\nA **state** describes what the system is doing right now. At any point in\ntime, a statechart is \"in\" one or more states — the **configuration**. States\ndetermine which transitions are available and which events are accepted.\n\nIn the turnstile example, `locked` and `unlocked` are the two possible\nstates. The machine starts in `locked` (its **initial state**) and can only\nreach `unlocked` when the `coin` event fires.\n\n```{seealso}\nSee [](states.md) for the full reference: initial and final states, compound\n(nested) states, parallel regions, history pseudo-states, and more.\n```\n\n\n(concepts-transitions)=\n\n## Transitions\n\nA **transition** is a link between a **source** state and a **target** state.\nWhen a transition fires, the system leaves the source and enters the target.\nTransitions can carry {ref}`actions <actions>` (side-effects) and\n{ref}`conditions <conditions>` (guards that must be satisfied).\n\nIn the turnstile, `locked.to(unlocked)` is a transition: it moves the system\nfrom `locked` to `unlocked` and runs the `thank_you` action along the way.\n\n```{seealso}\nSee [](transitions.md) for the full reference: declaring transitions,\nself-transitions, internal transitions, eventless (automatic) transitions,\nand more.\n```\n\n\n(concepts-events)=\n\n## Events\n\nAn **event** is a signal that something has happened. Events trigger\ntransitions — without an event, a transition will not fire (unless it is\nan {ref}`eventless <eventless>` transition with a guard condition).\n\nIn the turnstile, `coin` and `push` are events. When you call `sm.coin()` or\n`sm.send(\"coin\")`, the engine looks for a matching transition from the current\nstate and fires it. Events are processed following a **run-to-completion**\nmodel — each event is fully handled before the next one starts.\n\n```{seealso}\nSee [](events.md) for the full reference: declaring, triggering, scheduling,\nand naming conventions. See [](processing_model.md) for how macrosteps and\nmicrosteps work under the hood.\n```\n\n\n(concepts-actions)=\n\n## Actions\n\nAn **action** is a side-effect that runs during a transition or on\nentry/exit of a state. Actions are how the statechart interacts with the\noutside world — sending notifications, updating a database, logging,\nor returning a value.\n\nIn the turnstile, `thank_you` is an action attached to the `coin` transition\nvia the `on` parameter.\n\n```{seealso}\nSee [](actions.md) for the full reference: callback naming conventions,\nexecution order, dependency injection, and all available hooks.\n```\n\n\n(concepts-conditions)=\n\n## Conditions\n\nA **condition** (also called a **guard**) is a predicate that must evaluate\nto `True` for a transition to fire. A **validator** is similar but raises an\nexception to block the transition instead of silently preventing it.\n\nConditions let you have multiple transitions for the same event, each with a\ndifferent guard — the first one that passes wins.\n\n```{seealso}\nSee [](guards.md) for the full reference: `cond`, `unless`, `validators`,\nboolean expressions, and checking enabled events.\n```\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python\n#\n# statemachine documentation build configuration file, created by\n# sphinx-quickstart on Tue Jul  9 22:26:36 2013.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\nimport os\nimport sys\n\nfrom sphinx_gallery import gen_gallery\n\n# If extensions (or modules to document with autodoc) are in another\n# directory, add these directories to sys.path here. If the directory is\n# relative to the documentation root, use os.path.abspath to make it\n# absolute, like shown here.\n# sys.path.insert(0, os.path.abspath('.'))\n\n# Get the project root dir, which is the parent dir of this\ncwd = os.getcwd()\nproject_root = os.path.dirname(cwd)\n\n# Insert the project root dir as the first element in the PYTHONPATH.\n# This lets us ensure that the source package is imported, and that its\n# version is used.\nsys.path.insert(0, project_root)\n\nfrom tests.scrape_images import MachineScraper  # noqa: E402\n\nimport statemachine  # noqa: E402\n\n# -- General configuration ---------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"myst_parser\",\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.napoleon\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx.ext.autosectionlabel\",\n    \"sphinx_gallery.gen_gallery\",\n    \"sphinx_copybutton\",\n    \"statemachine.contrib.diagram.sphinx_ext\",\n    \"sphinxcontrib.mermaid\",\n]\n\nautosectionlabel_prefix_document = True\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"Python State Machine\"\ncopyright = \"2024, Fernando Macedo\"\n\n# The version info for the project you're documenting, acts as replacement\n# for |version| and |release|, also used in various other places throughout\n# the built documents.\n#\n# The short X.Y version.\nversion = statemachine.__version__\n# The full version, including alpha/beta/rc tags.\nrelease = statemachine.__version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to\n# some non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = [\"_build\", \"examples/.ipynb_checkpoints\", \"*.ipynb\"]\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built\n# documents.\n# keep_warnings = False\n\n\n# -- Options for HTML output -------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"furo\"\n# https://pradyunsg.me/furo/\n\n# Theme options are theme-specific and customize the look and feel of a\n# theme further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {}\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as\n# html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the\n# top of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon\n# of the docs.  This file should be a Windows icon file (.ico) being\n# 16x16 or 32x32 pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets)\n# here, relative to this directory. They are copied after the builtin\n# static files, so a file named \"default.css\" will overwrite the builtin\n# \"default.css\".\nhtml_static_path = [\"_static\"]\n\nhtml_css_files = [\n    \"custom_machine.css\",\n]\n\nhtml_js_files = [\n    \"https://buttons.github.io/buttons.js\",\n]\n\nhtml_title = f\"python-statemachine {release}\"\nhtml_logo = \"images/python-statemachine.png\"\n\nhtml_copy_source = False\nhtml_show_sourcelink = False\n\nhtml_theme_options = {\n    \"navigation_with_keys\": True,\n    \"top_of_page_buttons\": [\"view\", \"edit\"],\n    \"source_repository\": \"https://github.com/fgmacedo/python-statemachine/\",\n    # \"source_branch\": \"develop\",\n    \"source_directory\": \"docs/\",\n}\n\npygments_style = \"monokai\"\npygments_dark_style = \"monokai\"\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page\n# bottom, using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names\n# to template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer.\n# Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer.\n# Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages\n# will contain a <link> tag referring to it.  The value of this option\n# must be the base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"statemachinedoc\"\n\n\n# Documents to append as an appendix to all manuals.\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n# texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n# texinfo_no_detailmenu = False\n\n# Napoleon settings (Google format )\nnapoleon_google_docstring = True\nnapoleon_numpy_docstring = True\nnapoleon_include_init_with_doc = True\nnapoleon_include_private_with_doc = False\nnapoleon_include_special_with_doc = True\nnapoleon_use_admonition_for_examples = True\nnapoleon_use_admonition_for_notes = False\nnapoleon_use_admonition_for_references = False\nnapoleon_use_ivar = False\nnapoleon_use_param = True\nnapoleon_use_rtype = True\nnapoleon_preprocess_types = False\nnapoleon_type_aliases = None\nnapoleon_attr_annotations = True\n\n# Markdown (MyST) configs\nmyst_heading_anchors = 3\nmyst_enable_extensions = [\"deflist\", \"substitution\"]\n\nmyst_substitutions = {\n    \"state\": \"{ref}`state`\",\n    \"event\": \"{ref}`event`\",\n}\n\n# Github\n\nhtml_context = {\n    \"display_github\": True,  # Integrate GitHub\n    \"github_user\": \"fgmacedo\",  # Username\n    \"github_repo\": \"python-statemachine\",  # Repo name\n    \"github_version\": \"develop\",  # Version\n    \"conf_py_path\": \"/docs/\",  # Path in the checkout to the docs root\n}\n\n# Sphinx Galery\nsphinx_gallery_conf = {\n    \"examples_dirs\": [\n        \"../tests/examples\",\n    ],  # path to your example scripts\n    \"gallery_dirs\": \"auto_examples\",  # path to where to save gallery generated output\n    \"capture_repr\": (\"_repr_html_\", \"__repr__\"),\n    \"filename_pattern\": r\"/.*\\_machine.py\",\n    \"download_all_examples\": False,\n    \"show_signature\": False,\n    \"min_reported_time\": 9999,\n    \"thumbnail_size\": (400, 280),\n    \"image_scrapers\": (MachineScraper(project_root),),\n    \"reset_modules\": [],\n}\n\n\ncopybutton_exclude = \".linenos, .gp, .go\"\n\n\ndef dummy_write_computation_times(gallery_conf, target_dir, costs):\n    \"patch gen_gallery to disable write_computation_times\"\n    pass\n\n\ngen_gallery.write_computation_times = dummy_write_computation_times\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "# Contributing\n\n* <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>\n* <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>\n* <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>\n\nContributions are welcome, and they are greatly appreciated! Every little bit helps, and credit\nwill always be given.\n\nYou can contribute in many ways:\n\n## Types of Contributions\n\n### Report Bugs\n\nReport bugs at [https://github.com/fgmacedo/python-statemachine/issues](https://github.com/fgmacedo/python-statemachine/issues).\n\nIf you are reporting a bug, please include:\n\n* Your operating system name and version.\n* Any details about your local setup that might be helpful in troubleshooting.\n* Detailed steps to reproduce the bug.\n\n### Fix Bugs\n\nLook through the GitHub issues for bugs. Anything tagged with \"bug\"\nand \"help wanted\" is open to whoever wants to implement it.\n\n### Implement Features\n\nLook through the GitHub issues for features. Anything tagged with \"enhancement\"\nand \"help wanted\" is open to whoever wants to implement it.\n\n### Write Documentation\n\nPython State Machine could always use more documentation, whether as part of the\nofficial Python State Machine docs, in docstrings, or even on the web in blog posts,\narticles, and such.\n\n### Add a translation\n\n\nExtract a `Portable Object Template` (`POT`) file:\n\n```shell\npybabel extract statemachine -o statemachine/locale/statemachine.pot\n```\n\nThen, 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`:\n\n```shell\ncp statemachine/locale/statemachine.pot statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po\n```\n\nThen open the `statemachine.po` and translate.\n\nAfter translation, to get the new language working locally, you need to compile the `.po` files into `.mo`  (binary format). Run:\n\n```shell\npybabel compile -d statemachine/locale/ -D statemachine\n```\n\n\nOn Linux (Debian based), you can test changing the `LANGUAGE` environment variable.\n\n```shell\n# If the last line is `Can't guess when in Won.` something went wrong.\nLANGUAGE=pt_BR python tests/examples/guess_the_number_machine.py\n```\n\nThen open a [pull request](https://github.com/fgmacedo/python-statemachine/pulls) with your translation file.\n\n### Submit Feedback\n\nThe best way to send feedback is to file an issue at https://github.com/fgmacedo/python-statemachine/issues.\n\nIf you are proposing a feature:\n\n* Explain in detail how it would work.\n* Keep the scope as narrow as possible, to make it easier to implement.\n* Remember that this is a volunteer-driven project, and that contributions\n  are welcome :)\n\n## Get Started!\n\nReady to contribute? Here's how to set up `python-statemachine` for local development.\n\n1. Install dependencies.\n   1. [graphviz](https://graphviz.org/download/#linux)\n   1. [uv](https://docs.astral.sh/uv/getting-started/installation/)\n\n1. Fork the `python-statemachine` repository on GitHub.\n\n1. Clone the forked repository to your local machine by running::\n\n        git clone https://github.com/YOUR-USERNAME/python-statemachine.git.\n\n\n1. Run `uv sync` once to install all the development dependencies and create a virtual environment::\n\n        uv sync --all-extras\n\n2. Install the pre-commit validations:\n\n        pre-commit install\n\n3. Create a branch for local development:\n\n        git checkout -b <name-of-your-bugfix-or-feature>\n\n4. Make changes to the code.\n\n5. Run tests to ensure they pass by running:\n\n        uv run pytest\n\n6. Update the documentation as needed.\n\n    Build the documentation:\n\n        uv run sphinx-build docs docs/_build/html\n\n\n    Now you can serve the local documentation using a webserver, like the built-in included\n    with python:\n\n        python -m http.server --directory docs/_build/html\n\n    And access your browser at http://localhost:8000/\n\n    If you're specially writting documentation, I strongly recommend using `sphinx-autobuild`\n    as it improves the workflow watching for file changes and with live reloading:\n\n        uv run sphinx-autobuild docs docs/_build/html --re-ignore \"auto_examples/.*\"\n\n    Sometimes you need a full fresh of the files being build for docs, you can safely remove\n    all automatically generated files to get a clean state by running:\n\n        rm -rf docs/_build/ docs/auto_examples\n\n1. Commit your changes and push them to your forked repository:\n\n        git add -A .\n        git commit -s -m \"Your detailed description of your changes.\"\n        git push origin name-of-your-bugfix-or-feature\n\n1. Create a pull request on the original repository for your changes to be reviewed and potentially\nmerged. Be sure to follow the project's code of conduct and contributing guidelines.\n\n1. Use `exit` to leave the virtual environment.\n\n## Pull Request Guidelines\n\nBefore you submit a pull request, check that it meets these guidelines:\n\n1. The pull request should include tests.\n2. If the pull request adds functionality, the docs should be updated. Put\n   your new functionality into a function with a docstring, and add the\n   feature to the list in the next release notes.\n3. Consider adding yourself to the contributor's list.\n4. The pull request should work for all supported Python versions.\n\n## Releasing a New Version\n\nThis project uses [git-flow](https://github.com/nvie/gitflow) for release management and\npublishes to PyPI automatically via GitHub Actions when a version tag is pushed.\n\n### Prerequisites\n\n- You must be on the `develop` branch with a clean working tree.\n- `git-flow` must be installed and initialized:\n\n  ```shell\n  brew install git-flow   # macOS\n  git flow init           # use main for production, develop for next release\n  ```\n\n- All changes intended for the release must already be merged into `develop`.\n\n### Step-by-step release process\n\nThe following steps use version `X.Y.Z` as a placeholder. Replace it with the actual version\nnumber (e.g., `2.6.0`).\n\n#### 1. Start the release branch\n\n```shell\ngit checkout develop\ngit pull origin develop\ngit flow release start X.Y.Z\n```\n\nThis creates and switches to a `release/X.Y.Z` branch based on `develop`.\n\n#### 2. Bump the version number\n\nUpdate the version string in **both** files:\n\n- `pyproject.toml` — the `version` field under `[project]`\n- `statemachine/__init__.py` — the `__version__` variable\n\n#### 3. Update translations\n\nExtract new translatable strings, merge them into all existing `.po` files, translate the\nnew entries, and compile:\n\n```shell\nuv run pybabel extract statemachine -o statemachine/locale/statemachine.pot\nuv run pybabel update -i statemachine/locale/statemachine.pot -d statemachine/locale/ -D statemachine\n# Edit each .po file to translate new empty msgstr entries\nuv run pybabel compile -d statemachine/locale/ -D statemachine\n```\n\n```{note}\nThe `.pot` and `.mo` files are git-ignored. Only the `.po` source files are committed.\nThe compiled `.mo` files may cause test failures if your system locale matches a translated\nlanguage (error messages will appear translated instead of in English). Delete them after\nverifying translations work: `rm -f statemachine/locale/*/LC_MESSAGES/statemachine.mo`\n```\n\n#### 4. Write release notes\n\nCreate `docs/releases/X.Y.Z.md` documenting all changes since the previous release. Include\nsections for new features, bugfixes, performance improvements, and miscellaneous changes.\nReference GitHub issues/PRs where applicable.\n\nAdd the new file to the toctree in `docs/releases/index.md` (at the top of the appropriate\nmajor version section).\n\nUpdate any related documentation pages (e.g., if a bugfix adds a new behavior that users\nshould know about).\n\n#### 5. Run linters and tests\n\n```shell\nuv run ruff check .\nuv run ruff format --check .\nuv run mypy statemachine/\nuv run pytest -n auto\n```\n\nAll checks must pass before committing.\n\n#### 6. Commit\n\nStage all changed files and commit. The pre-commit hooks will run ruff, mypy, and pytest\nautomatically.\n\n```shell\ngit add <files>\ngit commit -m \"chore: prepare release X.Y.Z\"\n```\n\n#### 7. Finish the release\n\n```shell\ngit flow release finish X.Y.Z -m \"vX.Y.Z\"\n```\n\nThis will:\n- Merge `release/X.Y.Z` into `main`\n- Create an annotated tag `X.Y.Z` on `main`\n- Merge `main` back into `develop`\n- Delete the `release/X.Y.Z` branch\n\n```{note}\nIf tagging fails (e.g., GPG or editor issues), create the tag manually and re-run:\n`git tag -a X.Y.Z -m \"vX.Y.Z\"` then `git flow release finish X.Y.Z -m \"vX.Y.Z\"`.\n```\n\n#### 8. Update the `latest` tag and push\n\n```shell\ngit tag latest -f\ngit push origin main develop --tags -f\n```\n\nForce-pushing tags is needed to move the `latest` tag.\n\n#### 9. Verify the release\n\nThe tag push triggers the `release` GitHub Actions workflow (`.github/workflows/release.yml`),\nwhich will:\n\n1. Check out the tag\n2. Run the full test suite\n3. Build the sdist and wheel with `uv build`\n4. Publish to PyPI using trusted publishing\n\nMonitor the workflow run at `https://github.com/fgmacedo/python-statemachine/actions` to\nconfirm the release was published successfully.\n"
  },
  {
    "path": "docs/diagram.md",
    "content": "(diagram)=\n(diagrams)=\n# Diagrams\n\nYou can generate visual diagrams from any\n{class}`~statemachine.statemachine.StateChart` — useful for documentation,\ndebugging, or sharing your machine's structure with teammates.\n\n```{statemachine-diagram} tests.examples.order_control_machine.OrderControl\n:target:\n```\n\n## Installation\n\nDiagram generation requires [pydot](https://github.com/pydot/pydot) and\n[Graphviz](https://graphviz.org/):\n\n```bash\npip install python-statemachine[diagrams]  # installs pydot\n```\n\nYou also need the `dot` command-line tool from Graphviz. On Debian/Ubuntu:\n\n```bash\nsudo apt install graphviz\n```\n\nFor other systems, see the [Graphviz downloads page](https://graphviz.org/download/).\n\n## Generating diagrams\n\nEvery state machine instance exposes a `_graph()` method that returns a\n[pydot.Dot](https://github.com/pydot/pydot) graph object:\n\n```python\nfrom tests.examples.order_control_machine import OrderControl\n\nsm = OrderControl()\ngraph = sm._graph()  # returns a pydot.Dot object\n```\n\n### Highlighting the current state\n\nThe diagram automatically highlights the current state of the instance.\nSend events to advance the machine and see the active state change:\n\n```python\nfrom tests.examples.traffic_light_machine import TrafficLightMachine\n\nsm = TrafficLightMachine()\nsm.send(\"cycle\")\nsm._graph().write_png(\"traffic_light_yellow.png\")\n```\n\n```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine\n:events: cycle\n:caption: TrafficLightMachine after one cycle\n```\n\n\n### Exporting to a file\n\nThe `pydot.Dot` object supports writing to many formats — use\n`write_png()`, `write_svg()`, `write_pdf()`, etc.:\n\n```python\nsm = OrderControl()\nsm._graph().write_png(\"order_control.png\")\n```\n\n```{statemachine-diagram} tests.examples.order_control_machine.OrderControl\n:caption: OrderControl\n```\n\nFor higher resolution PNGs, set the DPI before exporting:\n\n```python\ngraph = sm._graph()\ngraph.set_dpi(300).write_png(\"order_control_300dpi.png\")\n```\n\n```{note}\nSupported formats include `dia`, `dot`, `fig`, `gif`, `jpg`, `pdf`,\n`png`, `ps`, `svg`, and many others. See\n[Graphviz output formats](https://graphviz.org/docs/outputs/) for the\ncomplete list.\n```\n\n\n## Text representations\n\nState machines support multiple text-based output formats, all accessible\nthrough Python's built-in `format()` protocol, the `formatter` API, or\nthe command line.\n\n| Format | Aliases | Description | Dependencies |\n|--------|---------|-------------|--------------|\n| `mermaid` | | [Mermaid stateDiagram-v2](https://mermaid.js.org/syntax/stateDiagram.html) source | None [^mermaid] |\n| `md` | `markdown` | Transition table (pipe-delimited Markdown) | None |\n| `rst` | | Transition table (RST grid table) | None |\n| `dot` | | [Graphviz DOT](https://graphviz.org/doc/info/lang.html) language source | pydot |\n| `svg` | | SVG markup (generated via DOT) | pydot, Graphviz |\n\n[^mermaid]: Mermaid has a known rendering bug\n    ([mermaid-js/mermaid#4052](https://github.com/mermaid-js/mermaid/issues/4052))\n    where transitions targeting or originating from a compound state inside a\n    parallel region crash the renderer.  As a workaround, the `MermaidRenderer`\n    redirects such transitions to the compound's initial child state.  The\n    visual result is equivalent — Mermaid draws the arrow crossing into the\n    compound boundary — but the arrow points to the child rather than the\n    compound border.  This workaround will be revisited when the upstream bug\n    is resolved.\n\n\n### Using `format()`\n\nUse f-strings or the built-in `format()` function — no diagram imports needed:\n\n```py\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n>>> sm = TrafficLightMachine()\n>>> print(f\"{sm:mermaid}\")\nstateDiagram-v2\n    direction LR\n    state \"Green\" as green\n    state \"Yellow\" as yellow\n    state \"Red\" as red\n    [*] --> green\n    green --> yellow : Cycle\n    yellow --> red : Cycle\n    red --> green : Cycle\n<BLANKLINE>\n    classDef active fill:#40E0D0,stroke:#333\n    green:::active\n<BLANKLINE>\n\n>>> print(f\"{sm:md}\")\n| State  | Event | Guard | Target |\n| ------ | ----- | ----- | ------ |\n| Green  | Cycle |       | Yellow |\n| Yellow | Cycle |       | Red    |\n| Red    | Cycle |       | Green  |\n<BLANKLINE>\n\n```\n\nWorks on **classes** too (no active-state highlighting):\n\n```py\n>>> print(f\"{TrafficLightMachine:mermaid}\")\nstateDiagram-v2\n    direction LR\n    state \"Green\" as green\n    state \"Yellow\" as yellow\n    state \"Red\" as red\n    [*] --> green\n    green --> yellow : Cycle\n    yellow --> red : Cycle\n    red --> green : Cycle\n<BLANKLINE>\n\n```\n\nThe `dot` format returns the Graphviz DOT language source:\n\n```py\n>>> print(f\"{sm:dot}\")  # doctest: +ELLIPSIS\ndigraph TrafficLightMachine {\n...\n}\n\n```\n\nAn empty format spec (e.g., `f\"{sm:}\"`) falls back to `repr()`.\n\n\n(formatter-api)=\n### Using the `formatter` API\n\nThe `formatter` object is the programmatic entry point for rendering\nstate machines in any registered text format:\n\n```py\n>>> from statemachine.contrib.diagram import formatter\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n\n>>> print(formatter.render(TrafficLightMachine, \"mermaid\"))\nstateDiagram-v2\n    direction LR\n    state \"Green\" as green\n    state \"Yellow\" as yellow\n    state \"Red\" as red\n    [*] --> green\n    green --> yellow : Cycle\n    yellow --> red : Cycle\n    red --> green : Cycle\n<BLANKLINE>\n\n>>> formatter.supported_formats()\n['dot', 'markdown', 'md', 'mermaid', 'rst', 'svg']\n\n```\n\nBoth `format()` and the Sphinx directive delegate to this same `formatter`\nunder the hood.\n\n\n#### Registering custom formats\n\nThe `formatter` is extensible — register your own format with a\ndecorator and it becomes available everywhere (`format()`, CLI,\nSphinx directive):\n\n```python\nfrom statemachine.contrib.diagram import formatter\n\n@formatter.register_format(\"plantuml\", \"puml\")\ndef _render_plantuml(machine_or_class):\n    # your PlantUML renderer here\n    ...\n```\n\nAfter registration, `f\"{sm:plantuml}\"` and `--format plantuml` work\nimmediately.\n\n\n### Command line\n\nYou can generate diagrams without writing Python code:\n\n```bash\npython -m statemachine.contrib.diagram <classpath> <output_file>\n```\n\nThe output format is inferred from the file extension:\n\n```bash\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png\n```\n\nTo highlight the current state, use `--events` to instantiate the machine and\nsend events before rendering:\n\n```bash\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine diagram.png --events cycle cycle cycle\n```\n\nUse `--format` to produce a text format instead of a Graphviz image:\n\n```bash\n# Mermaid stateDiagram-v2\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.mmd --format mermaid\n\n# DOT source\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.dot --format dot\n\n# Markdown transition table\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.md --format md\n\n# RST transition table\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine output.rst --format rst\n```\n\nUse `-` as the output file to write to stdout (handy for piping):\n\n```bash\npython -m statemachine.contrib.diagram tests.examples.traffic_light_machine.TrafficLightMachine - --format mermaid\n```\n\n\n## Auto-expanding docstrings\n\nUse `{statechart:FORMAT}` placeholders in your class docstring to embed\na live representation of the state machine. The placeholder is replaced\nat class definition time, so the docstring always reflects the actual\nstates and transitions:\n\n```py\n>>> from statemachine.statemachine import StateChart\n>>> from statemachine.state import State\n\n>>> class TrafficLight(StateChart):\n...     \"\"\"A traffic light.\n...\n...     {statechart:md}\n...     \"\"\"\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n>>> print(TrafficLight.__doc__)\nA traffic light.\n<BLANKLINE>\n| State  | Event | Guard | Target |\n| ------ | ----- | ----- | ------ |\n| Green  | Cycle |       | Yellow |\n| Yellow | Cycle |       | Red    |\n| Red    | Cycle |       | Green  |\n<BLANKLINE>\n<BLANKLINE>\n\n```\n\nAny registered format works: `{statechart:rst}`, `{statechart:mermaid}`,\n`{statechart:dot}`, etc.\n\n### Choosing the right format\n\n| Context | Recommended format |\n|---------|-------------------|\n| Sphinx with RST (autodoc default) | `{statechart:rst}` |\n| Sphinx with MyST Markdown | `{statechart:md}` |\n| `help()` in terminal / IDE | Either works; `md` reads more cleanly |\n\n### Sphinx autodoc integration\n\nSince the placeholder is expanded at class definition time, Sphinx `autodoc`\nsees the final rendered text — no extra configuration needed.\n\nFor example, this class uses `{statechart:rst}` in its docstring:\n\n```{literalinclude} ../tests/machines/showcase_simple.py\n:pyobject: SimpleSC\n:language: python\n```\n\nAnd here is the rendered autodoc output:\n\n```{eval-rst}\n.. autoclass:: tests.machines.showcase_simple.SimpleSC\n   :noindex:\n```\n\n\n## Sphinx directive\n\nIf you use [Sphinx](https://www.sphinx-doc.org/) to build your documentation, the\n`statemachine-diagram` directive renders diagrams inline — no need to generate\nimage files manually.\n\n### Setup\n\nAdd the extension to your `conf.py`:\n\n```python\nextensions = [\n    ...\n    \"statemachine.contrib.diagram.sphinx_ext\",\n]\n```\n\n### Basic usage\n\nReference any importable {class}`~statemachine.statemachine.StateChart` class by\nits fully qualified path:\n\n````markdown\n```{statemachine-diagram} myproject.machines.OrderControl\n```\n````\n\n```{statemachine-diagram} tests.examples.order_control_machine.OrderControl\n:alt: OrderControl state machine\n:align: center\n```\n\n### Highlighting a specific state\n\nPass `:events:` to instantiate the machine and send events before rendering.\nThis highlights the current state after processing:\n\n````markdown\n```{statemachine-diagram} myproject.machines.TrafficLight\n:events: cycle\n:caption: Traffic light after one cycle\n```\n````\n\n```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine\n:events: cycle\n:caption: Traffic light after one cycle\n:align: center\n```\n\n### Enabling zoom\n\nFor complex diagrams, add `:target:` (without a value) to make the diagram\nclickable — it opens the full SVG in a new browser tab where users can\nzoom and pan freely:\n\n````markdown\n```{statemachine-diagram} myproject.machines.OrderControl\n:target:\n```\n````\n\n```{statemachine-diagram} tests.examples.order_control_machine.OrderControl\n:caption: Click to open full-size SVG\n:target:\n:align: center\n```\n\n### Mermaid format\n\nUse `:format: mermaid` to render via\n[sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)\ninstead of Graphviz SVG — useful when you don't want to install Graphviz\nin your docs build environment:\n\n````markdown\n```{statemachine-diagram} myproject.machines.TrafficLight\n:format: mermaid\n:caption: Rendered as Mermaid\n```\n````\n\n```{statemachine-diagram} tests.examples.traffic_light_machine.TrafficLightMachine\n:format: mermaid\n:caption: TrafficLightMachine (Mermaid)\n:align: center\n```\n\n### Directive options\n\nThe directive supports the same layout options as the standard `image` and\n`figure` directives, plus state-machine-specific ones.\n\n**State-machine options:**\n\n`:events:` *(comma-separated string)*\n: Events to send in sequence. When present, the machine is instantiated and\n  each event is sent before rendering.\n\n`:format:` *(string)*\n: Output format. Use `mermaid` to render via sphinxcontrib-mermaid\n  instead of Graphviz SVG. Default: DOT/SVG.\n\n**Image/figure options:**\n\n`:caption:` *(string)*\n: Caption text; wraps the image in a `figure` node.\n\n`:alt:` *(string)*\n: Alt text for the image. Defaults to the class name.\n\n`:width:` *(CSS length, e.g. `400px`, `80%`)*\n: Explicit width for the diagram.\n\n`:height:` *(CSS length)*\n: Explicit height for the diagram.\n\n`:scale:` *(integer percentage, e.g. `50%`)*\n: Uniform scaling relative to the intrinsic size.\n\n`:align:` *(left | center | right)*\n: Image alignment. Defaults to `center`.\n\n`:target:` *(URL or empty)*\n: Makes the diagram clickable. When set without a value, the raw SVG is\n  saved as a file and linked so users can open it in a new tab for\n  full-resolution zooming — useful for large or complex diagrams.\n\n`:class:` *(space-separated strings)*\n: Extra CSS classes for the wrapper element.\n\n`:figclass:` *(space-separated strings)*\n: Extra CSS classes for the `figure` element (only when `:caption:` is set).\n\n`:name:` *(string)*\n: Reference target name for cross-referencing with `{ref}`.\n\n```{note}\nThe directive imports the state machine class at Sphinx parse time. Machines\ndefined inline in doctest blocks cannot be referenced — use the\n`_graph()` method for those cases.\n```\n\n\n## Jupyter integration\n\nState machine instances are automatically rendered as diagrams in\nJupyterLab cells — no extra code needed:\n\n![Approval machine on JupyterLab](images/lab_approval_machine_accepted.png)\n\n\n## Online generation (QuickChart)\n\nIf you prefer not to install Graphviz locally, you can generate diagrams\nusing the [QuickChart](https://quickchart.io/) online service:\n\n```{eval-rst}\n.. autofunction:: statemachine.contrib.diagram.quickchart_write_svg\n```\n\n\n## Customizing the output\n\nThe `DotGraphMachine` class gives you control over the diagram's visual\nproperties. Subclass it and override the class attributes to customize\nfonts, colors, and layout:\n\n```python\nfrom statemachine.contrib.diagram import DotGraphMachine\nfrom tests.examples.order_control_machine import OrderControl\n```\n\nAvailable attributes:\n\n| Attribute | Default | Description |\n|-----------|---------|-------------|\n| `graph_rankdir` | `\"LR\"` | Graph direction (`\"LR\"` left-to-right, `\"TB\"` top-to-bottom) |\n| `font_name` | `\"Helvetica\"` | Font face for labels |\n| `state_font_size` | `\"10\"` | State label font size |\n| `state_active_penwidth` | `2` | Border width of the active state |\n| `state_active_fillcolor` | `\"turquoise\"` | Fill color of the active state |\n| `transition_font_size` | `\"9\"` | Transition label font size |\n\nFor example, to generate a top-to-bottom diagram with a custom active\nstate color:\n\n```python\nclass CustomDiagram(DotGraphMachine):\n    graph_rankdir = \"TB\"\n    state_active_fillcolor = \"lightyellow\"\n\nsm = OrderControl()\nsm.receive_payment(10)\n\ngraph = CustomDiagram(sm)\ndot = graph()\ndot.write_svg(\"order_control_custom.svg\")\n```\n\n`DotGraphMachine` also works with **classes** (not just instances) to\ngenerate diagrams without an active state:\n\n```python\ndot = DotGraphMachine(OrderControl)()\ndot.write_png(\"order_control_class.png\")\n```\n\n\n## Visual showcase\n\nThis section shows how each state machine feature is rendered in diagrams.\nEach example includes the class definition, diagrams in both **Graphviz**\nand **Mermaid** formats, and **instance** diagrams with the current state\nhighlighted after sending events.\n\n\n### Simple states\n\nA minimal state machine with three atomic states and linear transitions.\n\n```{literalinclude} ../tests/machines/showcase_simple.py\n:pyobject: SimpleSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC\n:caption: Class (Graphviz)\n```\n\n```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC\n:events:\n:caption: Initial\n```\n\n```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC\n:events: start\n:caption: Running\n```\n\n```{statemachine-diagram} tests.machines.showcase_simple.SimpleSC\n:events: start, finish\n:caption: Done (final)\n```\n\n\n### Entry and exit actions\n\nStates can declare `entry` / `exit` callbacks, shown in the state label.\n\n```{literalinclude} ../tests/machines/showcase_actions.py\n:pyobject: ActionsSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_actions.ActionsSC\n:caption: Class (Graphviz)\n```\n\n```{statemachine-diagram} tests.machines.showcase_actions.ActionsSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_actions.ActionsSC\n:events: power_on\n:caption: Active: On\n```\n\n\n### Guard conditions\n\nTransitions can have `cond` guards, shown in brackets on the edge label.\n\n```{literalinclude} ../tests/machines/showcase_guards.py\n:pyobject: GuardSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_guards.GuardSC\n:caption: Class (Graphviz)\n```\n\n```{statemachine-diagram} tests.machines.showcase_guards.GuardSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_guards.GuardSC\n:events:\n:caption: Active: Pending\n```\n\n\n### Self-transitions\n\nA transition from a state back to itself.\n\n```{literalinclude} ../tests/machines/showcase_self_transition.py\n:pyobject: SelfTransitionSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_self_transition.SelfTransitionSC\n:caption: Class (Graphviz)\n```\n\n```{statemachine-diagram} tests.machines.showcase_self_transition.SelfTransitionSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_self_transition.SelfTransitionSC\n:events:\n:caption: Active: Counting\n```\n\n\n### Internal transitions\n\nInternal transitions execute actions without exiting/entering the state.\n\n```{literalinclude} ../tests/machines/showcase_internal.py\n:pyobject: InternalSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_internal.InternalSC\n:caption: Class (Graphviz)\n```\n\n```{statemachine-diagram} tests.machines.showcase_internal.InternalSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_internal.InternalSC\n:events:\n:caption: Active: Monitoring\n```\n\n\n### Compound states\n\nA compound state contains child states. Entering the compound activates\nits initial child.\n\n```{literalinclude} ../tests/machines/showcase_compound.py\n:pyobject: CompoundSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC\n:caption: Class (Graphviz)\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC\n:events:\n:caption: Off\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC\n:events: turn_on\n:caption: Active/Idle\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_compound.CompoundSC\n:events: turn_on, begin\n:caption: Active/Working\n:target:\n```\n\n\n### Parallel states\n\nA parallel state activates all its regions simultaneously.\n\n```{literalinclude} ../tests/machines/showcase_parallel.py\n:pyobject: ParallelSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC\n:caption: Class (Graphviz)\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC\n:events: enter\n:caption: Both active\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel.ParallelSC\n:events: enter, go_l\n:caption: Left done\n:target:\n```\n\n\n### Parallel with cross-boundary transitions\n\nA transition targeting a compound state **inside** a parallel region triggers a\nrendering bug in Mermaid (`mermaid-js/mermaid#4052`).  The Mermaid renderer works\naround this by redirecting the arrow to the compound's initial child — compare the\n``rebuild`` arrow in both diagrams below.\n\n```{literalinclude} ../tests/machines/showcase_parallel_compound.py\n:pyobject: ParallelCompoundSC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC\n:caption: Class (Graphviz) — ``rebuild`` points to the Build compound border\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC\n:format: mermaid\n:caption: Class (Mermaid) — ``rebuild`` is redirected to Compile (initial child of Build)\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC\n:events: start, do_build\n:caption: Build done\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_parallel_compound.ParallelCompoundSC\n:events: start, do_build, do_test\n:caption: Pipeline done → Review\n:target:\n```\n\n\n### History states (shallow)\n\nA history pseudo-state remembers the last active child of a compound state.\n\n```{literalinclude} ../tests/machines/showcase_history.py\n:pyobject: HistorySC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_history.HistorySC\n:caption: Class (Graphviz)\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_history.HistorySC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_history.HistorySC\n:events: begin, advance\n:caption: Step2\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_history.HistorySC\n:events: begin, advance, pause\n:caption: Paused\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_history.HistorySC\n:events: begin, advance, pause, resume\n:caption: Resumed (→Step2)\n:target:\n```\n\n\n### Deep history\n\nDeep history remembers the exact leaf state across nested compounds.\n\n```{literalinclude} ../tests/machines/showcase_deep_history.py\n:pyobject: DeepHistorySC\n:language: python\n```\n\n```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC\n:caption: Class (Graphviz)\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC\n:format: mermaid\n:caption: Class (Mermaid)\n```\n\n```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC\n:events: dive, enter_inner, go\n:caption: Inner/B\n:target:\n```\n\n```{statemachine-diagram} tests.machines.showcase_deep_history.DeepHistorySC\n:events: dive, enter_inner, go, leave, restore\n:caption: Restored (→Inner/B)\n:target:\n```\n"
  },
  {
    "path": "docs/error_handling.md",
    "content": "\n(error-handling)=\n# Error handling\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nWhat happens when a callback raises an exception during a transition?\n\nWith `StateChart`, errors in actions are caught by the engine and dispatched\nas `error.execution` internal events — so the machine itself can react to\nfailures by transitioning to an error state, retrying, or recovering. This\nfollows the [SCXML error handling specification](https://www.w3.org/TR/scxml/#errorsAndEvents).\n\n```{tip}\n`catch_errors_as_events` is a class attribute that controls this behavior.\n`StateChart` uses `True` by default (SCXML-compliant); set it to `False`\nto let exceptions propagate to the caller instead. See {ref}`behaviour`\nfor the full comparison of behavioral attributes and how to customize them.\n```\n\n\n(error-execution)=\n\n## How errors are caught\n\nWhen an action raises during a {ref}`microstep <macrostep-microstep>`, the\nengine catches the exception at the **block level**. Each phase of the\nmicrostep is an independent block:\n\n| Block | Callbacks |\n|---|---|\n| Exit | `on_exit_state()`, `on_exit_<state>()` |\n| On | `on_transition()`, `on_<event>()` |\n| Enter | `on_enter_state()`, `on_enter_<state>()` |\n\nAn error in one block:\n\n- **Stops remaining actions in that block** — per the SCXML spec, execution\n  MUST NOT continue within the same block after an error.\n- **Does not affect other blocks** — subsequent phases of the microstep\n  still execute. In particular, **`after` callbacks always run** regardless\n  of errors in earlier blocks.\n\nThis means that even if a transition's `on` action raises, the target states\nare still entered and `after_<event>()` callbacks still run. The error is\ncaught and queued as an `error.execution` internal event that fires within\nthe same {ref}`macrostep <macrostep-microstep>`.\n\n```{note}\n`before` callbacks run before any state changes, so an error in `before`\nprevents the transition from executing — but `after` still runs because\nit belongs to a separate block.\n```\n\n\n## The `error.execution` event\n\nAfter catching an error, the engine places an `error.execution` event on the\ninternal queue. You can define transitions for this event to handle errors\nwithin the state machine itself — transitioning to error states, logging, or\nrecovering.\n\n### The `error_` naming convention\n\nSince Python identifiers cannot contain dots, any event attribute starting\nwith `error_` automatically matches both the underscore and dot-notation\nforms. For example, `error_execution` matches both `\"error_execution\"` and\n`\"error.execution\"`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class ResilientChart(StateChart):\n...     operational = State(initial=True)\n...     broken = State(final=True)\n...\n...     do_work = operational.to(operational, on=\"risky_action\")\n...     error_execution = operational.to(broken)\n...\n...     def risky_action(self):\n...         raise RuntimeError(\"something went wrong\")\n\n>>> sm = ResilientChart()\n>>> sm.send(\"do_work\")\n>>> \"broken\" in sm.configuration_values\nTrue\n\n```\n\n```{note}\nIf you provide an explicit `id=` parameter on the `Event`, it takes\nprecedence and the naming convention is not applied.\n```\n\n### Accessing error data\n\nThe original exception is available as `error` in the keyword arguments\nof callbacks on the `error.execution` transition. Use\n{ref}`dependency injection <dependency-injection>` to receive it:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class ErrorLogger(StateChart):\n...     running = State(initial=True)\n...     failed = State(final=True)\n...\n...     process = running.to(running, on=\"do_process\")\n...     error_execution = running.to(failed, on=\"log_error\")\n...\n...     def do_process(self):\n...         raise ValueError(\"bad data\")\n...\n...     def log_error(self, error):\n...         self.last_error = error\n\n>>> sm = ErrorLogger()\n>>> sm.send(\"process\")\n>>> str(sm.last_error)\n'bad data'\n\n```\n\n\n### Error in error handler\n\nIf the `error.execution` handler itself raises, the engine **ignores** the\nsecond error (logging a warning) to prevent infinite loops. The machine\nremains in whatever configuration it reached before the failed handler.\n\n```{note}\nDuring `error.execution` processing, errors in transition `on` content\nare **not** caught at block level — they propagate to the microstep where\nthey are silently discarded. This prevents infinite loops when an error\nhandler's own action raises (e.g., a self-transition\n`error_execution = s1.to(s1, on=\"handler\")` where `handler` raises).\nEntry/exit blocks still use block-level catching regardless of the\ncurrent event.\n```\n\n\n(error-handling-cleanup-finalize)=\n\n## Cleanup / finalize pattern\n\nA common need is to run cleanup code after a transition **regardless of\nsuccess or failure** — releasing a lock, closing a connection, or clearing\ntemporary state.\n\nBecause errors are caught at the block level, `after_<event>()` callbacks\nalways run — making them a natural **finalize** hook, similar to Python's\n`try/finally`:\n\n```py\n>>> from statemachine import Event, State, StateChart\n\n>>> class ResourceManager(StateChart):\n...     idle = State(initial=True)\n...     working = State()\n...     recovering = State()\n...\n...     start = idle.to(working)\n...     done = working.to(idle)\n...     recover = recovering.to(idle)\n...     error_execution = Event(working.to(recovering), id=\"error.execution\")\n...\n...     def __init__(self, should_fail=False):\n...         self.should_fail = should_fail\n...         self.released = False\n...         super().__init__()\n...\n...     def on_enter_working(self):\n...         if self.should_fail:\n...             raise RuntimeError(\"something went wrong\")\n...         self.raise_(\"done\")\n...\n...     def after_start(self):\n...         self.released = True  # always runs — finalize hook\n...\n...     def on_enter_recovering(self, error):\n...         self.last_error = error\n...         self.raise_(\"recover\")\n\n```\n\nOn the **success** path, the machine transitions `idle → working → idle`\nand `after_start` releases the resource:\n\n```py\n>>> sm = ResourceManager(should_fail=False)\n>>> sm.send(\"start\")\n>>> \"idle\" in sm.configuration_values\nTrue\n>>> sm.released\nTrue\n\n```\n\nOn the **failure** path, the action raises, but `after_start` **still runs**.\nThen `error.execution` fires, transitions to `recovering`, and auto-recovers\nback to `idle`:\n\n```py\n>>> sm = ResourceManager(should_fail=True)\n>>> sm.send(\"start\")\n>>> \"idle\" in sm.configuration_values\nTrue\n>>> sm.released  # finalize ran despite the error\nTrue\n>>> str(sm.last_error)\n'something went wrong'\n\n```\n\n```{seealso}\nSee {ref}`sphx_glr_auto_examples_statechart_cleanup_machine.py` for a\nmore detailed version of this pattern with annotated output.\n```\n\n\n## Validators do not trigger error events\n\n{ref}`Validators <validators>` operate in the **transition-selection** phase,\nbefore any state changes occur. Their exceptions **always propagate** to the\ncaller — they are never caught by the engine and never converted to\n`error.execution` events, regardless of the `catch_errors_as_events` setting.\n\nThis is intentional: a validator rejection means the transition should not\nhappen at all. It is semantically equivalent to a condition returning\n`False`, but communicates the reason via an exception.\n\n```{seealso}\nSee {ref}`validators` for examples and the full semantics of validator\npropagation.\n```\n\n\n```{seealso}\nSee {ref}`behaviour` for the full comparison of behavioral attributes\nand how to customize `catch_errors_as_events` and other settings.\nSee {ref}`actions` for the callback execution order within each\nmicrostep.\n```\n"
  },
  {
    "path": "docs/events.md",
    "content": "(events)=\n(event)=\n# Events\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nAn **event** is a named signal that drives the state machine forward. When you\nassign a transition to a class-level name, that name becomes an event — the\nlibrary creates an `Event` object automatically. Events are the external\ninterface of your machine: callers send event names, and the machine decides\nwhich transitions to take.\n\n\n(declaring-events)=\n\n## Declaring events\n\nThe simplest way to declare an event is by assigning a transition to a name:\n\n```py\n>>> from statemachine import Event, State, StateChart\n\n>>> class SimpleSM(StateChart):\n...     initial = State(initial=True)\n...     final = State(final=True)\n...\n...     start = initial.to(final)\n\n>>> isinstance(SimpleSM.start, Event)\nTrue\n\n```\n\nThe name `start` is automatically converted to an `Event` with\n`id=\"start\"`. Multiple transitions can share the same event using\nthe `|` operator:\n\n```py\n>>> class TrafficLight(StateChart):\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n>>> sm = TrafficLight()\n>>> sm.send(\"cycle\")\n>>> sm.yellow.is_active\nTrue\n\n```\n\nFor better IDE support (autocompletion, type checking) or to set a\nhuman-readable display name, use the `Event` class explicitly:\n\n```py\n>>> class SimpleSM(StateChart):\n...     initial = State(initial=True)\n...     final = State(final=True)\n...\n...     start = Event(initial.to(final), name=\"Start the machine\")\n\n>>> SimpleSM.start.name\n'Start the machine'\n\n>>> SimpleSM.start.id\n'start'\n\n```\n\n\n(event-identity)=\n\n## Event identity: `id` vs `name`\n\nEvery event has two string properties:\n\n- **`id`** — the programmatic identifier, derived from the class attribute name.\n  Use this in `send()`, guards, and comparisons.\n- **`name`** — a human-readable label for display purposes. Auto-generated from\n  the `id` by replacing `_` and `.` with spaces and capitalizing the first word.\n  You can override the automatic name by passing `name=` explicitly when\n  declaring the event:\n\n```py\n>>> TrafficLight.cycle.id\n'cycle'\n\n>>> TrafficLight.cycle.name\n'Cycle'\n\n>>> class Example(StateChart):\n...     on = State(initial=True)\n...     off = State(final=True)\n...     shut_down = Event(on.to(off), name=\"Shut the system down\")\n\n>>> Example.shut_down.name\n'Shut the system down'\n\n```\n\n```{tip}\nAlways use `event.id` for programmatic checks. The `name` property is intended\nfor UI display and may differ from the `id`.\n```\n\n\n(triggering-events)=\n(triggering events)=\n\n## Triggering events\n\nOnce declared, events are triggered on a {ref}`StateChart <statechart>` instance\nin two ways:\n\n- **As a method call:** `sm.cycle()` — when the event name is known at\n  development time.\n- **Via `send()`:** `sm.send(\"cycle\")` — when the event name is dynamic (e.g.,\n  from user input, a message queue, or a data file).\n\nBoth styles produce the same result. The machine evaluates\n{ref}`guard conditions <validators and guards>`, executes {ref}`actions`, and\nupdates the {ref}`configuration <querying-configuration>`.\n\n```{seealso}\nSee {ref}`sending-events` for the full runtime API — `send()`, `raise_()`,\ndelayed events, and cancellation.\n```\n\n\n(event-parameter)=\n\n## The `event` parameter on transitions\n\nEach transition accepts an optional `event` parameter that binds it to a\nspecific event, overriding the default (which is the class-level attribute\nname). This lets individual transitions within a group respond to their own\nevent identifiers:\n\n```py\n>>> from statemachine import Event, State, StateChart\n\n>>> class TrafficLightMachine(StateChart):\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     slowdown = Event(name=\"Slowing down\")\n...\n...     cycle = Event(\n...         green.to(yellow, event=slowdown)\n...         | yellow.to(red, event=\"stop\")\n...         | red.to(green, event=\"go\"),\n...         name=\"Loop\",\n...     )\n\n>>> sm = TrafficLightMachine()\n\n>>> sm.send(\"cycle\")  # umbrella event — dispatches green→yellow\n>>> sm.yellow.is_active\nTrue\n\n>>> sm.send(\"stop\")   # individual event — dispatches yellow→red\n>>> sm.red.is_active\nTrue\n\n>>> sm.send(\"go\")     # individual event — dispatches red→green\n>>> sm.green.is_active\nTrue\n\n```\n\nThe `event` parameter accepts a string, an `Event` instance, a reference\nto a previously declared `Event` (like `slowdown` above), or a **list** of\nany of these. A space-separated string is also accepted and split into\nindividual events automatically:\n\n```py\n>>> class MultiEvent(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...\n...     # Both forms are equivalent — the transition responds to \"move\", \"go\" and \"start\"\n...     move = a.to(b, event=[\"go\", \"start\"])\n\n>>> sm = MultiEvent()\n>>> sm.send(\"move\")\n>>> sm.b.is_active\nTrue\n\n>>> sm = MultiEvent()\n>>> sm.send(\"go\")\n>>> sm.b.is_active\nTrue\n\n>>> sm = MultiEvent()\n>>> sm.send(\"start\")\n>>> sm.b.is_active\nTrue\n\n```\n\n```{tip}\nThis is an advanced feature. Most state machines only need the simple\n`name = source.to(target)` form. Use the `event` parameter when you need\nfine-grained control over event routing within a composite transition group.\n```\n\n\n(done-state-events)=\n\n## Automatic events\n\nThe engine generates certain events automatically in response to structural\nchanges. You don't send these yourself — you define transitions that react\nto them.\n\n\n### `done.state` events\n\n```{versionadded} 3.0.0\n```\n\nWhen a {ref}`compound state's <compound-states>` final child is entered, the\nengine queues a `done.state.{parent_id}` internal event. Define a transition\nfor this event to react when a compound's work is complete:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class QuestWithDone(StateChart):\n...     class quest(State.Compound):\n...         traveling = State(initial=True)\n...         arrived = State(final=True)\n...         finish = traveling.to(arrived)\n...     celebration = State(final=True)\n...     done_state_quest = quest.to(celebration)\n\n>>> sm = QuestWithDone()\n>>> sm.send(\"finish\")\n>>> set(sm.configuration_values) == {\"celebration\"}\nTrue\n\n```\n\nFor {ref}`parallel states <parallel-states>`, the `done.state` event fires\nonly when **all** regions have reached a final state:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class WarWithDone(StateChart):\n...     class war(State.Parallel):\n...         class quest(State.Compound):\n...             start_q = State(initial=True)\n...             end_q = State(final=True)\n...             finish_q = start_q.to(end_q)\n...         class battle(State.Compound):\n...             start_b = State(initial=True)\n...             end_b = State(final=True)\n...             finish_b = start_b.to(end_b)\n...     peace = State(final=True)\n...     done_state_war = war.to(peace)\n\n>>> sm = WarWithDone()\n>>> sm.send(\"finish_q\")\n>>> \"war\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"finish_b\")\n>>> set(sm.configuration_values) == {\"peace\"}\nTrue\n\n```\n\n(donedata)=\n\n#### DoneData\n\nFinal states can carry data to their `done.state` handlers via the `donedata`\nparameter. The value should be a callable (or method name string) that returns\na `dict`, which is forwarded as keyword arguments to the transition handler:\n\n```py\n>>> from statemachine import Event, State, StateChart\n\n>>> class QuestCompletion(StateChart):\n...     class quest(State.Compound):\n...         traveling = State(initial=True)\n...         completed = State(final=True, donedata=\"get_result\")\n...         finish = traveling.to(completed)\n...         def get_result(self):\n...             return {\"hero\": \"frodo\", \"outcome\": \"victory\"}\n...     epilogue = State(final=True)\n...     done_state_quest = Event(quest.to(epilogue, on=\"capture_result\"))\n...     def capture_result(self, hero=None, outcome=None, **kwargs):\n...         self.result = f\"{hero}: {outcome}\"\n\n>>> sm = QuestCompletion()\n>>> sm.send(\"finish\")\n>>> sm.result\n'frodo: victory'\n\n```\n\n```{note}\n`donedata` can only be specified on `final=True` states. Attempting to use it\non a non-final state raises `InvalidDefinition`.\n```\n\n\n### `error.execution` events\n\nWhen a callback raises during a macrostep and\n{ref}`catch_errors_as_events <behaviour>` is enabled, the engine dispatches an\n`error.execution` internal event. Define a transition for this event to\nrecover from errors within the statechart:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class ResilientChart(StateChart):\n...     working = State(initial=True)\n...     failed = State(final=True)\n...\n...     go = working.to.itself(on=\"do_work\")\n...     error_execution = working.to(failed)\n...\n...     def do_work(self):\n...         raise RuntimeError(\"something went wrong\")\n\n>>> sm = ResilientChart()\n>>> sm.send(\"go\")\n>>> \"failed\" in sm.configuration_values\nTrue\n\n```\n\n```{seealso}\nSee {ref}`error-execution` for the full error handling reference: recovery\npatterns, `after` as a finalize hook, and nested error scenarios.\n```\n\n\n(naming-conventions)=\n\n## Dot-notation naming conventions\n\nSCXML uses dot-separated event names (`done.state.quest`, `error.execution`),\nbut Python identifiers cannot contain dots. The library provides prefix-based\nnaming conventions that automatically register both forms:\n\n(done-state-convention)=\n\n### `done_state_` prefix\n\nAny event attribute starting with `done_state_` matches both the underscore\nform and the dot-notation form. Only the prefix is replaced — the suffix is\nkept as-is, preserving multi-word state names:\n\n| Attribute name                | Matches event names |\n|-------------------------------|---------------------|\n| `done_state_quest`            | `\"done_state_quest\"` and `\"done.state.quest\"` |\n| `done_state_lonely_mountain`  | `\"done_state_lonely_mountain\"` and `\"done.state.lonely_mountain\"` |\n\n\n### `error_` prefix\n\nAny event attribute starting with `error_` matches both the underscore form\nand the dot-notation form. Unlike `done_state_`, **all** underscores after\nthe prefix are replaced with dots:\n\n| Attribute name       | Matches event names |\n|----------------------|---------------------|\n| `error_execution`    | `\"error_execution\"` and `\"error.execution\"` |\n\n```{note}\nIf you provide an explicit `id=` parameter on the `Event`, it takes precedence\nand the naming convention is not applied.\n```\n"
  },
  {
    "path": "docs/guards.md",
    "content": "(validators-and-guards)=\n(validators and guards)=\n\n# Conditions\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nConditions and validators are checked **before** a transition starts — they\ndecide whether the transition is allowed to proceed.\n\nThe difference is in how they communicate rejection:\n\n| Mechanism | Rejects by | Use when |\n|---|---|---|\n| {ref}`Conditions <conditions>` (`cond` / `unless`) | Returning a falsy value | You want the engine to silently skip the transition and try the next one. |\n| {ref}`Validators <validators>` | Raising an exception | You want the caller to know *why* the transition was rejected. |\n\nBoth run in the **transition-selection** phase, before any state changes\noccur. See the {ref}`execution order <actions>` table for where they fit in\nthe callback sequence.\n\n\n(guards)=\n(conditions)=\n\n## Conditions\n\nA **condition** (also known as a _guard_) is a boolean predicate attached to a\ntransition. When an event arrives, the engine checks each candidate transition\nin {ref}`declaration order <transition-priority>` — the first transition whose\nconditions are all satisfied is selected. If none match, the event is either\nignored or raises an exception (see `allow_event_without_transition` in the\n{ref}`behaviour reference <behaviour>`).\n\n```{important}\nA condition must not have side effects. Side effects belong in\n{ref}`actions`.\n```\n\nThere are two guard clause variants:\n\n`cond`\n: A list of condition expressions. The transition is allowed only if **all**\n  evaluate to `True`.\n  - Single: `cond=\"is_valid\"`\n  - Multiple: `cond=[\"is_valid\", \"has_stock\"]`\n\n`unless`\n: Same as `cond`, but the transition is allowed only if **all** evaluate to\n  `False`.\n  - Single: `unless=\"is_blocked\"`\n  - Multiple: `unless=[\"is_blocked\", \"is_expired\"]`\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class ApprovalFlow(StateChart):\n...     pending = State(initial=True)\n...     approved = State(final=True)\n...     rejected = State(final=True)\n...\n...     approve = pending.to(approved, cond=\"is_manager\")\n...     reject = pending.to(rejected)\n...\n...     is_manager = False\n\n>>> sm = ApprovalFlow()\n>>> sm.send(\"approve\")  # cond is False — no transition\n>>> \"pending\" in sm.configuration_values\nTrue\n\n>>> sm.is_manager = True\n>>> sm.send(\"approve\")\n>>> \"approved\" in sm.configuration_values\nTrue\n\n```\n\n### Multiple conditional transitions\n\nWhen multiple transitions share the same event, guards let the engine pick the\nright one at runtime. Transitions are checked in **declaration order** (the\norder of `.to()` calls), not the order they appear in the `|` composition:\n\n```py\n>>> class PriorityRouter(StateChart):\n...     inbox = State(initial=True)\n...     urgent = State(final=True)\n...     normal = State(final=True)\n...     low = State(final=True)\n...\n...     # Declaration order = evaluation order\n...     route = (\n...         inbox.to(urgent, cond=\"is_urgent\")\n...         | inbox.to(normal, cond=\"is_normal\")\n...         | inbox.to(low)  # fallback — no condition\n...     )\n...\n...     def is_urgent(self, priority=0, **kwargs):\n...         return priority >= 9\n...\n...     def is_normal(self, priority=0, **kwargs):\n...         return priority >= 5\n\n>>> sm = PriorityRouter()\n>>> sm.send(\"route\", priority=2)\n>>> \"low\" in sm.configuration_values  # fallback\nTrue\n\n>>> sm = PriorityRouter()\n>>> sm.send(\"route\", priority=7)\n>>> \"normal\" in sm.configuration_values\nTrue\n\n>>> sm = PriorityRouter()\n>>> sm.send(\"route\", priority=10)\n>>> \"urgent\" in sm.configuration_values  # checked first\nTrue\n\n```\n\nCondition methods receive the same keyword arguments passed to `send()` via\n{ref}`dependency injection <dependency-injection>` — declare only the\nparameters you need.\n\n```{seealso}\nSee {ref}`sphx_glr_auto_examples_air_conditioner_machine.py` for another\nexample combining multiple transitions on the same event.\n```\n\n\n(condition expressions)=\n\n### Condition expressions\n\nConditions support a mini-language for boolean expressions, allowing guards\nto be defined as strings that reference attributes on the state machine, its\nmodel, or registered {ref}`listeners <listeners>`.\n\nThe mini-language is based on Python's built-in\n[`ast`](https://docs.python.org/3/library/ast.html) parser, so the syntax\nis familiar:\n\n```py\n>>> class AccessControl(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State(final=True)\n...\n...     unlock = locked.to(unlocked, cond=\"has_key and not is_locked_out\")\n...\n...     has_key = True\n...     is_locked_out = False\n\n>>> sm = AccessControl()\n>>> sm.send(\"unlock\")\n>>> \"unlocked\" in sm.configuration_values\nTrue\n\n```\n\n```{tip}\nAll condition expressions are validated when the `StateChart` class is\ncreated. Invalid attribute names raise `InvalidDefinition` immediately,\nhelping you catch typos early.\n```\n\n#### Syntax elements\n\n**Names** refer to attributes on the state machine instance, its model, or\nlisteners. They can point to properties, attributes, or methods:\n\n- `is_active` — evaluated as `self.is_active` (property/attribute)\n- `check_stock` — if it's a method, it's called with\n  {ref}`dependency injection <dependency-injection>`\n\n**Boolean operators** (highest to lowest precedence):\n\n1. `not` / `!` — Logical negation\n2. `and` / `^` — Logical conjunction\n3. `or` / `v` — Logical disjunction\n\n**Comparison operators:**\n\n`>`, `>=`, `==`, `!=`, `<`, `<=`\n\n**Parentheses** control evaluation order:\n\n```python\ncond=\"(is_admin or is_moderator) and not is_banned\"\n```\n\n#### Expression examples\n\n- `is_logged_in and has_permission`\n- `not is_active or is_admin`\n- `!(is_guest ^ has_access)`\n- `(is_admin or is_moderator) and !is_banned`\n- `count > 0 and count <= 10`\n\n```{seealso}\nSee {ref}`sphx_glr_auto_examples_lor_machine.py` for a complete example\nusing boolean algebra in conditions.\n```\n\n\n(checking enabled events)=\n\n### Checking enabled events\n\nThe {ref}`allowed_events <querying-events>` property returns events\nreachable from the current state based on topology alone — it does\n**not** evaluate guards. To check which events currently have their\nconditions satisfied, use `enabled_events()`:\n\n```py\n>>> class TaskMachine(StateChart):\n...     idle = State(initial=True)\n...     running = State(final=True)\n...\n...     start = idle.to(running, cond=\"has_enough_resources\")\n...\n...     def has_enough_resources(self, cpu=0, **kwargs):\n...         return cpu >= 4\n\n>>> sm = TaskMachine()\n\n>>> [e.id for e in sm.allowed_events]\n['start']\n\n>>> sm.enabled_events()\n[]\n\n>>> [e.id for e in sm.enabled_events(cpu=8)]\n['start']\n\n```\n\n`enabled_events()` accepts `*args` / `**kwargs` that are forwarded to the\ncondition callbacks, just like when triggering an event. This makes it\nuseful for UI scenarios where you want to show or hide buttons based on\nwhether an event's conditions are currently satisfied.\n\n```{note}\nAn event is considered **enabled** if at least one of its transitions from\nthe current state has all conditions satisfied. If a condition raises an\nexception, the event is treated as enabled (permissive behavior).\n```\n\n\n(validators)=\n\n## Validators\n\nValidators are imperative guards that **raise an exception** to reject a\ntransition. While conditions silently skip a transition and let the engine\ntry the next candidate, validators communicate the rejection reason directly\nto the caller.\n\n- Single: `validators=\"check_stock\"`\n- Multiple: `validators=[\"check_stock\", \"check_credit\"]`\n\n```py\n>>> class OrderMachine(StateChart):\n...     pending = State(initial=True)\n...     confirmed = State(final=True)\n...\n...     confirm = pending.to(confirmed, validators=\"check_stock\")\n...\n...     def check_stock(self, quantity=0, **kwargs):\n...         if quantity <= 0:\n...             raise ValueError(\"Quantity must be positive\")\n\n>>> sm = OrderMachine()\n\n>>> sm.send(\"confirm\", quantity=0)\nTraceback (most recent call last):\n    ...\nValueError: Quantity must be positive\n\n>>> \"pending\" in sm.configuration_values  # state unchanged\nTrue\n\n>>> sm.send(\"confirm\", quantity=5)  # retry with valid data\n>>> \"confirmed\" in sm.configuration_values\nTrue\n\n```\n\n\n### Validators always propagate\n\nValidator exceptions **always propagate** to the caller, regardless of the\n`catch_errors_as_events` flag. This is intentional: validators operate in the\n**transition-selection** phase, not the execution phase. A validator that\nrejects is semantically equivalent to a condition that returns `False` —\nthe transition simply should not happen. The difference is that the\nvalidator communicates the reason via an exception.\n\nThis means that even when `catch_errors_as_events = True` (the default for\n`StateChart`):\n\n- Validator exceptions are **not** converted to `error.execution` events.\n- Validator exceptions do **not** trigger `error.execution` transitions.\n- The caller receives the exception directly and can handle it with\n  `try`/`except`.\n\n```py\n>>> class GuardedWithErrorHandler(StateChart):\n...     idle = State(initial=True)\n...     active = State()\n...     error_state = State(final=True)\n...\n...     start = idle.to(active, validators=\"check_input\")\n...     do_work = active.to.itself(on=\"risky_action\")\n...     error_execution = active.to(error_state)\n...\n...     def check_input(self, value=None, **kwargs):\n...         if value is None:\n...             raise ValueError(\"Input required\")\n...\n...     def risky_action(self, **kwargs):\n...         raise RuntimeError(\"Boom\")\n\n>>> sm = GuardedWithErrorHandler()\n\n>>> sm.send(\"start\")\nTraceback (most recent call last):\n    ...\nValueError: Input required\n\n>>> \"idle\" in sm.configuration_values  # NOT in error_state\nTrue\n\n>>> sm.send(\"start\", value=\"ok\")\n>>> \"active\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"do_work\")  # action error → goes to error_state\n>>> \"error_state\" in sm.configuration_values\nTrue\n\n```\n\nThe validator rejection propagates directly to the caller, while the action\nerror in `risky_action()` is caught by the engine and routed through the\n`error.execution` transition.\n\n\n### Combining validators and conditions\n\nValidators and conditions can be used together on the same transition.\nValidators run **first** — if a validator rejects, conditions are never\nevaluated:\n\n```py\n>>> class CombinedGuards(StateChart):\n...     idle = State(initial=True)\n...     active = State(final=True)\n...\n...     start = idle.to(active, validators=\"check_auth\", cond=\"has_permission\")\n...\n...     has_permission = True\n...\n...     def check_auth(self, token=None, **kwargs):\n...         if token != \"valid\":\n...             raise PermissionError(\"Invalid token\")\n\n>>> sm = CombinedGuards()\n\n>>> sm.send(\"start\", token=\"bad\")\nTraceback (most recent call last):\n    ...\nPermissionError: Invalid token\n\n>>> sm.send(\"start\", token=\"valid\")\n>>> \"active\" in sm.configuration_values\nTrue\n\n```\n\n```{seealso}\nSee the example {ref}`sphx_glr_auto_examples_all_actions_machine.py` for\na complete demonstration of validator and condition resolution order.\n```\n\n```{hint}\nIn Python, specific values are considered **falsy** and evaluate as `False`\nin a boolean context: `None`, `0`, `0.0`, empty strings, lists, tuples,\nsets, and dictionaries, and instances of classes whose `__bool__()` or\n`__len__()` returns `False` or `0`.\n\nSo `cond=lambda: []` evaluates as `False`.\n```\n"
  },
  {
    "path": "docs/how-to/coming_from_state_pattern.md",
    "content": "(coming-from-state-pattern)=\n\n# Coming from the State Pattern\n\nThis guide is for developers familiar with the classic **State Pattern** from the\nGang of Four book (*Design Patterns: Elements of Reusable Object-Oriented Software*).\nIt walks through a typical State Pattern implementation, discusses its trade-offs,\nand shows how to express the same behavior declaratively with python-statemachine.\n\n## The classic State Pattern\n\nThe GoF State Pattern models an object whose behavior changes based on its internal\nstate. The standard recipe has three ingredients:\n\n1. A **Context** class that delegates behavior to a state object.\n2. An **abstract State** base class (or protocol) defining the interface.\n3. **Concrete State** classes implementing state-specific behavior.\n\nHere is a complete example — an order workflow with four states\n(draft, confirmed, shipped, delivered) and a guard condition\n(orders can only be confirmed if they have at least one item):\n\n```python\nfrom abc import ABC, abstractmethod\n\n\nclass OrderState(ABC):\n    \"\"\"Abstract base for all order states.\"\"\"\n\n    @abstractmethod\n    def confirm(self, order):\n        ...\n\n    @abstractmethod\n    def ship(self, order):\n        ...\n\n    @abstractmethod\n    def deliver(self, order):\n        ...\n\n\nclass DraftState(OrderState):\n    def confirm(self, order):\n        if order.item_count <= 0:\n            raise ValueError(\"Cannot confirm an empty order\")\n        order._state = ConfirmedState()\n        print(\"Order confirmed\")\n\n    def ship(self, order):\n        raise RuntimeError(\"Cannot ship a draft order\")\n\n    def deliver(self, order):\n        raise RuntimeError(\"Cannot deliver a draft order\")\n\n\nclass ConfirmedState(OrderState):\n    def confirm(self, order):\n        raise RuntimeError(\"Order already confirmed\")\n\n    def ship(self, order):\n        order._state = ShippedState()\n        print(\"Order shipped\")\n\n    def deliver(self, order):\n        raise RuntimeError(\"Cannot deliver before shipping\")\n\n\nclass ShippedState(OrderState):\n    def confirm(self, order):\n        raise RuntimeError(\"Cannot confirm a shipped order\")\n\n    def ship(self, order):\n        raise RuntimeError(\"Order already shipped\")\n\n    def deliver(self, order):\n        order._state = DeliveredState()\n        print(\"Order delivered\")\n\n\nclass DeliveredState(OrderState):\n    def confirm(self, order):\n        raise RuntimeError(\"Order already delivered\")\n\n    def ship(self, order):\n        raise RuntimeError(\"Order already delivered\")\n\n    def deliver(self, order):\n        raise RuntimeError(\"Order already delivered\")\n\n\nclass Order:\n    def __init__(self, item_count=0):\n        self._state = DraftState()\n        self.item_count = item_count\n\n    def confirm(self):\n        self._state.confirm(self)\n\n    def ship(self):\n        self._state.ship(self)\n\n    def deliver(self):\n        self._state.deliver(self)\n```\n\nThis works — but notice how much code it takes for just four states and three events.\n\n\n## Pros and cons of the classic pattern\n\n**Pros:**\n\n- Encapsulates state-specific behavior in dedicated classes, eliminating large\n  `if/elif` chains.\n- Follows the Open/Closed Principle for adding new states — you create a new class\n  without modifying existing ones.\n- Each state class is independently testable.\n\n**Cons:**\n\n- **Class explosion** — every state requires a full class, even if most methods just\n  raise \"invalid operation\" errors. The example above has 4 state classes and 12\n  method implementations, 9 of which only raise exceptions.\n- **Transitions are scattered** — to understand the full workflow you must read every\n  concrete state class. There is no single place showing all transitions at a glance.\n- **No structural validation** — orphaned states, unreachable states, or missing\n  transitions are only discovered at runtime.\n- **Guards are manual** — conditions like \"only confirm if items > 0\" are embedded in\n  method bodies, mixed with transition logic.\n- **No diagrams** — visualizing the state machine requires manual drawing.\n- **No async support** — adding async behavior requires rewriting the entire interface.\n- **Signature duplication** — every state class must implement every method, even the\n  ones that are not valid for that state.\n\n\n## Porting to python-statemachine\n\nThe same order workflow expressed declaratively:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.exceptions import TransitionNotAllowed\n\n>>> class OrderMachine(StateChart):\n...     allow_event_without_transition = False\n...\n...     # States\n...     draft = State(initial=True)\n...     confirmed = State()\n...     shipped = State()\n...     delivered = State(final=True)\n...\n...     # Transitions (the complete workflow at a glance)\n...     confirm = draft.to(confirmed, cond=\"has_items\")\n...     ship = confirmed.to(shipped)\n...     deliver = shipped.to(delivered)\n...\n...     item_count = 0\n...\n...     @property\n...     def has_items(self):\n...         return self.item_count > 0\n\n>>> sm = OrderMachine()\n>>> sm.item_count = 3\n>>> sm.send(\"confirm\")\n>>> sm.confirmed.is_active\nTrue\n\n>>> sm.send(\"ship\")\n>>> sm.shipped.is_active\nTrue\n\n>>> sm.send(\"deliver\")\n>>> sm.delivered.is_active\nTrue\n\n```\n\nThat is the **entire** state machine — states, transitions, and the guard condition,\nall in one place. Setting `allow_event_without_transition = False` gives strict\nbehavior equivalent to the GoF pattern — invalid events raise\n`TransitionNotAllowed`:\n\n```py\n>>> sm = OrderMachine()\n>>> sm.item_count = 3\n\n>>> try:\n...     sm.send(\"ship\")  # can't ship from draft\n... except TransitionNotAllowed:\n...     print(\"Blocked: can't ship from draft\")\nBlocked: can't ship from draft\n\n```\n\nGuards work the same way — when the condition is not met, the transition is\nrejected:\n\n```py\n>>> sm = OrderMachine()\n\n>>> try:\n...     sm.send(\"confirm\")  # item_count is 0\n... except TransitionNotAllowed:\n...     print(\"Cannot confirm an empty order\")\nCannot confirm an empty order\n\n```\n\n### Going reactive\n\nThe strict mode above is a direct equivalent of the GoF pattern. But `StateChart`'s\ndefault (`allow_event_without_transition = True`) follows the SCXML specification:\nevents that have no valid transition are **skipped**. This makes the\nmachine reactive — it only responds to events that are meaningful in its current\nstate, without requiring the caller to know which events are valid:\n\n```py\n>>> class ReactiveOrderMachine(StateChart):\n...     draft = State(initial=True)\n...     confirmed = State()\n...     shipped = State()\n...     delivered = State(final=True)\n...\n...     confirm = draft.to(confirmed, cond=\"has_items\")\n...     ship = confirmed.to(shipped)\n...     deliver = shipped.to(delivered)\n...\n...     item_count = 0\n...\n...     @property\n...     def has_items(self):\n...         return self.item_count > 0\n\n>>> sm = ReactiveOrderMachine()\n>>> sm.item_count = 3\n\n>>> sm.send(\"ship\")       # no transition for \"ship\" from draft — skipped\n>>> sm.draft.is_active    # still in draft\nTrue\n\n>>> sm.send(\"confirm\")    # this one is valid\n>>> sm.confirmed.is_active\nTrue\n\n```\n\nThis is particularly useful when the machine receives events from external sources\n(message queues, UI frameworks, network protocols) where the sender doesn't track\nthe machine's current state. See {ref}`behaviour` for a comparison of all\nclass-level defaults.\n\n### Adding callbacks\n\nState-specific behavior (e.g., sending notifications) uses naming conventions\nor inline declarations — no need to scatter logic across state classes:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class OrderWithCallbacks(StateChart):\n...     draft = State(initial=True)\n...     confirmed = State()\n...     shipped = State()\n...     delivered = State(final=True)\n...\n...     confirm = draft.to(confirmed, cond=\"has_items\")\n...     ship = confirmed.to(shipped)\n...     deliver = shipped.to(delivered)\n...\n...     item_count = 0\n...\n...     def __init__(self, **kwargs):\n...         self.log = []\n...         super().__init__(**kwargs)\n...\n...     @property\n...     def has_items(self):\n...         return self.item_count > 0\n...\n...     def on_enter_confirmed(self):\n...         self.log.append(\"confirmed\")\n...\n...     def on_enter_shipped(self):\n...         self.log.append(\"shipped\")\n...\n...     def on_enter_delivered(self):\n...         self.log.append(\"delivered\")\n\n>>> sm = OrderWithCallbacks()\n>>> sm.item_count = 2\n>>> sm.send(\"confirm\")\n>>> sm.send(\"ship\")\n>>> sm.send(\"deliver\")\n>>> sm.log\n['confirmed', 'shipped', 'delivered']\n\n```\n\n### Structural validation catches design errors\n\nImagine a new requirement: orders can be cancelled from `draft` or `confirmed`.\nWith the GoF pattern, a developer adds a `CancelledState` class — but forgets to\nwire the transitions in `DraftState` and `ConfirmedState`. The code compiles and\nruns fine; the bug only surfaces when someone tries to cancel an order and\ndiscovers there is no way to reach `CancelledState`. In a large codebase with\ndozens of states, this kind of mistake can go unnoticed for a long time.\n\npython-statemachine catches this at **class definition time**:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.exceptions import InvalidDefinition\n\n>>> try:\n...     class BrokenOrderMachine(StateChart):\n...         draft = State(initial=True)\n...         confirmed = State()\n...         shipped = State()\n...         delivered = State(final=True)\n...         cancelled = State(final=True)  # added but never connected\n...\n...         confirm = draft.to(confirmed)\n...         ship = confirmed.to(shipped)\n...         deliver = shipped.to(delivered)\n... except InvalidDefinition as e:\n...     print(e)\nThere are unreachable states. ...Disconnected states: ['cancelled']\n\n```\n\nThe fix is to declare the missing transitions — and now the full workflow is\nvisible in a single glance:\n\n```py\n>>> class FixedOrderMachine(StateChart):\n...     draft = State(initial=True)\n...     confirmed = State()\n...     shipped = State()\n...     delivered = State(final=True)\n...     cancelled = State(final=True)\n...\n...     confirm = draft.to(confirmed)\n...     ship = confirmed.to(shipped)\n...     deliver = shipped.to(delivered)\n...     cancel = draft.to(cancelled) | confirmed.to(cancelled)\n\n>>> sm = FixedOrderMachine()\n>>> sm.send(\"cancel\")\n>>> sm.cancelled.is_active\nTrue\n\n```\n\n\n## Side-by-side comparison\n\n| Concept | State Pattern (GoF) | python-statemachine |\n|---|---|---|\n| State definition | One class per state | `State()` class attribute |\n| Transition | Method in source state class sets `_state` | `.to()` declaration |\n| Guard / condition | `if` check inside method body | `cond=` / `unless=` parameter |\n| Invalid transition | Manual `raise` in every method | `TransitionNotAllowed` or skipped ({ref}`configurable <behaviour>`) |\n| All transitions | Scattered across state classes | Visible in the class body |\n| Context / model | Separate `Order` class | `StateChart` itself (or `model=`) |\n| Adding a new state | New class + update all interfaces | New `State()` attribute + transitions |\n| Entry / exit actions | Manual in transition methods | `on_enter_<state>()` / `on_exit_<state>()` |\n| Diagrams | Manual | Built-in `_graph()` |\n| Validation | None (runtime errors only) | Definition-time structural checks |\n| Async support | Rewrite entire interface | Auto-detected from `async def` |\n| Dependency injection | Not available | Built-in via `SignatureAdapter` |\n\n\n## What you gain\n\nBy moving from the State Pattern to python-statemachine, you get:\n\n- **Declarative definition** — the entire workflow is visible in one class body.\n- **Structural validation** — unreachable states, missing transitions, and unresolved\n  callbacks are caught before the machine ever runs\n  (see {ref}`validations`).\n- **Automatic diagrams** — call `_graph()` on any instance to generate a Graphviz\n  diagram (see {ref}`diagrams`).\n- **Guards and conditions** — use `cond=`, `unless=`, or\n  {ref}`expression strings <condition expressions>` instead of manual `if` checks.\n- **Dependency injection** — callbacks receive only the parameters they declare\n  (see {ref}`actions`).\n- **Async support** — define `async def` callbacks and the engine auto-switches to\n  async processing (see {ref}`async`).\n- **Listeners** — attach cross-cutting concerns (logging, auditing) as separate\n  objects without modifying the state machine\n  (see {ref}`listeners`).\n- **No class explosion** — four states and three events require one class with a few\n  attributes, not four classes with twelve methods.\n"
  },
  {
    "path": "docs/how-to/coming_from_transitions.md",
    "content": "(coming-from-transitions)=\n\n# Coming from pytransitions\n\nThis guide helps users of the [*transitions*](https://github.com/pytransitions/transitions)\nlibrary migrate to python-statemachine (or evaluate the differences). Code examples are\nshown side by side where possible. For a quick overview, jump to the\n{ref}`feature matrix <feature-matrix>`.\n\n## At a glance\n\n| Aspect | *transitions* | python-statemachine |\n|---|---|---|\n| Definition style | Imperative (dicts/lists passed to `Machine`) | Declarative (class-level `State` and `.to()`) |\n| State definition | Strings or `State` objects in a list | Class attributes (`State(...)`) |\n| Transition definition | `add_transition()` / dicts | `.to()` chaining, `\\|` composition |\n| Event triggers | Auto-generated methods on the model | `sm.send(\"event\")` or `sm.event()` |\n| Callbacks | String names or callables, per-transition | Naming conventions + decorators, {ref}`dependency injection <dependency-injection>` |\n| Conditions | `conditions=`, `unless=` | `cond=`, `unless=`, {ref}`expression strings <condition expressions>` |\n| Nested states | `HierarchicalMachine` + separator strings | `State.Compound` / `State.Parallel` nested classes |\n| Completion events | `on_final` callback only | `done.state` / `done.invoke` {ref}`automatic events <done-state-events>` with `donedata` |\n| Invoke | No | {ref}`Background work <invoke>` tied to state lifecycle |\n| Async | Separate `AsyncMachine` class | {ref}`Auto-detected <async>` from `async def` callbacks |\n| API surface | [12 Machine classes](https://github.com/pytransitions/transitions#-extensions) to combine features | {ref}`Single StateChart class <unified-api>` — all features built in |\n| Diagrams | `GraphMachine` (separate base class) | Built-in {ref}`_graph() <diagrams>` on every instance |\n| Model binding | `Machine(model=obj)` | {ref}`MachineMixin <machinemixin>` or `model=` parameter |\n| Listeners | Machine-level callbacks only | Full {ref}`observer pattern <listeners>` (class-level, constructor, runtime) |\n| Error handling | Exceptions propagate | Optional {ref}`catch_errors_as_events <error-execution>` (`error.execution`) |\n| Validations | None | {ref}`Structural + callback checks <validations>` at definition and creation time |\n| SCXML compliance | [Not a goal](https://github.com/pytransitions/transitions/issues/446#issuecomment-646837282) | {ref}`W3C conformant <processing-model>` with automated test suite |\n| Processing model | Immediate or queued | Always queued ({ref}`run-to-completion <rtc-model>`) |\n\n\n## Defining states\n\nIn *transitions*, states are defined as strings or dicts passed to the `Machine` constructor.\nStates can exist without any transitions — the library does not validate structural\nconsistency:\n\n```python\nfrom transitions import Machine\n\nstates = [\"draft\", \"producing\", \"closed\"]\nmachine = Machine(states=states, initial=\"draft\")\n# No transitions defined — \"producing\" and \"closed\" are unreachable, but no error is raised\n```\n\nIn python-statemachine, states are class-level descriptors and **transitions are\nrequired**. The library validates structural integrity at class definition time —\nstates without transitions are rejected:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.exceptions import InvalidDefinition\n\n>>> try:\n...     class BadWorkflow(StateChart):\n...         draft = State(initial=True)\n...         producing = State()\n...         closed = State(final=True)\n... except InvalidDefinition as e:\n...     print(e)\nThere are unreachable states. ...Disconnected states: [...]\n\n```\n\nA valid definition requires transitions connecting all states:\n\n```py\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n\n>>> sm = Workflow()\n>>> sm.draft.is_active\nTrue\n\n```\n\nStates are first-class objects with properties like `is_active`, `value`, and `id`.\nYou can set a human-readable name and a persistence value directly on the state.\nSee {ref}`states` for the full reference.\n\n```py\n>>> producing = State(\"Being produced\", value=2)\n\n```\n\n### Flat vs compound definitions\n\nIn *transitions*, flat and hierarchical machines are **separate classes**. To use\ncompound states you must switch from `Machine` to `HierarchicalMachine` and define\nthe hierarchy through nested dicts — states and their children are described far from\nthe transitions that connect them:\n\n```python\nfrom transitions.extensions import HierarchicalMachine\n\nstates = [\n    \"idle\",\n    {\n        \"name\": \"active\",\n        \"children\": [\n            {\"name\": \"working\", \"on_enter\": \"start_work\"},\n            {\"name\": \"paused\"},\n        ],\n        \"initial\": \"working\",\n    },\n    \"done\",\n]\n\ntransitions = [\n    {\"trigger\": \"start\", \"source\": \"idle\", \"dest\": \"active\"},\n    {\"trigger\": \"pause\", \"source\": \"active_working\", \"dest\": \"active_paused\"},\n    {\"trigger\": \"resume\", \"source\": \"active_paused\", \"dest\": \"active_working\"},\n    {\"trigger\": \"finish\", \"source\": \"active\", \"dest\": \"done\"},\n]\n\nmachine = HierarchicalMachine(states=states, transitions=transitions, initial=\"idle\")\n```\n\nNote how child states are referenced with separator-based names (`active_working`,\n`active_paused`) and the structure is split across two separate data structures.\n\nIn python-statemachine, `StateChart` handles both flat and compound machines. Compound\nstates are nested Python classes that act as **namespaces** — children, transitions,\nand callbacks are declared together in the class body, mirroring the state hierarchy\ndirectly in code:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class TaskMachine(StateChart):\n...     idle = State(initial=True)\n...\n...     class active(State.Compound):\n...         working = State(initial=True)\n...         paused = State()\n...         pause = working.to(paused)\n...         resume = paused.to(working)\n...\n...         def on_enter_working(self):\n...             self.started = True\n...\n...     done = State(final=True)\n...\n...     start = idle.to(active)\n...     finish = active.to(done)\n\n>>> sm = TaskMachine()\n>>> sm.send(\"start\")\n>>> sm.started\nTrue\n\n>>> sm.send(\"pause\")\n>>> \"paused\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"resume\")\n>>> sm.send(\"finish\")\n>>> sm.done.is_active\nTrue\n\n```\n\nEach compound class is self-contained: its children, internal transitions, and callbacks\nlive inside the same block. This scales naturally to deeper hierarchies and parallel\nregions without switching to a different API.\n\npython-statemachine also supports hierarchical features not available in *transitions*:\n\n- {ref}`History pseudo-states <history-states>` (`HistoryState`) — remember and restore previous child states\n- {ref}`Eventless transitions <eventless>` — fire automatically when their guard condition is met\n\nSee {ref}`compound-states` and {ref}`parallel-states` for the full reference.\n\n\n### Creating machines from dicts\n\nIf you prefer the dict-based definition style familiar from *transitions*, you can\nuse {func}`~statemachine.io.create_machine_class_from_definition` to build a\n`StateChart` dynamically. It supports states, transitions, conditions, and\ncallbacks (`on`, `before`, `after`, `enter`, `exit`):\n\n```py\n>>> from statemachine.io import create_machine_class_from_definition\n\n>>> TrafficLight = create_machine_class_from_definition(\n...     \"TrafficLight\",\n...     states={\n...         \"green\": {\n...             \"initial\": True,\n...             \"on\": {\"change\": [{\"target\": \"yellow\"}]},\n...         },\n...         \"yellow\": {\n...             \"on\": {\"change\": [{\"target\": \"red\"}]},\n...         },\n...         \"red\": {\n...             \"on\": {\"change\": [{\"target\": \"green\"}]},\n...         },\n...     },\n... )\n\n>>> sm = TrafficLight()\n>>> sm.send(\"change\")\n>>> sm.yellow.is_active\nTrue\n>>> sm.send(\"change\")\n>>> sm.red.is_active\nTrue\n\n```\n\nThe result is a regular `StateChart` subclass — all features (validations, diagrams,\nlisteners, async) work exactly the same way. See\n{func}`~statemachine.io.create_machine_class_from_definition` for the full API.\n\n\n## Defining transitions\n\n*transitions* uses dicts or `add_transition()`:\n\n```python\ntransitions = [\n    {\"trigger\": \"produce\", \"source\": \"draft\", \"dest\": \"producing\"},\n    {\"trigger\": \"deliver\", \"source\": \"producing\", \"dest\": \"closed\"},\n    {\"trigger\": \"cancel\", \"source\": [\"draft\", \"producing\"], \"dest\": \"cancelled\"},\n]\nmachine = Machine(states=states, transitions=transitions, initial=\"draft\")\n```\n\npython-statemachine uses `.to()` with `|` for composing multiple origins:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...     cancelled = State(final=True)\n...\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n...     cancel = draft.to(cancelled) | producing.to(cancelled)\n\n>>> sm = Workflow()\n>>> sm.send(\"produce\")\n>>> sm.producing.is_active\nTrue\n\n```\n\nThe `|` operator composes transitions from different sources into a single event.\nYou can also use `from_()` to express the same thing from the target's perspective.\nSee {ref}`transitions` for the full reference.\n\n```py\n>>> class Workflow2(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...     cancelled = State(final=True)\n...\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n...     cancel = cancelled.from_(draft, producing)\n\n>>> sm = Workflow2()\n>>> sm.send(\"produce\")\n>>> sm.send(\"cancel\")\n>>> sm.cancelled.is_active\nTrue\n\n```\n\n\n## Triggering events\n\nIn *transitions*, events are called as methods on the model:\n\n```python\nmachine.produce()   # triggers the \"produce\" event\nmachine.deliver()   # triggers the \"deliver\" event\n```\n\npython-statemachine supports both styles:\n\n```py\n>>> sm = Workflow()\n\n>>> sm.send(\"produce\")   # send by name (recommended for dynamic dispatch)\n>>> sm.producing.is_active\nTrue\n\n>>> sm.deliver()          # call as method (convenient for static usage)\n>>> sm.closed.is_active\nTrue\n\n```\n\n`send()` is preferred when the event name comes from external input (e.g., an API\nendpoint or message queue). Direct method calls are convenient when you know the\nevent at coding time. See {ref}`events` for the full reference.\n\n\n## Callbacks and actions\n\n### *transitions* callback order\n\nIn *transitions*, callbacks execute in this order per transition:\n`prepare` &rarr; `conditions` &rarr; `before` &rarr; `on_exit_<state>` &rarr; `on_enter_<state>` &rarr; `after`.\n\nCallbacks are specified as strings (method names) or callables:\n\n```python\nmachine = Machine(\n    states=states,\n    transitions=[{\n        \"trigger\": \"produce\",\n        \"source\": \"draft\",\n        \"dest\": \"producing\",\n        \"before\": \"validate_job\",\n        \"after\": \"notify_team\",\n    }],\n    initial=\"draft\",\n)\n```\n\n### python-statemachine callback order\n\npython-statemachine has a similar but more granular order:\n`prepare` &rarr; `validators` &rarr; `conditions` &rarr; `before` &rarr; `on_exit` &rarr; `on` &rarr; `on_enter` &rarr; `after`.\n\nThe `on` group (between exit and enter) is unique to python-statemachine — it runs the\ntransition's own action, separate from state entry/exit. See {ref}`actions` for the\nfull execution order table.\n\nCallbacks are resolved by **naming convention** or by **inline declaration**:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n...\n...     # naming convention: on_enter_<state>\n...     def on_enter_producing(self):\n...         self.entered = True\n...\n...     # naming convention: after_<event>\n...     def after_produce(self):\n...         self.notified = True\n\n>>> sm = Workflow()\n>>> sm.send(\"produce\")\n>>> sm.entered\nTrue\n>>> sm.notified\nTrue\n\n```\n\nInline callbacks are also supported:\n\n```py\n>>> class Workflow2(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...\n...     produce = draft.to(producing, on=\"do_produce\")\n...     deliver = producing.to(closed)\n...\n...     def do_produce(self):\n...         return \"producing\"\n\n>>> sm = Workflow2()\n>>> sm.send(\"produce\")\n'producing'\n\n```\n\n### Dependency injection\n\nA key difference: python-statemachine callbacks use **dependency injection** via\n`SignatureAdapter`. The engine inspects each callback's signature and passes only\nthe parameters it accepts. You never need `**kwargs` unless you want to capture extras:\n\n```py\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n...\n...     def on_produce(self, source, target):\n...         return f\"{source.id} -> {target.id}\"\n\n>>> sm = Workflow()\n>>> sm.send(\"produce\")\n'draft -> producing'\n\n```\n\nAvailable parameters include `source`, `target`, `event`, `state`, `error`, and\nany custom kwargs passed to `send()`. See {ref}`actions` for the complete list of\navailable parameters.\n\nIn *transitions*, you must accept `**kwargs` or use `EventData`:\n\n```python\ndef on_enter_producing(self, **kwargs):\n    event_data = kwargs.get(\"event_data\")\n```\n\n\n## Conditions and guards\n\nIn *transitions*:\n\n```python\nmachine.add_transition(\n    \"produce\", \"draft\", \"producing\",\n    conditions=[\"is_valid\", \"has_resources\"],\n    unless=[\"is_locked\"],\n)\n```\n\nIn python-statemachine, use `cond=` and `unless=`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...\n...     produce = draft.to(producing, cond=\"is_valid\", unless=\"is_locked\")\n...     deliver = producing.to(closed)\n...\n...     is_valid = True\n...     is_locked = False\n\n>>> sm = Workflow()\n>>> sm.send(\"produce\")\n>>> sm.producing.is_active\nTrue\n\n```\n\npython-statemachine also supports **condition expressions** — boolean strings\nevaluated at runtime. See {ref}`validators and guards` for the full reference.\n\n```py\n>>> class Workflow2(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...\n...     produce = draft.to(producing, cond=\"is_valid and not is_locked\")\n...     deliver = producing.to(closed)\n...\n...     is_valid = True\n...     is_locked = False\n\n>>> sm = Workflow2()\n>>> sm.send(\"produce\")\n>>> sm.producing.is_active\nTrue\n\n```\n\n\n## Completion events (`done.state`)\n\nIn *transitions*, the `on_final` callback fires when a final state is entered (and\npropagates upward when all children of a compound are final). However, it is just a\n**callback** — it cannot trigger transitions automatically. You must wire separate\ntriggers manually.\n\nIn python-statemachine, when a compound state's final child is entered, the engine\nautomatically dispatches a `done.state.<parent_id>` **event**. You define transitions\nfor it using the `done_state_` naming convention, and the transition fires\nautomatically — no manual wiring needed:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Pipeline(StateChart):\n...     class processing(State.Compound):\n...         step1 = State(initial=True)\n...         step2 = State()\n...         completed = State(final=True)\n...         advance = step1.to(step2)\n...         finish = step2.to(completed)\n...     done = State(final=True)\n...     done_state_processing = processing.to(done)\n\n>>> sm = Pipeline()\n>>> sm.send(\"advance\")\n>>> sm.send(\"finish\")\n>>> sm.done.is_active\nTrue\n\n```\n\nFor parallel states, `done.state` fires only when **all** regions have reached a\nfinal state. Final states can also carry data via `donedata`, which is forwarded\nas keyword arguments to the transition handler.\n\nSee {ref}`done.state events <done-state-events>` and {ref}`DoneData <donedata>` for\nfull details.\n\n\n## Invoke\n\n*transitions* does not have a built-in mechanism for spawning background work tied to\na state's lifecycle.\n\nIn python-statemachine, a state can **invoke** external work — API calls, file I/O,\nchild state machines — when it is entered, and automatically cancel that work when\nthe state is exited. Handlers run in a background thread (sync engine) or a thread\nexecutor (async engine). When the work completes, a `done.invoke.<state>` event\nis automatically dispatched:\n\n```py\n>>> import time\n>>> from statemachine import State, StateChart\n\n>>> class FetchMachine(StateChart):\n...     loading = State(initial=True, invoke=lambda: {\"status\": \"ok\"})\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n\n>>> sm = FetchMachine()\n>>> time.sleep(0.1)\n>>> sm.ready.is_active\nTrue\n\n```\n\nInvoke supports multiple handlers (`invoke=[a, b]`), grouped invocations\n(`invoke_group`), child state machines, and the full callback naming conventions\n(`on_invoke_<state>`, `@state.invoke`).\n\nSee {ref}`invoke` for full documentation.\n\n\n## Async support\n\n*transitions* requires a separate class:\n\n```python\nfrom transitions.extensions import AsyncMachine\n\nclass AsyncModel:\n    async def on_enter_producing(self):\n        await some_async_operation()\n\nmachine = AsyncMachine(model=AsyncModel(), states=states, initial=\"draft\")\nawait machine.produce()\n```\n\npython-statemachine auto-detects async callbacks — no special class needed:\n\n```py\n>>> import asyncio\n\n>>> from statemachine import State, StateChart\n\n>>> class AsyncWorkflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State(final=True)\n...\n...     produce = draft.to(producing)\n...\n...     async def on_enter_producing(self):\n...         return \"async entered\"\n\n>>> async def main():\n...     sm = AsyncWorkflow()\n...     await sm.send(\"produce\")\n...     return sm.producing.is_active\n\n>>> asyncio.run(main())\nTrue\n\n```\n\nIf any callback is `async def`, the engine automatically switches to the async\nprocessing loop. Sync and async callbacks can be mixed freely.\nSee {ref}`async` for the full reference.\n\n\n## Diagrams\n\nIn *transitions*, diagram support requires replacing `Machine` with `GraphMachine`\n— a separate base class. If you also need nested states, you must use\n`HierarchicalGraphMachine`; add async and it becomes\n`HierarchicalAsyncGraphMachine`. This is part of the\n{ref}`class composition problem <unified-api>` discussed below.\n\n```python\nfrom transitions.extensions import GraphMachine\n\nmachine = GraphMachine(model=model, states=states, transitions=transitions, initial=\"draft\")\nmachine.get_graph().draw(\"diagram.png\", prog=\"dot\")\n```\n\nIn python-statemachine, diagram generation is available on **every** state machine\nwith no class changes. Every instance has a `_graph()` method built in, and\n`_repr_svg_()` renders directly in Jupyter notebooks:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State()\n...     closed = State(final=True)\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n\n>>> sm = Workflow()\n>>> graph = sm._graph()\n>>> type(graph).__name__\n'Dot'\n\n```\n\nFor more control, use `DotGraphMachine` directly:\n\n```python\nfrom statemachine.contrib.diagram import DotGraphMachine\n\ngraph = DotGraphMachine(Workflow)\ngraph().write_png(\"diagram.png\")\n```\n\nDiagrams automatically render compound and parallel state hierarchies.\nSee {ref}`diagrams` for the full reference.\n\n\n(unified-api)=\n\n## Unified API vs class composition\n\nOne of the most significant architectural differences between the two libraries\nis how features are composed.\n\nIn *transitions*, each feature lives in a separate `Machine` subclass. Combining\nfeatures requires using pre-built combined classes — the number of variants grows\ncombinatorially:\n\n| Class | Nested | Diagrams | Locked | Async |\n|---|:---:|:---:|:---:|:---:|\n| `Machine` | | | | |\n| `HierarchicalMachine` | x | | | |\n| `GraphMachine` | | x | | |\n| `LockedMachine` | | | x | |\n| `AsyncMachine` | | | | x |\n| `HierarchicalGraphMachine` | x | x | | |\n| `LockedGraphMachine` | | x | x | |\n| `LockedHierarchicalMachine` | x | | x | |\n| `LockedHierarchicalGraphMachine` | x | x | x | |\n| `AsyncGraphMachine` | | x | | x |\n| `HierarchicalAsyncMachine` | x | | | x |\n| `HierarchicalAsyncGraphMachine` | x | x | | x |\n\nThat is **12 classes** to cover all combinations — and switching from a flat\nmachine to a hierarchical one requires changing the base class across your\ncodebase.\n\nIn python-statemachine, `StateChart` is the single base class. All features are\nalways available:\n\n- **Nested states** — use `State.Compound` / `State.Parallel` in the class body\n- **Async** — auto-detected from `async def` callbacks\n- **Diagrams** — built-in `_graph()` on every instance\n- **Thread safety** — handled by the engine's run-to-completion processing loop\n\n```py\n>>> import asyncio\n>>> from statemachine import State, StateChart\n\n>>> class FullFeatured(StateChart):\n...     \"\"\"Nested + async + diagrams — same single base class.\"\"\"\n...     class phase(State.Compound):\n...         step1 = State(initial=True)\n...         step2 = State(final=True)\n...         advance = step1.to(step2)\n...     done = State(final=True)\n...     done_state_phase = phase.to(done)\n...\n...     async def on_enter_done(self):\n...         self.result = \"async action completed\"\n\n>>> async def main():\n...     sm = FullFeatured()\n...     graph = sm._graph()  # diagrams work\n...     await sm.send(\"advance\")  # async works\n...     return sm.result\n\n>>> asyncio.run(main())\n'async action completed'\n\n```\n\nNo class swapping, no feature matrices to consult — just `StateChart`.\n\n\n## Model integration\n\n*transitions* binds directly to a model object:\n\n```python\nclass MyModel:\n    pass\n\nmodel = MyModel()\nmachine = Machine(model=model, states=states, transitions=transitions, initial=\"draft\")\nmodel.produce()  # events are added to the model\n```\n\npython-statemachine offers two approaches. See {ref}`domain models` for the full\nreference.\n\n**1. Pass a model to the state machine:**\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class MyModel:\n...     pass\n\n>>> class Workflow(StateChart):\n...     draft = State(initial=True)\n...     producing = State(final=True)\n...     produce = draft.to(producing)\n\n>>> model = MyModel()\n>>> sm = Workflow(model=model)\n>>> sm.model is model\nTrue\n\n```\n\n**2. Use `MachineMixin` for ORM integration:**\n\n```py\n>>> from statemachine.mixins import MachineMixin\n\n>>> class WorkflowModel(MachineMixin):\n...     state_machine_name = \"__main__.Workflow\"\n...     state_machine_attr = \"sm\"\n...     bind_events_as_methods = True\n...\n...     state = 0  # persisted field\n\n```\n\n`MachineMixin` is particularly useful with Django models, where the state field\nis a database column. See {ref}`integrations <machinemixin>` for details.\n\n\n## Listeners\n\nIn *transitions*, cross-cutting concerns like logging or auditing are handled through\nmachine-level callbacks (`prepare_event`, `finalize_event`, `on_exception`). These are\ncallables passed to the `Machine` constructor — not separate objects. All callbacks\nmust live on the model or be passed as functions:\n\n```python\nmachine = Machine(\n    model=model,\n    states=states,\n    transitions=transitions,\n    initial=\"draft\",\n    prepare_event=\"log_event\",\n    finalize_event=\"cleanup\",\n)\n```\n\npython-statemachine has a full **listener/observer pattern**. A listener is any object\nwith methods matching the callback naming conventions — no base class required. Listeners\nare first-class: they receive the same callbacks as the state machine itself, with full\n{ref}`dependency injection <dependency-injection>`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class AuditListener:\n...     def __init__(self):\n...         self.log = []\n...     def after_transition(self, event, source, target):\n...         self.log.append(f\"{event}: {source.id} -> {target.id}\")\n\n>>> class OrderMachine(StateChart):\n...     listeners = [AuditListener]\n...     draft = State(initial=True)\n...     confirmed = State(final=True)\n...     confirm = draft.to(confirmed)\n\n>>> sm = OrderMachine()\n>>> sm.send(\"confirm\")\n>>> sm.active_listeners[0].log\n['confirm: draft -> confirmed']\n\n```\n\nListeners can be declared at the class level (`listeners = [...]`), passed at\nconstruction time (`OrderMachine(listeners=[...])`), or attached at runtime\n(`sm.add_listener(...)`). Multiple independent listeners compose naturally — each\nreceives only the parameters it declares.\n\nClass-level listeners support inheritance (child listeners append after parent),\na `setup()` protocol for receiving runtime dependencies (DB sessions, Redis\nclients), and `functools.partial` for configuration.\n\nSee {ref}`listeners` for the full reference.\n\n\n## Error handling\n\n*transitions* lets exceptions propagate normally:\n\n```python\ntry:\n    machine.produce()\nexcept SomeError:\n    # handle error\n    pass\n```\n\npython-statemachine supports both styles. With `StateMachine` (the 2.x base class),\nexceptions propagate as in *transitions*. With `StateChart`, you can opt into\nstructured error handling:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class RobustWorkflow(StateChart):\n...     draft = State(initial=True)\n...     error_state = State(final=True)\n...\n...     go = draft.to(draft, on=\"bad_action\")\n...     error_execution = draft.to(error_state)\n...\n...     def bad_action(self):\n...         raise RuntimeError(\"something went wrong\")\n\n>>> sm = RobustWorkflow()\n>>> sm.send(\"go\")\n>>> sm.error_state.is_active\nTrue\n\n```\n\nWhen `catch_errors_as_events=True` (default in `StateChart`), runtime exceptions\nare caught and dispatched as `error.execution` internal events. You can define\ntransitions that handle these errors, keeping the state machine in a consistent\nstate. The error object is available as `error` in callback kwargs.\n\nSee {ref}`error handling <error-execution>` for full details.\n\n\n## Validations\n\n*transitions* does not validate the consistency of your state machine definition.\nYou can define unreachable states, trap states (non-final states with no outgoing\ntransitions), or reference nonexistent callback names — and the library will not\nwarn you. Errors only surface at runtime, when an event fails to trigger or a\ncallback is not found.\n\npython-statemachine validates the statechart structure at **two stages**:\n\n1. **Class definition time** — structural checks run as soon as the class body is\n   evaluated. If any check fails, the class itself is not created:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.exceptions import InvalidDefinition\n\n>>> try:\n...     class Bad(StateChart):\n...         red = State(initial=True)\n...         green = State()\n...         hazard = State()\n...         cycle = red.to(green) | green.to(red)\n...         blink = hazard.to.itself()\n... except InvalidDefinition as e:\n...     print(e)\nThere are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard']\n\n```\n\n2. **Instance creation time** — callback resolution, boolean expression parsing,\n   and other runtime checks:\n\n```py\n>>> class MyChart(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...     go = a.to(b, on=\"nonexistent_method\")\n\n>>> try:\n...     MyChart()\n... except InvalidDefinition as e:\n...     assert \"Did not found name 'nonexistent_method'\" in str(e)\n\n```\n\nBuilt-in validations include: exactly one initial state, no transitions from final\nstates, unreachable states, trap states, final state reachability, internal\ntransition targets, callback resolution, and boolean expression parsing.\nSee {ref}`validations` for the full list.\n\n\n(feature-matrix)=\n\n## Feature matrix\n\n| Feature | *transitions* | python-statemachine |\n|---|:---:|:---:|\n| Flat state machines | Yes | Yes |\n| {ref}`Compound (nested) states <compound-states>` | Yes | Yes |\n| {ref}`Parallel states <parallel-states>` | Yes | Yes |\n| {ref}`History pseudo-states <history-states>` | No | **Yes** |\n| {ref}`Eventless transitions <eventless>` | No | **Yes** |\n| {ref}`Final states <final-state>` | Yes | Yes |\n| {ref}`Condition expressions <condition expressions>` | No | **Yes** |\n| {ref}`In() state checks <condition expressions>` | No | **Yes** |\n| {ref}`Dependency injection <dependency-injection>` | No | **Yes** |\n| {ref}`Auto async detection <async>` | No | **Yes** |\n| {ref}`error.execution handling <error-execution>` | No | **Yes** |\n| {ref}`done.state / done.invoke events <done-state-events>` | Callback only | **Yes** |\n| {ref}`Delayed events <delayed-events>` | No | **Yes** |\n| {ref}`Internal events (raise_()) <sending-events>` | No | **Yes** |\n| {ref}`Invoke (background work) <invoke>` | No | **Yes** |\n| {ref}`Listener/observer pattern <listeners>` | No | **Yes** |\n| {ref}`Definition-time validations <validations>` | No | **Yes** |\n| {ref}`SCXML conformance <processing-model>` | No | **Yes** |\n| {ref}`Diagrams <diagrams>` | Yes | Yes |\n| {ref}`Django integration <machinemixin>` | Community | Built-in |\n| {ref}`Model binding <models>` | Yes | Yes |\n| {ref}`Wildcard transitions (*) <events>` | Yes | Yes |\n| {ref}`Reflexive transitions <self-transition>` | Yes | Yes |\n| Ordered transitions | Yes | Via explicit wiring |\n| Tags on states | Yes | Via subclassing |\n| {ref}`Machine nesting (children) <invoke>` | Yes | Yes (invoke) |\n| {ref}`Timeout transitions <timeout>` | Yes | Yes |\n"
  },
  {
    "path": "docs/index.md",
    "content": "```{include} ../README.md\n```\n\n---\n\n```{toctree}\n:caption: Getting started\n:maxdepth: 2\n:hidden:\n\ninstallation\ntutorial\n```\n\n```{toctree}\n:caption: Core Concepts\n:maxdepth: 2\n:hidden:\n\nconcepts\nstates\ntransitions\nevents\nactions\nguards\n```\n\n```{toctree}\n:caption: Runtime\n:maxdepth: 2\n:hidden:\n\nstatechart\nprocessing_model\nerror_handling\nasync\nlisteners\n```\n\n```{toctree}\n:caption: Configuration\n:maxdepth: 2\n:hidden:\n\nbehaviour\nvalidations\n```\n\n```{toctree}\n:caption: Advanced\n:maxdepth: 2\n:hidden:\n\ninvoke\nmodels\nintegrations\nweighted_transitions\ntimeout\n```\n\n```{toctree}\n:caption: How to\n:maxdepth: 2\n:hidden:\n\nhow-to/coming_from_transitions\nhow-to/coming_from_state_pattern\n```\n\n```{toctree}\n:caption: Reference\n:maxdepth: 2\n:hidden:\n\napi\ndiagram\nauto_examples/index\ncontributing\nauthors\n```\n\n```{toctree}\n:caption: Releases\n:maxdepth: 2\n:hidden:\n\nreleases/3.0.0\nreleases/upgrade_2x_to_3\nreleases/index\n```\n"
  },
  {
    "path": "docs/installation.md",
    "content": "# Installation\n\n\n## Latest release\n\nTo install using [uv](https://docs.astral.sh/uv):\n\n```shell\nuv add python-statemachine\n```\n\nTo install using [poetry](https://python-poetry.org/):\n\n```shell\npoetry add python-statemachine\n```\n\nAlternatively, if you prefer using [pip](https://pip.pypa.io):\n\n```shell\npython3 -m pip install python-statemachine\n```\n\nFor those looking to generate diagrams from your state machines, [pydot](https://github.com/pydot/pydot) and [Graphviz](https://graphviz.org/) are required.\nConveniently, you can install python-statemachine along with the `pydot` dependency using the extras option.\nFor more information, please refer to our documentation.\n\n```shell\npython3 -m pip install \"python-statemachine[diagrams]\"\n```\n\n\n\n## From sources\n\nThe sources for Python State Machine can be downloaded from the [Github repo](https://github.com/fgmacedo/python-statemachine).\n\nYou can either clone the public repository:\n\n```shell\ngit clone git://github.com/fgmacedo/python-statemachine\n```\n\nOr download the `tarball`:\n\n```shell\ncurl  -OL https://github.com/fgmacedo/python-statemachine/tarball/main\n```\n\nOnce you have a copy of the source, you can install it with:\n\n```shell\npython3 -m pip install -e .\n```\n"
  },
  {
    "path": "docs/integrations.md",
    "content": "\n# Integrations\n\n(machinemixin)=\n## MachineMixin\n\n{ref}`Domain models` can inherit from `MachineMixin` to automatically instantiate\nand bind a {ref}`StateChart` to any Python class. This is the foundation for\nintegrating state machines with ORMs and other domain objects.\n\n```{seealso}\nSee the [MachineMixin API reference](api.md#machinemixin) for the full list of attributes.\n```\n\n### Example\n\nGiven this state machine:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> from statemachine.mixins import MachineMixin\n\n>>> class CampaignMachine(StateChart):\n...     \"A workflow machine\"\n...     draft = State('Draft', initial=True, value=1)\n...     producing = State('Being produced', value=2)\n...     closed = State('Closed', value=3, final=True)\n...     cancelled = State('Cancelled', value=4, final=True)\n...\n...     add_job = draft.to.itself() | producing.to.itself()\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n...     cancel = cancelled.from_(draft, producing)\n\n```\n\nYou can attach it to a model by inheriting from `MachineMixin` and setting\n`state_machine_name` to the fully qualified class name:\n\n``` py\n>>> from statemachine import registry\n>>> registry.register(CampaignMachine)  # register for lookup by qualname\n<class '...CampaignMachine'>\n>>> registry._initialized = True  # skip Django autodiscovery in doctest\n\n>>> class Workflow(MachineMixin):\n...     state_machine_name = '__main__.CampaignMachine'\n...     state_machine_attr = 'sm'\n...     state_field_name = 'workflow_step'\n...     bind_events_as_methods = True\n...\n...     workflow_step = 1\n\n>>> model = Workflow()\n\n>>> isinstance(model.sm, CampaignMachine)\nTrue\n\n>>> model.workflow_step\n1\n\n>>> model.sm.draft in model.sm.configuration\nTrue\n\n```\n\nWith `bind_events_as_methods = True`, events become methods on the model itself:\n\n``` py\n>>> model = Workflow()\n>>> model.produce()\n>>> model.workflow_step\n2\n\n>>> model.sm.cancel()  # you can still call the SM directly\n\n>>> model.workflow_step\n4\n\n>>> model.sm.cancelled in model.sm.configuration\nTrue\n\n```\n\n```{note}\nIn this example `state_machine_name` uses a `__main__` prefix because the class\nis defined inline for doctest purposes. In your code, use the fully qualified\npath (e.g., `'myapp.statemachines.CampaignMachine'`).\n```\n\n(django integration)=\n## Django integration\n\nWhen used in a Django App, this library implements an auto-discovery hook similar to how Django's\nbuilt-in **admin** [autodiscover](https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.autodiscover).\n\n> This library attempts to import a **statemachine** or **statemachines** module in each installed\n> application. Such modules are expected to register `StateChart` classes to be used with\n> the {ref}`MachineMixin`.\n\n\n```{hint}\nWe advise keeping {ref}`StateChart` definitions in their own modules to avoid circular\nreferences. If you place state machines in modules named `statemachine` or `statemachines`\ninside installed Django Apps, they will be automatically imported and registered.\n\nThat said, nothing stops you from declaring your state machine alongside your models.\n```\n\n\n### Django example\n\n```py\n# campaign/statemachines.py\n\nfrom statemachine import StateChart\nfrom statemachine import State\n\n\nclass CampaignMachine(StateChart):\n    \"A workflow machine\"\n    draft = State('Draft', initial=True, value=1)\n    producing = State('Being produced', value=2)\n    closed = State('Closed', value=3)\n    cancelled = State('Cancelled', value=4)\n\n    add_job = draft.to.itself() | producing.to.itself()\n    produce = draft.to(producing)\n    deliver = producing.to(closed)\n    cancel = cancelled.from_(draft, producing)\n```\n\nIntegrate with your Django model using `MachineMixin`:\n\n```py\n# campaign/models.py\n\nfrom django.db import models\n\nfrom statemachine.mixins import MachineMixin\n\n\nclass Campaign(models.Model, MachineMixin):\n    state_machine_name = 'campaign.statemachines.CampaignMachine'\n    state_machine_attr = 'sm'\n    state_field_name = 'step'\n\n    name = models.CharField(max_length=30)\n    step = models.IntegerField()\n```\n\n### Data migrations\n\nDjango's `apps.get_model()` returns **historical model** classes that are dynamically created\nand don't carry user-defined class attributes like `state_machine_name`. Since version 2.6.0,\n`MachineMixin` detects these historical models and gracefully skips state machine\ninitialization, so data migrations that use `apps.get_model()` work without errors.\n\n```{note}\nThe state machine instance will **not** be available on historical model objects.\nIf your data migration needs to interact with the state machine, set the attributes\nmanually on the historical model class:\n\n    def backfill_data(apps, schema_editor):\n        MyModel = apps.get_model(\"myapp\", \"MyModel\")\n        MyModel.state_machine_name = \"myapp.statemachines.MyStateMachine\"\n        for obj in MyModel.objects.all():\n            obj.statemachine  # now available\n```\n"
  },
  {
    "path": "docs/invoke.md",
    "content": "(invoke)=\n# Invoke\n\nInvoke lets a state spawn external work — API calls, file I/O, child state machines —\nwhen it is entered, and automatically cancel that work when the state is exited. This\nfollows the [SCXML `<invoke>` semantics](https://www.w3.org/TR/scxml/#invoke) and is\nsimilar to the **do activity** (`do/`) concept in UML Statecharts — an ongoing behavior\nthat runs for the duration of a state and is cancelled when the state is exited.\n\n## Execution model\n\nInvoke handlers run **outside** the main state machine processing loop:\n\n- **Sync engine**: each invoke handler runs in a **daemon thread**.\n- **Async engine**: each invoke handler runs in a **thread executor**\n  (`loop.run_in_executor`), wrapped in an `asyncio.Task`. The executor is used because\n  invoke handlers are expected to perform blocking I/O (network calls, file access,\n  subprocess communication) that would freeze the event loop if run directly.\n\nWhen a handler completes, a `done.invoke.<state>.<id>` event is automatically sent back\nto the machine. If the handler raises an exception, an `error.execution` event is sent\ninstead. If the owning state is exited before the handler finishes, the invocation is\n**cancelled** — `ctx.cancelled` is set and `on_cancel()` is called on `IInvoke` handlers.\n\n## Callback group\n\nInvoke is a first-class callback group, just like `enter` and `exit`. This means\nconvention naming (`on_invoke_<state>`), decorators (`@state.invoke`), inline callables,\nand the full {ref}`SignatureAdapter <actions>` dependency injection all work out of the box.\n\nSee the {ref}`actions` page for how invoke fits into the overall\ncallback {ref}`Ordering` and the available\n{ref}`dependency injection <dynamic-dispatch>` parameters.\n\n## Quick start\n\nThe simplest invoke is a plain callable passed to the `invoke` parameter. Here we read a\nconfig file in a background thread and transition to `ready` when the data is available:\n\n```py\n>>> import json\n>>> import tempfile\n>>> import time\n>>> from pathlib import Path\n>>> from statemachine import State, StateChart\n\n>>> config_file = Path(tempfile.mktemp(suffix=\".json\"))\n>>> _ = config_file.write_text('{\"db_host\": \"localhost\", \"db_port\": 5432}')\n\n>>> def load_config():\n...     return json.loads(config_file.read_text())\n\n>>> class ConfigLoader(StateChart):\n...     loading = State(initial=True, invoke=load_config)\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.config = data\n\n>>> sm = ConfigLoader()\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n>>> sm.config\n{'db_host': 'localhost', 'db_port': 5432}\n\n>>> config_file.unlink()\n\n```\n\nWhen `loading` is entered, `load_config()` runs in a background thread. When it returns,\na `done.invoke.loading.<id>` event is automatically sent to the machine, triggering\nthe `done_invoke_loading` transition. The return value is available as the `data`\nkeyword argument in callbacks on the target state.\n\n## Naming conventions\n\nLike `on_enter_<state>` and `on_exit_<state>`, invoke supports naming conventions\n(see {ref}`State actions` for the general pattern):\n\n- `on_invoke_state` — generic, called for every state with invoke\n- `on_invoke_<state_id>` — specific to a state\n\n```py\n>>> config_file = Path(tempfile.mktemp(suffix=\".json\"))\n>>> _ = config_file.write_text('{\"feature_flags\": [\"dark_mode\", \"beta_api\"]}')\n\n>>> class FeatureLoader(StateChart):\n...     loading = State(initial=True)\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def on_invoke_loading(self, **kwargs):\n...         \"\"\"Naming convention: on_invoke_<state_id>.\"\"\"\n...         return json.loads(config_file.read_text())\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.features = data\n\n>>> sm = FeatureLoader()\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n>>> sm.features[\"feature_flags\"]\n['dark_mode', 'beta_api']\n\n>>> config_file.unlink()\n\n```\n\n## Decorator syntax\n\nUse the `@state.invoke` decorator (same pattern as `@state.enter` and `@state.exit` —\nsee {ref}`Bind state actions using decorator syntax`):\n\n```py\n>>> config_file = Path(tempfile.mktemp(suffix=\".txt\"))\n>>> _ = config_file.write_text(\"line 1\\nline 2\\nline 3\\n\")\n\n>>> class LineCounter(StateChart):\n...     counting = State(initial=True)\n...     done = State(final=True)\n...     done_invoke_counting = counting.to(done)\n...\n...     @counting.invoke\n...     def count_lines(self, **kwargs):\n...         text = config_file.read_text()\n...         return len(text.splitlines())\n...\n...     def on_enter_done(self, data=None, **kwargs):\n...         self.total_lines = data\n\n>>> sm = LineCounter()\n>>> time.sleep(0.2)\n\n>>> \"done\" in sm.configuration_values\nTrue\n>>> sm.total_lines\n3\n\n>>> config_file.unlink()\n\n```\n\n## `done.invoke` transitions\n\nUse the `done_invoke_<state>` naming convention to declare transitions that fire when\nan invoke handler completes:\n\n```py\n>>> config_file = Path(tempfile.mktemp(suffix=\".json\"))\n>>> _ = config_file.write_text('{\"version\": \"3.0.0\"}')\n\n>>> class VersionChecker(StateChart):\n...     checking = State(initial=True, invoke=lambda: json.loads(config_file.read_text()))\n...     checked = State(final=True)\n...     done_invoke_checking = checking.to(checked)\n...\n...     def on_enter_checked(self, data=None, **kwargs):\n...         self.version = data[\"version\"]\n\n>>> sm = VersionChecker()\n>>> time.sleep(0.2)\n\n>>> \"checked\" in sm.configuration_values\nTrue\n>>> sm.version\n'3.0.0'\n\n>>> config_file.unlink()\n\n```\n\nThe `done_invoke_<state>` prefix maps to the `done.invoke.<state>` event family,\nmatching any invoke completion for that state regardless of the specific invoke ID.\n\n## IInvoke protocol\n\nFor advanced use cases, implement the `IInvoke` protocol. This gives you access to\nthe `InvokeContext` — with the invoke ID, cancellation signal, event kwargs, and a\nreference to the parent machine:\n\n```py\n>>> from statemachine.invoke import IInvoke, InvokeContext\n\n>>> class FileReader:\n...     \"\"\"Reads a file and returns its content. Supports cancellation.\"\"\"\n...     def run(self, ctx: InvokeContext):\n...         # ctx.invokeid — unique ID for this invocation\n...         # ctx.state_id — the state that triggered invoke\n...         # ctx.cancelled — threading.Event, set when state exits\n...         # ctx.send — send events to parent machine\n...         # ctx.machine — reference to parent machine\n...         # ctx.kwargs — keyword arguments from the triggering event\n...         path = ctx.machine.file_path\n...         return Path(path).read_text()\n...\n...     def on_cancel(self):\n...         pass  # cleanup resources if needed\n\n>>> isinstance(FileReader(), IInvoke)\nTrue\n\n```\n\nPass a class to the `invoke` parameter — each state machine instance gets a fresh handler:\n\n```py\n>>> config_file = Path(tempfile.mktemp(suffix=\".csv\"))\n>>> _ = config_file.write_text(\"name,age\\nAlice,30\\nBob,25\\n\")\n\n>>> class CSVLoader(StateChart):\n...     loading = State(initial=True, invoke=FileReader)\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def __init__(self, file_path, **kwargs):\n...         self.file_path = file_path\n...         super().__init__(**kwargs)\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.content = data\n\n>>> sm = CSVLoader(file_path=str(config_file))\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n>>> sm.content\n'name,age\\nAlice,30\\nBob,25\\n'\n\n>>> config_file.unlink()\n\n```\n\n## Cancellation\n\nWhen a state with active invoke handlers is exited:\n\n1. `ctx.cancelled` is set (a `threading.Event`) — handlers should poll this\n2. `on_cancel()` is called on `IInvoke` handlers (if defined)\n3. For the async engine, the asyncio Task is cancelled\n\nEvents from cancelled invocations are silently ignored.\n\n```py\n>>> cancel_called = []\n\n>>> class SlowFileReader:\n...     def run(self, ctx: InvokeContext):\n...         ctx.cancelled.wait(timeout=5.0)\n...\n...     def on_cancel(self):\n...         cancel_called.append(True)\n\n>>> class CancelMachine(StateChart):\n...     loading = State(initial=True, invoke=SlowFileReader)\n...     stopped = State(final=True)\n...     cancel = loading.to(stopped)\n\n>>> sm = CancelMachine()\n>>> time.sleep(0.05)\n>>> sm.send(\"cancel\")\n>>> time.sleep(0.05)\n>>> cancel_called\n[True]\n\n```\n\n## Event data propagation\n\nWhen a state with invoke handlers is entered via an event, the keyword arguments from\nthat event are forwarded to the invoke handlers. Plain callables receive them via\n{ref}`SignatureAdapter <actions>` dependency injection; `IInvoke` handlers receive them\nvia `ctx.kwargs`:\n\n```py\n>>> config_file = Path(tempfile.mktemp(suffix=\".json\"))\n>>> _ = config_file.write_text('{\"debug\": true}')\n\n>>> class ConfigByName(StateChart):\n...     idle = State(initial=True)\n...     loading = State()\n...     ready = State(final=True)\n...     start = idle.to(loading)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def on_invoke_loading(self, file_name=None, **kwargs):\n...         \"\"\"file_name comes from send('start', file_name=...).\"\"\"\n...         return json.loads(Path(file_name).read_text())\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.config = data\n\n>>> sm = ConfigByName()\n>>> sm.send(\"start\", file_name=str(config_file))\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n>>> sm.config\n{'debug': True}\n\n>>> config_file.unlink()\n\n```\n\nFor initial states, any extra keyword arguments passed to the `StateChart` constructor\nare forwarded as event data. This makes self-contained machines that start processing\nimmediately especially useful:\n\n```py\n>>> config_file = Path(tempfile.mktemp(suffix=\".json\"))\n>>> _ = config_file.write_text('{\"theme\": \"dark\"}')\n\n>>> class AppLoader(StateChart):\n...     loading = State(initial=True)\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def on_invoke_loading(self, config_path=None, **kwargs):\n...         \"\"\"config_path comes from the constructor: AppLoader(config_path=...).\"\"\"\n...         return json.loads(Path(config_path).read_text())\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.config = data\n\n>>> sm = AppLoader(config_path=str(config_file))\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n>>> sm.config\n{'theme': 'dark'}\n\n>>> config_file.unlink()\n\n```\n\n## Error handling\n\nIf an invoke handler raises an exception, `error.execution` is sent to the machine's\ninternal queue (when `catch_errors_as_events=True`, the default for `StateChart`). You can\nhandle it with a transition for `error.execution`:\n\n```py\n>>> class MissingFileLoader(StateChart):\n...     loading = State(\n...         initial=True,\n...         invoke=lambda: Path(\"/tmp/nonexistent_file_12345.json\").read_text(),\n...     )\n...     error_state = State(final=True)\n...     error_execution = loading.to(error_state)\n...\n...     def on_enter_error_state(self, error=None, **kwargs):\n...         self.error_type = type(error).__name__\n\n>>> sm = MissingFileLoader()\n>>> time.sleep(0.2)\n\n>>> \"error_state\" in sm.configuration_values\nTrue\n>>> sm.error_type\n'FileNotFoundError'\n\n```\n\n## Multiple invokes\n\n### Independent invokes (one event each)\n\nPass a list to run multiple handlers concurrently. Each handler is an independent\ninvocation that sends its own `done.invoke.<state>.<id>` completion event.\n\nThis means that the **first** handler to complete triggers the `done_invoke_<state>`\ntransition, which exits the owning state and **cancels all remaining invocations**.\nIf you need all handlers to complete before transitioning, use\n{func}`~statemachine.invoke.invoke_group` instead (see below).\n\n```py\n>>> file_a = Path(tempfile.mktemp(suffix=\".txt\"))\n>>> file_b = Path(tempfile.mktemp(suffix=\".txt\"))\n>>> _ = file_a.write_text(\"hello\")\n>>> _ = file_b.write_text(\"world\")\n\n>>> class MultiLoader(StateChart):\n...     loading = State(\n...         initial=True,\n...         invoke=[lambda: file_a.read_text(), lambda: file_b.read_text()],\n...     )\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n\n>>> sm = MultiLoader()\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n\n>>> file_a.unlink()\n>>> file_b.unlink()\n\n```\n\nThis follows the [SCXML spec](https://www.w3.org/TR/scxml/#invoke): each `<invoke>`\nis independent and generates its own completion event. Use this when you only need\n**any one** of the handlers to complete, or when each invoke is handled by a\nseparate transition.\n\n### Grouped invokes (wait for all)\n\nUse {func}`~statemachine.invoke.invoke_group` to run multiple callables concurrently\nand wait for **all** of them to complete before sending a single `done.invoke` event.\nUnlike independent invokes (list), the transition only fires after every callable\nfinishes, and the `data` is a list of results in the same order as the input callables:\n\n```py\n>>> from statemachine.invoke import invoke_group\n\n>>> file_a = Path(tempfile.mktemp(suffix=\".txt\"))\n>>> file_b = Path(tempfile.mktemp(suffix=\".txt\"))\n>>> _ = file_a.write_text(\"hello\")\n>>> _ = file_b.write_text(\"world\")\n\n>>> class BatchLoader(StateChart):\n...     loading = State(\n...         initial=True,\n...         invoke=invoke_group(\n...             lambda: file_a.read_text(),\n...             lambda: file_b.read_text(),\n...         ),\n...     )\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.results = data\n\n>>> sm = BatchLoader()\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n>>> sm.results\n['hello', 'world']\n\n>>> file_a.unlink()\n>>> file_b.unlink()\n\n```\n\nIf any callable raises, the remaining ones are cancelled and an `error.execution`\nevent is sent. If the owning state is exited before all callables finish, the group\nis cancelled.\n\n## Child state machines\n\nPass a `StateChart` subclass to spawn a child machine:\n\n```py\n>>> class ChildMachine(StateChart):\n...     start = State(initial=True)\n...     end = State(final=True)\n...     go = start.to(end)\n...\n...     def on_enter_start(self, **kwargs):\n...         self.send(\"go\")\n\n>>> class ParentMachine(StateChart):\n...     loading = State(initial=True, invoke=ChildMachine)\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n\n>>> sm = ParentMachine()\n>>> time.sleep(0.2)\n\n>>> \"ready\" in sm.configuration_values\nTrue\n\n```\n\nThe child machine is instantiated and run when the parent's `loading` state is entered.\nWhen the child terminates (reaches a final state), a `done.invoke` event is sent to the\nparent, triggering the `done_invoke_loading` transition.\n"
  },
  {
    "path": "docs/listeners.md",
    "content": "(observers)=\n(listeners)=\n\n# Listeners\n\nA **listener** is an external object that observes a state machine's lifecycle\nwithout modifying its class definition. Listeners receive the same\n{ref}`generic callbacks <actions>` as the state machine itself —\n`on_enter_state()`, `after_transition()`, `on_exit_state()`, and so on —\nmaking them ideal for cross-cutting concerns like logging, persistence,\ntelemetry, or UI updates.\n\nUnder the hood, the `StateChart` class itself is registered as a listener —\nthis is how naming-convention callbacks like `on_enter_idle()` are\ndiscovered. {ref}`Domain models <models>` are also registered as listeners.\nThis means that an external listener has the **same level of access** to\ncallbacks as methods defined directly on the state machine class.\n\n```{tip}\nWhy use a listener instead of defining callbacks directly on the class?\nListeners keep concerns **separate and reusable** — the same logging\nlistener can observe any state machine, and you can attach multiple\nindependent listeners without them interfering with each other.\n```\n\n\n## Defining a listener\n\nA listener is any object with methods that match the\n{ref}`callback naming conventions <actions>`. The library inspects the\nmethod signatures and calls them with {ref}`dependency injection <dependency-injection>`,\nso each listener receives only the parameters it declares:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class LogListener:\n...     def __init__(self, name):\n...         self.name = name\n...\n...     def after_transition(self, event, source, target):\n...         print(f\"{self.name} after: {source.id}--({event})-->{target.id}\")\n...\n...     def on_enter_state(self, target, event):\n...         print(f\"{self.name} enter: {target.id} from {event}\")\n\n```\n\nNo base class or interface is required — any object with matching method\nnames works.\n\n\n## Class-level declarations\n\nThe most common way to attach listeners is at the class level, using the\n`listeners` class attribute. This ensures listeners are automatically\ncreated for every instance:\n\n```py\n>>> class AuditListener:\n...     def __init__(self):\n...         self.log = []\n...\n...     def after_transition(self, event, source, target):\n...         self.log.append(f\"{event}: {source.id} -> {target.id}\")\n\n>>> class OrderMachine(StateChart):\n...     listeners = [AuditListener]\n...\n...     draft = State(initial=True)\n...     confirmed = State(final=True)\n...     confirm = draft.to(confirmed)\n\n>>> sm = OrderMachine()\n>>> sm.send(\"confirm\")\n>>> sm.active_listeners[0].log\n['confirm: draft -> confirmed']\n\n```\n\nThe `listeners` attribute accepts two forms:\n\n- **Callable** (class, `functools.partial`, lambda): acts as a **factory** —\n  called once per instance to produce a fresh listener. Use this for\n  listeners that accumulate state.\n- **Instance** (pre-built object): **shared** across all instances. Use\n  this for stateless listeners like a global logger.\n\n\n### Configuration with `functools.partial`\n\nUse `functools.partial` to pass configuration to listener factories:\n\n```py\n>>> from functools import partial\n\n>>> class HistoryListener:\n...     def __init__(self, max_size=50):\n...         self.max_size = max_size\n...         self.entries = []\n...\n...     def after_transition(self, event, source, target):\n...         self.entries.append(f\"{source.id} -> {target.id}\")\n...         if len(self.entries) > self.max_size:\n...             self.entries.pop(0)\n\n>>> class TrackedMachine(StateChart):\n...     listeners = [partial(HistoryListener, max_size=10)]\n...\n...     s1 = State(initial=True)\n...     s2 = State(final=True)\n...     go = s1.to(s2)\n\n>>> sm = TrackedMachine()\n>>> sm.send(\"go\")\n>>> sm.active_listeners[0].entries\n['s1 -> s2']\n\n```\n\n\n### Inheritance\n\nChild class listeners are appended after parent listeners. The full MRO\nchain is respected:\n\n```py\n>>> class SimpleLogListener:\n...     def after_transition(self, event, source, target):\n...         pass\n\n>>> class BaseMachine(StateChart):\n...     listeners = [SimpleLogListener]\n...\n...     s1 = State(initial=True)\n...     s2 = State(final=True)\n...     go = s1.to(s2)\n\n>>> class ChildMachine(BaseMachine):\n...     listeners = [AuditListener]\n\n>>> sm = ChildMachine()\n>>> [type(l).__name__ for l in sm.active_listeners]\n['SimpleLogListener', 'AuditListener']\n\n```\n\nTo **replace** parent listeners instead of extending, set\n`listeners_inherit = False`:\n\n```py\n>>> class ReplacedMachine(BaseMachine):\n...     listeners_inherit = False\n...     listeners = [AuditListener]\n\n>>> sm = ReplacedMachine()\n>>> [type(l).__name__ for l in sm.active_listeners]\n['AuditListener']\n\n```\n\n\n## Attaching at construction\n\nPass listeners to the constructor for instance-specific observers.\nRuntime listeners are appended **after** class-level listeners:\n\n```py\n>>> runtime_listener = AuditListener()\n>>> sm = OrderMachine(listeners=[runtime_listener])\n>>> sm.send(\"confirm\")\n>>> [type(l).__name__ for l in sm.active_listeners]\n['AuditListener', 'AuditListener']\n\n>>> runtime_listener.log\n['confirm: draft -> confirmed']\n\n```\n\n\n## Attaching at runtime\n\nUse `add_listener()` to attach listeners to an already running instance.\nThis is useful when the listener depends on runtime context or when you\nwant to start observing after initialization:\n\n```py\n>>> class LedPanel:\n...     def __init__(self, color):\n...         self.color = color\n...         self.is_on = False\n...\n...     def on_enter_state(self, target, **kwargs):\n...         if target.id == self.color:\n...             self.is_on = True\n...\n...     def on_exit_state(self, source, **kwargs):\n...         if source.id == self.color:\n...             self.is_on = False\n\n>>> class TrafficLight(StateChart):\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n>>> sm = TrafficLight()\n>>> green_led = LedPanel(\"green\")\n>>> yellow_led = LedPanel(\"yellow\")\n>>> sm.add_listener(green_led, yellow_led)  # doctest: +ELLIPSIS\nTrafficLight...\n\n>>> green_led.is_on, yellow_led.is_on\n(False, False)\n\n>>> sm.send(\"cycle\")\n>>> green_led.is_on, yellow_led.is_on\n(False, True)\n\n```\n\n\n## The `setup()` protocol\n\nListeners that need runtime dependencies (e.g., a database session, a\nRedis client) can define a `setup()` method. It is called during the\nstate machine's `__init__` with the instance and any extra `**kwargs`\npassed to the constructor. {ref}`Dependency injection <dependency-injection>`\nensures each listener receives only the kwargs it declares:\n\n```py\n>>> class DBListener:\n...     def __init__(self):\n...         self.session = None\n...\n...     def setup(self, sm, session=None, **kwargs):\n...         self.session = session\n\n>>> class CacheListener:\n...     def __init__(self):\n...         self.redis = None\n...\n...     def setup(self, sm, redis=None, **kwargs):\n...         self.redis = redis\n\n>>> class PersistentMachine(StateChart):\n...     listeners = [DBListener, CacheListener]\n...\n...     s1 = State(initial=True)\n...     s2 = State(final=True)\n...     go = s1.to(s2)\n\n>>> sm = PersistentMachine(session=\"db_conn\", redis=\"redis_conn\")\n>>> sm.active_listeners[0].session\n'db_conn'\n>>> sm.active_listeners[1].redis\n'redis_conn'\n\n```\n\nMultiple listeners with different dependencies compose naturally — each\n`setup()` picks only the kwargs it needs.\n\n```{note}\nThe `setup()` method is only called on **factory-created** instances\n(callable entries in the `listeners` list). Shared instances (pre-built\nobjects) do not receive `setup()` calls — they are assumed to be already\nconfigured.\n```\n\n\n```{seealso}\nSee {ref}`actions` for the full list of callback groups and\n{ref}`dependency injection <dependency-injection>` for how method\nsignatures are matched.\n```\n"
  },
  {
    "path": "docs/models.md",
    "content": "(domain models)=\n(models)=\n# Domain models\n\nIf you need to use any other object to persist the current state, or you're using the\nstate machine to control the flow of another object, you can pass this object\nto the `StateChart` constructor.\n\nIf you don't pass an explicit model instance, this simple `Model` will be used:\n\n\n```{literalinclude} ../statemachine/model.py\n:language: python\n:linenos:\n```\n\n\n```{seealso}\nSee the {ref}`sphx_glr_auto_examples_order_control_rich_model_machine.py` as example of using a\ndomain object to hold attributes and methods to be used on the `StateChart` definition.\n```\n\n```{hint}\nDomain models are registered as {ref}`listeners`, so you can have the same level of functionalities\nprovided to the built-in {ref}`StateChart`, such as implementing all {ref}`actions` and\n{ref}`guards` on your domain model and keeping only the definition of {ref}`states` and\n{ref}`transitions` on the {ref}`StateChart`.\n```\n\n## Typed models\n\n`StateChart` supports a generic type parameter so that type checkers (mypy, pyright) and IDEs\ncan infer the type of `sm.model` and provide code completion.\n\nDeclare your model class and pass it as a type parameter to `StateChart`:\n\n```python\n>>> from statemachine import State, StateChart\n\n>>> class OrderModel:\n...     order_id: str = \"\"\n...     total: float = 0.0\n...     def confirm(self):\n...         return f\"Order {self.order_id} confirmed: ${self.total}\"\n\n>>> class OrderWorkflow(StateChart[\"OrderModel\"]):\n...     draft = State(initial=True)\n...     confirmed = State(final=True)\n...     confirm = draft.to(confirmed, on=\"on_confirm\")\n...     def on_confirm(self):\n...         return self.model.confirm()\n\n>>> model = OrderModel()\n>>> model.order_id = \"A-123\"\n>>> model.total = 49.90\n>>> sm = OrderWorkflow(model=model)\n\n>>> sm.send(\"confirm\")\n'Order A-123 confirmed: $49.9'\n\n```\n\nWith this declaration, `sm.model` is typed as `OrderModel` instead of `Any`, so\n`sm.model.order_id`, `sm.model.total`, and `sm.model.confirm()` all get full\nautocompletion and type checking in your IDE.\n\n```{note}\nWhen no type parameter is given (e.g. `class MySM(StateChart)`), the model defaults\nto `Any`, preserving full backward compatibility.\n```\n"
  },
  {
    "path": "docs/processing_model.md",
    "content": "(processing-model)=\n(processing model)=\n\n# Processing model\n\nThe engine processes events following the\n[SCXML](https://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation)\n**run-to-completion** (RTC) model: each event is fully processed — all\ncallbacks executed, all states entered/exited — before the next event\nstarts. This guarantees the system is always in a consistent state when\na new event arrives.\n\n> **Run to completion** — SCXML adheres to a run to completion semantics\n> in the sense that an external event can only be processed when the\n> processing of the previous external event has completed, i.e. when all\n> microsteps (involving all triggered transitions) have been completely\n> taken.\n>\n> — [W3C SCXML Specification](https://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation)\n\n```{seealso}\nSee {ref}`actions` for the callback execution order within each step,\n{ref}`sending-events` for how to trigger events, and {ref}`behaviour`\nfor customizations that affect how the engine processes transitions.\n```\n\n\n(macrostep-microstep)=\n\n## Macrosteps and microsteps\n\nThe processing loop is organized into two levels:\n\n### Microstep\n\nA **microstep** is the smallest unit of processing. It takes a set of\nenabled transitions and walks them through a fixed sequence of\ncallback groups defined in the {ref}`execution order <actions>`:\n\n1. **Prepare** — enrich event kwargs.\n2. **Validators / Conditions** — check if the transition is allowed.\n3. **Before** — run pre-transition callbacks.\n4. **Exit** — leave source states (innermost first).\n5. **On** — execute transition actions.\n6. **Enter** — enter target states (outermost first).\n7. **Invoke** — spawn background work.\n8. **After** — run post-transition callbacks (always runs, even on error).\n\n```{tip}\nIf an error occurs during steps 3–6 and `catch_errors_as_events` is enabled,\nthe error is caught at the **block level** — remaining actions in that block\nare skipped, but the microstep continues. See\n{ref}`error-execution` and the\n{ref}`cleanup / finalize pattern <error-handling-cleanup-finalize>`.\n```\n\n\n### Macrostep\n\nA **macrostep** is a complete processing cycle triggered by a single\nexternal event. It consists of one or more microsteps and only ends when\nthe machine reaches a **stable configuration** — no eventless transitions\nare enabled and the internal queue is empty.\n\nWithin a single macrostep, the engine repeats:\n\n1. **Check eventless transitions** — transitions without an event that\n   fire automatically when their guard conditions are met.\n2. **Drain the internal queue** — events placed by `raise_()` are\n   processed immediately, before any external events.\n3. If neither step produced a transition, the macrostep is **done**.\n\nAfter the macrostep completes, the engine picks the next event from the\n**external queue** (placed by `send()`) and starts a new macrostep.\n\n\n### Event queues\n\nThe engine maintains two separate FIFO queues:\n\n| Queue | How to enqueue | When processed |\n|---|---|---|\n| **Internal** | {func}`raise_() <StateMachine.raise_>` or `send(..., internal=True)` | Within the current macrostep |\n| **External** | {func}`send() <StateMachine.send>` | After the current macrostep ends |\n\nThis distinction matters when you trigger events from inside callbacks.\nUsing `raise_()` ensures the event is handled as part of the current\nprocessing cycle, while `send()` defers it to after the machine reaches\na stable configuration.\n\n```{seealso}\nSee {ref}`sending-events` for examples of `send()` vs `raise_()`.\n```\n\n\n### Processing loop overview\n\nThe following diagram shows the complete processing loop:\n\n```\n    send(\"event\")\n         │\n         ▼\n  ┌──────────────┐\n  │ External     │\n  │ Queue        │◄─────────────────────────────┐\n  └──────┬───────┘                              │\n         │ pop event                            │\n         ▼                                      │\n  ┌──────────────────────────────────────┐      │\n  │          Macrostep                   │      │\n  │                                      │      │\n  │   ┌──────────────────────┐           │      │\n  │   │ Eventless transitions│◄──┐       │      │\n  │   │ enabled?             │   │       │      │\n  │   └──────┬───────────────┘   │       │      │\n  │     yes  │  no               │       │      │\n  │          │   │               │       │      │\n  │          │   ▼               │       │      │\n  │          │  ┌──────────────┐ │       │      │\n  │          │  │ Internal     │ │       │      │\n  │          │  │ queue empty? │ │       │      │\n  │          │  └──┬───────┬───┘ │       │      │\n  │          │  no │  yes  │     │       │      │\n  │          │     │       │     │       │      │\n  │          │     │       ▼     │       │      │\n  │          │     │   Stable    │       │      │\n  │          │     │   config ───┼───────┼──────┘\n  │          │     │             │       │\n  │          ▼     ▼             │       │\n  │     ┌──────────────┐        │       │\n  │     │  Microstep   │────────┘       │\n  │     │  (execute    │                │\n  │     │  transitions)│                │\n  │     └──────────────┘                │\n  │                                     │\n  └─────────────────────────────────────┘\n```\n\n\n(rtc-model)=\n(rtc model)=\n(non-rtc model)=\n\n## Run-to-completion in practice\n\nConsider a state machine where one transition triggers another via an\n`after` callback:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class ServerConnection(StateChart):\n...     disconnected = State(initial=True)\n...     connecting = State()\n...     connected = State(final=True)\n...\n...     connect = disconnected.to(connecting, after=\"connection_succeed\")\n...     connection_succeed = connecting.to(connected)\n...\n...     def on_connect(self):\n...         return \"on_connect\"\n...\n...     def on_enter_state(self, event: str, state: State, source: State):\n...         print(f\"enter '{state.id}' from '{source.id if source else ''}' given '{event}'\")\n...\n...     def on_exit_state(self, event: str, state: State, target: State):\n...         print(f\"exit '{state.id}' to '{target.id}' given '{event}'\")\n...\n...     def on_transition(self, event: str, source: State, target: State):\n...         print(f\"on '{event}' from '{source.id}' to '{target.id}'\")\n...         return \"on_transition\"\n...\n...     def after_transition(self, event: str, source: State, target: State):\n...         print(f\"after '{event}' from '{source.id}' to '{target.id}'\")\n...         return \"after_transition\"\n\n```\n\nWhen `connect` is sent, the `after` callback triggers `connection_succeed`.\nUnder the RTC model, `connection_succeed` is enqueued and processed only\nafter `connect` completes:\n\n```py\n>>> sm = ServerConnection()\nenter 'disconnected' from '' given '__initial__'\n\n>>> sm.send(\"connect\")\nexit 'disconnected' to 'connecting' given 'connect'\non 'connect' from 'disconnected' to 'connecting'\nenter 'connecting' from 'disconnected' given 'connect'\nafter 'connect' from 'disconnected' to 'connecting'\nexit 'connecting' to 'connected' given 'connection_succeed'\non 'connection_succeed' from 'connecting' to 'connected'\nenter 'connected' from 'connecting' given 'connection_succeed'\nafter 'connection_succeed' from 'connecting' to 'connected'\n['on_transition', 'on_connect']\n\n```\n\nNotice that `connect` runs all its phases (exit → on → enter → after) before\n`connection_succeed` starts. The `after` callback of `connect` fires while\nthe machine is still in `connecting` — and only then does `connection_succeed`\nbegin its own microstep.\n\n```{note}\nThe `__initial__` event is a synthetic event that the engine fires during\ninitialization to enter the initial state. It follows the same RTC model\nas any other event.\n```\n\n\n(continuous-machines)=\n\n## Chaining transitions\n\nSome use cases require a machine that processes multiple steps automatically\nwithin a single macrostep, driven by internal events or eventless transitions\nrather than external calls.\n\n\n### With `raise_()`\n\nUsing {func}`raise_() <StateMachine.raise_>` inside callbacks places events\non the **internal queue**, so they are processed within the current macrostep.\nThis lets you chain multiple transitions from a single `send()` call:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Pipeline(StateChart):\n...     start = State(\"Start\", initial=True)\n...     step1 = State(\"Step 1\")\n...     step2 = State(\"Step 2\")\n...     done = State(\"Done\", final=True)\n...\n...     begin = start.to(step1)\n...     advance_1 = step1.to(step2)\n...     advance_2 = step2.to(done)\n...\n...     def on_enter_step1(self):\n...         print(\"  step 1: extract\")\n...         self.raise_(\"advance_1\")\n...\n...     def on_enter_step2(self):\n...         print(\"  step 2: transform\")\n...         self.raise_(\"advance_2\")\n...\n...     def on_enter_done(self):\n...         print(\"  done: load complete\")\n\n>>> sm = Pipeline()\n>>> sm.send(\"begin\")\n  step 1: extract\n  step 2: transform\n  done: load complete\n\n>>> [s.id for s in sm.configuration]\n['done']\n\n```\n\nAll three steps execute within a single macrostep — the caller receives\ncontrol back only after the pipeline reaches a stable configuration.\n\n\n### With eventless transitions\n\n{ref}`Eventless transitions <eventless>` fire automatically whenever their\nguard condition is satisfied. Combined with a self-transition, this creates\na loop that keeps running within the macrostep until the condition becomes\nfalse:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class RetryMachine(StateChart):\n...     trying = State(\"Trying\", initial=True)\n...     success = State(\"Success\", final=True)\n...     failed = State(\"Failed\", final=True)\n...\n...     # Eventless transitions: fire automatically based on guards\n...     trying.to.itself(cond=\"can_retry\")\n...     trying.to(failed, cond=\"max_retries_reached\")\n...\n...     # Event-driven transition (external input)\n...     succeed = trying.to(success)\n...\n...     def __init__(self, max_retries=3):\n...         self.attempts = 0\n...         self.max_retries = max_retries\n...         super().__init__()\n...\n...     def can_retry(self):\n...         return self.attempts < self.max_retries\n...\n...     def max_retries_reached(self):\n...         return self.attempts >= self.max_retries\n...\n...     def on_enter_trying(self):\n...         self.attempts += 1\n...         print(f\"  attempt {self.attempts}\")\n\n>>> sm = RetryMachine(max_retries=3)\n  attempt 1\n  attempt 2\n  attempt 3\n\n>>> [s.id for s in sm.configuration]\n['failed']\n\n```\n\nThe machine starts, enters `trying` (attempt 1), and the eventless\nself-transition keeps firing as long as `can_retry()` returns `True`. Once\nthe limit is reached, the second eventless transition fires — all within a\nsingle macrostep triggered by initialization.\n\n\n(thread-safety)=\n\n## Thread safety\n\nState machines are **thread-safe** for concurrent event sending. Multiple threads\ncan call `send()` or trigger events on the **same state machine instance**\nsimultaneously — the engine guarantees correct behavior through its internal\nlocking mechanism.\n\n### How it works\n\nThe processing loop uses a non-blocking lock (`threading.Lock`). When a thread\nsends an event:\n\n1. The event is placed on the **external queue** (backed by a thread-safe\n   `PriorityQueue` from the standard library).\n2. If no other thread is currently running the processing loop, the sending\n   thread acquires the lock and processes all queued events.\n3. If another thread is already processing, the event is simply enqueued and\n   will be processed by the thread that holds the lock — no event is lost.\n\nThis means that **at most one thread executes transitions at any time**, preserving\nthe run-to-completion (RTC) guarantee while allowing safe concurrent access.\n\n### What is safe\n\n- **Multiple threads sending events** to the same state machine instance.\n- **Reading state** (`current_state_value`, `configuration`) from any thread\n  while events are being processed. Note that transient `None` values may be\n  observed for `current_state_value` during configuration updates when using\n  [`atomic_configuration_update`](behaviour.md#atomic_configuration_update) `= False`\n  (the default on `StateChart`, SCXML-compliant). With `atomic_configuration_update = True`\n  (the default on `StateMachine`), the configuration is updated atomically at\n  the end of the microstep, so `None` is not observed.\n- **Invoke handlers** running in background threads or thread executors\n  communicate with the parent machine via the thread-safe event queue.\n\n### What to avoid\n\n- **Do not share a state machine instance across threads with the async engine**\n  unless you ensure only one event loop drives the machine. The async engine is\n  designed for `asyncio` concurrency, not thread-based concurrency.\n- **Callbacks execute in the processing thread**, not in the thread that sent\n  the event. Design callbacks accordingly (e.g., use locks if they access\n  shared external state).\n"
  },
  {
    "path": "docs/releases/0.1.0.md",
    "content": "# StateMachine 0.1.0\n\n*2017-03-21*\n\n* First release on PyPI.\n"
  },
  {
    "path": "docs/releases/0.2.0.md",
    "content": "# 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.\n- Travis-CI integration.\n- RTD integration.\n"
  },
  {
    "path": "docs/releases/0.3.0.md",
    "content": "# 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",
    "content": "# 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 Python 3.3.\n\n\n- `Transition` can be used as decorator for `on_execute` callback definition.\n- `Transition` can point to multiple destination states.\n"
  },
  {
    "path": "docs/releases/0.5.0.md",
    "content": "# StateMachine 0.5.0\n\n*2017-07-13*\n\n- Custom exceptions.\n- Duplicated definition of ``on_execute`` callback is not allowed.\n- Fix bug on ``StateMachine.on_<transition.identifier>`` being called with extra ``self`` param.\n"
  },
  {
    "path": "docs/releases/0.5.1.md",
    "content": "# StateMachine 0.5.1\n\n*2017-07-24*\n\n\n- Fix bug on ``CombinedTransition._can_run`` not allowing transitions to run if there are more than\n  two transitions combined.\n"
  },
  {
    "path": "docs/releases/0.6.0.md",
    "content": "# StateMachine 0.6.0\n\n*2017-08-25*\n\n\n- Auto-discovering `statemachine`/`statemachines` under a Django project when\n  they are requested using the mixin/registry feature.\n"
  },
  {
    "path": "docs/releases/0.6.1.md",
    "content": "# StateMachine 0.6.1\n\n*2017-08-25*\n\n\n- Fix deploy issues.\n"
  },
  {
    "path": "docs/releases/0.6.2.md",
    "content": "# StateMachine 0.6.2\n\n*2017-08-25*\n\n\n- Fix README.\n"
  },
  {
    "path": "docs/releases/0.7.0.md",
    "content": "# 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",
    "content": "# 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",
    "content": "# 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 test matrix).\n- Drop official support for Python 3.4 (removing from test matrix, code may still work).\n\n## What's new in 0.8\n\n- Update development requirements.\n- State machine names should now be fully qualified for mixins, simple names are deprecated and\n  will no longer be supported on a future version.\n- Development: Adding mypy linter.\n- Add support for State machine inheritance. Thanks @rschrader.\n- Add support for reverse transitions: ``transition = state_a.from_(state_b)``.\n  Thanks @romulorosa.\n- Fix current state equal to destination on enter events. Thanks @robnils and @joshuacc1.\n- Check: StateMachine now validates if it's states/transitions graph has only one component.\n  Thanks @rafaelrds.\n"
  },
  {
    "path": "docs/releases/0.9.0.md",
    "content": "# 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, 3.8.\n\n## What's new in 0.9\n\n### Args and kwargs now are passed to bounded transitions\n\nParameters sent with the event trigger will now be propagated to the transition handlers.\n\n```py\n>>> from statemachine import StateMachine, State\n\n>>> class CampaignMachine(StateMachine):\n...     draft = State(\"Draft\", initial=True)\n...     producing = State(\"Being produced\")\n...\n...     produce = draft.to(producing) | producing.to(producing)\n...\n...     def on_enter_producing(self, approver=None):\n...         print(f\"Approved by: {approver}\")\n\n>>> sm = CampaignMachine()\n\n>>> sm.produce(approver=\"Aragorn\")  # imperative\nApproved by: Aragorn\n\n```\n\n\n### State machine declarations now with final states\n\n\nNow you can declare `final` states and the machine will make sure they have no transitions.\n\n```py\n>>> from statemachine import StateMachine, State\n\n>>> class ApprovalMachine(StateMachine):\n...     \"\"\"A workflow machine\"\"\"\n...     requested = State(\"Requested\", initial=True)\n...     accepted = State(\"Accepted\")\n...     rejected = State(\"Rejected\")\n...     completed = State(\"Completed\", final=True)\n...\n...     validate = requested.to(accepted, cond=\"is_ok\") | requested.to(rejected)\n...     release = accepted.to(completed)\n...     reopen = completed.to(requested)\nTraceback (most recent call last):\n...\nInvalidDefinition: Cannot declare transitions from final state. Invalid state(s): ['completed']\n\n```\n\nSee {ref}`final-state` for more details.\n\n## Minor features and bug fixes\n\n- Doctesting all documentation including README (with issues on examples fixed).\n- Fix state value misjudged when state value is an \"boolean False value\" (tks @the5fire)\n- Fix returning dict as result of transitions callback.\n"
  },
  {
    "path": "docs/releases/1.0.0.md",
    "content": "# 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 metadata when uploading to\npypi.\n"
  },
  {
    "path": "docs/releases/1.0.1.md",
    "content": "# 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 lot of new and exciting features. We hope that you enjoy it.\n\nThese release notes cover the [new features in 1.0](#whats-new-in-10), as well as\nsome [backward incompatible changes](#backward-incompatible-changes-in-10) you'll\nwant to be aware of when upgrading from StateMachine 0.9.0 or earlier. We've\n[begun the deprecation process for some features](#deprecated-features-in-10).\n\n\n## Python compatibility in 1.0\n\nStateMachine 1.0 supports Python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, and 3.11.\n\nThis is the last release to support Python 2.7, 3.5, and 3.6.\n\n## What's new in 1.0\n\n\n### Added validators and Guards\n\nTransitions now support `cond` and `unless` parameters, to restrict\nthe execution.\n\n```python\n    class ApprovalMachine(StateMachine):\n        \"A workflow machine\"\n        requested = State(\"Requested\", initial=True)\n        accepted = State(\"Accepted\")\n        rejected = State(\"Rejected\")\n        completed = State(\"Completed\")\n\n        validate = requested.to(accepted, cond=\"is_ok\") | requested.to(rejected)\n```\n\n```{seealso}\nSee {ref}`validators and guards` for more details.\n```\n\n### Support for diagrams\n\nYou can generate diagrams from your state machine.\n\nExample:\n\n```{statemachine-diagram} tests.examples.order_control_machine.OrderControl\n:caption: OrderControl\n```\n\n\n```{seealso}\nSee {ref}`diagrams` for more details.\n```\n\n### Unified dispatch mechanism for callbacks (actions and guards)\n\nEvery single callback, being {ref}`actions` or {ref}`guards`, is now handled equally by the library.\n\nAlso, we've improved the internals in a way that you can implement your callbacks with any\nnumber of arbitrary positional or keyword arguments (`*args, **kwargs`), and the dispatch will\nmatch the available arguments with your method signature.\n\nThis means that if on your `on_enter_<state>()` or `on_execute_<event>()` method, you also\nneed to know the `source` ({ref}`state`), or the `event` ({ref}`event`), or access a keyword\nargument passed with the trigger, you're covered. Just add this parameter to the method and It\n will be passed by the dispatch mechanics.\n\nExample of what's available:\n\n```py\ndef action_or_guard_method_name(self, *args, event_data, event, source, state, model, **kwargs):\n    pass\n```\n\n```{seealso}\nSee {ref}`dynamic-dispatch` for more details.\n```\n\n### Add observers to a running StateMachine\n\nObservers are a way do generically add behavior to a StateMachine without\nchanging it's internal implementation.\n\nThe `StateMachine` itself is registered as an observer, so by using `StateMachine.add_observer()`\nan external object can have the same level of functionalities provided to the built-in class.\n\n```{seealso}\nSee {ref}`observers` for more details.\n```\n\n## Minor features in 1.0\n\n- Fixed mypy complaining about incorrect type for ``StateMachine`` class.\n- The initial {ref}`state` is now entered when the machine starts. The {ref}`actions`, if defined,\n  `on_enter_state` and `on_enter_<state>` are now called.\n\n\n\n## Backward incompatible changes in 1.0\n\n\n### Multiple targets from the same origin state\n\nPrior to this release, as we didn't have {ref}`validators and guards`, there wasn't an elegant way\nto declare multiple target states starting from the same pair (event, state). But the library\nallowed a near-hackish way, by declaring a target state as the result of the `on_<event>` callback.\n\nSo, the previous code (not valid anymore):\n\n```py\nclass ApprovalMachine(StateMachine):\n    \"A workflow machine\"\n    requested = State('Requested', initial=True)\n    accepted = State('Accepted')\n    rejected = State('Rejected')\n\n    validate = requested.to(accepted, rejected)\n\n    def on_validate(self, current_time):\n        if self.model.is_ok():\n            self.model.accepted_at = current_time\n            return self.accepted\n        else:\n            return self.rejected\n```\n\nShould be rewritten to use {ref}`guards`, like this:\n\n``` py\nclass ApprovalMachine(StateMachine):\n    \"A workflow machine\"\n    requested = State(\"Requested\", initial=True)\n    accepted = State(\"Accepted\")\n    rejected = State(\"Rejected\")\n\n    validate = requested.to(accepted, conditions=\"is_ok\") | requested.to(rejected)\n\n    def on_validate(self, current_time):\n        self.model.accepted_at = current_time\n```\n\n```{seealso}\nSee {ref}`validators and guards` of more details.\n```\n\n### StateMachine now enters the initial state\n\nThis issue was reported at [#265](https://github.com/fgmacedo/python-statemachine/issues/265).\n\nNow StateMachine will execute the actions associated with the `on_enter_state` and\n`on_enter_<state`>` when initialized if they exist.\n\n```{seealso}\nSee {ref}`State actions` for more details.\n```\n\n### Integrity is checked at class definition\n\nStatemachine integrity checks are now performed at class declaration (import time) instead of on\ninstance creation. This allows early feedback on invalid definitions.\n\nThis was the previous behavior, you only got an error when trying to instantiate a StateMachine:\n\n```py\nclass CampaignMachine(StateMachine):\n    \"A workflow machine\"\n    draft = State('Draft', initial=True)\n    producing = State('Being produced')\n    closed = State('Closed', initial=True)  # Should raise an Exception when instantiated\n\n    add_job = draft.to(draft) | producing.to(producing)\n    produce = draft.to(producing)\n    deliver = producing.to(closed)\n\nwith pytest.raises(exceptions.InvalidDefinition):\n    CampaignMachine()\n```\n\nNot this is performed as the class definition is performed:\n\n```py\nwith pytest.raises(exceptions.InvalidDefinition):\n\n    class CampaignMachine(StateMachine):\n        \"A workflow machine\"\n        draft = State(\"Draft\", initial=True)\n        producing = State(\"Being produced\")\n        closed = State(\n            \"Closed\", initial=True\n        )  # Should raise an Exception right after the class is defined\n\n        add_job = draft.to(draft) | producing.to(producing)\n        produce = draft.to(producing)\n        deliver = producing.to(closed)\n```\n\n### Other backward incompatible changes in 1.0\n\n- Due to the check validations and setup performed at the machine initialization, it's now harder\n  to perform monkey-patching to add callbacks at runtime (not a bad thing after all).\n- `TransitionNotAllowed` changed internal attr from `transition` to `event`.\n- `CombinedTransition` does not exist anymore. {ref}`State` now holds a flat {ref}`Transition` list\n  called `TransitionList` that implements de `OR` operator. This turns a valid StateMachine\n  traversal much easier: `[transition for state in machine.states for transition in state.transitions]`.\n- `StateMachine.get_transition` is removed. See {ref}`event`.\n- The previous exceptions `MultipleStatesFound` and `MultipleTransitionCallbacksFound` are removed.\n  Since now you can have more than one callback defined to the same transition.\n- `on_enter_state` and `on_exit_state` now accepts any combination of parameters following the\n  {ref}`dynamic-dispatch` rules. Previously it only accepted the `state` param.\n- `Transition.__init__` param `on_execute` renamed to simply `on`, and now follows the\n{ref}`dynamic-dispatch`.\n- `Transition.destinations` removed in favor of `Transition.target` (following SCXML convention).\nNow each transition only points to a unique target. Each `source->target` pair is held by a\nsingle `Transition`.\n\n## Deprecated features in 1.0\n\n### Statemachine class deprecations\n\n- `StateMachine.run` is deprecated in favor of `StateMachine.send`.\n- `StateMachine.allowed_transitions` is deprecated in favor of `StateMachine.allowed_events`.\n- `Statemachine.is_<state>` is deprecated in favor of `StateMachine.<state>.is_active`.\n\n\n### State class\n\n- `State.identification` is deprecated in favor of `State.id`.\n"
  },
  {
    "path": "docs/releases/1.0.2.md",
    "content": "# StateMachine 1.0.2\n\n*January 12, 2023*\n\n\nStateMachine 1.0.2 fixes a regression bug blocking the library usage on\nPython 3.11.\n\n\n## Bugfixes\n\n- Fixes [#316](https://github.com/fgmacedo/python-statemachine/issues/316) a bad\n  import of 'inspect.getargspec' that was removed on Python 3.11,\n  still backwards compatible with older versions.\n"
  },
  {
    "path": "docs/releases/1.0.3.md",
    "content": "# StateMachine 1.0.3\n\n*January 27, 2023*\n\n\nStateMachine 1.0.3 fixes a bug between {ref}`State` and {ref}`transition` instances sharing\nreferences of callbacks when there were multiple concurrent instances of the same `StateMachine`\nclass.\n\n\n## Bugfixes in 1.0.3\n\n- [#334](https://github.com/fgmacedo/python-statemachine/issues/334): Fixed a shared reference\n  of callbacks when there were multiple concurrent instances of the same `StateMachine` class.\n"
  },
  {
    "path": "docs/releases/2.0.0.md",
    "content": "# 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 the Python3 improvements and is a huge internal refactoring removing the deprecated features on 1.*. We hope that you enjoy it.\n\nThese release notes cover the [](#whats-new-in-20), as well as\nsome [backward incompatible changes](#backward-incompatible-changes-in-20) you'll\nwant to be aware of when upgrading from StateMachine 1.*.\n\n\n## Python compatibility in 2.0\n\nStateMachine 2.0 supports Python 3.7, 3.8, 3.9, 3.10, and 3.11.\n\n\n## What's new in 2.0\n\n### Run to completion (RTC) by default\n\nThere are now two distinct methods for processing events in the library. The **new default** is to run in\n{ref}`RTC model` to be compliant with the specs, where the {ref}`event` is put on a queue before processing.\nYou can also configure your state machine to run back in {ref}`Non-RTC model`, where the {ref}`event` will\nbe run immediately and nested events will be chained.\n\nThis means that the state machine now completes all the actions associated with an event before moving on to the next event.\nEven if you trigger an event inside an action.\n\n```{seealso}\nSee {ref}`processing model` for more details.\n```\n\n### State names are now optional\n\n{ref}`State` names are now by default derived from the class variable that they are assigned to.\nYou can keep declaring explicit names, but we encourage you to only assign a name\nwhen it is different than the one derived from its id.\n\n```py\n>>> from statemachine import StateMachine, State\n\n>>> class ApprovalMachine(StateMachine):\n...     pending = State(initial=True)\n...     waiting_approval = State()\n...     approved = State(final=True)\n...\n...     start = pending.to(waiting_approval)\n...     approve = waiting_approval.to(approved)\n...\n\n>>> ApprovalMachine.pending.name\n'Pending'\n\n>>> ApprovalMachine.waiting_approval.name\n'Waiting approval'\n\n>>> ApprovalMachine.approved.name\n'Approved'\n\n```\n\n### Added support for internal transitions\n\nAn internal transition is like a {ref}`self transition`, but in contrast, no entry or exit actions\nare ever executed as a result of an internal transition.\n\n```py\n>>> from statemachine import StateMachine, State\n\n>>> class TestStateMachine(StateMachine):\n...     initial = State(initial=True)\n...\n...     loop = initial.to.itself(internal=True)\n\n```\n\n```{seealso}\nSee {ref}`internal transition` for more details.\n```\n\n### Added option to ignore unknown events\n\nYou can now instantiate a {ref}`StateMachine` with `allow_event_without_transition=True`,\nso the state machine will allow triggering events that may not lead to a state {ref}`transition`,\nincluding tolerance to unknown {ref}`event` triggers.\n\nThe default value is ``False``, that keeps the backward compatible behavior of when an\nevent does not result in a {ref}`transition`, an exception ``TransitionNotAllowed`` will be raised.\n\n```\n>>> import pytest\n>>> pytest.skip(\"Since 3.0.0 `allow_event_without_transition` is now a class attribute.\")\n\n>>> sm = ApprovalMachine(allow_event_without_transition=True)\n\n>>> sm.send(\"unknow_event_name\")\n\n>>> sm.pending.is_active\nTrue\n\n>>> sm.send(\"approve\")\n\n>>> sm.pending.is_active\nTrue\n\n>>> sm.send(\"start\")\n\n>>> sm.waiting_approval.is_active\nTrue\n\n```\n\n### Added support for translations (i18n)\n\nNow the library messages can be translated into any language.\n\nSee {ref}`Add a translation` on how to contribute with translations.\n\n\n## Minor features in 2.0\n\n- Modernization of the development tools to use linters and improved mypy support.\n- [#342](https://github.com/fgmacedo/python-statemachine/pull/342): Guards now supports the\n  evaluation of **truthy** and **falsy** values.\n- [#342](https://github.com/fgmacedo/python-statemachine/pull/342): Assignment of `Transition`\n  guards using decorators is now possible.\n- [#331](https://github.com/fgmacedo/python-statemachine/pull/331): Added a way to generate diagrams using [QuickChart.io](https://quickchart.io) instead of GraphViz. See {ref}`diagrams` for more details.\n- [#353](https://github.com/fgmacedo/python-statemachine/pull/353): Support for abstract state machine classes, so you can subclass `StateMachine` to add behavior on your own base class. Abstract `StateMachine` cannot be instantiated.\n- [#355](https://github.com/fgmacedo/python-statemachine/pull/355): Now is possible to trigger an event as an action by registering the event name as the callback param.\n\n## Bugfixes in 2.0\n\n- [#341](https://github.com/fgmacedo/python-statemachine/issues/341): Fix dynamic dispatch\n  on methods with default parameters.\n- [#365](https://github.com/fgmacedo/python-statemachine/pull/365): Fix transition with multiple\n  events was calling actions of all events.\n\n\n## Backward incompatible changes in 2.0\n\n- Dropped support for Django <= `1.6` for auto-discovering and registering `StateMachine` classes\n  to be used on {ref}`django integration`.\n\n### Transitions with multiple events only execute actions associated to the triggered event\n\nPrior to [#365](https://github.com/fgmacedo/python-statemachine/pull/365), when you {ref}`Declare transition actions by naming convention`, all callbacks of the transition were called even if the triggered event was not the one that originated the transition.\n\nThis behavior was fixed in this release. Now, only the transitions associated with the triggered event or directly assigned to the transition are called.\n\nConsider the following state machine as an example:\n\n```py\n>>> from statemachine import State\n>>> from statemachine import StateMachine\n\n>>> class TrafficLightMachine(StateMachine):\n...     \"A traffic light machine\"\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     slowdown = green.to(yellow)\n...     stop = yellow.to(red)\n...     go = red.to(green)\n...\n...     cycle = slowdown | stop | go\n...\n...     def before_slowdown(self):\n...         print(\"Slowdown\")\n...\n...     def before_cycle(self, event: str, source: State, target: State):\n...         print(f\"Running {event} from {source.id} to {target.id}\")\n\n```\n\nBefore, if you send the `cycle` event, the behavior was to also trigger actions associated with\n`slowdown`, because they're sharing the same instance of {ref}`Transition`:\n\n```py\n>>> sm = TrafficLightMachine()\n>>> sm.send(\"cycle\")  # doctest: +SKIP\nSlowdown\nRunning cycle from green to yellow\n\n```\n\nNow the behavior is to only execute actions bound to the triggered {ref}`event` or directly\nassociated to the {ref}`Transition`:\n\n```py\n>>> sm = TrafficLightMachine()\n>>> sm.send(\"cycle\")\nRunning cycle from green to yellow\n\n```\n\nIf you want to emulate the previous behavior, consider one of these alternatives.\n\nYou can {ref}`Bind transition actions using params` or {ref}`Bind transition actions using decorator syntax`:\n\n```py\n>>> from statemachine import State\n>>> from statemachine import StateMachine\n\n>>> class TrafficLightMachine(StateMachine):\n...     \"A traffic light machine\"\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     slowdown = green.to(yellow, before=\"do_before_slowdown\")  # assign to the transition\n...     stop = yellow.to(red)\n...     go = red.to(green)\n...\n...     cycle = slowdown | stop | go\n...\n...     def do_before_slowdown(self):\n...         print(\"Slowdown\")\n...\n...     @stop.before    # assign to the transition\n...     def do_before_stop(self):\n...         print(\"Stop\")\n...\n...     def before_cycle(self, event: str, source: State, target: State):\n...         print(f\"Running {event} from {source.id} to {target.id}\")\n\n```\n\nYou can go an step further and if the events are not called externally, get rid of them and put the actions directly on the transitions:\n\n\n```py\n>>> from statemachine import State\n>>> from statemachine import StateMachine\n\n>>> class TrafficLightMachine(StateMachine):\n...     \"A traffic light machine\"\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = (\n...         green.to(yellow, before=\"slowdown\")\n...         | yellow.to(red, before=\"stop\")\n...         | red.to(green, before=\"go\")\n...     )\n...\n...     def slowdown(self):\n...         print(\"Slowdown\")\n...\n...     def stop(self):\n...         print(\"Stop\")\n...\n...     def go(self):\n...         print(\"Go\")\n...\n...     def before_cycle(self, event: str, source: State, target: State):\n...         print(f\"Running {event} from {source.id} to {target.id}\")\n\n```\n\n```py\n>>> sm = TrafficLightMachine()\n>>> [sm.send(\"cycle\") for _ in range(3)]\nSlowdown\nRunning cycle from green to yellow\nStop\nRunning cycle from yellow to red\nGo\nRunning cycle from red to green\n[[None, None], [None, None], [None, None]]\n\n```\n\n\n### Statemachine class changes in 2.0\n\n#### The new processing model (RTC) by default\n\nWhile we've figured out a way to keep near complete backwards compatible changes to the new\n{ref}`Run to completion (RTC) by default` feature (all built-in examples run without change),\nif you encounter problems when upgrading to this version, you can still switch back to the old\n{ref}`Non-RTC model`. Be aware that we may remove the {ref}`Non-RTC model` in the future.\n\n#### `StateMachine.run` removed in favor of `StateMachine.send`\n\n```py\nfrom tests.examples.traffic_light_machine import TrafficLightMachine\n\nsm = TrafficLightMachine()\nsm.run(\"cycle\")\n\n```\n\nShould become:\n\n```py\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n\n>>> sm = TrafficLightMachine()\n>>> sm.send(\"cycle\")\nRunning cycle from green to yellow\n\n```\n\n\n#### `StateMachine.allowed_transitions` removed in favor of `StateMachine.allowed_events`\n\n```py\nfrom tests.examples.traffic_light_machine import TrafficLightMachine\n\nsm = TrafficLightMachine()\nassert [t.name for t in sm.allowed_transitions] == [\"cycle\"]\n\n```\n\nShould become:\n\n```py\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n\n>>> sm = TrafficLightMachine()\n>>> assert [t.name for t in sm.allowed_events] == [\"cycle\"]\n\n```\n\n#### `Statemachine.is_<state>` removed in favor of `StateMachine.<state>.is_active`\n\n```py\nfrom tests.examples.traffic_light_machine import TrafficLightMachine\n\nsm = TrafficLightMachine()\nassert sm.is_green\n\n```\n\nShould become:\n\n```py\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n\n>>> sm = TrafficLightMachine()\n>>> assert sm.green.is_active\n\n```\n\n### State class changes in 2.0\n\n#### `State.identification` removed in favor of `State.id`\n\n```py\nfrom tests.examples.traffic_light_machine import TrafficLightMachine\n\nsm = TrafficLightMachine()\nassert sm.current_state.identification == \"green\"\n\n```\n\nShould become:\n\n```py\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n\n>>> sm = TrafficLightMachine()\n>>> assert sm.current_state.id == \"green\"\n\n```\n"
  },
  {
    "path": "docs/releases/2.1.0.md",
    "content": "# 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 an ``Enum`` type that declares our expected states:\n\n```py\n>>> from enum import Enum\n\n>>> class Status(Enum):\n...     pending = 1\n...     completed = 2\n\n```\n\nA {ref}`StateMachine` can be declared as follows:\n\n```py\n>>> from statemachine import StateMachine\n>>> from statemachine.states import States\n\n>>> class ApprovalMachine(StateMachine):\n...\n...     _ = States.from_enum(Status, initial=Status.pending, final=Status.completed)\n...\n...     finish = _.pending.to(_.completed)\n...\n...     def on_enter_completed(self):\n...         print(\"Completed!\")\n\n```\n\nSee {ref}`States from Enum types`.\n\n## Bugfixes in 2.1.0\n\n- Fixes [#369](https://github.com/fgmacedo/python-statemachine/issues/369) adding support to wrap\n  methods used as {ref}`Actions` decorated with `functools.partial`.\n- Fixes [#384](https://github.com/fgmacedo/python-statemachine/issues/384) so multiple observers can watch the same callback.\n"
  },
  {
    "path": "docs/releases/2.1.1.md",
    "content": "# 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-statemachine/issues/391) adding support to\n  [pytest-mock](https://pytest-mock.readthedocs.io/en/latest/index.html) `spy` method.\n- Improved factory type hints [#399](https://github.com/fgmacedo/python-statemachine/pull/399).\n"
  },
  {
    "path": "docs/releases/2.1.2.md",
    "content": "# StateMachine 2.1.2\n\n*October  6, 2023*\n\nThis release improves the setup performance of the library by a 10x factor, with a major\nrefactoring on how we handle the callbacks registry and validations.\n\nSee [#401](https://github.com/fgmacedo/python-statemachine/issues/401) for the technical details.\n\n\n## Python compatibility 2.1.2\n\nStateMachine 2.1.2 supports Python 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12.\n\nOn the next major release (3.0.0), we will drop support for Python 3.7.\n\n## Bugfixes in 2.1.2\n\n- Fixes [#406](https://github.com/fgmacedo/python-statemachine/issues/406) action callback being\n  called twice when mixing decorator syntax combined with the naming convention.\n"
  },
  {
    "path": "docs/releases/2.2.0.md",
    "content": "# 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 refactoring across various modules to enhance code readability and maintainability. We improved exception handling and reduced code redundancy.\n\nAs a result, we achieved a **~2.2x** faster setup in our performance tests and significantly simplified the callback machinery.\n\n\n### Check of unreachable and non-final states\n\nWe included one more state machine definition validation for non-final states.\n\nWe already check if any states are unreachable from the initial state, if not, an `InvalidDefinition` exception is thrown.\n\n```py\n>>> from statemachine import StateMachine, State\n\n>>> class TrafficLightMachine(StateMachine):\n...     \"A workflow machine\"\n...     red = State('Red', initial=True, value=1)\n...     green = State('Green', value=2)\n...     orange = State('Orange', value=3)\n...     hazard = State('Hazard', value=4)\n...\n...     cycle = red.to(green) | green.to(orange) | orange.to(red)\n...     blink = hazard.to.itself()\nTraceback (most recent call last):\n...\nInvalidDefinition: There are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard']\n```\n\nFrom this release, `StateMachine` will also check that all non-final states have an outgoing transition,\nand warn you if any states would result in the statemachine becoming trapped in a non-final state with no further transitions possible.\n\n```{note}\nThis will currently issue a warning, but can be turned into an exception by setting `strict_states=True` on the class.\n```\n\n```python\nfrom statemachine import StateMachine, State\n\nclass TrafficLightMachine(StateMachine, strict_states=True):\n    \"A workflow machine\"\n    red = State('Red', initial=True, value=1)\n    green = State('Green', value=2)\n    orange = State('Orange', value=3)\n    hazard = State('Hazard', value=4)\n\n    cycle = red.to(green) | green.to(orange) | orange.to(red)\n    fault = red.to(hazard) | green.to(hazard) | orange.to(hazard)\n\n# InvalidDefinition: All non-final states should have at least one outgoing transition.\n# These states have no outgoing transition: ['hazard']\n```\n\n```{warning}\n`strict_states=True` will become the default behaviour in the next major release.\n```\n\nSee {ref}`State Transitions`.\n\n\n## Bugfixes in 2.2.0\n\n- Fixes [#424](https://github.com/fgmacedo/python-statemachine/issues/424) allowing `deepcopy` of state machines.\n- **Dispatch Mechanism**: Resolved issues in the dispatch mechanism in `statemachine/dispatcher.py` that affected the reliability\nof event handling across different states. This fix ensures consistent behavior when events are dispatched in complex state\nmachine configurations.\n"
  },
  {
    "path": "docs/releases/2.3.0.md",
    "content": "# 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 [asynchronous support](../async.md), and enhancing overall functionality. In fact, the approach we took was to go all the way down changing the internals of the library to be fully async, keeping only the current external API as a thin sync/async adapter.\n\n\n### Python compatibility 2.3.0\n\nStateMachine 2.3.0 supports Python 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12.\n\nWe've fixed a bug on the package declaration that was preventing users from Python 3.7 to install the latest version.\n\n### Asynchronous Support in 2.3.0\n\nThis release introduces native coroutine support using asyncio, enabling seamless integration with asynchronous code.\n\nNow you can send and await for events, and also write async {ref}`Actions`, {ref}`Conditions` and {ref}`Validators`.\n\n\n```{seealso}\nSee {ref}`sphx_glr_auto_examples_air_conditioner_machine.py` for an example of\nasync code with a state machine.\n```\n\n\n```python\nclass AsyncStateMachine(StateMachine):\n    initial = State('Initial', initial=True)\n    final = State('Final', final=True)\n\n    advance = initial.to(final)\n\n    async def on_advance(self):\n        return 42\n\n\nasync def run_sm():\n    sm = AsyncStateMachine()\n    res = await sm.advance()\n    return (42, sm.current_state.name)\n\nasyncio.run(run_sm())\n# (42, 'Final')\n```\n"
  },
  {
    "path": "docs/releases/2.3.1.md",
    "content": "# 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-statemachine/issues/443) regression that caused `RuntimeError` when running SM with threads. Thanks [@gwidion](https://x.com/gwidion)!\n"
  },
  {
    "path": "docs/releases/2.3.2.md",
    "content": "# 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 expanted support for adding listeners when\ninstantiating a state machine. This allows covering more use cases. We also improved the async support.\n\n### Improved async support\n\nSince version 2.3.0, we have added async support. However, we encountered use cases, such as the [async safety on Django ORM](https://docs.djangoproject.com/en/5.0/topics/async/#async-safety), which expects no running event loop and blocks if it detects one on the current thread.\n\nTo address this issue, we developed a solution that maintains a unified API for both synchronous and asynchronous operations while effectively handling these scenarios.\n\nThis is achieved through a new concept called \"engine,\" an internal strategy pattern abstraction that manages transitions and callbacks.\n\nThere are two engines:\n\nSyncEngine\n: Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.\n\nAsyncEngine\n: Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.\n\nThese engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios:\n\n```{seealso}\nSee {ref}`async` for more details.\n```\n\n\n### Listeners at class initialization\n\nListeners are a way to generically add behavior to a state machine without changing its internal implementation.\n\nExample:\n\n```py\n>>> from tests.examples.traffic_light_machine import TrafficLightMachine\n\n>>> class LogListener(object):\n...     def __init__(self, name):\n...         self.name = name\n...\n...     def after_transition(self, event, source, target):\n...         print(f\"{self.name} after: {source.id}--({event})-->{target.id}\")\n...\n...     def on_enter_state(self, target, event):\n...         print(f\"{self.name} enter: {target.id} from {event}\")\n\n\n>>> sm = TrafficLightMachine(listeners=[LogListener(\"Paulista Avenue\")])\nPaulista Avenue enter: green from __initial__\n\n>>> sm.cycle()\nRunning cycle from green to yellow\nPaulista Avenue enter: yellow from cycle\nPaulista Avenue after: green--(cycle)-->yellow\n\n\n```\n\n```{seealso}\nSee {ref}`listeners` for more details.\n```\n\n### Binding event triggers to external objects\n\nNow it's possible to bind events to external objets. One expected use case is in conjunction with the {ref}`Mixins` models,\nthat wrap state machines internally. This way you don't need to expose the state machine.\n\n\n```{seealso}\nSee {ref}`sphx_glr_auto_examples_user_machine.py` for an example binding event triggers with a state machine.\n```\n\n\n## Bugfixes in 2.3.2\n\n- Fixes [#446](https://github.com/fgmacedo/python-statemachine/issues/446): Regression that broke sync callbacks\n  interacting with Django ORM due to the added async support and\n  [Django's async safety guards](https://docs.djangoproject.com/en/5.1/topics/async/#async-safety).\n- Fixes [#449](https://github.com/fgmacedo/python-statemachine/issues/449): Regression that did not trigger events\n  in nested calls within an already running transition.\n\n\n## Deprecation notes\n\n### Statemachine class deprecations in 2.3.2\n\nDeprecations that will be removed on the next major release:\n\n- `StateMachine.add_observer` is deprecated in favor of `StateMachine.add_listener`.\n- `StateMachine.rtc` option is deprecated. We'll keep only the **run-to-completion** (RTC) model.\n"
  },
  {
    "path": "docs/releases/2.3.3.md",
    "content": "# 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-statemachine/issues/457) regression that caused backwards incomplatible changes when using Enums. Thanks [@hperrey](https://github.com/hperrey)!\n\n\n\n## Deprecation notes in 2.3.3\n\nDeprecations that will be removed on the next major release:\n\n- The `States.from_enum(..., use_enum_instance=True)` will be the default.\n\n```{seealso}\nSee {ref}`States from Enum types` for more details.\n```\n"
  },
  {
    "path": "docs/releases/2.3.4.md",
    "content": "# 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-statemachine/issues/465) regression that caused exception when registering a listener with unbounded callbacks.\n"
  },
  {
    "path": "docs/releases/2.3.5.md",
    "content": "# 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. StateMachine 2.3.5 supports Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13.\n\n## Bugfixes in 2.3.5\n\n- Fixes [#469](https://github.com/fgmacedo/python-statemachine/issues/469) compatibility with pydot 3.0.0+.\n- Fixes [#473](https://github.com/fgmacedo/python-statemachine/issues/473) property support for type checking.\n"
  },
  {
    "path": "docs/releases/2.3.6.md",
    "content": "# 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-statemachine/issues/474) install with extra was not working to install `pydot`.\n- Fixes [#480](https://github.com/fgmacedo/python-statemachine/issues/480) error when trying to trigger an event inside the initial callback.\n"
  },
  {
    "path": "docs/releases/2.4.0.md",
    "content": "# 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 `StateMachine` library: {ref}`Condition expressions` and explicit definition of {ref}`Events`. These updates make it easier to define complex transition conditions and enhance performance, especially in workflows with nested or recursive event structures.\n\n### Python compatibility in 2.4.0\n\nStateMachine 2.4.0 supports Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13.\n\n### Conditions expressions in 2.4.0\n\nThis release introduces support for conditionals with Boolean algebra. You can now use expressions like `or`, `and`, and `not` directly within transition conditions, simplifying the definition of complex state transitions. This allows for more flexible and readable condition setups in your state machine configurations.\n\nExample (with a spoiler of the next highlight):\n\n```python\nfrom statemachine import StateMachine, State, Event\n\nclass AnyConditionSM(StateMachine):\n    start = State(initial=True)\n    end = State(final=True)\n\n    submit = Event(\n        start.to(end, cond=\"used_money or used_credit\"),\n        name=\"finish order\",\n    )\n\n    used_money: bool = False\n    used_credit: bool = False\n\nsm = AnyConditionSM()\nsm.submit()\n# TransitionNotAllowed: Can't finish order when in Start.\n\nsm.used_credit = True\nsm.submit()\nsm.current_state.id\n# 'end'\n```\n\n```{seealso}\nSee {ref}`Condition expressions` for more details or take a look at the {ref}`sphx_glr_auto_examples_lor_machine.py` example.\n```\n\n### Explicit event creation in 2.4.0\n\nNow you can explicit declare {ref}`Events` using the {ref}`event` class. This allows custom naming, translations, and also helps your IDE to know that events are callable.\n\n```py\n>>> from statemachine import StateMachine, State, Event\n\n>>> class StartMachine(StateMachine):\n...     created = State(initial=True)\n...     started = State(final=True)\n...\n...     start = Event(created.to(started), name=\"Launch the machine\")\n...\n>>> [e.id for e in StartMachine.events]\n['start']\n>>> [e.name for e in StartMachine.events]\n['Launch the machine']\n>>> StartMachine.start.name\n'Launch the machine'\n\n```\n\n```{seealso}\nSee {ref}`Events` for more details.\n```\n\n### Recursive state machines (infinite loop)\n\nWe removed a note from the docs saying to avoid recursion loops. Since the {ref}`StateMachine 2.0.0` release we've turned the RTC model enabled by default, allowing nested events to occour as all events are put on an internal queue before being executed.\n\n```{seealso}\nSee {ref}`sphx_glr_auto_examples_recursive_event_machine.py` for an example of an infinite loop state machine declaration using `after` action callback to call the same event over and over again.\n\n```\n\n\n## Bugfixes in 2.4.0\n\n- Fixes [#484](https://github.com/fgmacedo/python-statemachine/issues/484) issue where nested events inside loops could leak memory by incorrectly\n  referencing previous `event_data` when queuing the next event. This fix improves performance and stability in event-heavy workflows.\n"
  },
  {
    "path": "docs/releases/2.5.0.md",
    "content": "# 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 explicit definition of {ref}`Events` and introduces the helper `State.from_.any()`.\n\n### Python compatibility in 2.5.0\n\nStateMachine 2.5.0 supports Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13.\n\n### Helper to declare transition from any state\n\nYou can now declare that a state is accessible from any other state with a simple constructor. Using `State.from_.any()`, the state machine meta class automatically creates transitions from all non-final states to the target state.\n\nFurthermore, both `State.from_.itself()` and `State.to.itself()` have been refactored to support type hints and are now fully visible for code completion in your preferred editor.\n\n``` py\n>>> from statemachine import Event\n\n>>> class AccountStateMachine(StateMachine):\n...     active = State(\"Active\", initial=True)\n...     suspended = State(\"Suspended\")\n...     overdrawn = State(\"Overdrawn\")\n...     closed = State(\"Closed\", final=True)\n...\n...     suspend = Event(active.to(suspended))\n...     activate = Event(suspended.to(active))\n...     overdraft = Event(active.to(overdrawn))\n...     resolve_overdraft = Event(overdrawn.to(active))\n...\n...     close_account = Event(closed.from_.any(cond=\"can_close_account\"))\n...\n...     can_close_account: bool = True\n...\n...     def on_close_account(self):\n...         print(\"Account has been closed.\")\n\n>>> sm = AccountStateMachine()\n>>> sm.close_account()\nAccount has been closed.\n>>> sm.closed.is_active\nTrue\n\n```\n\n\n### Allowed events are now bounded to the state machine instance\n\nSince 2.0, the state machine can return a list of allowed events given the current state:\n\n```\n>>> sm = AccountStateMachine()\n>>> [str(e) for e in sm.allowed_events]\n['suspend', 'overdraft', 'close_account']\n\n```\n\n`Event` instances are now bound to the state machine instance, allowing you to pass the event by reference and call it like a method, which triggers the event in the state machine.\n\nYou can think of the event as an implementation of the **command** design pattern.\n\nOn this example, we iterate until the state machine reaches a final state,\nlisting the current state allowed events and executing the simulated user choice:\n\n```python\nimport random\nrandom.seed(\"15\")\n\nsm = AccountStateMachine()\n\nwhile not sm.current_state.final:\n    allowed_events = sm.allowed_events\n    print(\"Choose an action: \")\n    for idx, event in enumerate(allowed_events):\n        print(f\"{idx} - {event.name}\")\n\n    user_input = random.randint(0, len(allowed_events)-1)\n    print(f\"User input: {user_input}\")\n\n    event = allowed_events[user_input]\n    print(f\"Running the option {user_input} - {event.name}\")\n    event()\n\nprint(f\"SM is in {sm.current_state.name} state.\")\n# SM is in Closed state.\n```\n\n### Conditions expressions in 2.5.0\n\nThis release adds support for comparison operators into {ref}`Condition expressions`.\n\nThe following comparison operators are supported:\n  1. `>` — Greather than.\n  2. `>=` — Greather than or equal.\n  3. `==` — Equal.\n  4. `!=` — Not equal.\n  5. `<` — Lower than.\n  6. `<=` — Lower than or equal.\n\nExample:\n\n```python\nfrom statemachine import StateMachine, State, Event\n\nclass AnyConditionSM(StateMachine):\n    start = State(initial=True)\n    end = State(final=True)\n\n    submit = Event(\n        start.to(end, cond=\"order_value > 100\"),\n        name=\"finish order\",\n    )\n\n    order_value: float = 0\n\nsm = AnyConditionSM()\nsm.submit()\n# TransitionNotAllowed: Can't finish order when in Start.\n\nsm.order_value = 135.0\nsm.submit()\nsm.current_state.id\n# 'end'\n```\n\n```{seealso}\nSee {ref}`Condition expressions` for more details or take a look at the {ref}`sphx_glr_auto_examples_lor_machine.py` example.\n```\n\n### Decorator callbacks with explicit event creation in 2.5.0\n\nNow you can add callbacks using the decorator syntax using {ref}`Events`. Note that this syntax is also available without the explicit `Event`.\n\n```py\n>>> from statemachine import StateMachine, State, Event\n\n>>> class StartMachine(StateMachine):\n...     created = State(initial=True)\n...     started = State(final=True)\n...\n...     start = Event(created.to(started), name=\"Launch the machine\")\n...\n...     @start.on\n...     def call_service(self):\n...         return \"calling...\"\n...\n\n>>> sm = StartMachine()\n>>> sm.start()\n'calling...'\n\n\n```\n\n\n## Bugfixes in 2.5.0\n\n- Fixes [#500](https://github.com/fgmacedo/python-statemachine/issues/500) issue adding support for Pickle.\n\n\n## Misc in 2.5.0\n\n- We're now using `uv`  [#491](https://github.com/fgmacedo/python-statemachine/issues/491).\n- Simplification of the engines code [#498](https://github.com/fgmacedo/python-statemachine/pull/498).\n- The dispatcher and callback modules where refactored with improved separation of concerns [#490](https://github.com/fgmacedo/python-statemachine/pull/490).\n"
  },
  {
    "path": "docs/releases/2.6.0.md",
    "content": "# 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` method, Python 3.14 support,\na significant performance improvement for callback dispatch, and several bugfixes\nfor async condition expressions, type checker compatibility, and Django integration.\n\n### Python compatibility in 2.6.0\n\nStateMachine 2.6.0 supports Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14.\n\n### Checking enabled events\n\nA new {ref}`StateMachine.enabled_events` method lets you query which events have their\n`cond`/`unless` guards currently satisfied, going beyond {ref}`StateMachine.allowed_events`\nwhich only checks reachability from the current state.\n\nThis is particularly useful for **UI scenarios** where you want to enable or disable buttons\nbased on whether an event's conditions are met at runtime.\n\n```{testsetup}\n\n>>> from statemachine import StateMachine, State\n\n```\n\n```py\n>>> class ApprovalMachine(StateMachine):\n...     pending = State(initial=True)\n...     approved = State(final=True)\n...     rejected = State(final=True)\n...\n...     approve = pending.to(approved, cond=\"is_manager\")\n...     reject = pending.to(rejected)\n...\n...     is_manager = False\n\n>>> sm = ApprovalMachine()\n\n>>> [e.id for e in sm.allowed_events]\n['approve', 'reject']\n\n>>> [e.id for e in sm.enabled_events()]\n['reject']\n\n>>> sm.is_manager = True\n\n>>> [e.id for e in sm.enabled_events()]\n['approve', 'reject']\n\n```\n\nSince conditions may depend on runtime arguments, any `*args`/`**kwargs` passed to\n`enabled_events()` are forwarded to the condition callbacks:\n\n```py\n>>> class TaskMachine(StateMachine):\n...     idle = State(initial=True)\n...     running = State(final=True)\n...\n...     start = idle.to(running, cond=\"has_enough_resources\")\n...\n...     def has_enough_resources(self, cpu=0):\n...         return cpu >= 4\n\n>>> sm = TaskMachine()\n\n>>> sm.enabled_events()\n[]\n\n>>> [e.id for e in sm.enabled_events(cpu=8)]\n['start']\n\n```\n\n```{seealso}\nSee {ref}`Checking enabled events` in the Guards documentation for more details.\n```\n\n### Performance: cached signature binding\n\nCallback dispatch is now significantly faster thanks to cached signature binding in\n`SignatureAdapter`. The first call to a callback computes the argument binding and\ncaches a fast-path template; subsequent calls with the same argument shape skip the\nfull binding logic.\n\nThis results in approximately **60% faster** `bind_expected()` calls and\naround **30% end-to-end improvement** on hot transition paths.\n\nSee [#548](https://github.com/fgmacedo/python-statemachine/issues/548) for benchmarks.\n\n\n## Bugfixes in 2.6.0\n\n- Fixes [#531](https://github.com/fgmacedo/python-statemachine/issues/531) domain model\n  with falsy `__bool__` was being replaced by the default `Model()`.\n- Fixes [#535](https://github.com/fgmacedo/python-statemachine/issues/535) async predicates\n  in condition expressions (`not`, `and`, `or`) were not being awaited, causing guards to\n  silently return incorrect results.\n- Fixes [#548](https://github.com/fgmacedo/python-statemachine/issues/548)\n  `VAR_POSITIONAL` and kwargs precedence bugs in the signature binding cache introduced\n  by the performance optimization.\n- Fixes [#511](https://github.com/fgmacedo/python-statemachine/issues/511) Pyright/Pylance\n  false positive \"Argument missing for parameter f\" when calling events. Static analyzers\n  could not follow the metaclass transformation from `TransitionList` to `Event`.\n- Fixes [#551](https://github.com/fgmacedo/python-statemachine/issues/551) `MachineMixin`\n  now gracefully skips state machine initialization for Django historical models in data\n  migrations, instead of raising `ValueError`.\n- Fixes [#526](https://github.com/fgmacedo/python-statemachine/issues/526) sanitize project\n  path on Windows for documentation builds.\n\n\n## Misc in 2.6.0\n\n- Added Python 3.14 support [#552](https://github.com/fgmacedo/python-statemachine/pull/552).\n- Upgraded dev dependencies: ruff to 0.15.0, mypy to 1.14.1\n  [#552](https://github.com/fgmacedo/python-statemachine/pull/552).\n- Clarified conditional transition evaluation order in documentation\n  [#546](https://github.com/fgmacedo/python-statemachine/pull/546).\n- Added pydot DPI resolution settings to diagram documentation\n  [#514](https://github.com/fgmacedo/python-statemachine/pull/514).\n- Fixed miscellaneous typos in documentation\n  [#522](https://github.com/fgmacedo/python-statemachine/pull/522).\n- Removed Python 3.7 from CI build matrix\n  [ef351d5](https://github.com/fgmacedo/python-statemachine/commit/ef351d5).\n"
  },
  {
    "path": "docs/releases/3.0.0.md",
    "content": "# 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-step\nmigration guide.\n```\n\n## What's new in 3.0.0\n\n**Statecharts are here!** 🎉\n\nVersion 3.0 brings full statechart support to the library — compound states, parallel states,\nhistory pseudo-states, and an SCXML-compliant processing model. It also introduces a new\n`StateChart` base class with modern defaults, a richer event dispatch system (delayed events,\ninternal queues, cancellation), structured error handling, and several developer-experience\nimprovements.\n\nThe implementation follows the [SCXML specification](https://www.w3.org/TR/scxml/) (W3C),\nwhich defines a standard for statechart semantics. This ensures predictable behavior on\nedge cases and compatibility with other SCXML-based tools. The automated test suite now\nincludes W3C-provided `.scxml` test cases to verify conformance.\n\nWhile this is a major version with backward-incompatible changes, the existing `StateMachine`\nclass preserves 2.x defaults. See the\n[upgrade guide](upgrade_2x_to_3.md) for a smooth migration path.\n\n\n### Compound states\n\n**Compound states** have inner child states. Use `State.Compound` to define them\nwith Python class syntax — the class body becomes the state's children:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class ShireToRoad(StateChart):\n...     class shire(State.Compound):\n...         bag_end = State(initial=True)\n...         green_dragon = State()\n...         visit_pub = bag_end.to(green_dragon)\n...\n...     road = State(final=True)\n...     depart = shire.to(road)\n\n>>> sm = ShireToRoad()\n>>> set(sm.configuration_values) == {\"shire\", \"bag_end\"}\nTrue\n\n>>> sm.send(\"visit_pub\")\n>>> \"green_dragon\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"depart\")\n>>> set(sm.configuration_values) == {\"road\"}\nTrue\n\n```\n\nEntering a compound activates both the parent and its initial child. Exiting removes\nthe parent and all descendants. See {ref}`compound-states` for full details.\n\n### Parallel states\n\n**Parallel states** activate all child regions simultaneously. Use `State.Parallel`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class WarOfTheRing(StateChart):\n...     class war(State.Parallel):\n...         class frodos_quest(State.Compound):\n...             shire = State(initial=True)\n...             mordor = State(final=True)\n...             journey = shire.to(mordor)\n...         class aragorns_path(State.Compound):\n...             ranger = State(initial=True)\n...             king = State(final=True)\n...             coronation = ranger.to(king)\n\n>>> sm = WarOfTheRing()\n>>> \"shire\" in sm.configuration_values and \"ranger\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"journey\")\n>>> \"mordor\" in sm.configuration_values and \"ranger\" in sm.configuration_values\nTrue\n\n```\n\nEvents in one region don't affect others. See {ref}`parallel-states` for full details.\n\n\n### History pseudo-states\n\nThe **History pseudo-state** records the configuration of a compound state when it\nis exited. Re-entering via the history state restores the previously active child.\nSupports both shallow (`HistoryState()`) and deep (`HistoryState(type=\"deep\")`) history:\n\n```py\n>>> from statemachine import HistoryState, State, StateChart\n\n>>> class GollumPersonality(StateChart):\n...     class personality(State.Compound):\n...         smeagol = State(initial=True)\n...         gollum = State()\n...         h = HistoryState()\n...         dark_side = smeagol.to(gollum)\n...         light_side = gollum.to(smeagol)\n...     outside = State()\n...     leave = personality.to(outside)\n...     return_via_history = outside.to(personality.h)\n\n>>> sm = GollumPersonality()\n>>> sm.send(\"dark_side\")\n>>> \"gollum\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"leave\")\n>>> sm.send(\"return_via_history\")\n>>> \"gollum\" in sm.configuration_values\nTrue\n\n```\n\nSee {ref}`history-states` for full details on shallow vs deep history.\n\n\n### Eventless (automatic) transitions\n\nTransitions without an event trigger fire automatically when their guard condition\nis met:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class BeaconChain(StateChart):\n...     class beacons(State.Compound):\n...         first = State(initial=True)\n...         second = State()\n...         last = State(final=True)\n...         first.to(second)\n...         second.to(last)\n...     signal_received = State(final=True)\n...     done_state_beacons = beacons.to(signal_received)\n\n>>> sm = BeaconChain()\n>>> set(sm.configuration_values) == {\"signal_received\"}\nTrue\n\n```\n\nThe entire eventless chain cascades in a single macrostep. See {ref}`eventless`\nfor full details.\n\n\n### DoneData on final states\n\nFinal states can provide data to `done.state` handlers via the `donedata` parameter:\n\n```py\n>>> from statemachine import Event, State, StateChart\n\n>>> class QuestCompletion(StateChart):\n...     class quest(State.Compound):\n...         traveling = State(initial=True)\n...         completed = State(final=True, donedata=\"get_result\")\n...         finish = traveling.to(completed)\n...         def get_result(self):\n...             return {\"hero\": \"frodo\", \"outcome\": \"victory\"}\n...     epilogue = State(final=True)\n...     done_state_quest = Event(quest.to(epilogue, on=\"capture_result\"))\n...     def capture_result(self, hero=None, outcome=None, **kwargs):\n...         self.result = f\"{hero}: {outcome}\"\n\n>>> sm = QuestCompletion()\n>>> sm.send(\"finish\")\n>>> sm.result\n'frodo: victory'\n\n```\n\nThe `done_state_` naming convention automatically registers the `done.state.{suffix}`\nform — no explicit `id=` needed. See {ref}`done-state-convention` for details.\n\n\n### Invoke\n\nStates can now spawn external work when entered and cancel it when exited, following the\nSCXML `<invoke>` semantics (similar to UML's `do/` activity). Handlers run in a daemon\nthread (sync engine) or a thread executor wrapped in an asyncio Task (async engine).\nInvoke is a first-class callback group — convention naming (`on_invoke_<state>`),\ndecorators (`@state.invoke`), inline callables, and the full `SignatureAdapter` dependency\ninjection all work out of the box.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class FetchMachine(StateChart):\n...     loading = State(initial=True, invoke=lambda: {\"status\": \"ok\"})\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n\n>>> sm = FetchMachine()\n>>> import time; time.sleep(0.1)  # wait for background invoke to complete\n>>> \"ready\" in sm.configuration_values\nTrue\n\n```\n\nPassing a list of callables (`invoke=[a, b]`) creates independent invocations — each\nsends its own `done.invoke` event, so the first to complete triggers the transition and\ncancels the rest. Use {func}`~statemachine.invoke.invoke_group` when you need all\ncallables to complete before transitioning:\n\n```py\n>>> from statemachine.invoke import invoke_group\n\n>>> class BatchFetch(StateChart):\n...     loading = State(initial=True, invoke=invoke_group(lambda: \"a\", lambda: \"b\"))\n...     ready = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...\n...     def on_enter_ready(self, data=None, **kwargs):\n...         self.results = data\n\n>>> sm = BatchFetch()\n>>> import time; time.sleep(0.2)\n>>> sm.results\n['a', 'b']\n\n```\n\nInvoke also supports child state machines (pass a `StateChart` subclass) and SCXML\n`<invoke>` with `<finalize>`, autoforward, and `#_<invokeid>` / `#_parent` send targets\nfor parent-child communication.\n\nSee {ref}`invoke` for full documentation.\n\n\n### Event dispatch\n\n#### Event matching following SCXML spec\n\nEvent matching now follows the [SCXML spec](https://www.w3.org/TR/scxml/#events) — a\ntransition's event descriptor is a prefix match against the dot-separated event name. For\nexample, a transition with `event=\"error\"` matches `error`, `error.send`,\n`error.send.failed`, etc.\n\nAn event designator consisting solely of `*` can be used as a wildcard matching any event.\n\nSee {ref}`events` for full details.\n\n#### Delayed events\n\nEvents can be scheduled for future processing using `delay` (in milliseconds). The engine\ntracks execution time and processes the event only when the delay has elapsed.\n\n```python\nsm.send(\"light_beacons\", delay=500)  # fires after 500ms\n```\n\nDelayed events can be cancelled before they fire using `send_id` and `cancel_event()`.\nCancellation is most useful in async codebases, where other coroutines can cancel the\nevent while the delay is pending. In the sync engine, the delay is **blocking** — the\nprocessing loop sleeps until the delay elapses.\n\n```python\nsm.send(\"light_beacons\", delay=5000, send_id=\"beacon_signal\")\nsm.cancel_event(\"beacon_signal\")  # cancel from another coroutine or callback\n```\n\nSee {ref}`delayed-events` for details.\n\n#### `raise_()` — internal events\n\nA new `raise_()` method sends events to the internal queue, equivalent to\n`send(..., internal=True)`. Internal events are processed immediately within the current\nmacrostep, before any external events. See {ref}`sending-events`.\n\n#### New `send()` parameters\n\nThe `send()` method now accepts additional optional parameters:\n\n- `delay` (float): Time in milliseconds before the event is processed.\n- `send_id` (str): Identifier for the event, useful for cancelling delayed events.\n- `internal` (bool): If `True`, the event is placed in the internal queue and processed in the\n  current macrostep.\n\nExisting calls to `send()` are fully backward compatible.\n\n\n### Error handling with `error.execution`\n\nWhen `catch_errors_as_events` is enabled (default in `StateChart`), runtime exceptions during\ntransitions are caught and result in an internal `error.execution` event. This follows\nthe [SCXML error handling specification](https://www.w3.org/TR/scxml/#errorsAndEvents).\n\nA naming convention makes this easy to use: any event attribute starting with `error_`\nautomatically matches both the underscore and dot-notation forms:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class MyChart(StateChart):\n...     s1 = State(\"s1\", initial=True)\n...     error_state = State(\"error_state\", final=True)\n...\n...     go = s1.to(s1, on=\"bad_action\")\n...     error_execution = s1.to(error_state)  # matches \"error.execution\" automatically\n...\n...     def bad_action(self):\n...         raise RuntimeError(\"something went wrong\")\n\n>>> sm = MyChart()\n>>> sm.send(\"go\")\n>>> sm.configuration == {sm.error_state}\nTrue\n\n```\n\nErrors are caught at the **block level**: each microstep phase (exit, transition `on`,\nenter) is an independent block. An error in one block does not prevent subsequent blocks\nfrom executing — in particular, `after` callbacks always run, making `after_<event>()` a\nnatural finalize hook.\n\nThe error object is available as `error` in handler kwargs. See {ref}`error-execution`\nfor full details.\n\n\n### New API\n\n#### `configuration` and `configuration_values`\n\nDue to compound and parallel states, the state machine can now have multiple active states.\nThe new `configuration` property returns an `OrderedSet[State]` of all currently active\nstates, and `configuration_values` returns their values. These replace the deprecated\n`current_state` property. See {ref}`querying-configuration`.\n\n\n#### `is_terminated` property\n\nA new read-only property that returns `True` when the state machine has reached a final\nstate and the engine is no longer running. Works correctly for all topologies — flat,\ncompound, and parallel. See {ref}`checking-termination`.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class SimpleSM(StateChart):\n...     idle = State(initial=True)\n...     done = State(final=True)\n...     finish = idle.to(done)\n\n>>> sm = SimpleSM()\n>>> sm.is_terminated\nFalse\n\n>>> sm.send(\"finish\")\n>>> sm.is_terminated\nTrue\n\n```\n\n\n#### `In(state)` condition checks\n\nConditions can now check if a state is in the current configuration using the\n`In('<state-id>')` syntax. This is particularly useful in parallel regions where\na transition depends on the state of another region. See {ref}`condition expressions`.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Spaceship(StateChart):\n...     class systems(State.Parallel):\n...         class engine(State.Compound):\n...             off = State(initial=True)\n...             on = State()\n...             ignite = off.to(on)\n...         class hatch(State.Compound):\n...             open = State(initial=True)\n...             sealed = State()\n...             seal = open.to(sealed)\n...     orbit = State(final=True)\n...     launch = systems.to(orbit, cond=\"In('on') and In('sealed')\")\n\n>>> sm = Spaceship()\n>>> sm.send(\"launch\")  # engine off, hatch open — guard fails\n>>> \"off\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"ignite\")\n>>> sm.send(\"launch\")  # engine on, hatch still open — guard fails\n>>> \"on\" in sm.configuration_values and \"open\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"seal\")\n>>> sm.send(\"launch\")  # both conditions met — launches!\n>>> sm.is_terminated\nTrue\n\n```\n\n#### `prepare_event()` callback\n\nThe `prepare_event` callback lets you inject custom data into `**kwargs` for all\nother callbacks in the same event processing cycle. See {ref}`preparing-events`.\n\n```py\n>>> from statemachine import State, StateMachine\n\n>>> class ExampleStateMachine(StateMachine):\n...     initial = State(initial=True)\n...\n...     loop = initial.to.itself()\n...\n...     def prepare_event(self):\n...         return {\"foo\": \"bar\"}\n...\n...     def on_loop(self, foo):\n...         return f\"On loop: {foo}\"\n\n>>> sm = ExampleStateMachine()\n\n>>> sm.loop()\n'On loop: bar'\n\n```\n\n\n#### Constructor kwargs forwarded to initial state callbacks\n\nConstructor keyword arguments are forwarded to initial state callbacks, so self-contained\nmachines can receive context at creation time:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Greeter(StateChart):\n...     idle = State(initial=True)\n...     done = State(final=True)\n...     idle.to(done)\n...\n...     def on_enter_idle(self, name=None, **kwargs):\n...         self.greeting = f\"Hello, {name}!\"\n\n>>> sm = Greeter(name=\"Alice\")\n>>> sm.greeting\n'Hello, Alice!'\n\n```\n\n\n### Developer experience\n\n#### `StateChart` base class\n\nThe new `StateChart` class is the recommended base for all new state machines. It enables\nSCXML-compliant defaults: `catch_errors_as_events`, `enable_self_transition_entries`, and\nnon-atomic configuration updates. The existing `StateMachine` class is now a subclass with\nbackward-compatible defaults. See {ref}`behaviour` for a comparison table.\n\n\n#### Typed models with `Generic[TModel]`\n\n`StateChart` now supports a generic type parameter for the model, enabling full type\ninference and IDE autocompletion on `sm.model`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class MyModel:\n...     name: str = \"\"\n...     value: int = 0\n\n>>> class MySM(StateChart[\"MyModel\"]):\n...     idle = State(initial=True)\n...     active = State(final=True)\n...     go = idle.to(active)\n\n>>> sm = MySM(model=MyModel())\n>>> sm.model.name\n''\n\n```\n\nWith this declaration, type checkers infer `sm.model` as `MyModel` (not `Any`), so\naccessing `sm.model.name` or `sm.model.value` gets full autocompletion and type safety.\nWhen no type parameter is given, `StateChart` defaults to `StateChart[Any]` for backward\ncompatibility. See {ref}`domain models` for details.\n\n\n#### Improved type checking with pyright\n\nThe library now supports [pyright](https://github.com/microsoft/pyright) in addition to mypy.\nType annotations have been improved throughout the codebase, and a catch-all `__getattr__`\nthat previously returned `Any` has been removed — type checkers can now detect misspelled\nattribute names and unresolved references on `StateChart` subclasses.\n\n\n#### Self-transition entry/exit behavior\n\nIn `StateChart`, self-transitions now execute entry and exit actions, following the SCXML\nspec. The `enable_self_transition_entries` class attribute controls this behavior.\n`StateMachine` preserves the 2.x default (no entry/exit on self-transitions).\nSee {ref}`self-transition`.\n\n\n#### Class-level listener declarations\n\nListeners can now be declared at the class level using the `listeners` attribute, so they are\nautomatically attached to every instance. The list accepts callables (classes, `partial`, lambdas)\nas factories that create a fresh listener per instance, or pre-built instances that are shared.\n\nA `setup()` protocol allows factory-created listeners to receive runtime dependencies\n(DB sessions, Redis clients, etc.) via `**kwargs` forwarded from the SM constructor.\n\nInheritance is supported: child listeners are appended after parent listeners, unless\n`listeners_inherit = False` is set to replace them entirely.\n\nSee {ref}`observers` for full documentation.\n\n\n#### Weighted (probabilistic) transitions\n\nA new contrib module `statemachine.contrib.weighted` provides `weighted_transitions()`,\nenabling probabilistic transition selection based on relative weights. This works entirely\nthrough the existing condition system — no engine changes required.\n\nSee {ref}`weighted-transitions` for full documentation.\n\n\n#### State timeouts\n\nA new contrib module `statemachine.contrib.timeout` provides a `timeout()` invoke helper\nfor per-state watchdog timers. When a state is entered, a background timer starts; if the\nstate is not exited before the timer expires, an event is sent automatically. The timer is\ncancelled on state exit, with no manual cleanup needed.\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.contrib.timeout import timeout\n\n>>> class WaitingMachine(StateChart):\n...     waiting = State(initial=True, invoke=timeout(5, on=\"expired\"))\n...     timed_out = State(final=True)\n...     expired = waiting.to(timed_out)\n\n>>> sm = WaitingMachine()\n>>> sm.waiting.is_active\nTrue\n\n```\n\nSee {ref}`timeout` for full documentation.\n\n\n#### Create state machine from a dict definition\n\nDynamically create state machine classes using\n{func}`~statemachine.io.create_machine_class_from_definition`:\n\n``` py\n>>> from statemachine.io import create_machine_class_from_definition\n\n>>> machine = create_machine_class_from_definition(\n...     \"TrafficLightMachine\",\n...     **{\n...         \"states\": {\n...             \"green\": {\"initial\": True, \"on\": {\"change\": [{\"target\": \"yellow\"}]}},\n...             \"yellow\": {\"on\": {\"change\": [{\"target\": \"red\"}]}},\n...             \"red\": {\"on\": {\"change\": [{\"target\": \"green\"}]}},\n...         },\n...     }\n... )\n\n>>> sm = machine()\n>>> sm.green.is_active\nTrue\n>>> sm.send(\"change\")\n>>> sm.yellow.is_active\nTrue\n\n```\n\n\n#### Async concurrent event result routing\n\nWhen multiple coroutines send events concurrently via `asyncio.gather`, each\ncaller now receives its own event's result (or exception). Previously, only the\nfirst caller to acquire the processing lock would get a result — subsequent\ncallers received `None` and exceptions could leak to the wrong caller.\n\nThis is implemented by attaching an `asyncio.Future` to each externally\nenqueued event in the async engine. See {ref}`async` for details.\n\nFixes [#509](https://github.com/fgmacedo/python-statemachine/issues/509).\n\n\n#### Migration guide from pytransitions\n\nA new {ref}`Coming from pytransitions <coming-from-transitions>` guide helps users of the\n[*transitions*](https://github.com/pytransitions/transitions) library evaluate the differences\nand migrate their state machines. It includes side-by-side code comparisons and a feature matrix.\n\n\n#### Migration guide from the GoF State Pattern\n\nA new {ref}`Coming from the State Pattern <coming-from-state-pattern>` guide helps developers\nfamiliar with the classic Gang of Four State Pattern understand how to port their hand-rolled\nstate implementations to python-statemachine. It walks through a complete example, compares\nthe two approaches, and highlights what you gain from the declarative style.\n\n\n#### Validation flags\n\nThe `strict_states` class parameter has been replaced by two independent, always-on\nclass-level attributes:\n\n- `validate_trap_states`: non-final states must have at least one outgoing transition.\n- `validate_final_reachability`: when final states exist, all non-final states must have\n  a path to at least one final state.\n- `validate_disconnected_states`: all states must be reachable from the initial state.\n\nSee {ref}`validations` for details.\n\n\n## Known limitations\n\nThe following SCXML features are **not yet implemented** and are deferred to a future release:\n\n- HTTP and other external communication targets (only `#_internal`, `#_parent`, and\n  `#_<invokeid>` send targets are supported)\n\n```{seealso}\nFor a step-by-step migration guide with before/after examples, see\n{ref}`Upgrading from 2.x to 3.0 <Upgrading from 2.x to 3.0>`.\n```\n\n\n## Backward incompatible changes in 3.0\n\nThis section summarizes the breaking changes. For detailed before/after examples and\nmigration instructions, see the [upgrade guide](upgrade_2x_to_3.md).\n\n- **Python 3.7 and 3.8 dropped.** StateMachine 3.0 supports Python 3.9 through 3.14.\n- **Non-RTC model removed.** The `rtc` parameter (deprecated since 2.3.2) has been removed.\n  All events are now queued before being processed.\n- **`current_state` deprecated.** Use `configuration` / `configuration_values` instead.\n  With compound and parallel states, multiple states can be active simultaneously.\n- **Configuration update timing.** In `StateChart`, states are exited *before* `on` callbacks\n  and entered *after*, following the SCXML spec. Two new kwargs — `previous_configuration` and\n  `new_configuration` — are available in `on` callbacks. Use `atomic_configuration_update=True`\n  or the `StateMachine` class to restore the 2.x behavior.\n- **Self-transition entry/exit.** In `StateChart`, self-transitions now trigger `on_enter_*` /\n  `on_exit_*` callbacks. Set `enable_self_transition_entries = False` to restore the old behavior.\n- **`add_observer()` removed.** Use `add_listener()` instead.\n- **`TransitionNotAllowed` changes.** Now stores `configuration` (a set) instead of `state`,\n  and `event` can be `None`.\n- **`allow_event_without_transition` moved to class level.** No longer an `__init__` parameter.\n- **`States.from_enum` default changed.** `use_enum_instance` now defaults to `True`.\n- **Short registry names removed.** Use fully-qualified names with `get_machine_cls()`.\n- **`strict_states` removed.** Replaced by `validate_trap_states` and\n  `validate_final_reachability` (both default to `True`).\n- **`__repr__` output changed.** Now shows `configuration=[...]` instead of `current_state=...`.\n"
  },
  {
    "path": "docs/releases/3.1.0.md",
    "content": "# 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 now support Python's built-in `format()` protocol. Use f-strings\nor `format()` to get text representations — on both classes and instances:\n\n```python\nf\"{TrafficLightMachine:md}\"\nf\"{sm:mermaid}\"\nformat(sm, \"rst\")\n```\n\nSupported formats:\n\n| Format    | Output                    | Requires              |\n|-----------|---------------------------|-----------------------|\n| `dot`     | Graphviz DOT source       | `pydot`               |\n| `svg`     | SVG markup (via Graphviz) | `pydot` + `graphviz`  |\n| `mermaid` | Mermaid stateDiagram-v2   | —                     |\n| `md`      | Markdown transition table | —                     |\n| `rst`     | RST transition table      | —                     |\n\nSee {ref}`diagram:Text representations` for details.\n\n\n### Formatter facade\n\nA new `Formatter` facade with decorator-based registration unifies all text\nformat rendering behind a single API. Adding a new format requires only\nregistering a render function — no changes to `__format__`, the CLI, or the\nSphinx directive:\n\n```python\nfrom statemachine.contrib.diagram import formatter\n\nformatter.render(sm, \"mermaid\")\nformatter.supported_formats()\n\n@formatter.register_format(\"custom\")\ndef _render_custom(machine_or_class):\n    ...\n```\n\nSee {ref}`formatter-api` for details.\n\n\n### Mermaid diagram support\n\nState machines can now be rendered as\n[Mermaid `stateDiagram-v2`](https://mermaid.js.org/syntax/stateDiagram.html)\nsource text — no Graphviz installation required. Supports compound states,\nparallel regions, history states, guards, and active-state highlighting.\n\nThree ways to use it:\n\n- **f-strings:** `f\"{sm:mermaid}\"`\n- **CLI:** `python -m statemachine.contrib.diagram MyMachine - --format mermaid`\n- **Sphinx directive:** `:format: mermaid` renders via `sphinxcontrib-mermaid`.\n\nSee {ref}`diagram:Mermaid format` for details.\n\n\n### Auto-expanding docstrings\n\nUse `{statechart:FORMAT}` placeholders in your class docstring to embed a\nlive representation of the state machine. The placeholder is replaced at\nclass definition time, so the docstring always stays in sync with the code:\n\n```python\nclass TrafficLight(StateChart):\n    \"\"\"A traffic light.\n\n    {statechart:md}\n    \"\"\"\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n    cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n```\n\nAny registered format works: `md`, `rst`, `mermaid`, `dot`, etc.\nWorks with Sphinx autodoc — the expanded docstring is what gets rendered.\nSee {ref}`diagram:Auto-expanding docstrings` for details.\n\n\n### Sphinx directive for inline diagrams\n\nA new Sphinx extension renders state machine diagrams directly in your\ndocumentation from an importable class path — no manual image generation\nneeded.\n\nAdd `\"statemachine.contrib.diagram.sphinx_ext\"` to your `conf.py`\nextensions, then use the directive in any MyST Markdown page:\n\n````markdown\n```{statemachine-diagram} myproject.machines.OrderControl\n:events: receive_payment\n:caption: After payment\n:target:\n```\n````\n\nThe directive supports the same options as the standard `image`/`figure`\ndirectives (`:width:`, `:height:`, `:scale:`, `:align:`, `:target:`,\n`:class:`, `:name:`), plus `:events:` to instantiate the machine and send\nevents before rendering (highlighting the current state).\n\nUsing `:target:` without a value makes the diagram clickable, opening the\nfull SVG in a new browser tab for zooming — useful for large statecharts.\n\nThe `:format: mermaid` option renders via `sphinxcontrib-mermaid` instead of\nGraphviz.\n\nSee {ref}`diagram:Sphinx directive` for full documentation.\n[#589](https://github.com/fgmacedo/python-statemachine/pull/589).\n\n\n### Diagram CLI `--events` and `--format` options\n\nThe `python -m statemachine.contrib.diagram` command now accepts:\n\n- `--events` to instantiate the machine and send events before rendering,\n  highlighting the current active state.\n- `--format` to choose the output format (`mermaid`, `md`, `rst`, `dot`, `svg`,\n  or image formats via Graphviz). Use `-` as the output path to write text\n  formats to stdout.\n\nSee {ref}`diagram:Command line` for details.\n[#593](https://github.com/fgmacedo/python-statemachine/pull/593).\n\n\n### Performance: 5x–7x faster event processing\n\nThe engine's hot paths have been systematically profiled and optimized, resulting in\n**4.7x–7.7x faster event throughput** and **1.9x–2.6x faster setup** across all\nmachine types. All optimizations are internal — no public API changes.\nSee [#592](https://github.com/fgmacedo/python-statemachine/pull/592) for details.\n\n\n### Thread safety documentation\n\nThe sync engine is thread-safe: multiple threads can send events to the same state\nmachine instance concurrently. This is now documented in the\n{ref}`processing model <thread-safety>` and verified by stress tests.\n[#592](https://github.com/fgmacedo/python-statemachine/pull/592).\n\n\n### Bugfixes in 3.1.0\n\n- Fixes silent misuse of `Event()` with multiple positional arguments. Passing more than one\n  transition to `Event()` (e.g., `Event(t1, t2)`) now raises `InvalidDefinition` with a\n  clear message suggesting the `|` operator. Previously, the second argument was silently\n  interpreted as the event `id`, leaving the extra transitions eventless (auto-firing).\n  [#588](https://github.com/fgmacedo/python-statemachine/pull/588).\n\n- `Event.name` is now auto-humanized from the `id` (e.g., `cycle` → `Cycle`,\n  `pick_up` → `Pick up`). Diagrams, Mermaid output, and text tables all display\n  the human-readable name. Explicit `name=` values are preserved. The same\n  `humanize_id()` helper is now shared by `Event` and `State`.\n  [#601](https://github.com/fgmacedo/python-statemachine/pull/601),\n  fixes [#600](https://github.com/fgmacedo/python-statemachine/issues/600).\n\n## Misc in 3.1.0\n"
  },
  {
    "path": "docs/releases/index.md",
    "content": "# Release notes\n\nVersions follow [Semantic Versioning](https://semver.org/) (`<major>.<minor>.<patch>`).\n\nBackward incompatible (breaking) changes will only be introduced in major versions\nwith advance notice in the **Deprecations** section of releases.\n\n```{seealso}\nUpgrading from 2.x? See [](upgrade_2x_to_3.md) for a step-by-step migration guide.\n```\n\n## 3.x releases\n\nRequires Python 3.9+.\n\n```{toctree}\n:maxdepth: 2\n\n3.1.0\n3.0.0\n\n```\n\n## 2.x releases\n\nLast series to support Python 3.7 and 3.8.\n\n```{toctree}\n:maxdepth: 2\n\n2.6.0\n2.5.0\n2.4.0\n2.3.6\n2.3.5\n2.3.4\n2.3.3\n2.3.2\n2.3.1\n2.3.0\n2.2.0\n2.1.2\n2.1.1\n2.1.0\n2.0.0\n\n```\n\n\n## 1.x releases\n\nLast series to support Python 2.x.\n\n```{toctree}\n:maxdepth: 2\n\n1.0.3\n1.0.2\n1.0.1\n1.0.0\n\n```\n\n## 0.x releases\n\n```{toctree}\n:maxdepth: 1\n\n0.9.0\n0.8.0\n0.7.1\n0.7.0\n0.6.2\n0.6.1\n0.6.0\n0.5.1\n0.5.0\n0.4.2\n0.3.0\n0.2.0\n0.1.0\n\n```\n"
  },
  {
    "path": "docs/releases/upgrade_2x_to_3.md",
    "content": "# Upgrading from 2.x to 3.0\n\nThis guide covers all backward-incompatible changes in python-statemachine 3.0 and provides\nstep-by-step migration instructions from the 2.x series.\n\n```{tip}\nMost 2.x code continues to work unchanged — the `StateMachine` class preserves backward-compatible\ndefaults. Review this guide to understand what changed and adopt the new APIs at your own pace.\n```\n\n```{tip}\n**Using an AI coding assistant?** You can use this guide as context for automated migration.\nTry a prompt like:\n\n> Update my usage of python-statemachine following this upgrade guide:\n> https://python-statemachine.readthedocs.io/en/latest/releases/upgrade_2x_to_3.html\n>\n> Apply only the changes that are relevant to my codebase. Do not change working behavior.\n```\n\n\n## Quick checklist\n\n1. Upgrade Python to 3.9+ (3.7 and 3.8 are no longer supported).\n2. Replace `rtc=True/False` in constructors — the non-RTC model has been removed.\n3. Replace `allow_event_without_transition` init parameter with a class-level attribute.\n4. Replace `sm.current_state` with `sm.configuration` / `sm.configuration_values`.\n5. Replace `sm.current_state.final` with `sm.is_terminated`.\n6. Replace `sm.add_observer(...)` with `sm.add_listener(...)`.\n7. Update code that catches `TransitionNotAllowed` and accesses `.state` → use `.configuration`.\n8. Review `on` callbacks that query `is_active` or `current_state` during transitions.\n9. If using `StateChart`, note that self-transitions now trigger entry/exit callbacks.\n10. If using `States.from_enum`, note that `use_enum_instance` now defaults to `True`.\n11. If using `get_machine_cls()` with short names, switch to fully-qualified names.\n12. Remove `strict_states=True/False` — replace with `validate_trap_states` / `validate_final_reachability`.\n13. Update code that parses `__repr__` output — format changed to `configuration=[...]`.\n\n---\n\n\n## Python compatibility\n\nSupport for Python 3.7 and 3.8 has been dropped. If you need these versions, stay on the 2.x\nseries.\n\nStateMachine 3.0 supports Python 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14.\n\n\n## `StateChart` vs `StateMachine`\n\nVersion 3.0 introduces `StateChart` as the new base class. The existing `StateMachine` class is\nnow a subclass of `StateChart` with defaults that preserve 2.x behavior:\n\n| Attribute                         | `StateChart` | `StateMachine` |\n|-----------------------------------|:------------:|:--------------:|\n| `allow_event_without_transition`  | `True`       | `False`        |\n| `enable_self_transition_entries`  | `True`       | `False`        |\n| `atomic_configuration_update`     | `False`      | `True`         |\n| `catch_errors_as_events`              | `True`       | `False`        |\n\n**Recommendation:** Use `StateChart` for new code. It follows the\n[SCXML specification](https://www.w3.org/TR/scxml/) defaults — structured error handling,\nself-transition entry/exit, and non-atomic configuration updates.\n\nFor existing code, you can continue using `StateMachine` — it works as before. You can also adopt\nindividual `StateChart` behaviors granularly by overriding class-level attributes:\n\n**Before (2.x):**\n\n```python\nclass MyMachine(StateMachine):\n    ...\n```\n\n**After (3.0) — gradual adoption:**\n\n```python\n# Adopt SCXML error handling without switching to StateChart\nclass MyMachine(StateMachine):\n    catch_errors_as_events = True\n    # ... rest of your definition unchanged\n```\n\nSee {ref}`behaviour` for full details on each attribute.\n\n\n## Remove the `rtc` parameter\n\nThe `rtc` parameter was deprecated in 2.3.2 and has been removed. All events are now queued\nbefore processing (Run-to-Completion semantics). See {ref}`rtc-model`.\n\n**Before (2.x):**\n\n```python\nsm = MyMachine(rtc=False)  # synchronous, non-queued processing\n```\n\n**After (3.0):**\n\n```python\nsm = MyMachine()  # RTC is always enabled, remove the parameter\n```\n\nIf you were passing `rtc=True` (the default), simply remove the parameter.\n\n\n## `allow_event_without_transition` moved to class level\n\nThis was previously an `__init__` parameter and is now a class-level attribute.\n\n**Before (2.x):**\n\n```python\nsm = MyMachine(allow_event_without_transition=True)\n```\n\n**After (3.0):**\n\n```python\nclass MyMachine(StateMachine):\n    allow_event_without_transition = True\n    # ... states and transitions\n```\n\n```{note}\n`StateMachine` defaults to `False` (same as 2.x). `StateChart` defaults to `True`.\n```\n\n\n## `current_state` deprecated — use `configuration`\n\nDue to compound and parallel states, the state machine can now have multiple active states. The\n`current_state` property is deprecated in favor of `configuration`, which always returns an\n`OrderedSet[State]`. See {ref}`querying-configuration`.\n\n**Before (2.x):**\n\n```python\nstate = sm.current_state          # returns a single State\nvalue = sm.current_state.value    # get the value\n```\n\n**After (3.0):**\n\n```python\nstates = sm.configuration             # returns OrderedSet[State]\nvalues = sm.configuration_values       # returns OrderedSet of values\n\n# If you know you have a single active state (flat machine):\nstate = next(iter(sm.configuration))   # get the single State\n```\n\n```{tip}\nFor flat state machines (no compound/parallel states), `current_state_value` still returns a\nsingle value and works as before. But we strongly recommend using `configuration` /\n`configuration_values` for forward compatibility.\n```\n\n\n## Replace `current_state.final` with `is_terminated`\n\nThe old `current_state.final` pattern still works for flat state machines, but `is_terminated`\nis the recommended replacement — it works correctly for all topologies (flat, compound, and\nparallel), where \"terminated\" means all regions have reached a final state.\nSee {ref}`checking-termination`.\n\n**Before (2.x):**\n\n```python\nif sm.current_state.final:\n    print(\"done\")\n\nwhile not sm.current_state.final:\n    sm.send(\"next\")\n```\n\n**After (3.0):**\n\n```python\nif sm.is_terminated:\n    print(\"done\")\n\nwhile not sm.is_terminated:\n    sm.send(\"next\")\n```\n\n\n## Replace `add_observer()` with `add_listener()`\n\nThe method `add_observer` has been removed in v3.0. Use `add_listener` instead.\n\nFor new code, consider using class-level listener declarations — they attach listeners\nautomatically to every instance and support a `setup()` protocol for dependency injection.\nSee {ref}`listeners`.\n\n**Before (2.x):**\n\n```python\nsm.add_observer(my_listener)\n```\n\n**After (3.0) — runtime attachment:**\n\n```python\nsm.add_listener(my_listener)\n```\n\n**After (3.0) — class-level declaration (recommended for new code):**\n\n```python\nclass MyMachine(StateChart):\n    listeners = [MyListener]\n    # ... states and transitions\n```\n\n\n## Update `TransitionNotAllowed` exception handling\n\n`TransitionNotAllowed` is raised when an event has no valid transition from the current\nconfiguration. Note that this exception only applies when `allow_event_without_transition`\nis `False` (the `StateMachine` default). In `StateChart`, events without matching\ntransitions are discarded — this follows the SCXML recommendation, where statecharts\nare reactive systems and not every event is expected to be handled in every state.\n\nThe exception now stores a `configuration` attribute (a set of states) instead of a single\n`state` attribute, and the `event` attribute can be `None`.\n\n**Before (2.x):**\n\n```python\ntry:\n    sm.send(\"go\")\nexcept TransitionNotAllowed as e:\n    print(e.event)   # Event instance\n    print(e.state)   # single State\n```\n\n**After (3.0):**\n\n```python\ntry:\n    sm.send(\"go\")\nexcept TransitionNotAllowed as e:\n    print(e.event)           # Event instance or None\n    print(e.configuration)   # MutableSet[State]\n```\n\n```{tip}\nIf you are migrating to `StateChart`, consider handling errors as events instead of\ncatching exceptions. With `catch_errors_as_events=True` (the default in `StateChart`),\nruntime errors are dispatched as `error.execution` events that you can handle with\ntransitions. See {ref}`error-execution`.\n```\n\n\n## Configuration update timing during transitions\n\nThis is the most impactful behavioral change for existing code. See {ref}`behaviour` for\nfull details on `atomic_configuration_update`.\n\n**In 2.x**, the active state was updated atomically _after_ the transition `on` callbacks,\nmeaning `sm.current_state` and `state.is_active` reflected the **source** state during `on`\ncallbacks.\n\n**In 3.0** (SCXML-compliant behavior in `StateChart`), states are exited _before_ `on` callbacks\nand entered _after_, so during `on` callbacks the configuration may be **empty**.\n\n```{important}\nIf you use `StateMachine` (not `StateChart`), the default `atomic_configuration_update=True`\n**preserves the 2.x behavior**. This section only affects code using `StateChart` or\n`StateMachine` with `atomic_configuration_update=False`.\n```\n\n**Before (2.x):**\n\n```python\ndef on_validate(self):\n    if self.accepted.is_active:    # True during on callback in 2.x\n        return \"congrats!\"\n```\n\n**After (3.0):**\n\nTwo new keyword arguments are available in `on` callbacks to inspect the transition context:\n\n```python\ndef on_validate(self, previous_configuration, new_configuration):\n    if self.accepted in previous_configuration:\n        return \"congrats!\"\n```\n\n- `previous_configuration`: the set of states that were active before the microstep.\n- `new_configuration`: the set of states that will be active after the microstep.\n\nTo restore the old behavior globally, set the class attribute:\n\n```python\nclass MyChart(StateChart):\n    atomic_configuration_update = True  # restore 2.x behavior\n```\n\nOr simply use `StateMachine`, which has `atomic_configuration_update=True` by default.\n\n\n## Self-transition entry/exit behavior\n\nIn `StateChart`, self-transitions (a state transitioning to itself) now execute entry and exit\nactions, following the SCXML spec. In `StateMachine`, the 2.x behavior is preserved (no\nentry/exit on self-transitions). See {ref}`self-transition`.\n\n**Before (2.x):**\n\n```python\n# Self-transitions did NOT trigger on_enter_*/on_exit_* callbacks\nloop = s1.to.itself()\n```\n\n**After (3.0 with `StateChart`):**\n\n```python\n# Self-transitions DO trigger on_enter_*/on_exit_* callbacks\nloop = s1.to.itself()\n\n# To disable (preserve 2.x behavior):\nclass MyChart(StateChart):\n    enable_self_transition_entries = False\n```\n\n\n## `States.from_enum` default changed to `use_enum_instance=True`\n\nIn 2.x, `States.from_enum` defaulted to `use_enum_instance=False`, meaning state values were the\nraw enum values (e.g., integers). In 3.0, the default is `True`, so state values are the enum\ninstances themselves. See {ref}`states from enum types`.\n\n**Before (2.x):**\n\n```python\nstates = States.from_enum(MyEnum, initial=MyEnum.start)\n# states.start.value == 1  (raw value)\n```\n\n**After (3.0):**\n\n```python\nstates = States.from_enum(MyEnum, initial=MyEnum.start)\n# states.start.value == MyEnum.start  (enum instance)\n```\n\nIf your code relies on raw enum values, pass `use_enum_instance=False` explicitly.\n\n\n## Short registry names removed\n\nIn 2.x, state machine classes were registered both by their fully-qualified name and their short\nclass name. The short-name lookup was deprecated since v0.8 and has been removed in 3.0.\n\n**Before (2.x):**\n\n```python\nfrom statemachine.registry import get_machine_cls\n\ncls = get_machine_cls(\"MyMachine\")  # short name — worked with warning\n```\n\n**After (3.0):**\n\n```python\nfrom statemachine.registry import get_machine_cls\n\ncls = get_machine_cls(\"myapp.machines.MyMachine\")  # fully-qualified name\n```\n\n\n## `strict_states` removed — use `validate_trap_states` / `validate_final_reachability`\n\nThe `strict_states` class parameter has been removed. The two validations it controlled are now\nalways-on by default, each controlled by its own class-level attribute.\nSee {ref}`validations`.\n\n**Before (2.x) — `s2` is a trap state (no outgoing transitions, not marked `final`):**\n\n```python\nclass MyMachine(StateMachine, strict_states=False):\n    s1 = State(initial=True)\n    s2 = State()          # trap state — no outgoing transitions, not final\n    go = s1.to(s2)\n```\n\n**After (3.0) — recommended: fix the definition by marking terminal states as `final`:**\n\n```python\nclass MyMachine(StateMachine):\n    s1 = State(initial=True)\n    s2 = State(final=True)        # was State() — now correctly marked as final\n    go = s1.to(s2)\n```\n\n**After (3.0) — opt out if you intentionally have non-final trap states:**\n\n```python\nclass MyMachine(StateMachine):\n    validate_trap_states = False           # allow non-final states without outgoing transitions\n    validate_final_reachability = False    # allow non-final states without path to final\n    s1 = State(initial=True)\n    s2 = State()\n    go = s1.to(s2)\n```\n\nThe two flags are independent — you can disable one while keeping the other enabled.\n\n\n## `send()` method — new parameters\n\nThe `send()` method has new optional parameters for delayed events and internal events.\nExisting code calling `sm.send(\"event\")` works unchanged. See {ref}`sending-events`.\n\n**Before (2.x):**\n\n```python\nsm.send(\"event_name\", *args, **kwargs)\n```\n\n**After (3.0) — fully backward compatible:**\n\n```python\nsm.send(\"event_name\", *args, delay=0, send_id=None, internal=False, **kwargs)\n```\n\n- `delay`: Time in milliseconds before the event is processed.\n- `send_id`: Identifier for the event, used to cancel delayed events with `sm.cancel_event(send_id)`.\n- `internal`: If `True`, the event is placed in the internal queue (processed in the current macrostep).\n\n\n## `__repr__` output changed\n\nThe string representation now shows `configuration=[...]` instead of `current_state=...`.\n\n**Before (2.x):**\n\n```\nMyMachine(model=Model(), state_field='state', current_state='initial')\n```\n\n**After (3.0):**\n\n```\nMyMachine(model=Model(), state_field='state', configuration=['initial'])\n```\n\n\n## New public exports\n\nThe package now exports two additional symbols:\n\n**Before (2.x):**\n\n```python\nfrom statemachine import StateMachine, State, Event\n```\n\n**After (3.0):**\n\n```python\nfrom statemachine import StateChart      # new base class\nfrom statemachine import HistoryState    # history pseudo-state for compound states\nfrom statemachine import StateMachine    # unchanged\nfrom statemachine import State           # unchanged\nfrom statemachine import Event           # unchanged\n```\n\n\n## What's new\n\nFor full details on all new features in 3.0 — including compound states, parallel states,\ninvoke, error handling, and more — see the\n{ref}`3.0.0 release notes <StateMachine 3.0.0>`.\n"
  },
  {
    "path": "docs/statechart.md",
    "content": "(statechart-instance)=\n(statechart)=\n(statemachine)=\n\n# StateChart\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nOnce you define a `StateChart` class with states, transitions, and events, you\nwork with **instances** of that class. Each instance is a live, running machine\nwith its own configuration, event queues, and listeners. This page documents what\nyou can do with that instance at runtime.\n\n\n## Creating an instance\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class TrafficLight(StateChart):\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n>>> sm = TrafficLight()\n\n```\n\nThe constructor activates the initial state and runs any `on_enter` callbacks.\nYou can pass a `model` object to store state externally (see {ref}`models`) and\n`listeners` to observe the machine (see {ref}`listeners`).\n\n\n(sending-events)=\n\n## Sending events\n\nThe primary way to drive a state machine is by sending events.\n\n**`send(event, **kwargs)`** places the event on the **external queue**. External\nevents are processed after the current macrostep completes:\n\n```py\n>>> sm.send(\"cycle\")\n>>> sm.yellow.is_active\nTrue\n\n```\n\nEvents can also be called as methods — `sm.cycle()` is equivalent to\n`sm.send(\"cycle\")`:\n\n```py\n>>> sm.cycle()\n>>> sm.red.is_active\nTrue\n\n```\n\n**`raise_(event, **kwargs)`** places the event on the **internal queue**. Internal\nevents are processed within the current macrostep, before any pending external\nevents. This is useful inside callbacks when you need to trigger follow-up\ntransitions immediately:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class WithInternalEvent(StateChart):\n...     a = State(initial=True)\n...     b = State()\n...     c = State(final=True)\n...\n...     go = a.to(b)\n...     finish = b.to(c)\n...\n...     def on_enter_b(self):\n...         self.raise_(\"finish\")\n\n>>> sm = WithInternalEvent()\n>>> sm.send(\"go\")\n>>> sm.c.is_active\nTrue\n\n```\n\nBoth methods accept arbitrary keyword arguments that are forwarded to all\ncallbacks via {ref}`dependency injection <dependency-injection>`.\n\n```{seealso}\nSee {ref}`processing model` for the full macrostep/microstep lifecycle and how\ninternal and external queues interact.\n```\n\n(delayed-events)=\n\n### Delayed events\n\nEvents can be scheduled to fire after a delay (in milliseconds):\n\n```python\nsm.send(\"timeout\", delay=5000)\n```\n\nDelayed events can be cancelled before firing by providing a `send_id`:\n\n```python\nsm.send(\"timeout\", delay=5000, send_id=\"my_timeout\")\nsm.cancel_event(\"my_timeout\")\n```\n\n```{note}\nThe delay is **blocking** in the sync engine — the processing loop sleeps until the\ndelay elapses, holding the calling thread. In the async engine, delays are scheduled\nwith `asyncio` and do not block the event loop.\n```\n\n\n(querying-events)=\n\n## Querying events\n\nNot every event is relevant in every state. The instance provides two levels\nof event introspection:\n\n**`allowed_events`** — events that have at least one transition **from the\ncurrent configuration**, regardless of whether guards pass:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Turnstile(StateChart):\n...     locked = State(initial=True)\n...     unlocked = State()\n...\n...     coin = locked.to(unlocked)\n...     push = unlocked.to(locked)\n\n>>> sm = Turnstile()\n>>> [e.id for e in sm.allowed_events]\n['coin']\n\n```\n\n**`enabled_events(**kwargs)`** — a subset of `allowed_events` where at least one\ntransition's guards are **satisfied** given the provided arguments. Use this when\nguards depend on runtime data:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Gate(StateChart):\n...     closed = State(initial=True)\n...     open = State()\n...\n...     enter = closed.to(open, cond=\"has_badge\")\n...     close = open.to(closed)\n...\n...     def has_badge(self, badge: bool = False):\n...         return badge\n\n>>> sm = Gate()\n>>> [e.id for e in sm.allowed_events]\n['enter']\n\n>>> [e.id for e in sm.enabled_events()]\n[]\n\n>>> [e.id for e in sm.enabled_events(badge=True)]\n['enter']\n\n```\n\n`allowed_events` is cheap — it only checks the state topology. `enabled_events`\nevaluates guards, so pass the same keyword arguments you would pass to `send()`.\n\n\n(querying-configuration)=\n\n## Querying the configuration\n\nThe **configuration** is the set of currently active states. In a flat machine\nthis is a single state; with compound and parallel states, multiple states are\nactive simultaneously.\n\n**`configuration`** returns the active states as an `OrderedSet[State]`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Journey(StateChart):\n...     class shire(State.Compound):\n...         bag_end = State(initial=True)\n...         green_dragon = State()\n...         visit_pub = bag_end.to(green_dragon)\n...     road = State(final=True)\n...     depart = shire.to(road)\n\n>>> sm = Journey()\n>>> {s.id for s in sm.configuration} == {\"shire\", \"bag_end\"}\nTrue\n\n```\n\n**`configuration_values`** returns the values (or IDs when no custom `value` is\nset) instead of `State` objects — useful for serialization or quick checks:\n\n```py\n>>> set(sm.configuration_values) == {\"shire\", \"bag_end\"}\nTrue\n\n```\n\n\n(checking-termination)=\n\n## Checking termination\n\n**`is_terminated`** returns `True` when the machine has completed its work. In a\nflat machine this means a final state is active. With compound and parallel\nstates, the condition is structural — all parallel regions must have completed,\nnested compounds must have reached their final children, and so on:\n\n```py\n>>> sm.send(\"visit_pub\")\n>>> sm.is_terminated\nFalse\n\n>>> sm.send(\"depart\")\n>>> sm.is_terminated\nTrue\n\n```\n\nUse `is_terminated` instead of checking individual states — it handles\narbitrarily nested structures for you.\n\n**`final_states`** lists all top-level states marked as `final`:\n\n```py\n>>> sm.final_states\n[State('Road', id='road', value='road', initial=False, final=True, parallel=False)]\n\n```\n\n\n(runtime-listeners)=\n\n## Managing listeners at runtime\n\nClass-level listeners are declared on the class (see {ref}`listeners`). You can\nalso add listeners to a running instance:\n\n```py\n>>> class Logger:\n...     def after_transition(self, source: State, target: State, event: str):\n...         print(f\"[log] {source.id} →({event})→ {target.id}\")\n\n>>> sm = Turnstile()\n>>> _ = sm.add_listener(Logger())\n>>> sm.send(\"coin\")\n[log] locked →(coin)→ unlocked\n\n```\n\n`add_listener()` returns the instance for chaining. Use `active_listeners` to\ninspect all currently attached listeners.\n\n\n## Class-level attributes\n\nThese are set by the metaclass at class definition time and are available on both\nthe class and its instances:\n\n| Attribute | Type | Description |\n|---|---|---|\n| `name` | `str` | The class name (e.g., `\"TrafficLight\"`) |\n| `states` | `States` | Collection of all top-level states |\n| `final_states` | `list[State]` | Top-level states marked as `final` |\n| `events` | `list[Event]` | All events declared on the class |\n| `initial_state` | `State` | The top-level initial state |\n| `states_map` | `dict` | Mapping from state values to `State` objects (all nesting levels) |\n"
  },
  {
    "path": "docs/states.md",
    "content": "(states)=\n(state)=\n\n# States\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nA **state** represents a distinct mode or condition of the system at a given\npoint in time. States are the building blocks of a statechart — you define them\nas class attributes, and the library handles initialization, validation, and\nlifecycle management.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class TrafficLight(StateChart):\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n>>> sm = TrafficLight()\n>>> \"green\" in sm.configuration_values\nTrue\n\n```\n\n\n## State parameters\n\n| Parameter | Default | Description |\n|---|---|---|\n| `name` | `\"\"` | Human-readable display name. Defaults to the attribute name, capitalized. |\n| `value` | `None` | Custom value for this state, accessible via `configuration_values`. |\n| `initial` | `False` | Marks this as the initial state. Exactly one per machine (or per compound). |\n| `final` | `False` | Marks this as a final (accepting) state. No outgoing transitions allowed. |\n| `enter` | `None` | Callback(s) to run when entering this state. See {ref}`state-actions`. |\n| `exit` | `None` | Callback(s) to run when leaving this state. See {ref}`state-actions`. |\n| `invoke` | `None` | Background work spawned on entry, cancelled on exit. See {ref}`invoke-actions`. |\n\n```py\n>>> class CampaignMachine(StateChart):\n...     draft = State(\"Draft\", value=1, initial=True)\n...     producing = State(\"Being produced\", value=2)\n...     closed = State(\"Closed\", value=3, final=True)\n...\n...     produce = draft.to(producing)\n...     deliver = producing.to(closed)\n\n>>> sm = CampaignMachine()\n>>> sm.send(\"produce\")\n>>> list(sm.configuration_values)\n[2]\n\n```\n\n\n## Initial state\n\nA {ref}`StateChart` must have exactly one `initial` state. The initial state is\nentered when the machine starts, and the corresponding {ref}`enter actions\n<state-actions>` are called.\n\n\n(final-state)=\n\n## Final state\n\nA **final** state signals that the machine has completed its work. No outgoing\ntransitions are allowed from a final state.\n\n```py\n>>> sm = CampaignMachine()\n>>> sm.send(\"produce\")\n>>> sm.send(\"deliver\")\n>>> sm.is_terminated\nTrue\n\n```\n\nYou can query the list of all declared final states:\n\n```py\n>>> sm.final_states\n[State('Closed', id='closed', value=3, initial=False, final=True, parallel=False)]\n\n```\n\n```{seealso}\nSee {ref}`validations` for the checks the library performs at class definition\ntime — including final state reachability, unreachable states, and trap states.\n```\n\n\n(compound-states)=\n\n## Compound states\n\n```{versionadded} 3.0.0\n```\n\nCompound states contain inner child states, enabling hierarchical state machines.\nDefine them using the `State.Compound` inner class syntax:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class Journey(StateChart):\n...     class shire(State.Compound):\n...         bag_end = State(initial=True)\n...         green_dragon = State()\n...         visit_pub = bag_end.to(green_dragon)\n...     road = State(final=True)\n...     depart = shire.to(road)\n\n>>> sm = Journey()\n>>> set(sm.configuration_values) == {\"shire\", \"bag_end\"}\nTrue\n\n```\n\nEntering a compound activates both the parent and its `initial` child. You can query\nwhether a state is compound using the `is_compound` property.\n\n```{seealso}\nSee {ref}`done-state-events` for completion events when a compound state's\nfinal child is reached.\n```\n\n\n(parallel-states)=\n\n## Parallel states\n\n```{versionadded} 3.0.0\n```\n\nParallel states activate all child regions simultaneously. Each region operates\nindependently. Define them using `State.Parallel`:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class WarOfTheRing(StateChart):\n...     class war(State.Parallel):\n...         class quest(State.Compound):\n...             start = State(initial=True)\n...             end = State(final=True)\n...             go = start.to(end)\n...         class battle(State.Compound):\n...             fighting = State(initial=True)\n...             won = State(final=True)\n...             victory = fighting.to(won)\n\n>>> sm = WarOfTheRing()\n>>> \"start\" in sm.configuration_values and \"fighting\" in sm.configuration_values\nTrue\n\n```\n\n```{seealso}\nSee {ref}`done-state-events` for how `done.state` events work with parallel\nstates (all regions must reach a final state).\n```\n\n\n(history-states)=\n\n## History pseudo-states\n\n```{versionadded} 3.0.0\n```\n\nA history pseudo-state records the active child of a compound state when it is exited.\nRe-entering via the history state restores the previously active child. Import and use\n`HistoryState` inside a `State.Compound`:\n\n```py\n>>> from statemachine import HistoryState, State, StateChart\n\n>>> class WithHistory(StateChart):\n...     class mode(State.Compound):\n...         a = State(initial=True)\n...         b = State()\n...         h = HistoryState()\n...         switch = a.to(b)\n...     outside = State()\n...     leave = mode.to(outside)\n...     resume = outside.to(mode.h)\n\n>>> sm = WithHistory()\n>>> sm.send(\"switch\")\n>>> sm.send(\"leave\")\n>>> sm.send(\"resume\")\n>>> \"b\" in sm.configuration_values\nTrue\n\n```\n\nUse `HistoryState(type=\"deep\")` for deep history that remembers the exact leaf state\nin nested compounds.\n\n\n```{seealso}\nSee {ref}`querying-configuration` for how to inspect which states are currently\nactive at runtime.\n```\n\n\n(states from enum types)=\n\n## States from Enum types\n\n{ref}`States` can also be declared from standard `Enum` classes.\n\nFor this, use {ref}`States (class)` to convert your `Enum` type to a list of {ref}`State` objects.\n\n\n```{eval-rst}\n.. automethod:: statemachine.states.States.from_enum\n  :noindex:\n```\n\n```{seealso}\nSee the example {ref}`sphx_glr_auto_examples_enum_campaign_machine.py`.\n```\n"
  },
  {
    "path": "docs/timeout.md",
    "content": "(timeout)=\n# State timeouts\n\nA common need is preventing a state machine from getting stuck — for example,\na \"waiting for response\" state that should time out after a few seconds. The\n{func}`~statemachine.contrib.timeout.timeout` helper makes this easy by\nleveraging the {ref}`invoke <invoke>` system: a background timer starts when\nthe state is entered and is automatically cancelled when the state is exited.\n\n## Basic usage\n\nWhen the timeout expires and no custom event is specified, the standard\n`done.invoke.<state>` event fires — just like any other invoke completion:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.contrib.timeout import timeout\n\n>>> class WaitingMachine(StateChart):\n...     waiting = State(initial=True, invoke=timeout(5))\n...     done = State(final=True)\n...     done_invoke_waiting = waiting.to(done)\n\n>>> sm = WaitingMachine()\n>>> sm.waiting.is_active\nTrue\n\n```\n\nIn this example, if the machine stays in `waiting` for 5 seconds,\n`done.invoke.waiting` fires and the machine transitions to `done`.\nIf any other event causes a transition out of `waiting` first,\nthe timer is cancelled automatically.\n\n\n## Custom timeout event\n\nUse the `on` parameter to send a specific event name instead of\n`done.invoke.<state>`. This is useful when you want to distinguish\ntimeouts from normal completions:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.contrib.timeout import timeout\n\n>>> class RequestMachine(StateChart):\n...     requesting = State(initial=True, invoke=timeout(30, on=\"request_timeout\"))\n...     timed_out = State(final=True)\n...     request_timeout = requesting.to(timed_out)\n\n>>> sm = RequestMachine()\n>>> sm.requesting.is_active\nTrue\n\n```\n\n## Composing with other invoke handlers\n\nSince `timeout()` returns a standard invoke handler, you can combine it with\nother handlers in a list. The first handler to complete and trigger a transition\nwins — the state exit cancels everything else:\n\n```py\n>>> from statemachine import State, StateChart\n>>> from statemachine.contrib.timeout import timeout\n\n>>> def fetch_data():\n...     return {\"status\": \"ok\"}\n\n>>> class LoadingMachine(StateChart):\n...     loading = State(initial=True, invoke=[fetch_data, timeout(30, on=\"too_slow\")])\n...     ready = State(final=True)\n...     stuck = State(final=True)\n...     done_invoke_loading = loading.to(ready)\n...     too_slow = loading.to(stuck)\n\n>>> sm = LoadingMachine()\n>>> sm.ready.is_active\nTrue\n\n```\n\nIn this example:\n- If `fetch_data` completes within 30 seconds, `done.invoke.loading` fires\n  and transitions to `ready`, cancelling the timeout.\n- If 30 seconds pass first, `too_slow` fires and transitions to `stuck`,\n  cancelling the `fetch_data` invoke.\n\n\n## API reference\n\nSee {func}`~statemachine.contrib.timeout.timeout` in the {ref}`API docs <api>`.\n"
  },
  {
    "path": "docs/transitions.md",
    "content": "(transitions)=\n(transition)=\n\n# Transitions\n\n```{seealso}\nNew to statecharts? See [](concepts.md) for an overview of how states,\ntransitions, events, and actions fit together.\n```\n\nA transition describes a valid state change: it connects a **source** state to\na **target** state and is triggered by an {ref}`event <events>`. Transitions\ncan carry {ref}`actions` (side-effects) and {ref}`conditions <validators and guards>`\nthat control whether the transition fires.\n\n\n## Declaring transitions\n\nLink states using `source.to(target)` and assign the result to a class\nattribute — the attribute name becomes the event:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class OrderSM(StateChart):\n...     pending = State(initial=True)\n...     confirmed = State(final=True)\n...\n...     confirm = pending.to(confirmed)\n\n>>> sm = OrderSM()\n>>> sm.send(\"confirm\")\n>>> \"confirmed\" in sm.configuration_values\nTrue\n\n```\n\n\n### Transition parameters\n\n| Parameter | Description |\n|---|---|\n| `on` | Action callback(s) to run during the transition. See {ref}`transition-actions`. |\n| `before` | Callback(s) to run before exit/on/enter. |\n| `after` | Callback(s) to run after the transition completes. |\n| `cond` | Guard condition(s). See {ref}`validators and guards`. |\n| `unless` | Negative guard — transition fires when this returns `False`. |\n| `validators` | Validation callback(s) that raise on failure. |\n| `event` | Override the event for this transition. See {ref}`event-parameter`. |\n| `internal` | If `True`, no exit/enter actions fire. See {ref}`internal transition`. |\n\n\n### Combining transitions with `|`\n\nThe `|` operator merges transitions under a single event. Each transition\nis evaluated in declaration order — the first whose conditions are met wins:\n\n```py\n>>> class TrafficLight(StateChart):\n...     green = State(initial=True)\n...     yellow = State()\n...     red = State()\n...\n...     cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n>>> sm = TrafficLight()\n>>> sm.send(\"cycle\")\n>>> \"yellow\" in sm.configuration_values\nTrue\n\n```\n\nCombine `|` with guards to route the same event to different targets:\n\n```py\n>>> class OrderReview(StateChart):\n...     pending = State(initial=True)\n...     approved = State(final=True)\n...     rejected = State(final=True)\n...\n...     review = (\n...         pending.to(approved, cond=\"is_valid\")\n...         | pending.to(rejected)\n...     )\n...\n...     def is_valid(self, score: int = 0):\n...         return score >= 70\n\n>>> sm = OrderReview()\n>>> sm.send(\"review\", score=50)\n>>> \"rejected\" in sm.configuration_values\nTrue\n\n>>> sm = OrderReview()\n>>> sm.send(\"review\", score=85)\n>>> \"approved\" in sm.configuration_values\nTrue\n\n```\n\nThe first transition whose guard passes wins. When `score < 70`, `is_valid`\nreturns `False`, so the second transition (no guard — always matches) fires.\n\n\n### `from_()` and `from_.any()`\n\n`target.from_(source)` declares the same transition from the target's\nperspective — useful when multiple sources converge on one target:\n\n```py\n>>> class OrderSM(StateChart):\n...     pending = State(initial=True)\n...     processing = State()\n...     shipped = State(final=True)\n...\n...     process = pending.to(processing)\n...     ship = shipped.from_(pending, processing)\n\n```\n\n`target.from_.any()` creates a transition from **every non-final state** —\nuseful for global events like \"cancel\" that should be reachable from anywhere:\n\n```py\n>>> class OrderWorkflow(StateChart):\n...     pending = State(initial=True)\n...     processing = State()\n...     done = State()\n...     completed = State(final=True)\n...     cancelled = State(final=True)\n...\n...     process = pending.to(processing)\n...     complete = processing.to(done)\n...     finish = done.to(completed)\n...     cancel = cancelled.from_.any()\n\n>>> sm = OrderWorkflow()\n>>> sm.send(\"cancel\")\n>>> \"cancelled\" in sm.configuration_values\nTrue\n\n```\n\nWith {ref}`compound states <compound-states>`, there is another way to model\nthe same workflow: group the cancellable states under a compound parent, and\ndefine a single transition out of it. The `cancel` event exits the compound\nregardless of which child is active:\n\n```py\n>>> class OrderWorkflowCompound(StateChart):\n...     class active(State.Compound):\n...         pending = State(initial=True)\n...         processing = State()\n...         done = State(final=True)\n...\n...         process = pending.to(processing)\n...         complete = processing.to(done)\n...     completed = State(final=True)\n...     cancelled = State(final=True)\n...     done_state_active = active.to(completed)\n...     cancel = active.to(cancelled)\n\n>>> sm = OrderWorkflowCompound()\n>>> sm.send(\"process\")\n>>> sm.send(\"cancel\")\n>>> \"cancelled\" in sm.configuration_values\nTrue\n\n```\n\nCompare the diagrams — both model the same behavior, but the compound version\nmakes the \"cancellable\" grouping explicit in the hierarchy:\n\n```{statemachine-diagram} tests.machines.transition_from_any.OrderWorkflow\n:caption: from_.any()\n```\n\n```{statemachine-diagram} tests.machines.transition_from_any.OrderWorkflowCompound\n:caption: Compound\n:target:\n```\n\nThe compound approach scales better as you add more states — no need to\nremember to include each new state in a `from_()` list.\n\n\n(self-transition)=\n(self transition)=\n\n## Self-transitions and internal transitions\n\nA **self-transition** goes from a state back to itself. It exits and\nre-enters the state, running all exit and entry actions:\n\n```py\n>>> class RetryOrder(StateChart):\n...     processing = State(initial=True)\n...     done = State(final=True)\n...\n...     retry = processing.to.itself(on=\"do_retry\")\n...     finish = processing.to(done)\n...\n...     attempts: int = 0\n...\n...     def do_retry(self):\n...         self.attempts += 1\n\n>>> sm = RetryOrder()\n>>> sm.send(\"retry\")\n>>> sm.send(\"retry\")\n>>> sm.attempts\n2\n\n```\n\n(internal transition)=\n(internal-transition)=\n\nAn **internal transition** stays in the same state **without** running exit\nor entry actions — only the `on` callback executes. Use `internal=True`:\n\n```py\n>>> class OrderCart(StateChart):\n...     shopping = State(initial=True)\n...     checkout = State(final=True)\n...\n...     add_item = shopping.to.itself(internal=True, on=\"do_add_item\")\n...     pay = shopping.to(checkout)\n...\n...     total: float = 0\n...\n...     def do_add_item(self, price: float = 0):\n...         self.total += price\n\n>>> sm = OrderCart()\n>>> sm.send(\"add_item\", price=9.99)\n>>> sm.send(\"add_item\", price=4.50)\n>>> sm.total\n14.49\n\n```\n\nThe key difference: self-transitions fire exit/enter callbacks (useful when\nentering a state has side-effects like resetting a timer), while internal\ntransitions skip them (useful for pure data updates that shouldn't re-trigger\nentry logic).\n\n```{seealso}\nThe `enable_self_transition_entries` flag in {ref}`behaviour` controls whether\nself-transitions run exit/enter actions. `StateChart` defaults to `True` (SCXML\nsemantics); `StateMachine` defaults to `False` (legacy behavior).\n```\n\n\n(eventless)=\n\n## Eventless (automatic) transitions\n\n```{versionadded} 3.0.0\n```\n\nEventless transitions have no event trigger — they fire automatically when\ntheir guard condition evaluates to `True`. If no guard is specified, they\nfire immediately (unconditional). Declare them as bare statements, without\nassigning to a variable:\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class AutoEscalation(StateChart):\n...     normal = State(initial=True)\n...     escalated = State(final=True)\n...     normal.to(escalated, cond=\"should_escalate\")\n...     report = normal.to.itself(internal=True, on=\"add_report\")\n...     report_count = 0\n...     def should_escalate(self):\n...         return self.report_count >= 3\n...     def add_report(self):\n...         self.report_count += 1\n\n>>> sm = AutoEscalation()\n>>> sm.send(\"report\")\n>>> sm.send(\"report\")\n>>> \"normal\" in sm.configuration_values\nTrue\n\n>>> sm.send(\"report\")\n>>> \"escalated\" in sm.configuration_values\nTrue\n\n```\n\nThe eventless transition fires automatically after the third report pushes\n`report_count` past the threshold.\n\n```{seealso}\nSee {ref}`continuous-machines` for chains, compound interactions, and `In()`\nguards.\n```\n\n(cross-boundary-transitions)=\n\n## Cross-boundary transitions\n\n```{versionadded} 3.0.0\n```\n\nIn statecharts, transitions can cross compound state boundaries — going from a\nstate inside one compound to a state outside, or into a different compound. The\nengine automatically determines which states to exit and enter by computing the\n**transition domain**: the smallest compound ancestor that contains both the\nsource and all target states.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class OrderFulfillment(StateChart):\n...     class picking(State.Compound):\n...         locating = State(initial=True)\n...         packing = State()\n...         locate = locating.to(packing)\n...     class shipping(State.Compound):\n...         labeling = State(initial=True)\n...         dispatched = State(final=True)\n...         dispatch = labeling.to(dispatched)\n...     ship = picking.to(shipping)\n\n>>> sm = OrderFulfillment()\n>>> set(sm.configuration_values) == {\"picking\", \"locating\"}\nTrue\n\n>>> sm.send(\"ship\")\n>>> set(sm.configuration_values) == {\"shipping\", \"labeling\"}\nTrue\n\n```\n\nWhen `ship` fires, the engine:\n1. Computes the transition domain (the root, since `picking` and `shipping` are\n   siblings)\n2. Exits `locating` and `picking` (running their exit actions)\n3. Enters `shipping` and its initial child `labeling` (running their entry\n   actions)\n\n\n(transition-priority)=\n\n## Transition priority in compound states\n\n```{versionadded} 3.0.0\n```\n\nWhen an event could match transitions at multiple levels of the state hierarchy,\ntransitions from **descendant states take priority** over transitions from\nancestor states. This follows the SCXML specification: the most specific\n(deepest) matching transition wins.\n\n```py\n>>> from statemachine import State, StateChart\n\n>>> class OrderProcessing(StateChart):\n...     log = []\n...     class fulfillment(State.Compound):\n...         class picking(State.Compound):\n...             s1 = State(initial=True)\n...             s2 = State(final=True)\n...             go = s1.to(s2, on=\"log_picking\")\n...         assert isinstance(picking, State)\n...         packed = State(final=True)\n...         done_state_picking = picking.to(packed)\n...     shipped = State(final=True)\n...     done_state_fulfillment = fulfillment.to(shipped)\n...     def log_picking(self):\n...         self.log.append(\"picking handled it\")\n\n>>> sm = OrderProcessing()\n>>> sm.send(\"go\")\n>>> sm.log\n['picking handled it']\n\n```\n\nIf two transitions at the same level would exit overlapping states (a conflict),\nthe one declared first wins.\n"
  },
  {
    "path": "docs/tutorial.md",
    "content": "\n# Tutorial\n\nThis tutorial walks you through python-statemachine from your first flat state\nmachine all the way to full statecharts — compound states, parallel regions,\nhistory, and async. Each section builds on the previous one using the same\ndomain: **a coffee shop order system**.\n\nBy the end you will be comfortable defining states, transitions, guards,\nactions, and listeners, and you will see how the same declarative API scales\nfrom a five-state FSM to a production-grade statechart — no new concepts\nrequired.\n\n\n## Your first state machine\n\nA coffee order goes through a few stages: the customer places it, the barista\nprepares it, and the customer picks it up.\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     # Define the states\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State()\n...     picked_up = State(final=True)\n...\n...     # Define events — each one groups one or more transitions\n...     start = pending.to(preparing)\n...     finish = preparing.to(ready)\n...     pick_up = ready.to(picked_up)\n\n```\n\nThat's it — states are class attributes, transitions are built with\n`state.to(target)`, and events are the names you assign them to.\n\nCreate an instance and start sending events:\n\n```py\n>>> order = CoffeeOrder()\n>>> order.pending.is_active\nTrue\n\n>>> order.send(\"start\")\n>>> order.preparing.is_active\nTrue\n\n>>> order.send(\"finish\")\n>>> order.send(\"pick_up\")\n>>> order.picked_up.is_active\nTrue\n\n```\n\nYou can also call events as methods — `order.start()` is equivalent to\n`order.send(\"start\")`:\n\n```py\n>>> order = CoffeeOrder()\n>>> order.start()\n>>> order.preparing.is_active\nTrue\n\n```\n\n```{tip}\nUse `sm.send(\"event_name\")` when the event name is dynamic (e.g., comes from\nuser input or a message queue). Use `sm.event_name()` when writing\napplication code where the event is known at development time.\n```\n\n\n## Adding behavior with actions\n\nA state machine without side effects is just a diagram. Actions let you\nattach behavior to state entries, exits, and transitions.\n\nDefine actions by naming convention — the library discovers them\nautomatically:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State()\n...     picked_up = State(final=True)\n...\n...     start = pending.to(preparing)\n...     finish = preparing.to(ready)\n...     pick_up = ready.to(picked_up)\n...\n...     # Called when entering the \"preparing\" state\n...     def on_enter_preparing(self):\n...         print(\"Barista starts making the drink.\")\n...\n...     # Called when the \"finish\" event fires\n...     def on_finish(self):\n...         print(\"Drink is ready!\")\n...\n...     # Called when entering the \"picked_up\" state\n...     def on_enter_picked_up(self):\n...         print(\"Customer picked up the order. Enjoy!\")\n\n>>> order = CoffeeOrder()\n>>> order.send(\"start\")\nBarista starts making the drink.\n\n>>> order.send(\"finish\")\nDrink is ready!\n\n>>> order.send(\"pick_up\")\nCustomer picked up the order. Enjoy!\n\n```\n\nThe naming conventions are:\n\n| Pattern                   | When it runs                          |\n|---------------------------|---------------------------------------|\n| `on_enter_<state>()`      | Every time `<state>` is entered       |\n| `on_exit_<state>()`       | Every time `<state>` is exited        |\n| `before_<event>()`        | Before any transition for `<event>`   |\n| `on_<event>()`            | During the transition for `<event>`   |\n| `after_<event>()`         | After the transition for `<event>`    |\n\n```{seealso}\nThe full list of action callbacks and their execution order is in\n[](actions.md).\n```\n\n\n### Dependency injection in callbacks\n\nCallbacks don't need to accept a fixed signature. Declare only the\nparameters you need, and the library injects them automatically:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State()\n...     picked_up = State(final=True)\n...\n...     start = pending.to(preparing)\n...     finish = preparing.to(ready)\n...     pick_up = ready.to(picked_up)\n...\n...     def on_enter_preparing(self, source: State, target: State):\n...         print(f\"{source.id} → {target.id}\")\n...\n...     def on_finish(self):\n...         print(\"Done!\")\n\n>>> order = CoffeeOrder()\n>>> order.send(\"start\")\npending → preparing\n\n>>> order.send(\"finish\")\nDone!\n\n```\n\n`on_enter_preparing` asks for `source` and `target` — it gets them.\n`on_finish` asks for nothing extra — that's fine too.\n\nAvailable parameters include `event`, `source`, `target`, `state`, and any\nkeyword arguments you pass to `send()`:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State(final=True)\n...\n...     start = pending.to(preparing)\n...     finish = preparing.to(ready)\n...\n...     def on_start(self, drink: str = \"coffee\"):\n...         print(f\"Making a {drink}.\")\n\n>>> order = CoffeeOrder()\n>>> order.send(\"start\", drink=\"cappuccino\")\nMaking a cappuccino.\n\n```\n\n\n## Guards: conditional transitions\n\nNot every transition should always be allowed. Guards are conditions that\nmust be satisfied for a transition to fire.\n\nA coffee order shouldn't move to `preparing` unless it has been paid for:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State()\n...     picked_up = State(final=True)\n...\n...     # Two transitions on the same event — checked in declaration order.\n...     # The first whose guard passes wins.\n...     start = (\n...         pending.to(preparing, cond=\"is_paid\")\n...         | pending.to(pending)  # fallback: stay in pending\n...     )\n...     finish = preparing.to(ready)\n...     pick_up = ready.to(picked_up)\n...\n...     paid: bool = False\n...\n...     def is_paid(self):\n...         return self.paid\n\n>>> order = CoffeeOrder()\n\n>>> order.send(\"start\")  # not paid — stays in pending\n>>> order.pending.is_active\nTrue\n\n>>> order.paid = True\n>>> order.send(\"start\")  # paid — moves to preparing\n>>> order.preparing.is_active\nTrue\n\n```\n\nGuards receive the same dependency injection as actions — you can\naccept `event`, `source`, `target`, and any extra keyword arguments:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State(final=True)\n...\n...     start = (\n...         pending.to(preparing, cond=\"is_paid\")\n...         | pending.to(pending)\n...     )\n...\n...     def is_paid(self, amount: float = 0):\n...         return amount >= 5.0\n\n>>> order = CoffeeOrder()\n\n>>> order.send(\"start\", amount=3.0)\n>>> order.pending.is_active\nTrue\n\n>>> order.send(\"start\", amount=5.0)\n>>> order.preparing.is_active\nTrue\n\n```\n\n```{seealso}\nSee [](guards.md) for `unless=`, validators, boolean expressions\nin condition strings, and evaluation order details.\n```\n\n\n## Observing from outside with listeners\n\nListeners let external objects react to state changes without touching the\nstate machine definition. Any object with methods matching the callback\nnaming conventions works as a listener.\n\nThe preferred way is to declare listeners at the class level — they are\nautomatically attached to every instance:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class NotificationService:\n...     def on_enter_state(self, target: State):\n...         print(f\"[notify] Order is now: {target.id}\")\n\n>>> class CoffeeOrder(StateChart):\n...     listeners = [NotificationService]\n...\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State(final=True)\n...\n...     start = pending.to(preparing)\n...     finish = preparing.to(ready)\n\n>>> order = CoffeeOrder()\n[notify] Order is now: pending\n\n>>> order.send(\"start\")\n[notify] Order is now: preparing\n\n>>> order.send(\"finish\")\n[notify] Order is now: ready\n\n```\n\nWhen the `listeners` list contains a **class** (like `NotificationService`\nabove), it acts as a factory — a fresh instance is created for each state\nmachine. Pass an already-built **instance** instead if you want a shared,\nstateless listener (e.g., a global logger).\n\nYou can also add listeners at runtime, either via the constructor or on an\nalready running machine:\n\n```py\n>>> class AuditLog:\n...     def after_transition(self, source: State, target: State, event: str):\n...         print(f\"[audit] {source.id} →({event})→ {target.id}\")\n\n>>> order = CoffeeOrder()\n[notify] Order is now: pending\n\n>>> _ = order.add_listener(AuditLog())\n\n>>> order.send(\"start\")\n[notify] Order is now: preparing\n[audit] pending →(start)→ preparing\n\n```\n\nThe machine knows nothing about the listener, and the listener knows\nnothing about the machine's internals — only the callback protocol.\n\n```{seealso}\nSee [](listeners.md) for class-level listener configuration, `functools.partial`\nfactories, and the full list of listener callbacks.\n```\n\n\n## Generating diagrams\n\nVisualize any state machine as a diagram:\n\n```{statemachine-diagram} tests.machines.tutorial_coffee_order.CoffeeOrder\n:alt: CoffeeOrder diagram\n```\n\nGenerate diagrams programmatically with `_graph()`:\n\n```python\norder = CoffeeOrder()\norder._graph().write_png(\"order.png\")\n```\n\nOr from the command line:\n\n```bash\npython -m statemachine.contrib.diagram my_module.CoffeeOrder order.png\n```\n\n### Text representations with `format()`\n\nYou can also get text representations of any state machine using Python's built-in\n`format()` or f-strings — no Graphviz needed:\n\n```py\n>>> from tests.machines.tutorial_coffee_order import CoffeeOrder\n\n>>> print(f\"{CoffeeOrder:md}\")\n| State     | Event   | Guard | Target    |\n| --------- | ------- | ----- | --------- |\n| Pending   | Start   |       | Preparing |\n| Preparing | Finish  |       | Ready     |\n| Ready     | Pick up |       | Picked up |\n\n```\n\nSupported formats include `mermaid`, `md` (markdown table), `rst`, `dot`, and `svg`.\nWorks on both classes and instances:\n\n```py\n>>> print(f\"{CoffeeOrder:mermaid}\")\nstateDiagram-v2\n    direction LR\n    state \"Pending\" as pending\n    state \"Preparing\" as preparing\n    state \"Ready\" as ready\n    state \"Picked up\" as picked_up\n    [*] --> pending\n    picked_up --> [*]\n    pending --> preparing : Start\n    preparing --> ready : Finish\n    ready --> picked_up : Pick up\n<BLANKLINE>\n\n```\n\n```{tip}\nGraphviz diagram generation requires [Graphviz](https://graphviz.org/) (`dot` command)\nand the `diagrams` extra:\n\n    pip install python-statemachine[diagrams]\n\nText formats (`md`, `rst`, `mermaid`) work without any extra dependencies.\n```\n\n```{seealso}\nSee [](diagram.md) for all formats, highlighting active states, auto-expanding\ndocstrings, Jupyter integration, Sphinx directive, and the `quickchart_write_svg`\nalternative that doesn't require Graphviz.\n```\n\n\n## Scaling up with statecharts\n\nSo far our coffee order has been a flat sequence of states. Real systems\nare rarely that simple — what happens when preparing a drink involves\nmultiple steps? What if the order includes both a drink *and* a snack\nprepared in parallel?\n\nThis is where python-statemachine shines: you scale from a flat FSM to a\nfull statechart using the **exact same API**. No new base class, no\nconfiguration flags — just nest your states.\n\n\n### Compound states: breaking complexity into levels\n\nPreparing a drink isn't a single step. Let's model it as a compound state\nwith sub-steps — grinding, brewing, and serving:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...\n...     class preparing(State.Compound):\n...         \"\"\"Drink preparation with internal steps.\"\"\"\n...         grinding = State(initial=True)\n...         brewing = State()\n...         serving = State(final=True)\n...\n...         grind = grinding.to(brewing)\n...         brew = brewing.to(serving)\n...\n...     picked_up = State(final=True)\n...\n...     start = pending.to(preparing)\n...     done_state_preparing = preparing.to(picked_up)\n\n>>> order = CoffeeOrder()\n>>> order.send(\"start\")\n>>> set(order.configuration_values) == {\"preparing\", \"grinding\"}\nTrue\n\n>>> order.send(\"grind\")\n>>> \"brewing\" in order.configuration_values\nTrue\n\n>>> order.send(\"brew\")\n>>> order.picked_up.is_active\nTrue\n\n```\n\nEntering `preparing` activates both the compound parent and its initial\nchild (`grinding`). When `serving` — a final child — is reached,\n`done.state.preparing` fires automatically and transitions to `picked_up`.\n\nNotice how nothing changed about the outer API. You still `send(\"start\")`\nto begin — the compound structure is an internal detail.\n\n\n### Parallel states: concurrent regions\n\nNow let's say the order includes both a drink and a snack, prepared at the\nsame time by different stations:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...\n...     class preparing(State.Parallel):\n...         class drink(State.Compound):\n...             brewing = State(initial=True)\n...             drink_done = State(final=True)\n...             brew = brewing.to(drink_done)\n...         class snack(State.Compound):\n...             heating = State(initial=True)\n...             snack_done = State(final=True)\n...             heat = heating.to(snack_done)\n...\n...     picked_up = State(final=True)\n...\n...     start = pending.to(preparing)\n...     done_state_preparing = preparing.to(picked_up)\n\n>>> order = CoffeeOrder()\n>>> order.send(\"start\")\n>>> \"brewing\" in order.configuration_values and \"heating\" in order.configuration_values\nTrue\n\n>>> order.send(\"brew\")  # drink done, snack still heating\n>>> \"drink_done\" in order.configuration_values and \"heating\" in order.configuration_values\nTrue\n\n>>> order.is_terminated  # drink region finished, but snack hasn't\nFalse\n\n>>> order.send(\"heat\")  # both done — auto-transitions to picked_up\n>>> order.picked_up.is_active\nTrue\n\n>>> order.is_terminated\nTrue\n\n```\n\n`State.Parallel` activates all child regions at once. Each region\nprocesses events independently. The machine only transitions out when\n**every** region reaches a final state.\n\n\n\n### Checking completion with `is_terminated`\n\nIn a flat state machine, checking whether you've reached a specific\nfinal state is enough. But with compound and parallel states, completion\ndepends on the structure — all regions of a parallel must finish, nested\ncompounds must reach their own final children, and so on. The\n`is_terminated` property handles this for you: it returns `True` only\nwhen the entire machine has completed its work, regardless of how deeply\nnested the structure is. Use it instead of checking individual states.\n\nA common pattern is to consume events from a queue or stream, feeding\nthem to the machine until it terminates:\n\n```py\n>>> from collections import deque\n\n>>> order = CoffeeOrder()\n>>> queue = deque([\"start\", \"brew\", \"heat\"])\n\n>>> while not order.is_terminated and queue:\n...     order.send(queue.popleft())\n\n>>> order.is_terminated\nTrue\n\n```\n\nThis decouples event production from consumption — the queue could come\nfrom a message broker, a file, user input, or any other source.\n\n\n### History states: remember where you left off\n\nWhat if the barista needs to pause preparation (e.g., to handle a rush)\nand resume later? A history state remembers which child was active when a\ncompound was exited:\n\n```py\n>>> from statemachine import HistoryState, StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     class preparing(State.Compound):\n...         grinding = State(initial=True)\n...         brewing = State()\n...         done = State(final=True)\n...         h = HistoryState()\n...\n...         grind = grinding.to(brewing)\n...         brew = brewing.to(done)\n...\n...     paused = State()\n...     finished = State(final=True)\n...\n...     pause = preparing.to(paused)\n...     resume = paused.to(preparing.h)  # ← return via history\n...     done_state_preparing = preparing.to(finished)\n\n>>> order = CoffeeOrder()\n>>> order.send(\"grind\")     # now in \"brewing\"\n>>> \"brewing\" in order.configuration_values\nTrue\n\n>>> order.send(\"pause\")     # leave preparing\n>>> order.send(\"resume\")    # history restores \"brewing\", not \"grinding\"\n>>> \"brewing\" in order.configuration_values\nTrue\n\n>>> order.send(\"brew\")      # finish preparation\n>>> order.finished.is_active\nTrue\n\n```\n\nUse `HistoryState(type=\"deep\")` for deep history that remembers the exact\nleaf state across nested compounds.\n\n\n### Eventless transitions: react automatically\n\nEventless transitions fire without an explicit event — they trigger\nautomatically when their guard condition is met:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State()\n...     picked_up = State(final=True)\n...\n...     # Eventless: fires automatically when the guard is satisfied\n...     pending.to(preparing, cond=\"is_paid\")\n...     ready.to(picked_up, cond=\"was_picked_up\")\n...\n...     finish = preparing.to(ready)\n...\n...     # A no-op event to re-enter the processing loop\n...     check = (\n...         pending.to.itself(internal=True)\n...         | ready.to.itself(internal=True)\n...     )\n...\n...     paid: bool = False\n...     picked: bool = False\n...\n...     def is_paid(self):\n...         return self.paid\n...     def was_picked_up(self):\n...         return self.picked\n\n>>> order = CoffeeOrder()\n>>> order.paid = True\n>>> order.send(\"check\")  # triggers the eventless transition\n>>> order.preparing.is_active\nTrue\n\n>>> order.send(\"finish\")\n>>> order.picked = True\n>>> order.send(\"check\")\n>>> order.picked_up.is_active\nTrue\n\n```\n\nEventless transitions are evaluated after every macrostep. Combined with\nguards, they let the machine react to changes in its own data without\nrequiring the outside world to name every event.\n\n\n### Error handling as events\n\nWith `StateChart`, runtime exceptions in callbacks don't crash the\nmachine — they become `error.execution` events that you can handle with\nregular transitions:\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     preparing = State(initial=True)\n...     out_of_stock = State(final=True)\n...\n...     make_drink = preparing.to(preparing, on=\"do_make_drink\")\n...     error_execution = preparing.to(out_of_stock)\n...\n...     def do_make_drink(self):\n...         raise RuntimeError(\"Out of oat milk!\")\n...\n...     def on_enter_out_of_stock(self, error=None):\n...         if error:\n...             print(f\"Problem: {error}\")\n\n>>> order = CoffeeOrder()\n>>> order.send(\"make_drink\")\nProblem: Out of oat milk!\n>>> order.out_of_stock.is_active\nTrue\n\n```\n\nThe exception is caught, dispatched as an internal `error.execution`\nevent, and handled by the `error_execution` transition — no try/except\nneeded in your application code.\n\n```{seealso}\nSee [](error_handling.md) for the full `error.execution` lifecycle,\nblock-level error catching, and the cleanup/finalize pattern.\n```\n\n\n### Async: same API, no changes needed\n\nEvery example above works with async callbacks too. Just use `async def`\nand the engine switches automatically:\n\n```py\n>>> import asyncio\n>>> from statemachine import StateChart, State\n\n>>> class CoffeeOrder(StateChart):\n...     pending = State(initial=True)\n...     preparing = State()\n...     ready = State(final=True)\n...\n...     start = pending.to(preparing)\n...     finish = preparing.to(ready)\n...\n...     async def on_start(self, drink: str = \"coffee\"):\n...         return f\"Started making {drink}\"\n...\n...     async def on_finish(self):\n...         return \"Drink is ready!\"\n\n>>> async def main():\n...     order = CoffeeOrder()\n...     result = await order.send(\"start\", drink=\"latte\")\n...     print(result)\n...     result = await order.send(\"finish\")\n...     print(result)\n\n>>> asyncio.run(main())\nStarted making latte\nDrink is ready!\n\n```\n\nNo special async base class. No configuration. The same `StateChart`\nclass, the same `send()` method, the same naming conventions — just\n`async def` and `await`.\n\n```{seealso}\nSee [](async.md) for the sync vs. async engine selection table,\ninitial state activation in async contexts, and concurrent event sending.\n```\n\n\n## Next steps\n\nYou now have a solid foundation. Here are the most useful pages to\nexplore next:\n\n- **[States](states.md)** — final states, compound states, parallel states, history, `DoneData`\n- **[Transitions](transitions.md)** — self-transitions, internal transitions, cross-boundary, delayed events\n- **[Actions](actions.md)** — the full callback execution order, `prepare_event()`\n- **[Guards](guards.md)** — `unless=`, validators, boolean expressions, `In()` for cross-region checks\n- **[Listeners](listeners.md)** — the observer pattern in depth\n- **[Error handling](error_handling.md)** — `error.execution` events, block-level catching, cleanup patterns\n- **[Processing model](processing_model.md)** — `send()` vs `raise_()`, microstep/macrostep, run-to-completion\n- **[Behaviour](behaviour.md)** — `StateChart` vs `StateMachine`, behavioral flags, and migration guide\n- **[Django integration](integrations.md)** — auto-discovery, `MachineMixin` with Django models\n- **[Diagrams](diagram.md)** — CLI generation, Jupyter, SVG, DPI settings\n- **[API reference](api.md)** — full class and method reference\n"
  },
  {
    "path": "docs/validations.md",
    "content": "(validations)=\n\n# Validations\n\nThe library validates your statechart structure at two stages: **class\ndefinition time** (when the Python class body is evaluated) and **instance\ncreation time** (when you call the constructor). These checks catch common\nmistakes early — before any event is ever processed.\n\nAll validation errors raise `InvalidDefinition`.\n\n```py\n>>> from statemachine import StateChart, State\n>>> from statemachine.exceptions import InvalidDefinition\n\n```\n\n\n## Class definition validations\n\nThese checks run as soon as the class body is evaluated by the\n`StateMachineMetaclass`. If any check fails, the class itself is not\ncreated.\n\n\n### Exactly one initial state\n\nEvery statechart must have exactly one `initial` state at the root level:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         a = State(initial=True)\n...         b = State(initial=True)\n...         go = a.to(b)\n... except InvalidDefinition as e:\n...     print(e)\nThere should be one and only one initial state. Your currently have these: a, b\n\n```\n\n### No transitions from final states\n\nFinal states represent completion — outgoing transitions are not allowed:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         draft = State(initial=True)\n...         closed = State(final=True)\n...         reopen = closed.to(draft)\n...         close = draft.to(closed)\n... except InvalidDefinition as e:\n...     print(e)\nCannot declare transitions from final state. Invalid state(s): ['closed']\n\n```\n\n(unreachable-states)=\n\n### Unreachable states\n\nEvery state must be reachable from the initial state. Isolated states\nindicate a wiring mistake:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         red = State(initial=True)\n...         green = State()\n...         hazard = State()\n...         cycle = red.to(green) | green.to(red)\n...         blink = hazard.to.itself()\n... except InvalidDefinition as e:\n...     print(e)\nThere are unreachable states. The statemachine graph should have a single component. Disconnected states: ['hazard']\n\n```\n\nDisable with `validate_disconnected_states = False`.\n\n\n(trap-states)=\n\n### Trap states\n\nEvery non-final state must have at least one outgoing transition.\nA state with no way out is a \"trap\" — likely a forgotten transition:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         red = State(initial=True)\n...         green = State()\n...         hazard = State()\n...         cycle = red.to(green) | green.to(red)\n...         fault = red.to(hazard) | green.to(hazard)\n... except InvalidDefinition as e:\n...     print(e)\nAll non-final states should have at least one outgoing transition. These states have no outgoing transition: ['hazard']\n\n```\n\nDisable with `validate_trap_states = False`:\n\n```py\n>>> class Accepted(StateChart):\n...     validate_trap_states = False\n...     red = State(initial=True)\n...     green = State()\n...     hazard = State()\n...     cycle = red.to(green) | green.to(red)\n...     fault = red.to(hazard) | green.to(hazard)\n\n```\n\n\n### Final state reachability\n\nWhen final states exist, every non-final state must have at least one path\nto a final state:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         draft = State(initial=True)\n...         abandoned = State()\n...         closed = State(final=True)\n...         produce = draft.to(abandoned) | abandoned.to(abandoned)\n...         close = draft.to(closed)\n... except InvalidDefinition as e:\n...     print(e)\nAll non-final states should have at least one path to a final state. These states have no path to a final state: ['abandoned']\n\n```\n\nDisable with `validate_final_reachability = False`.\n\n\n### Internal transition targets\n\nInternal transitions must target the same state (self) or a descendant —\nthey cannot cross to external states:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         a = State(initial=True)\n...         b = State(final=True)\n...         go = a.to(b, internal=True)\n... except InvalidDefinition as e:\n...     assert \"Not a valid internal transition\" in str(e)\n\n```\n\n### Initial transitions have no conditions\n\nInitial transitions (automatically generated for the initial state) cannot\ncarry conditions or events — they always fire unconditionally.\n\n\n### `donedata` on final states only\n\nThe `donedata` parameter can only be used on states marked as `final=True`:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         a = State(initial=True, donedata=\"get_data\")\n...         b = State(final=True)\n...         go = a.to(b)\n... except InvalidDefinition as e:\n...     print(e)\n'donedata' can only be specified on final states.\n\n```\n\n\n### Invalid listener entries\n\nEntries in the `listeners` class attribute must be classes, callables, or\nobject instances — not primitives like strings or numbers:\n\n```py\n>>> try:\n...     class Bad(StateChart):\n...         listeners = [\"not_a_listener\"]\n...         a = State(initial=True)\n...         b = State(final=True)\n...         go = a.to(b)\n... except InvalidDefinition as e:\n...     assert \"Invalid entry in 'listeners'\" in str(e)\n\n```\n\n\n## Instance creation validations\n\nThese checks run when you instantiate a statechart (call `MyChart()`).\nThey verify that the runtime wiring is correct — callbacks resolve to\nactual methods, boolean expressions parse, etc.\n\n\n### Callback resolution\n\nEvery callback name declared on a transition or state (via `on`, `before`,\n`after`, `enter`, `exit`, `cond`, etc.) must resolve to an actual attribute\non the statechart, model, or one of the registered listeners.\n\n```py\n>>> class MyChart(StateChart):\n...     a = State(initial=True)\n...     b = State(final=True)\n...     go = a.to(b, on=\"nonexistent_method\")\n\n>>> try:\n...     MyChart()\n... except InvalidDefinition as e:\n...     assert \"Did not found name 'nonexistent_method'\" in str(e)\n\n```\n\nThis validation ensures there are no typos in callback names. It checks all\nsources in order: the statechart class itself, then the model (if\nprovided), then each listener.\n\n```{note}\nConvention-based callbacks (like `on_enter_<state>` or `before_<event>`)\nare **not** validated — they are optional by design. Only explicitly\ndeclared callback names (passed as strings to `on`, `cond`, etc.) are\nchecked.\n```\n\n\n### Boolean expression parsing\n\nGuard conditions written as boolean expressions must be syntactically valid:\n\n```py\n>>> try:\n...     class MyChart(StateChart):\n...         a = State(initial=True)\n...         b = State(final=True)\n...         go = a.to(b, cond=\"valid_a and valid_b\")\n...         def valid_a(self):\n...             return True\n...         def valid_b(self):\n...             return True\n...     sm = MyChart()\n...     sm.send(\"go\")\n... except InvalidDefinition:\n...     pass  # would fail if expression didn't parse\n\n>>> \"b\" in sm.configuration_values\nTrue\n\n```\n\nExpressions support `and`, `or`, `not`, and parentheses. See\n{ref}`guards` for the full syntax.\n\n\n## Summary\n\n| Validation                        | When            | Configurable               |\n|-----------------------------------|-----------------|----------------------------|\n| Exactly one initial state         | Class definition| No                         |\n| No transitions from final states  | Class definition| No                         |\n| Unreachable states                | Class definition| `validate_disconnected_states` |\n| Trap states                       | Class definition| `validate_trap_states`     |\n| Final state reachability          | Class definition| `validate_final_reachability` |\n| Internal transition targets       | Class definition| No                         |\n| Initial transitions have no cond  | Class definition| No                         |\n| `donedata` on final states only   | Class definition| No                         |\n| Invalid listener entries          | Class definition| No                         |\n| Callback resolution               | Instance creation | No                       |\n| Boolean expression parsing        | Instance creation | No                       |\n\nAll configurable flags default to `True`. Set them to `False` on the class\nto disable the corresponding check.\n"
  },
  {
    "path": "docs/weighted_transitions.md",
    "content": "(weighted-transitions)=\n\n# Weighted transitions\n\n```{seealso}\nSee {ref}`conditions` for how the engine selects transitions, and\n{ref}`actions` for callbacks that run during transitions.\n```\n\nThe `weighted_transitions` utility lets you define **probabilistic\ntransitions** — where each transition from a state has a relative weight\nthat determines how likely it is to be selected when the event fires.\n\nThis is a contrib module that works entirely through the existing\n{ref}`conditions` system. No engine modifications are needed.\n\n\n## Basic usage\n\nImport `weighted_transitions` and pass a **source state** followed by\n`(target, weight)` tuples. The result is a regular {ref}`TransitionList`\nthat you assign to a class attribute as an event:\n\n```py\n>>> from statemachine.contrib.weighted import to, weighted_transitions\n\n>>> class GameCharacter(StateChart):\n...     standing = State(initial=True)\n...     shift_weight = State()\n...     adjust_hair = State()\n...     bang_shield = State()\n...\n...     idle = weighted_transitions(\n...         standing,\n...         (shift_weight, 70),\n...         (adjust_hair, 20),\n...         (bang_shield, 10),\n...         seed=42,\n...     )\n...\n...     finish = (\n...         shift_weight.to(standing)\n...         | adjust_hair.to(standing)\n...         | bang_shield.to(standing)\n...     )\n\n>>> sm = GameCharacter()\n>>> sm.send(\"idle\")\n>>> any(\n...     s in sm.configuration_values\n...     for s in (\"shift_weight\", \"adjust_hair\", \"bang_shield\")\n... )\nTrue\n\n```\n\nWhen `idle` fires, the engine randomly selects one of the three transitions based on\ntheir relative weights: 70% chance for `shift_weight`, 20% for `adjust_hair`,\n10% for `bang_shield`.\n\n## Weights\n\nWeights can be any **positive number** — integers, floats, or a mix of both. They are\nrelative, not absolute percentages:\n\n```python\n# These are equivalent (same 70/20/10 ratio):\nidle = weighted_transitions(\n    standing,\n    (shift_weight, 70),\n    (adjust_hair, 20),\n    (bang_shield, 10),\n)\n\nidle = weighted_transitions(\n    standing,\n    (shift_weight, 7),\n    (adjust_hair, 2),\n    (bang_shield, 1),\n)\n\nidle = weighted_transitions(\n    standing,\n    (shift_weight, 0.7),\n    (adjust_hair, 0.2),\n    (bang_shield, 0.1),\n)\n```\n\nThe tuple format `(target, weight)` follows the standard Python pattern used by\n{py:func}`random.choices`.\n\n## Reproducibility with `seed`\n\nPass a `seed` parameter for deterministic, reproducible sequences — useful for testing:\n\n```python\ngo = weighted_transitions(\n    s1,\n    (s2, 50),\n    (s3, 50),\n    seed=42,  # same seed always produces the same sequence\n)\n```\n\n```{note}\nThe seed initializes a per-group `random.Random` instance that is shared across all\ninstances of the same state machine class. This means the sequence is deterministic\nfor a given program execution, but different instances advance the same RNG.\n```\n\n## Per-transition options\n\nUse the {func}`~statemachine.contrib.weighted.to` helper to pass transition keyword\narguments (``cond``, ``unless``, ``before``, ``on``, ``after``, …) as natural kwargs.\nFor simple destinations without extra options, a plain ``(target, weight)`` tuple is\nenough — ``to()`` is only needed when you want to customize the transition:\n\n```py\n>>> class GuardedWeighted(StateChart):\n...     idle = State(initial=True)\n...     walk = State()\n...     run = State()\n...\n...     move = weighted_transitions(\n...         idle,\n...         (walk, 70),\n...         to(run, 30, cond=\"has_energy\"),\n...     )\n...     stop = walk.to(idle) | run.to(idle)\n...\n...     has_energy = True\n\n>>> sm = GuardedWeighted()\n\n```\n\n```{important}\n**No fallback when a guard fails.** If the weighted selection picks a transition whose\nguard evaluates to ``False``, the event fails — the engine does **not** silently fall back\nto another transition. This preserves the probability semantics: a 70/30 split means\nexactly that, not \"70/30 unless the 30% is blocked, in which case always 100% for\nthe other\".\n\nThis behavior follows {ref}`conditions` evaluation: the first transition whose **all**\nconditions pass is executed.\n```\n\n## Combining with callbacks\n\nAll standard {ref}`actions` work with weighted events — `before`, `on`, `after` callbacks\nand naming conventions like `on_<event>()`:\n\n```python\nclass WithCallbacks(StateChart):\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State()\n\n    go = weighted_transitions(s1, (s2, 60), (s3, 40))\n    back = s2.to(s1) | s3.to(s1)\n\n    def on_go(self):\n        print(\"go event fired!\")\n\n    def after_go(self):\n        print(\"after go!\")\n```\n\n## Multiple independent groups\n\nEach call to `weighted_transitions()` creates an independent weighted group with its\nown RNG. You can have multiple weighted events on the same state machine:\n\n```python\nclass MultiGroup(StateChart):\n    idle = State(initial=True)\n    walk = State()\n    run = State()\n    wave = State()\n    bow = State()\n\n    move = weighted_transitions(idle, (walk, 70), (run, 30), seed=1)\n    greet = weighted_transitions(idle, (wave, 80), (bow, 20), seed=2)\n    back = walk.to(idle) | run.to(idle) | wave.to(idle) | bow.to(idle)\n```\n\nThe `move` and `greet` events use separate RNGs and don't interfere with each other.\n\n## Validation\n\n`weighted_transitions()` validates inputs at class definition time:\n\n- The first argument must be a `State` (the source).\n- Each destination must be a `(target_state, weight)` or\n  `(target_state, weight, kwargs_dict)` tuple.\n- Weights must be positive numbers (`int` or `float`).\n- At least one destination is required.\n\n```py\n>>> weighted_transitions(State(initial=True))\nTraceback (most recent call last):\n    ...\nValueError: weighted_transitions() requires at least one (target, weight) destination\n\n>>> s1, s2 = State(initial=True), State()\n>>> weighted_transitions(s1, (s2, -5))\nTraceback (most recent call last):\n    ...\nValueError: Destination 0: weight must be positive, got -5\n\n>>> weighted_transitions(s1, (s2, \"ten\"))\nTraceback (most recent call last):\n    ...\nTypeError: Destination 0: weight must be a positive number, got str\n\n```\n\n## How it works\n\nUnder the hood, `weighted_transitions()`:\n\n1. Creates a `_WeightedGroup` holding the weights and a `random.Random` instance.\n2. Calls `source.to(target, **kwargs)` for each destination, creating standard\n   transitions.\n3. Attaches a lightweight condition callable to each transition's `cond` list.\n4. When the event fires, the engine evaluates conditions in order. The first condition\n   to run rolls the dice (using `random.choices`) and caches the result. Subsequent\n   conditions check against the cache.\n5. Only the selected transition's condition returns `True` — the engine picks it.\n\nThis means weighted transitions are fully compatible with all engine features:\n{ref}`actions`, {ref}`conditions`, {ref}`listeners`, async engines,\nand {ref}`diagram generation <diagram>`.\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"python-statemachine\"\nversion = \"3.1.0\"\ndescription = \"Python Finite State Machines made easy.\"\nauthors = [{ name = \"Fernando Macedo\", email = \"fgmacedo@gmail.com\" }]\nmaintainers = [{ name = \"Fernando Macedo\", email = \"fgmacedo@gmail.com\" }]\nlicense = { text = \"MIT License\" }\nreadme = \"README.md\"\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Framework :: AsyncIO\",\n    \"Framework :: Django\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Natural Language :: English\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Topic :: Home Automation\",\n    \"Topic :: Software Development :: Libraries\",\n]\nrequires-python = \">=3.9\"\n\n[project.urls]\nhomepage = \"https://github.com/fgmacedo/python-statemachine\"\n\n[project.optional-dependencies]\ndiagrams = [\"pydot >= 2.0.0\"]\n\n[dependency-groups]\ndev = [\n    \"ruff >=0.15.0\",\n    \"pre-commit\",\n    \"mypy\",\n    \"pytest\",\n    \"pytest-cov >=6.0.0; python_version >='3.9'\",\n    \"pytest-cov; python_version <'3.9'\",\n    \"pytest-sugar >=1.0.0\",\n    \"pytest-mock >=3.14.0\",\n    \"pytest-benchmark >=4.0.0\",\n    \"pytest-asyncio >=0.25.0\",\n    \"pydot\",\n    \"django >=5.2.11; python_version >='3.10'\",\n    \"pytest-django >=4.8.0; python_version >'3.8'\",\n    \"Sphinx; python_version >'3.8'\",\n    \"sphinx-gallery; python_version >'3.8'\",\n    \"myst-parser; python_version >'3.8'\",\n    \"pillow; python_version >'3.8'\",\n    \"sphinx-autobuild; python_version >'3.8'\",\n    \"furo >=2024.5.6; python_version >'3.8'\",\n    \"sphinx-copybutton >=0.5.2; python_version >'3.8'\",\n    \"sphinxcontrib-mermaid; python_version >'3.8'\",\n    \"pdbr>=0.8.9; python_version >'3.8'\",\n    \"babel >=2.16.0; python_version >='3.8'\",\n    \"pytest-xdist>=3.6.1\",\n    \"pytest-timeout>=2.3.1\",\n    \"pyright>=1.1.400\",\n]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.build.targets.wheel]\npackages = [\"statemachine/\"]\n\n[tool.pytest.ini_options]\naddopts = [\n    \"-s\",\n    \"--ignore=docs/conf.py\",\n    \"--ignore=docs/auto_examples/\",\n    \"--ignore=docs/_build/\",\n    \"--ignore=tests/examples/\",\n    \"--doctest-glob=*.md\",\n    \"--doctest-modules\",\n    \"--doctest-continue-on-failure\",\n    \"--benchmark-autosave\",\n    \"--benchmark-group-by=name\",\n    \"--pdbcls=pdbr:RichPdb\",\n]\ndoctest_optionflags = \"ELLIPSIS IGNORE_EXCEPTION_DETAIL NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL\"\nasyncio_mode = \"auto\"\nmarkers = [\n    \"\"\"slow: marks tests as slow (deselect with '-m \"not slow\"')\"\"\",\n    \"\"\"scxml: marks a tests as scxml (deselect with '-m \"not scxml\"')\"\"\",\n]\npython_files = [\"tests.py\", \"test_*.py\", \"*_tests.py\"]\nxfail_strict = true\n# Log level WARNING by default; the engine caches a no-op for logger.debug at\n# init time, so DEBUG here would bypass that optimization and slow benchmarks.\n# To enable DEBUG logging for a specific test run:\n#   uv run pytest -o log_cli_level=DEBUG\nlog_cli_level = \"WARNING\"\nlog_cli_format = \"%(relativeCreated)6.0fms %(threadName)-18s %(name)-35s %(message)s\"\nlog_cli_date_format = \"%H:%M:%S\"\nasyncio_default_fixture_loop_scope = \"module\"\nfilterwarnings = [\"ignore::pytest_benchmark.logger.PytestBenchmarkWarning\"]\n\n[tool.coverage.run]\nbranch = true\n# dynamic_context = \"test_function\"\nrelative_files = true\ndata_file = \".coverage\"\nsource = [\"statemachine\"]\nomit = [\"*test*.py\", \"tmp/*\", \"pytest_cov\"]\n\n[tool.coverage.report]\nshow_missing = true\nexclude_lines = [\n    # Have to re-enable the standard pragma\n    \"pragma: no cover\",\n\n    # Don't complain about missing debug-only code:\n    \"def __repr__\",\n    \"if self.debug\",\n\n    # Don't complain if tests don't hit defensive assertion code:\n    \"raise AssertionError\",\n    \"raise NotImplementedError\",\n    \"if TYPE_CHECKING\",\n    'if __name__ == \"__main__\"',\n]\n\n[tool.coverage.html]\ndirectory = \"tmp/htmlcov\"\nshow_contexts = true\n\n[tool.mypy]\npython_version = \"3.14\"\nwarn_return_any = true\nwarn_unused_configs = true\ndisable_error_code = \"annotation-unchecked\"\nmypy_path = \"$MYPY_CONFIG_FILE_DIR/tests/django_project\"\n\n[[tool.mypy.overrides]]\nmodule = [\n    'django.*',\n    'pytest.*',\n    'pydot.*',\n    'sphinx_gallery.*',\n    'docutils.*',\n    'sphinx.*',\n]\nignore_missing_imports = true\n\n[tool.ruff]\nsrc = [\"statemachine\"]\n\nline-length = 99\ntarget-version = \"py39\"\n\n# Exclude a variety of commonly ignored directories.\nexclude = [\n    \".direnv\",\n    \".eggs\",\n    \".git\",\n    \".mypy_cache\",\n    \".nox\",\n    \".pants.d\",\n    \".ruff_cache\",\n    \".tox\",\n    \".venv\",\n    \"__pypackages__\",\n    \"_build\",\n    \"buck-out\",\n    \"build\",\n    \"dist\",\n    \"node_modules\",\n    \"auto_examples\",\n    \"venv\",\n]\n\n[tool.ruff.lint]\n\n# Enable Pyflakes and pycodestyle rules.\nselect = [\n    \"E\",  # pycodestyle errors\n    \"W\",  # pycodestyle warnings\n    \"F\",  # pyflakes\n    \"I\",  # isort\n    \"UP\", # pyupgrade\n    \"C\",  # flake8-comprehensions\n    \"B\",  # flake8-bugbear\n    \"PT\", # flake8-pytest-style\n]\nignore = [\n    \"UP006\", # `use-pep585-annotation` Requires Python3.9+\n    \"UP035\", # `use-pep585-annotation` Requires Python3.9+\n    \"UP037\", # `remove-quotes-from-type-annotation` Not safe without `from __future__ import annotations`\n    \"UP042\", # `use-str-enum` Requires Python3.11+\n]\n\n# Allow unused variables when underscore-prefixed.\ndummy-variable-rgx = \"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$\"\n\n[tool.ruff.lint.per-file-ignores]\n# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.\n\"__init__.py\" = [\"E402\"]\n\"path/to/file.py\" = [\"E402\"]\n\"tests/examples/**.py\" = [\"B018\"]\n\n[tool.ruff.lint.mccabe]\nmax-complexity = 10\n\n[tool.ruff.lint.isort]\nforce-single-line = true\n\n[tool.ruff.lint.pydocstyle]\n# Use Google-style docstrings.\nconvention = \"google\"\n\n[tool.ruff.lint.flake8-pytest-style]\nfixture-parentheses = true\nmark-parentheses = true\n\n[tool.pyright]\npythonVersion = \"3.9\"\ntypeCheckingMode = \"basic\"\ninclude = [\"statemachine\"]\n"
  },
  {
    "path": "statemachine/__init__.py",
    "content": "from .event import Event\nfrom .state import HistoryState\nfrom .state import HistoryType\nfrom .state import State\nfrom .statemachine import StateChart\nfrom .statemachine import StateMachine\nfrom .statemachine import TModel\n\n__author__ = \"\"\"Fernando Macedo\"\"\"\n__email__ = \"fgmacedo@gmail.com\"\n__version__ = \"3.1.0\"\n\n__all__ = [\n    \"StateChart\",\n    \"StateMachine\",\n    \"State\",\n    \"HistoryState\",\n    \"HistoryType\",\n    \"Event\",\n    \"TModel\",\n]\n"
  },
  {
    "path": "statemachine/callbacks.py",
    "content": "import asyncio\nfrom bisect import insort\nfrom collections import defaultdict\nfrom collections import deque\nfrom enum import IntEnum\nfrom enum import IntFlag\nfrom enum import auto\nfrom functools import partial\nfrom inspect import isawaitable\nfrom typing import TYPE_CHECKING\nfrom typing import Callable\nfrom typing import Dict\nfrom typing import List\n\nfrom .exceptions import AttrNotFound\nfrom .i18n import _\nfrom .utils import ensure_iterable\n\nif TYPE_CHECKING:\n    from typing import Set\n\n\ndef allways_true(*args, **kwargs):\n    return True\n\n\nclass CallbackPriority(IntEnum):\n    GENERIC = 0\n    INLINE = 10\n    DECORATOR = 20\n    NAMING = 30\n    AFTER = 40\n\n\nclass SpecReference(IntFlag):\n    NAME = auto()\n    CALLABLE = auto()\n    PROPERTY = auto()\n\n\nSPECS_ALL = SpecReference.NAME | SpecReference.CALLABLE | SpecReference.PROPERTY\nSPECS_SAFE = SpecReference.NAME\n\n\nclass CallbackGroup(IntEnum):\n    PREPARE = auto()\n    ENTER = auto()\n    EXIT = auto()\n    INVOKE = auto()\n    VALIDATOR = auto()\n    BEFORE = auto()\n    ON = auto()\n    AFTER = auto()\n    COND = auto()\n\n    def build_key(self, specs: \"CallbackSpecList\") -> str:\n        return f\"{self.name}@{id(specs)}\"\n\n\nclass CallbackSpec:\n    \"\"\"Specs about callbacks.\n\n    At first, `func` can be a name (string), a property or a callable.\n\n    Names, properties and unbounded callables should be resolved to a callable\n    before any real call is performed.\n    \"\"\"\n\n    names_not_found: \"Set[str] | None\" = None\n    \"\"\"List of names that were not found on the model or statemachine\"\"\"\n\n    def __init__(\n        self,\n        func,\n        group: CallbackGroup,\n        is_convention=False,\n        is_event: bool = False,\n        cond=None,\n        priority: CallbackPriority = CallbackPriority.NAMING,\n        expected_value=None,\n    ):\n        self.func = func\n        self.group = group\n        self.is_convention = is_convention\n        self.is_event = is_event\n        self.cond = cond\n        self.expected_value = expected_value\n        self.priority = priority\n\n        if isinstance(func, property):\n            self.reference = SpecReference.PROPERTY\n            self.attr_name: str = func and func.fget and func.fget.__name__ or \"\"\n        elif callable(func):\n            self.reference = SpecReference.CALLABLE\n            is_partial = isinstance(func, partial)\n            self.is_bounded = is_partial or hasattr(func, \"__self__\")\n            name = func.func.__name__ if is_partial else func.__name__\n            self.attr_name = name if not self.is_event or self.is_bounded else f\"_{name}_\"\n            if not self.is_bounded:\n                func.attr_name = self.attr_name  # type: ignore[union-attr]\n                func.is_event = is_event  # type: ignore[union-attr]\n        else:\n            self.reference = SpecReference.NAME\n            self.attr_name = func\n\n        self.may_contain_boolean_expression = (\n            not self.is_convention\n            and self.group == CallbackGroup.COND\n            and self.reference == SpecReference.NAME\n        )\n\n    def __repr__(self):\n        return f\"{type(self).__name__}({self.func!r}, is_convention={self.is_convention!r})\"\n\n    def __str__(self):\n        name = self.attr_name\n        if self.expected_value is False:\n            name = f\"!{name}\"\n        return name\n\n    def __eq__(self, other):\n        return self.func == other.func and self.group == other.group\n\n    def __hash__(self):\n        return id(self)\n\n\nclass SpecListGrouper:\n    def __init__(self, list: \"CallbackSpecList\", group: CallbackGroup) -> None:\n        self.list = list\n        self.group = group\n        self.key = group.build_key(list)\n\n    def add(self, callbacks, **kwargs):\n        self.list.add(callbacks, group=self.group, **kwargs)\n        return self\n\n    def __call__(self, callback):\n        return self.list._add_unbounded_callback(callback, group=self.group)\n\n    def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs):\n        self.list._add_unbounded_callback(\n            func,\n            is_event=is_event,\n            transitions=transitions,\n            group=self.group,\n            **kwargs,\n        )\n\n    def __iter__(self):\n        return (item for item in self.list if item.group == self.group)\n\n\nclass CallbackSpecList:\n    \"\"\"List of {ref}`CallbackSpec` instances\"\"\"\n\n    def __init__(self, factory=CallbackSpec):\n        self.items: List[CallbackSpec] = []\n        self.conventional_specs = set()\n        self._groupers: Dict[CallbackGroup, SpecListGrouper] = {}\n        self.factory = factory\n\n    def __repr__(self):\n        return f\"{type(self).__name__}({self.items!r}, factory={self.factory!r})\"\n\n    def _add_unbounded_callback(self, func, transitions=None, **kwargs):\n        \"\"\"This list was a target for adding a func using decorator\n        `@<state|event>[.on|before|after|enter|exit]` syntax.\n\n        If we assign ``func`` directly as callable on the ``items`` list,\n        this will result in an `unbounded method error`, with `func` expecting a parameter\n        ``self`` not defined.\n\n        The implemented solution is to resolve the collision giving the func a reference method.\n        To update It's callback when the name is resolved on the\n        :func:`StateMachineMetaclass.add_from_attributes`.\n        If the ``func`` is bounded It will be used directly, if not, it's ref will be replaced\n        by the given attr name and on `statemachine._setup()` the dynamic name will be resolved\n        properly.\n\n        Args:\n            func (callable): The decorated method to add on the transitions occurs.\n            is_event (bool): If the func is also an event, we'll create a trigger and link the\n                event name to the transitions.\n            transitions (TransitionList): If ``is_event``, the transitions to be attached to the\n                event.\n\n        \"\"\"\n        self._add(func, **kwargs)\n        func._transitions = transitions\n\n        return func\n\n    def __iter__(self):\n        return iter(self.items)\n\n    def clear(self):\n        self.items = []\n\n    def grouper(self, group: CallbackGroup) -> SpecListGrouper:\n        if group not in self._groupers:\n            self._groupers[group] = SpecListGrouper(self, group)\n        return self._groupers[group]\n\n    def _add(self, func, group: CallbackGroup, **kwargs):\n        if isinstance(func, CallbackSpec):\n            spec = func\n        else:\n            spec = self.factory(func, group, **kwargs)\n\n        if spec in self.items:\n            return\n\n        self.items.append(spec)\n        if spec.is_convention:\n            self.conventional_specs.add(spec.func)\n        return spec\n\n    def add(self, callbacks, group: CallbackGroup, **kwargs):\n        if callbacks is None:\n            return self\n\n        unprepared = ensure_iterable(callbacks)\n        for func in unprepared:\n            self._add(func, group=group, **kwargs)\n\n        return self\n\n\nclass CallbackWrapper:\n    def __init__(\n        self,\n        callback: Callable,\n        condition: Callable,\n        meta: \"CallbackSpec\",\n        unique_key: str,\n    ) -> None:\n        self._callback = callback\n        self._iscoro = getattr(callback, \"is_coroutine\", False)\n        self.condition = condition\n        self.meta = meta\n        self.unique_key = unique_key\n        self.expected_value = self.meta.expected_value\n\n    def __repr__(self):\n        return f\"{type(self).__name__}({self.unique_key})\"\n\n    def __str__(self):\n        return str(self.meta)\n\n    def __lt__(self, other):\n        return self.meta.priority < other.meta.priority\n\n    async def __call__(self, *args, **kwargs):\n        value = self._callback(*args, **kwargs)\n        if isawaitable(value):\n            value = await value\n\n        if self.expected_value is not None:\n            return bool(value) == self.expected_value\n        return value\n\n    def call(self, *args, **kwargs):\n        value = self._callback(*args, **kwargs)\n        if self.expected_value is not None:\n            return bool(value) == self.expected_value\n        return value\n\n\nclass CallbacksExecutor:\n    \"\"\"A list of callbacks that can be executed in order.\"\"\"\n\n    def __init__(self):\n        self.items: \"deque[CallbackWrapper]\" = deque()\n        self.items_already_seen = set()\n\n    def __iter__(self):\n        return iter(self.items)\n\n    def __repr__(self):\n        return f\"{type(self).__name__}({self.items!r})\"\n\n    def __str__(self):\n        return \", \".join(str(c) for c in self)\n\n    def add(self, key: str, spec: CallbackSpec, builder: Callable[[], Callable]):\n        if key in self.items_already_seen:\n            return\n\n        self.items_already_seen.add(key)\n\n        condition = spec.cond if spec.cond is not None else allways_true\n        wrapper = CallbackWrapper(\n            callback=builder(),\n            condition=condition,\n            meta=spec,\n            unique_key=key,\n        )\n\n        insort(self.items, wrapper)\n\n    async def async_call(\n        self, *args, on_error: \"Callable[[Exception], None] | None\" = None, **kwargs\n    ):\n        if on_error is None:\n            return await asyncio.gather(\n                *(\n                    callback(*args, **kwargs)\n                    for callback in self\n                    if callback.condition(*args, **kwargs)\n                )\n            )\n\n        results = []\n        for callback in self:\n            if callback.condition(*args, **kwargs):  # pragma: no branch\n                try:\n                    results.append(await callback(*args, **kwargs))\n                except Exception as e:\n                    on_error(e)\n        return results\n\n    async def async_all(\n        self, *args, on_error: \"Callable[[Exception], None] | None\" = None, **kwargs\n    ):\n        for callback in self:\n            try:\n                if not await callback(*args, **kwargs):\n                    return False\n            except Exception as e:\n                if on_error is not None:\n                    on_error(e)\n                    return False\n                raise\n        return True\n\n    def call(self, *args, on_error: \"Callable[[Exception], None] | None\" = None, **kwargs):\n        if on_error is None:\n            return [\n                callback.call(*args, **kwargs)\n                for callback in self\n                if callback.condition(*args, **kwargs)\n            ]\n\n        results = []\n        for callback in self:\n            if callback.condition(*args, **kwargs):  # pragma: no branch\n                try:\n                    results.append(callback.call(*args, **kwargs))\n                except Exception as e:\n                    on_error(e)\n        return results\n\n    def all(self, *args, on_error: \"Callable[[Exception], None] | None\" = None, **kwargs):\n        for condition in self:\n            try:\n                if not condition.call(*args, **kwargs):\n                    return False\n            except Exception as e:\n                if on_error is not None:\n                    on_error(e)\n                    return False\n                raise\n        return True\n\n    def visit(self, visitor_fn, *args, **kwargs):\n        \"\"\"Like call() but delegates execution to visitor_fn for each matching callback.\"\"\"\n        for callback in self:\n            if callback.condition(*args, **kwargs):\n                visitor_fn(callback, *args, **kwargs)\n\n    async def async_visit(self, visitor_fn, *args, **kwargs):\n        \"\"\"Async variant of visit().\"\"\"\n        for callback in self:\n            if callback.condition(*args, **kwargs):\n                result = visitor_fn(callback, *args, **kwargs)\n                if isawaitable(result):\n                    await result\n\n\nclass CallbacksRegistry:\n    def __init__(self) -> None:\n        self._registry: Dict[str, CallbacksExecutor] = defaultdict(CallbacksExecutor)\n        self.has_async_callbacks: bool = False\n\n    def __getitem__(self, key: str) -> CallbacksExecutor:\n        return self._registry[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self._registry\n\n    def check(self, specs: CallbackSpecList):\n        for meta in specs:\n            if meta.is_convention:\n                continue\n\n            if any(\n                callback for callback in self[meta.group.build_key(specs)] if callback.meta == meta\n            ):\n                continue\n\n            if meta.names_not_found:\n                raise AttrNotFound(\n                    _(\"Did not found name '{}' from model or statemachine\").format(\n                        \", \".join(meta.names_not_found)\n                    ),\n                )\n            raise AttrNotFound(\n                _(\"Did not found name '{}' from model or statemachine\").format(meta.func)\n            )\n\n    def async_or_sync(self):\n        self.has_async_callbacks = any(\n            callback._iscoro for executor in self._registry.values() for callback in executor\n        )\n\n    def call(\n        self,\n        key: str,\n        *args,\n        on_error: \"Callable[[Exception], None] | None\" = None,\n        **kwargs,\n    ):\n        if key not in self._registry:\n            return []\n        return self._registry[key].call(*args, on_error=on_error, **kwargs)\n\n    async def async_call(\n        self,\n        key: str,\n        *args,\n        on_error: \"Callable[[Exception], None] | None\" = None,\n        **kwargs,\n    ):\n        if key not in self._registry:\n            return []\n        return await self._registry[key].async_call(*args, on_error=on_error, **kwargs)\n\n    def all(\n        self,\n        key: str,\n        *args,\n        on_error: \"Callable[[Exception], None] | None\" = None,\n        **kwargs,\n    ):\n        if key not in self._registry:\n            return True\n        return self._registry[key].all(*args, on_error=on_error, **kwargs)\n\n    async def async_all(\n        self,\n        key: str,\n        *args,\n        on_error: \"Callable[[Exception], None] | None\" = None,\n        **kwargs,\n    ):\n        if key not in self._registry:\n            return True\n        return await self._registry[key].async_all(*args, on_error=on_error, **kwargs)\n\n    def visit(self, key: str, visitor_fn, *args, **kwargs):\n        if key not in self._registry:\n            return\n        self._registry[key].visit(visitor_fn, *args, **kwargs)\n\n    async def async_visit(self, key: str, visitor_fn, *args, **kwargs):\n        if key not in self._registry:\n            return\n        await self._registry[key].async_visit(visitor_fn, *args, **kwargs)\n\n    def str(self, key: str) -> str:\n        if key not in self._registry:\n            return \"\"\n        return str(self._registry[key])\n"
  },
  {
    "path": "statemachine/configuration.py",
    "content": "from typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Dict\nfrom typing import Mapping\nfrom typing import MutableSet\n\nfrom .exceptions import InvalidStateValue\nfrom .i18n import _\nfrom .orderedset import OrderedSet\n\n_SENTINEL = object()\n\nif TYPE_CHECKING:\n    from .state import State\n\n\nclass Configuration:\n    \"\"\"Encapsulates the dual representation of the active state configuration.\n\n    Internally, ``current_state_value`` is either a scalar (single active state)\n    or an ``OrderedSet`` (parallel regions).  This class hides that detail behind\n    a uniform interface for reading, mutating, and caching the resolved\n    ``OrderedSet[State]``.\n    \"\"\"\n\n    __slots__ = (\n        \"_instance_states\",\n        \"_model\",\n        \"_state_field\",\n        \"_states_map\",\n        \"_cached\",\n        \"_cached_value\",\n    )\n\n    def __init__(\n        self,\n        instance_states: \"Mapping[str, State]\",\n        model: Any,\n        state_field: str,\n        states_map: \"Dict[Any, State]\",\n    ):\n        self._instance_states = instance_states\n        self._model = model\n        self._state_field = state_field\n        self._states_map = states_map\n        self._cached: \"OrderedSet[State] | None\" = None\n        self._cached_value: Any = _SENTINEL\n\n    # -- Raw value (persisted on the model) ------------------------------------\n\n    @property\n    def value(self) -> Any:\n        \"\"\"The raw state value stored on the model (scalar or ``OrderedSet``).\"\"\"\n        return getattr(self._model, self._state_field, None)\n\n    @value.setter\n    def value(self, val: Any):\n        if val is None:\n            self._write_to_model(OrderedSet())\n        elif isinstance(val, MutableSet):\n            self._write_to_model(OrderedSet(val) if not isinstance(val, OrderedSet) else val)\n        else:\n            self._write_to_model(OrderedSet([val]))\n\n    @property\n    def values(self) -> OrderedSet[Any]:\n        \"\"\"The set of raw state values currently active.\"\"\"\n        return self._read_from_model()\n\n    # -- Resolved states -------------------------------------------------------\n\n    @property\n    def states(self) -> \"OrderedSet[State]\":\n        \"\"\"The set of currently active :class:`State` instances (cached).\"\"\"\n        raw = self.value\n        if self._cached is not None and self._cached_value is raw:\n            return self._cached\n        if raw is None:\n            return OrderedSet()\n\n        # Normalize inline (avoid second getattr via _read_from_model)\n        values = raw if isinstance(raw, MutableSet) else (raw,)\n        result = OrderedSet(self._instance_states[self._states_map[v].id] for v in values)\n        self._cached = result\n        self._cached_value = raw\n        return result\n\n    @states.setter\n    def states(self, new_configuration: \"OrderedSet[State]\"):\n        self._write_to_model(OrderedSet(s.value for s in new_configuration))\n\n    # -- Incremental mutation (used by the engine) -----------------------------\n\n    def add(self, state: \"State\"):\n        \"\"\"Add *state* to the configuration.\"\"\"\n        values = self._read_from_model()\n        values.add(state.value)\n        self._write_to_model(values)\n\n    def discard(self, state: \"State\"):\n        \"\"\"Remove *state* from the configuration.\"\"\"\n        values = self._read_from_model()\n        values.discard(state.value)\n        self._write_to_model(values)\n\n    # -- Deprecated v2 compat --------------------------------------------------\n\n    @property\n    def current_state(self) -> \"State | OrderedSet[State]\":\n        \"\"\"Resolve the current state with validation.\n\n        Unlike ``states`` (which returns an empty set for ``None``), this\n        raises ``InvalidStateValue`` when the value is ``None`` or not\n        found in ``states_map`` — matching the v2 ``current_state`` contract.\n        \"\"\"\n        csv = self.value\n        if csv is None:\n            raise InvalidStateValue(\n                csv,\n                _(\n                    \"There's no current state set. In async code, \"\n                    \"did you activate the initial state? \"\n                    \"(e.g., `await sm.activate_initial_state()`)\"\n                ),\n            )\n        try:\n            config = self.states\n            if len(config) == 1:\n                return next(iter(config))\n            return config\n        except KeyError as err:\n            raise InvalidStateValue(csv) from err\n\n    # -- Internal: model boundary ----------------------------------------------\n\n    def _read_from_model(self) -> OrderedSet:\n        \"\"\"Normalize: model value → always ``OrderedSet``.\"\"\"\n        raw = self.value\n        if raw is None:\n            return OrderedSet()\n        if isinstance(raw, OrderedSet):\n            return raw\n        if isinstance(raw, MutableSet):\n            return OrderedSet(raw)\n        return OrderedSet([raw])\n\n    def _write_to_model(self, values: OrderedSet):\n        \"\"\"Denormalize: ``OrderedSet`` → ``None | scalar | OrderedSet`` for model.\"\"\"\n        self._invalidate()\n        if len(values) == 0:\n            raw = None\n        elif len(values) == 1:\n            raw = next(iter(values))\n        else:\n            raw = values\n        if raw is not None and not isinstance(raw, MutableSet) and raw not in self._states_map:\n            raise InvalidStateValue(raw)\n        setattr(self._model, self._state_field, raw)\n\n    def _invalidate(self):\n        self._cached = None\n        self._cached_value = _SENTINEL\n"
  },
  {
    "path": "statemachine/contrib/__init__.py",
    "content": ""
  },
  {
    "path": "statemachine/contrib/diagram/__init__.py",
    "content": "import importlib\nfrom urllib.parse import quote\nfrom urllib.request import urlopen\n\nfrom .extract import extract\nfrom .formatter import formatter as formatter\nfrom .renderers.dot import DotRenderer\nfrom .renderers.dot import DotRendererConfig\nfrom .renderers.mermaid import MermaidRenderer\nfrom .renderers.mermaid import MermaidRendererConfig\n\n\nclass DotGraphMachine:\n    \"\"\"Backwards-compatible facade that uses the extract + render pipeline.\n\n    Maintains the same public API and class-level customization attributes\n    as the original monolithic DotGraphMachine.\n    \"\"\"\n\n    graph_rankdir = \"LR\"\n    \"\"\"\n    Direction of the graph. Defaults to \"LR\" (option \"TB\" for top bottom)\n    http://www.graphviz.org/doc/info/attrs.html#d:rankdir\n    \"\"\"\n\n    font_name = \"Helvetica\"\n    \"\"\"Graph font face name\"\"\"\n\n    state_font_size = \"10\"\n    \"\"\"State font size\"\"\"\n\n    state_active_penwidth = 2\n    \"\"\"Active state external line width\"\"\"\n\n    state_active_fillcolor = \"turquoise\"\n\n    transition_font_size = \"9\"\n    \"\"\"Transition font size\"\"\"\n\n    def __init__(self, machine):\n        self.machine = machine\n\n    def _build_config(self) -> DotRendererConfig:\n        return DotRendererConfig(\n            graph_rankdir=self.graph_rankdir,\n            font_name=self.font_name,\n            state_font_size=self.state_font_size,\n            state_active_penwidth=self.state_active_penwidth,\n            state_active_fillcolor=self.state_active_fillcolor,\n            transition_font_size=self.transition_font_size,\n        )\n\n    def get_graph(self):\n        ir = extract(self.machine)\n        renderer = DotRenderer(config=self._build_config())\n        return renderer.render(ir)\n\n    def __call__(self):\n        return self.get_graph()\n\n\nclass MermaidGraphMachine:\n    \"\"\"Facade for generating Mermaid stateDiagram-v2 source from a state machine.\"\"\"\n\n    direction = \"LR\"\n    active_fill = \"#40E0D0\"\n    active_stroke = \"#333\"\n\n    def __init__(self, machine):\n        self.machine = machine\n\n    def _build_config(self) -> MermaidRendererConfig:\n        return MermaidRendererConfig(\n            direction=self.direction,\n            active_fill=self.active_fill,\n            active_stroke=self.active_stroke,\n        )\n\n    def get_mermaid(self) -> str:\n        ir = extract(self.machine)\n        renderer = MermaidRenderer(config=self._build_config())\n        return renderer.render(ir)\n\n    def __call__(self) -> str:\n        return self.get_mermaid()\n\n\ndef quickchart_write_svg(sm, path: str):\n    \"\"\"\n    If the default dependency of GraphViz installed locally doesn't work for you. As an option,\n    you can generate the image online from the output of the `dot` language,\n    using one of the many services available.\n\n    To get the **dot** representation of your state machine is as easy as follows:\n\n    >>> from tests.examples.order_control_machine import OrderControl\n    >>> sm = OrderControl()\n    >>> print(sm._graph().to_string())  # doctest: +ELLIPSIS\n    digraph OrderControl {\n    ...\n    }\n\n    To give you an example, we included this method that will serialize the dot, request the graph\n    to https://quickchart.io, and persist the result locally as an ``.svg`` file.\n\n\n    .. warning::\n        Quickchart is an external graph service that supports many formats to generate diagrams.\n\n        By using this method, you should trust http://quickchart.io.\n\n        Please read https://quickchart.io/documentation/faq/ for more information.\n\n    >>> quickchart_write_svg(sm, \"docs/images/oc_machine_processing.svg\")  # doctest: +SKIP\n\n    \"\"\"\n    dot_representation = sm._graph().to_string()\n\n    url = f\"https://quickchart.io/graphviz?graph={quote(dot_representation)}\"\n\n    response = urlopen(url)\n    data = response.read()\n\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n\ndef _find_sm_class(module):\n    \"\"\"Find the first StateChart subclass defined in a module.\"\"\"\n    import inspect\n\n    from statemachine.statemachine import StateChart\n\n    for _name, obj in inspect.getmembers(module, inspect.isclass):\n        if (\n            issubclass(obj, StateChart)\n            and obj is not StateChart\n            and obj.__module__ == module.__name__\n        ):\n            return obj\n    return None\n\n\ndef import_sm(qualname):\n    from statemachine.statemachine import StateChart\n\n    module_name, class_name = qualname.rsplit(\".\", 1)\n    module = importlib.import_module(module_name)\n    smclass = getattr(module, class_name, None)\n    if smclass is not None and isinstance(smclass, type) and issubclass(smclass, StateChart):\n        return smclass\n\n    # qualname may be a module path without a class name — try importing\n    # the whole path as a module and find the first StateChart subclass.\n    try:\n        module = importlib.import_module(qualname)\n    except ImportError as err:\n        raise ValueError(f\"{class_name} is not a subclass of StateMachine\") from err\n\n    smclass = _find_sm_class(module)\n    if smclass is None:\n        raise ValueError(f\"No StateMachine subclass found in module {qualname!r}\")\n\n    return smclass\n\n\ndef write_image(qualname, out, events=None, fmt=None):\n    \"\"\"\n    Given a `qualname`, that is the fully qualified dotted path to a StateMachine\n    classes, imports the class and generates a dot graph using the `pydot` lib.\n    Writes the graph representation to the filename 'out' that will\n    open/create and truncate such file and write on it a representation of\n    the graph defined by the statemachine, in the format specified by\n    the extension contained in the out path (out.ext).\n\n    If `events` is provided, the machine is instantiated and each event is sent\n    before rendering, so the diagram highlights the current active state.\n\n    If `fmt` is provided, it overrides the output format (any registered text\n    format such as ``\"mermaid\"``, ``\"dot\"``, ``\"md\"``, ``\"rst\"``).\n    Use ``out=\"-\"`` to write to stdout.\n    \"\"\"\n    import sys\n\n    smclass = import_sm(qualname)\n\n    if events:\n        machine = smclass()\n        for event_name in events:\n            machine.send(event_name)\n    else:\n        machine = smclass\n\n    if fmt is not None:\n        text = formatter.render(machine, fmt)\n        if out == \"-\":\n            sys.stdout.write(text)\n        else:\n            with open(out, \"w\") as f:\n                f.write(text)\n    else:\n        graph = DotGraphMachine(machine).get_graph()\n        if out == \"-\":\n            sys.stdout.buffer.write(graph.create_svg())  # type: ignore[attr-defined]\n        else:\n            out_extension = out.rsplit(\".\", 1)[1]\n            graph.write(out, format=out_extension)\n\n\ndef main(argv=None):\n    import argparse\n\n    parser = argparse.ArgumentParser(\n        usage=\"%(prog)s [OPTION] <class_path> <out>\",\n        description=\"Generate diagrams for StateMachine classes.\",\n    )\n    parser.add_argument(\n        \"class_path\", help=\"A fully-qualified dotted path to the StateMachine class.\"\n    )\n    parser.add_argument(\n        \"out\",\n        help=\"File to generate the image using extension as the output format.\",\n    )\n    parser.add_argument(\n        \"--events\",\n        nargs=\"+\",\n        help=\"Instantiate the machine and send these events before rendering.\",\n    )\n    parser.add_argument(\n        \"--format\",\n        choices=formatter.supported_formats(),\n        default=None,\n        help=\"Output as text format instead of Graphviz image.\",\n    )\n\n    args = parser.parse_args(argv)\n    write_image(qualname=args.class_path, out=args.out, events=args.events, fmt=args.format)\n"
  },
  {
    "path": "statemachine/contrib/diagram/__main__.py",
    "content": "import sys\n\nfrom . import main\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "statemachine/contrib/diagram/extract.py",
    "content": "from typing import TYPE_CHECKING\nfrom typing import List\nfrom typing import Set\nfrom typing import Union\n\nfrom .model import ActionType\nfrom .model import DiagramAction\nfrom .model import DiagramGraph\nfrom .model import DiagramState\nfrom .model import DiagramTransition\nfrom .model import StateType\n\nif TYPE_CHECKING:\n    from statemachine.state import State\n    from statemachine.statemachine import StateChart\n    from statemachine.transition import Transition\n\n    # A StateChart class or instance — both expose the same structural metadata.\n    MachineRef = Union[\"StateChart\", \"type[StateChart]\"]\n\n\ndef _determine_state_type(state: \"State\") -> StateType:\n    from statemachine.state import HistoryState\n    from statemachine.state import HistoryType\n\n    if isinstance(state, HistoryState):\n        if state.type == HistoryType.DEEP:\n            return StateType.HISTORY_DEEP\n        return StateType.HISTORY_SHALLOW\n    if getattr(state, \"parallel\", False):\n        return StateType.PARALLEL\n    if state.final:\n        return StateType.FINAL\n    return StateType.REGULAR\n\n\ndef _actions_getter(machine: \"MachineRef\"):\n    from statemachine.statemachine import StateChart\n\n    if isinstance(machine, StateChart):\n\n        def getter(grouper):  # pyright: ignore[reportRedeclaration]\n            return machine._callbacks.str(grouper.key)\n    else:\n\n        def getter(grouper):\n            all_names = set(dir(machine))\n            return \", \".join(str(c) for c in grouper if not c.is_convention or c.func in all_names)\n\n    return getter\n\n\ndef _extract_state_actions(state: \"State\", getter) -> List[DiagramAction]:\n    actions: List[DiagramAction] = []\n\n    entry = str(getter(state.enter))\n    exit_ = str(getter(state.exit))\n\n    if entry:\n        actions.append(DiagramAction(type=ActionType.ENTRY, body=entry))\n    if exit_:\n        actions.append(DiagramAction(type=ActionType.EXIT, body=exit_))\n\n    for transition in state.transitions:\n        if transition.internal:\n            on_text = str(getter(transition.on))\n            if on_text:\n                actions.append(\n                    DiagramAction(type=ActionType.INTERNAL, body=f\"{transition.event} / {on_text}\")\n                )\n\n    return actions\n\n\ndef _extract_state(\n    state: \"State\",\n    machine: \"MachineRef\",\n    getter,\n    active_values: set,\n) -> DiagramState:\n    state_type = _determine_state_type(state)\n    is_active = state.value in active_values\n    is_parallel_area = bool(state.parent and getattr(state.parent, \"parallel\", False))\n\n    children: List[DiagramState] = []\n    for substate in state.states:\n        children.append(_extract_state(substate, machine, getter, active_values))\n    for history_state in getattr(state, \"history\", []):\n        children.append(_extract_state(history_state, machine, getter, active_values))\n\n    actions = _extract_state_actions(state, getter)\n\n    return DiagramState(\n        id=state.id,\n        name=state.name,\n        type=state_type,\n        actions=actions,\n        children=children,\n        is_active=is_active,\n        is_parallel_area=is_parallel_area,\n        is_initial=getattr(state, \"initial\", False),\n    )\n\n\ndef _format_event_names(transition: \"Transition\") -> str:\n    \"\"\"Build a display string for the events that trigger a transition.\n\n    ``_expand_event_id`` registers both the Python attribute name\n    (``done_invoke_X``) and the SCXML dot form (``done.invoke.X``) under the\n    same transition.  For diagram display we only want unique *semantic* events,\n    keeping the Python attribute name when an alias pair exists.\n    \"\"\"\n    events = list(transition.events)\n    if not events:\n        return \"\"\n\n    all_ids = {str(e) for e in events}\n\n    seen_ids: Set[str] = set()\n    display: List[str] = []\n    for event in events:\n        eid = str(event)\n        # Skip dot-form aliases (e.g. \"done.invoke.X\") when the underscore\n        # form (\"done_invoke_X\") is also registered on this transition.\n        if \".\" in eid and eid.replace(\".\", \"_\") in all_ids:\n            continue\n        if eid not in seen_ids:  # pragma: no branch\n            seen_ids.add(eid)\n            display.append(event.name if event.name else eid)\n\n    return \" \".join(display)\n\n\ndef _extract_transitions_from_state(state: \"State\") -> List[DiagramTransition]:\n    \"\"\"Extract transitions from a single state (non-recursive).\"\"\"\n    result: List[DiagramTransition] = []\n    for transition in state.transitions:\n        targets = transition.targets if transition.targets else []\n        target_ids = [t.id for t in targets]\n\n        cond_strs = [str(c) for c in transition.cond]\n\n        result.append(\n            DiagramTransition(\n                source=transition.source.id,\n                targets=target_ids,\n                event=_format_event_names(transition),\n                guards=cond_strs,\n                is_internal=transition.internal,\n            )\n        )\n    return result\n\n\ndef _extract_all_transitions(states) -> List[DiagramTransition]:\n    \"\"\"Recursively extract transitions from all states.\"\"\"\n    result: List[DiagramTransition] = []\n    for state in states:\n        result.extend(_extract_transitions_from_state(state))\n        if state.states:\n            result.extend(_extract_all_transitions(state.states))\n        for history_state in getattr(state, \"history\", []):\n            result.extend(_extract_transitions_from_state(history_state))\n            if history_state.states:  # pragma: no cover\n                result.extend(_extract_all_transitions(history_state.states))\n    return result\n\n\ndef _collect_compound_ids(states: List[DiagramState]) -> Set[str]:\n    \"\"\"Collect IDs of states that have children (compound/parallel).\"\"\"\n    result: Set[str] = set()\n    for state in states:\n        if state.children:\n            result.add(state.id)\n        result.update(_collect_compound_ids(state.children))\n    return result\n\n\ndef _collect_bidirectional_compound_ids(\n    transitions: List[DiagramTransition],\n    compound_ids: Set[str],\n) -> Set[str]:\n    \"\"\"Find compound states that have both outgoing and incoming explicit edges.\"\"\"\n    outgoing: Set[str] = set()\n    incoming: Set[str] = set()\n    for t in transitions:\n        if t.is_internal:\n            continue\n        # Skip implicit initial transitions\n        if t.source in compound_ids and not t.event and t.targets:\n            continue\n        if t.source in compound_ids:\n            outgoing.add(t.source)\n        for target_id in t.targets:\n            if target_id in compound_ids:\n                incoming.add(target_id)\n    return outgoing & incoming\n\n\ndef _mark_initial_transitions(\n    transitions: List[DiagramTransition],\n    compound_ids: Set[str],\n) -> None:\n    \"\"\"Mark implicit initial transitions (compound state → child, no event).\"\"\"\n    for t in transitions:\n        if t.source in compound_ids and not t.event and t.targets and not t.is_internal:\n            t.is_initial = True\n\n\ndef _resolve_initial_states(states: List[DiagramState]) -> None:\n    \"\"\"Ensure exactly one state per level has is_initial=True.\n\n    Skips parallel areas and history states. Falls back to document order\n    (first non-history, non-parallel-area state) when no explicit initial exists.\n    Recurses into children.\n\n    Parallel areas (children of a parallel state) have their is_initial flag\n    cleared: all regions are auto-activated, so no initial arrow is needed.\n    \"\"\"\n    # Clear is_initial on parallel areas — all children of a parallel state\n    # are simultaneously active; initial arrows would be misleading.\n    for s in states:\n        if s.is_parallel_area:\n            s.is_initial = False\n\n    candidates = [\n        s\n        for s in states\n        if s.type not in (StateType.HISTORY_SHALLOW, StateType.HISTORY_DEEP)\n        and not s.is_parallel_area\n    ]\n\n    has_explicit_initial = any(s.is_initial for s in candidates)\n    if not has_explicit_initial and candidates:\n        candidates[0].is_initial = True\n\n    for state in states:\n        if state.children:\n            _resolve_initial_states(state.children)\n\n\ndef extract(machine_or_class: \"MachineRef\") -> DiagramGraph:\n    \"\"\"Extract a DiagramGraph IR from a state machine instance or class.\n\n    Accepts either a class or an instance.  The class is **never** instantiated\n    — all structural metadata (states, transitions, name) is available on the\n    class itself thanks to the metaclass.  Active-state highlighting is only\n    produced when an *instance* is passed.\n\n    Args:\n        machine_or_class: A StateMachine/StateChart instance or class.\n\n    Returns:\n        A DiagramGraph representing the machine's structure.\n    \"\"\"\n    from statemachine.statemachine import StateChart\n\n    if isinstance(machine_or_class, StateChart):\n        machine: \"MachineRef\" = machine_or_class\n    elif isinstance(machine_or_class, type) and issubclass(machine_or_class, StateChart):\n        machine = machine_or_class\n    else:\n        raise TypeError(f\"Expected a StateChart instance or class, got {type(machine_or_class)}\")\n\n    getter = _actions_getter(machine)\n\n    active_values: set = set()\n    if isinstance(machine, StateChart) and hasattr(machine, \"configuration_values\"):\n        active_values = set(machine.configuration_values)\n\n    states: List[DiagramState] = []\n    for state in machine.states:\n        states.append(_extract_state(state, machine, getter, active_values))\n\n    transitions = _extract_all_transitions(machine.states)\n\n    compound_ids = _collect_compound_ids(states)\n    bidir_ids = _collect_bidirectional_compound_ids(transitions, compound_ids)\n    _mark_initial_transitions(transitions, compound_ids)\n    _resolve_initial_states(states)\n\n    return DiagramGraph(\n        name=machine.name,\n        states=states,\n        transitions=transitions,\n        compound_state_ids=compound_ids,\n        bidirectional_compound_ids=bidir_ids,\n    )\n"
  },
  {
    "path": "statemachine/contrib/diagram/formatter.py",
    "content": "\"\"\"Unified facade for rendering state machines in multiple text formats.\n\nThe :class:`Formatter` class provides a decorator-based registry where each\nrenderer declares the format names it handles.  Adding a new format only\nrequires writing a renderer function and decorating it — no changes to\n``__format__``, ``factory.py``, or ``statemachine.py``.\n\nA module-level :data:`formatter` instance is the single public entry point::\n\n    from statemachine.contrib.diagram import formatter\n\n    print(formatter.render(sm, \"mermaid\"))\n\n    @formatter.register_format(\"plantuml\")\n    def _render_plantuml(machine):\n        ...\n\"\"\"\n\nfrom typing import TYPE_CHECKING\nfrom typing import Callable\nfrom typing import Dict\nfrom typing import List\n\nif TYPE_CHECKING:\n    from typing import Union\n\n    from statemachine.statemachine import StateChart\n\n    MachineRef = Union[\"StateChart\", \"type[StateChart]\"]\n\n\nclass Formatter:\n    \"\"\"Unified facade for rendering state machines in multiple text formats.\"\"\"\n\n    def __init__(self) -> None:\n        self._formats: Dict[str, \"Callable[[MachineRef], str]\"] = {}\n\n    def register_format(\n        self, *names: str\n    ) -> \"Callable[[Callable[[MachineRef], str]], Callable[[MachineRef], str]]\":\n        \"\"\"Decorator factory that registers a renderer under one or more format names.\n\n        Usage::\n\n            @formatter.register_format(\"md\", \"markdown\")\n            def _render_md(machine_or_class):\n                ...\n        \"\"\"\n\n        def decorator(\n            fn: \"Callable[[MachineRef], str]\",\n        ) -> \"Callable[[MachineRef], str]\":\n            for name in names:\n                self._formats[name] = fn\n            return fn\n\n        return decorator\n\n    def render(self, machine_or_class: \"MachineRef\", fmt: str) -> str:\n        \"\"\"Render a state machine in the given text format.\n\n        Args:\n            machine_or_class: A ``StateChart`` instance or class.\n            fmt: Format name (e.g., ``\"mermaid\"``, ``\"dot\"``, ``\"md\"``).\n                Empty string falls back to ``repr()``.\n\n        Raises:\n            ValueError: If ``fmt`` is not registered.\n        \"\"\"\n        if fmt == \"\":\n            return repr(machine_or_class)\n\n        renderer_fn = self._formats.get(fmt)\n        if renderer_fn is None:\n            primary = sorted({self._primary_name(fn) for fn in set(self._formats.values())})\n            raise ValueError(\n                f\"Unsupported format: {fmt!r}. Use {', '.join(repr(n) for n in primary)}.\"\n            )\n        return renderer_fn(machine_or_class)\n\n    def supported_formats(self) -> List[str]:\n        \"\"\"Return sorted list of all registered format names (including aliases).\"\"\"\n        return sorted(self._formats)\n\n    def _primary_name(self, fn: \"Callable[[MachineRef], str]\") -> str:\n        \"\"\"Return the first registered name for a given renderer function.\"\"\"\n        for name, registered_fn in self._formats.items():\n            if registered_fn is fn:\n                return name\n        return \"?\"  # pragma: no cover\n\n\nformatter = Formatter()\n\"\"\"Module-level :class:`Formatter` instance — the single public entry point.\"\"\"\n\n\n# ---------------------------------------------------------------------------\n# Built-in format registrations\n# ---------------------------------------------------------------------------\n\n\n@formatter.register_format(\"dot\")\ndef _render_dot(machine_or_class: \"MachineRef\") -> str:\n    from statemachine.contrib.diagram import DotGraphMachine\n\n    return DotGraphMachine(machine_or_class).get_graph().to_string()  # type: ignore[no-any-return]\n\n\n@formatter.register_format(\"svg\")\ndef _render_svg(machine_or_class: \"MachineRef\") -> str:\n    from statemachine.contrib.diagram import DotGraphMachine\n\n    svg_bytes: bytes = DotGraphMachine(machine_or_class).get_graph().create_svg()  # type: ignore[attr-defined]\n    return svg_bytes.decode(\"utf-8\")\n\n\n@formatter.register_format(\"mermaid\")\ndef _render_mermaid(machine_or_class: \"MachineRef\") -> str:\n    from statemachine.contrib.diagram import MermaidGraphMachine\n\n    return MermaidGraphMachine(machine_or_class).get_mermaid()\n\n\n@formatter.register_format(\"md\", \"markdown\")\ndef _render_md(machine_or_class: \"MachineRef\") -> str:\n    from statemachine.contrib.diagram.extract import extract\n    from statemachine.contrib.diagram.renderers.table import TransitionTableRenderer\n\n    return TransitionTableRenderer().render(extract(machine_or_class), fmt=\"md\")\n\n\n@formatter.register_format(\"rst\")\ndef _render_rst(machine_or_class: \"MachineRef\") -> str:\n    from statemachine.contrib.diagram.extract import extract\n    from statemachine.contrib.diagram.renderers.table import TransitionTableRenderer\n\n    return TransitionTableRenderer().render(extract(machine_or_class), fmt=\"rst\")\n"
  },
  {
    "path": "statemachine/contrib/diagram/model.py",
    "content": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom enum import Enum\nfrom typing import List\nfrom typing import Set\n\n\nclass StateType(Enum):\n    INITIAL = \"initial\"\n    REGULAR = \"regular\"\n    FINAL = \"final\"\n    HISTORY_SHALLOW = \"history_shallow\"\n    HISTORY_DEEP = \"history_deep\"\n    CHOICE = \"choice\"\n    FORK = \"fork\"\n    JOIN = \"join\"\n    JUNCTION = \"junction\"\n    PARALLEL = \"parallel\"\n    TERMINATE = \"terminate\"\n\n\nclass ActionType(Enum):\n    ENTRY = \"entry\"\n    EXIT = \"exit\"\n    INTERNAL = \"internal\"\n\n\n@dataclass\nclass DiagramAction:\n    type: ActionType\n    body: str\n\n\n@dataclass\nclass DiagramState:\n    id: str\n    name: str\n    type: StateType\n    actions: List[DiagramAction] = field(default_factory=list)\n    children: List[\"DiagramState\"] = field(default_factory=list)\n    is_active: bool = False\n    is_parallel_area: bool = False\n    is_initial: bool = False\n\n\n@dataclass\nclass DiagramTransition:\n    source: str\n    targets: List[str] = field(default_factory=list)\n    event: str = \"\"\n    guards: List[str] = field(default_factory=list)\n    actions: List[str] = field(default_factory=list)\n    is_internal: bool = False\n    is_initial: bool = False\n\n\n@dataclass\nclass DiagramGraph:\n    name: str\n    states: List[DiagramState] = field(default_factory=list)\n    transitions: List[DiagramTransition] = field(default_factory=list)\n    compound_state_ids: Set[str] = field(default_factory=set)\n    bidirectional_compound_ids: Set[str] = field(default_factory=set)\n"
  },
  {
    "path": "statemachine/contrib/diagram/renderers/__init__.py",
    "content": ""
  },
  {
    "path": "statemachine/contrib/diagram/renderers/dot.py",
    "content": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom typing import Dict\nfrom typing import List\nfrom typing import Optional\nfrom typing import Set\n\nimport pydot\n\nfrom ..model import ActionType\nfrom ..model import DiagramAction\nfrom ..model import DiagramGraph\nfrom ..model import DiagramState\nfrom ..model import DiagramTransition\nfrom ..model import StateType\n\n\ndef _escape_html(text: str) -> str:\n    \"\"\"Escape text for use inside HTML labels.\"\"\"\n    return text.replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\").replace(\">\", \"&gt;\")\n\n\n@dataclass\nclass DotRendererConfig:\n    \"\"\"Configuration for the DOT renderer, matching DotGraphMachine's class attributes.\"\"\"\n\n    graph_rankdir: str = \"LR\"\n    font_name: str = \"Helvetica\"\n    state_font_size: str = \"12\"\n    state_active_penwidth: int = 2\n    state_active_fillcolor: str = \"turquoise\"\n    transition_font_size: str = \"10\"\n    graph_attrs: Dict[str, str] = field(default_factory=dict)\n    node_attrs: Dict[str, str] = field(default_factory=dict)\n    edge_attrs: Dict[str, str] = field(default_factory=dict)\n\n\nclass DotRenderer:\n    \"\"\"Renders a DiagramGraph into a pydot.Dot graph with UML-inspired styling.\n\n    Uses techniques inspired by state-machine-cat for cleaner visual output:\n    - HTML TABLE labels for states with UML compartments\n    - plaintext nodes with near-transparent fill\n    - Refined graph/node/edge defaults\n    \"\"\"\n\n    def __init__(self, config: Optional[DotRendererConfig] = None):\n        self.config = config or DotRendererConfig()\n        self._compound_ids: Set[str] = set()\n        self._compound_bidir_ids: Set[str] = set()\n\n    def render(self, graph: DiagramGraph) -> pydot.Dot:\n        \"\"\"Render a DiagramGraph to a pydot.Dot object.\"\"\"\n        self._compound_ids = graph.compound_state_ids\n        self._compound_bidir_ids = graph.bidirectional_compound_ids\n        dot = self._create_graph(graph.name)\n        self._render_states(graph.states, graph.transitions, dot)\n        return dot\n\n    def _create_graph(self, name: str) -> pydot.Dot:\n        cfg = self.config\n        graph_attrs = {\n            \"fontname\": cfg.font_name,\n            \"fontsize\": cfg.state_font_size,\n            \"penwidth\": \"2.0\",\n            \"splines\": \"true\",\n            \"ordering\": \"out\",\n            \"compound\": \"true\",\n            \"nodesep\": \"0.3\",\n            \"ranksep\": \"0.3\",\n            \"forcelabels\": \"true\",\n        }\n        graph_attrs.update(cfg.graph_attrs)\n\n        dot = pydot.Dot(\n            name,\n            graph_type=\"digraph\",\n            label=name,\n            rankdir=cfg.graph_rankdir,\n            **graph_attrs,\n        )\n\n        # Set default node attributes\n        node_defaults = {\n            \"fontname\": cfg.font_name,\n            \"fontsize\": cfg.state_font_size,\n            \"penwidth\": \"2.0\",\n        }\n        node_defaults.update(cfg.node_attrs)\n        dot.set_node_defaults(**node_defaults)\n\n        # Set default edge attributes\n        edge_defaults = {\n            \"fontname\": cfg.font_name,\n            \"fontsize\": cfg.transition_font_size,\n            \"labeldistance\": \"1.5\",\n        }\n        edge_defaults.update(cfg.edge_attrs)\n        dot.set_edge_defaults(**edge_defaults)\n\n        return dot\n\n    def _state_node_id(self, state_id: str) -> str:\n        \"\"\"Get the node ID to use for edges. Compound states use an anchor node.\"\"\"\n        if state_id in self._compound_ids:\n            return f\"{state_id}_anchor\"\n        return state_id\n\n    def _compound_edge_anchor(self, state_id: str, direction: str) -> str:\n        \"\"\"Return the appropriate anchor node ID for a compound ↔ other edge.\n\n        Compound states that have both incoming and outgoing explicit transitions\n        get separate ``_anchor_out`` / ``_anchor_in`` nodes so Graphviz can route\n        the two directions through physically distinct points, avoiding overlap.\n        \"\"\"\n        if state_id in self._compound_bidir_ids:\n            return f\"{state_id}_anchor_{direction}\"\n        return f\"{state_id}_anchor\"\n\n    def _render_states(\n        self,\n        states: List[DiagramState],\n        transitions: List[DiagramTransition],\n        parent_graph: \"pydot.Dot | pydot.Subgraph\",\n        extra_nodes: Optional[List[pydot.Node]] = None,\n    ) -> None:\n        \"\"\"Render states and transitions into the parent graph.\"\"\"\n        initial_state = next((s for s in states if s.is_initial), None)\n\n        # The atomic subgraph groups all non-compound states and the inner\n        # initial dot (when inside a compound cluster) so Graphviz places them\n        # in the same rank region, keeping the initial arrow short.\n        atomic_subgraph = pydot.Subgraph(\n            graph_name=f\"cluster___atomic_{id(parent_graph)}\",\n            label=\"\",\n            peripheries=0,\n            margin=0,\n            cluster=\"true\",\n        )\n        has_atomic = False\n\n        if initial_state:\n            has_atomic = (\n                self._render_initial_arrow(initial_state, parent_graph, atomic_subgraph)\n                or has_atomic\n            )\n\n        for state in states:\n            if state.type in (StateType.HISTORY_SHALLOW, StateType.HISTORY_DEEP):\n                atomic_subgraph.add_node(self._create_history_node(state))\n                has_atomic = True\n            elif state.children:\n                subgraph = self._create_compound_subgraph(state)\n                anchor_nodes = self._create_compound_anchor_nodes(state)\n                self._render_states(\n                    state.children, transitions, subgraph, extra_nodes=anchor_nodes\n                )\n                parent_graph.add_subgraph(subgraph)\n                # Add transitions originating from this compound state\n                self._add_transitions_for_state(state, transitions, parent_graph)\n            else:\n                atomic_subgraph.add_node(self._create_atomic_node(state))\n                has_atomic = True\n\n        has_atomic = self._place_extra_nodes(\n            extra_nodes, atomic_subgraph, parent_graph, has_atomic\n        )\n\n        if has_atomic:\n            parent_graph.add_subgraph(atomic_subgraph)\n\n        # Add transitions for atomic/history states\n        for state in states:\n            if not state.children:\n                self._add_transitions_for_state(state, transitions, parent_graph)\n\n    @staticmethod\n    def _place_extra_nodes(\n        extra_nodes: Optional[List[pydot.Node]],\n        atomic_subgraph: pydot.Subgraph,\n        parent_graph: \"pydot.Dot | pydot.Subgraph\",\n        has_atomic: bool,\n    ) -> bool:\n        \"\"\"Place anchor nodes from the parent compound into the graph.\n\n        Co-locates them with real states when possible. If there are no atomic\n        states at this level (e.g. a parallel state with only compound children),\n        adds them directly to the parent graph to avoid an empty cluster.\n\n        Returns the updated ``has_atomic`` flag.\n        \"\"\"\n        if not extra_nodes:\n            return has_atomic\n        target = atomic_subgraph if has_atomic else parent_graph\n        for node in extra_nodes:\n            target.add_node(node)\n        return has_atomic or (target is atomic_subgraph)\n\n    def _render_initial_arrow(\n        self,\n        initial_state: DiagramState,\n        parent_graph: \"pydot.Dot | pydot.Subgraph\",\n        atomic_subgraph: pydot.Subgraph,\n    ) -> bool:\n        \"\"\"Render the black-dot initial arrow pointing to ``initial_state``.\n\n        Returns True if nodes were added to ``atomic_subgraph``.\n        \"\"\"\n        initial_node_id = f\"__initial_{id(parent_graph)}\"\n        initial_node = self._create_initial_node(initial_node_id)\n        added_to_atomic = False\n\n        extra = {}\n        if initial_state.children:\n            extra[\"lhead\"] = f\"cluster_{initial_state.id}\"\n\n        if initial_state.children or isinstance(parent_graph, pydot.Dot):\n            # Compound initial state, or top-level atomic initial state:\n            # keep the dot in a plain wrapper subgraph attached to parent.\n            wrapper = pydot.Subgraph(\n                graph_name=f\"{initial_node_id}_sg\",\n                label=\"\",\n                peripheries=0,\n                margin=0,\n            )\n            wrapper.add_node(initial_node)\n            parent_graph.add_subgraph(wrapper)\n        else:\n            # Inner (compound parent) with atomic initial state: add the\n            # dot directly into the atomic cluster so it shares the same\n            # rank region as the target state, avoiding a long arrow caused\n            # by the compound cluster's anchor nodes pushing step1 further.\n            atomic_subgraph.add_node(initial_node)\n            added_to_atomic = True\n\n        parent_graph.add_edge(\n            pydot.Edge(\n                initial_node_id,\n                self._state_node_id(initial_state.id),\n                label=\"\",\n                minlen=1,\n                weight=100,\n                **extra,\n            )\n        )\n        return added_to_atomic\n\n    def _create_initial_node(self, node_id: str) -> pydot.Node:\n        return pydot.Node(\n            node_id,\n            label=\"\",\n            shape=\"circle\",\n            style=\"filled\",\n            fillcolor=\"black\",\n            color=\"black\",\n            fixedsize=\"true\",\n            width=0.15,\n            height=0.15,\n            penwidth=\"0\",\n        )\n\n    def _create_atomic_node(self, state: DiagramState) -> pydot.Node:\n        \"\"\"Create a node for an atomic state.\n\n        All states use a native ``shape=\"rectangle\"`` with ``style=\"rounded, filled\"``\n        so that Graphviz clips edges at the actual rounded border.  States with\n        entry/exit actions embed an HTML TABLE (``border=\"0\"``) inside the native\n        shape to render UML-style compartments (name + separator + actions).\n        \"\"\"\n        actions = [a for a in state.actions if a.type != ActionType.INTERNAL or a.body]\n        fillcolor = self.config.state_active_fillcolor if state.is_active else \"white\"\n        penwidth = self.config.state_active_penwidth if state.is_active else 2\n\n        if not actions:\n            # Simple state: native rounded rectangle\n            node = pydot.Node(\n                state.id,\n                label=state.name,\n                shape=\"rectangle\",\n                style=\"rounded, filled\",\n                fontname=self.config.font_name,\n                fontsize=self.config.state_font_size,\n                fillcolor=fillcolor,\n                penwidth=penwidth,\n                peripheries=2 if state.type == StateType.FINAL else 1,\n            )\n        else:\n            # State with actions: native shape + HTML TABLE label (border=0).\n            # The native shape handles edge clipping; the TABLE provides\n            # UML compartment layout with <hr/> separator.\n            label = self._build_html_table_label(state, actions)\n            node = pydot.Node(\n                state.id,\n                label=f\"<{label}>\",\n                shape=\"rectangle\",\n                style=\"rounded, filled\",\n                fontname=self.config.font_name,\n                fontsize=self.config.state_font_size,\n                fillcolor=fillcolor,\n                penwidth=penwidth,\n                margin=\"0\",\n                peripheries=2 if state.type == StateType.FINAL else 1,\n            )\n\n        return node\n\n    def _build_html_table_label(\n        self,\n        state: DiagramState,\n        actions: List[DiagramAction],\n    ) -> str:\n        \"\"\"Build an HTML TABLE label with UML compartments (name | actions).\n\n        The TABLE has ``border=\"0\"`` because the visible border is drawn by\n        the native Graphviz shape, ensuring edges are clipped correctly.\n        \"\"\"\n        name = _escape_html(state.name)\n        font_size = self.config.state_font_size\n        action_font_size = self.config.transition_font_size\n\n        action_lines = \"<br/>\".join(\n            f'<font point-size=\"{action_font_size}\">{_escape_html(self._format_action(a))}</font>'\n            for a in actions\n        )\n\n        return (\n            f'<table border=\"0\" cellborder=\"0\" cellspacing=\"0\" cellpadding=\"0\">'\n            f'<tr><td cellpadding=\"4\">'\n            f'<font point-size=\"{font_size}\">{name}</font>'\n            f\"</td></tr>\"\n            f\"<hr/>\"\n            f'<tr><td align=\"left\" cellpadding=\"6\">'\n            f\"{action_lines}\"\n            f\"</td></tr>\"\n            f\"</table>\"\n        )\n\n    @staticmethod\n    def _format_action(action: DiagramAction) -> str:\n        if action.type == ActionType.INTERNAL:\n            return action.body\n        return f\"{action.type.value} / {action.body}\"\n\n    def _create_history_node(self, state: DiagramState) -> pydot.Node:\n        label = \"H*\" if state.type == StateType.HISTORY_DEEP else \"H\"\n        return pydot.Node(\n            state.id,\n            label=label,\n            shape=\"circle\",\n            style=\"filled\",\n            fillcolor=\"white\",\n            fontname=self.config.font_name,\n            fontsize=\"8pt\",\n            fixedsize=\"true\",\n            width=0.3,\n            height=0.3,\n        )\n\n    def _create_compound_anchor_nodes(self, state: DiagramState) -> List[pydot.Node]:\n        \"\"\"Create invisible anchor nodes for edge routing inside a compound cluster.\n\n        These nodes are injected into the children's atomic_subgraph so they\n        share the same layout row as the real states, avoiding blank space at\n        the top of the compound cluster.\n        \"\"\"\n        # For bidirectional compounds, all edges route through _anchor_in/_anchor_out;\n        # the generic _anchor node is never used and would become an orphan that\n        # Graphviz places arbitrarily, creating blank vertical space in the cluster.\n        if state.id not in self._compound_bidir_ids:\n            nodes = [\n                pydot.Node(\n                    f\"{state.id}_anchor\",\n                    shape=\"point\",\n                    style=\"invis\",\n                    width=0,\n                    height=0,\n                    fixedsize=\"true\",\n                )\n            ]\n        else:\n            nodes = []\n            for direction in (\"in\", \"out\"):\n                nodes.append(\n                    pydot.Node(\n                        f\"{state.id}_anchor_{direction}\",\n                        shape=\"point\",\n                        style=\"invis\",\n                        width=0,\n                        height=0,\n                        fixedsize=\"true\",\n                    )\n                )\n        return nodes\n\n    def _create_compound_subgraph(self, state: DiagramState) -> pydot.Subgraph:\n        \"\"\"Create a cluster subgraph for a compound/parallel state.\"\"\"\n        style = \"rounded, solid\"\n        if state.is_parallel_area:\n            style = \"rounded, dashed\"\n\n        label = self._build_compound_label(state)\n\n        return pydot.Subgraph(\n            graph_name=f\"cluster_{state.id}\",\n            label=f\"<{label}>\",\n            style=style,\n            cluster=\"true\",\n            penwidth=\"2.0\",\n            fontname=self.config.font_name,\n            fontsize=self.config.state_font_size,\n            margin=\"4\",\n        )\n\n    def _build_compound_label(self, state: DiagramState) -> str:\n        \"\"\"Build HTML label for a compound/parallel subgraph.\"\"\"\n        name = _escape_html(state.name)\n        if state.type == StateType.PARALLEL:\n            return f\"<b>{name}</b> &#9783;\"\n\n        actions = [a for a in state.actions if a.type != ActionType.INTERNAL or a.body]\n        if not actions:\n            return f\"<b>{name}</b>\"\n\n        rows = [f\"<b>{name}</b>\"]\n        for action in actions:\n            action_text = _escape_html(self._format_action(action))\n            rows.append(\n                f'<font point-size=\"{self.config.transition_font_size}\">{action_text}</font>'\n            )\n        return \"<br/>\".join(rows)\n\n    def _add_transitions_for_state(\n        self,\n        state: DiagramState,\n        all_transitions: List[DiagramTransition],\n        graph: \"pydot.Dot | pydot.Subgraph\",\n    ) -> None:\n        \"\"\"Add edges for all non-internal transitions originating from this state.\"\"\"\n        for transition in all_transitions:\n            if transition.source != state.id or transition.is_internal:\n                continue\n            # Skip implicit initial transitions — represented by the black-dot initial node.\n            if transition.is_initial:\n                continue\n            for edge in self._create_edges(transition):\n                graph.add_edge(edge)\n\n    def _create_edges(self, transition: DiagramTransition) -> List[pydot.Edge]:\n        \"\"\"Create pydot.Edge objects for a transition.\"\"\"\n        target_ids: List[Optional[str]] = (\n            list(transition.targets) if transition.targets else [None]\n        )\n\n        cond = \", \".join(transition.guards)\n        cond_html = f\"<br/>[{_escape_html(cond)}]\" if cond else \"\"\n\n        return [\n            self._create_single_edge(transition, target_id, i, cond_html)\n            for i, target_id in enumerate(target_ids)\n        ]\n\n    def _create_single_edge(\n        self,\n        transition: DiagramTransition,\n        target_id: Optional[str],\n        index: int,\n        cond_html: str,\n    ) -> pydot.Edge:\n        \"\"\"Create a single pydot.Edge for one target of a transition.\"\"\"\n        src, dst, extra = self._resolve_edge_endpoints(transition, target_id)\n        has_substates = bool(extra)\n        html_label = self._build_edge_label(transition.event, cond_html, index)\n\n        return pydot.Edge(\n            src,\n            dst,\n            label=html_label,\n            minlen=2 if has_substates else 1,\n            **extra,\n        )\n\n    def _resolve_edge_endpoints(\n        self,\n        transition: DiagramTransition,\n        target_id: Optional[str],\n    ) -> \"tuple[str, str, Dict[str, str]]\":\n        \"\"\"Resolve source/destination node IDs and cluster attributes for an edge.\"\"\"\n        extra: Dict[str, str] = {}\n        source_is_compound = transition.source in self._compound_ids\n        target_is_compound = target_id is not None and target_id in self._compound_ids\n\n        if source_is_compound:\n            extra[\"ltail\"] = f\"cluster_{transition.source}\"\n        if target_is_compound:\n            extra[\"lhead\"] = f\"cluster_{target_id}\"\n\n        dst = (\n            self._state_node_id(target_id)\n            if target_id is not None\n            else self._state_node_id(transition.source)\n        )\n        src = self._state_node_id(transition.source)\n\n        # For compound states in bidirectional pairs, route outgoing edges\n        # through _anchor_out and incoming through _anchor_in so Graphviz\n        # places them at different physical positions inside the cluster.\n        if source_is_compound and transition.source in self._compound_bidir_ids:\n            src = self._compound_edge_anchor(transition.source, \"out\")\n            extra[\"ltail\"] = f\"cluster_{transition.source}\"\n        if target_is_compound and target_id in self._compound_bidir_ids:\n            dst = self._compound_edge_anchor(target_id, \"in\")\n            extra[\"lhead\"] = f\"cluster_{target_id}\"\n\n        return src, dst, extra\n\n    def _build_edge_label(self, event: str, cond_html: str, index: int) -> str:\n        \"\"\"Build the HTML label for a transition edge.\"\"\"\n        event_text = _escape_html(event) if index == 0 else \"\"\n        if not event_text and not (cond_html and index == 0):\n            return \"\"\n\n        label_content = f\"{event_text}{cond_html}\" if index == 0 else \"\"\n        font_size = self.config.transition_font_size\n        return (\n            f'<<table border=\"0\" cellborder=\"0\" cellspacing=\"0\" cellpadding=\"0\">'\n            f'<tr><td cellpadding=\"4\">'\n            f'<font point-size=\"{font_size}\">{label_content}</font>'\n            f\"</td></tr>\"\n            f'<tr><td cellpadding=\"1\"></td></tr>'\n            f\"</table>>\"\n        )\n"
  },
  {
    "path": "statemachine/contrib/diagram/renderers/mermaid.py",
    "content": "from dataclasses import dataclass\nfrom typing import Dict\nfrom typing import List\nfrom typing import Optional\nfrom typing import Set\n\nfrom ..model import ActionType\nfrom ..model import DiagramAction\nfrom ..model import DiagramGraph\nfrom ..model import DiagramState\nfrom ..model import DiagramTransition\nfrom ..model import StateType\n\n\n@dataclass\nclass MermaidRendererConfig:\n    \"\"\"Configuration for the Mermaid renderer.\"\"\"\n\n    direction: str = \"LR\"\n    active_fill: str = \"#40E0D0\"\n    active_stroke: str = \"#333\"\n\n\nclass MermaidRenderer:\n    \"\"\"Renders a DiagramGraph into a Mermaid stateDiagram-v2 source string.\n\n    Mermaid's stateDiagram-v2 has a rendering bug\n    (`mermaid-js/mermaid#4052 <https://github.com/mermaid-js/mermaid/issues/4052>`_)\n    where transitions whose source or target is a compound state\n    (``state X { ... }``) **inside a parallel region** crash with\n    ``Cannot set properties of undefined (setting 'rank')``.  To work around\n    this, the renderer rewrites compound-state endpoints that are descendants\n    of a parallel state, redirecting them to the compound's initial child.\n    Compound states outside parallel regions are left unchanged.\n    \"\"\"\n\n    def __init__(self, config: Optional[MermaidRendererConfig] = None):\n        self.config = config or MermaidRendererConfig()\n        self._active_ids: List[str] = []\n        self._rendered_transitions: Set[tuple] = set()\n        self._compound_ids: Set[str] = set()\n        self._initial_child_map: Dict[str, str] = {}\n        self._parallel_descendant_ids: Set[str] = set()\n        self._all_descendants_map: Dict[str, Set[str]] = {}\n\n    def render(self, graph: DiagramGraph) -> str:\n        \"\"\"Render a DiagramGraph to a Mermaid stateDiagram-v2 string.\"\"\"\n        self._active_ids = []\n        self._rendered_transitions = set()\n        self._compound_ids = graph.compound_state_ids\n        self._initial_child_map = self._build_initial_child_map(graph.states)\n        self._parallel_descendant_ids = self._collect_parallel_descendants(graph.states)\n        self._all_descendants_map = self._build_all_descendants_map(graph.states)\n\n        lines: List[str] = []\n        lines.append(\"stateDiagram-v2\")\n        lines.append(f\"    direction {self.config.direction}\")\n\n        top_ids = {s.id for s in graph.states}\n        self._render_states(graph.states, graph.transitions, lines, indent=1)\n        self._render_initial_and_final(graph.states, lines, indent=1)\n        self._render_scope_transitions(graph.transitions, top_ids, lines, indent=1)\n\n        if self._active_ids:\n            cfg = self.config\n            lines.append(\"\")\n            lines.append(f\"    classDef active fill:{cfg.active_fill},stroke:{cfg.active_stroke}\")\n            for sid in self._active_ids:\n                lines.append(f\"    {sid}:::active\")\n\n        return \"\\n\".join(lines) + \"\\n\"\n\n    def _build_initial_child_map(self, states: List[DiagramState]) -> Dict[str, str]:\n        \"\"\"Build a map from compound state ID to its initial child ID (recursive).\"\"\"\n        result: Dict[str, str] = {}\n        for state in states:\n            if state.children:\n                initial = next((c for c in state.children if c.is_initial), None)\n                if initial:\n                    result[state.id] = initial.id\n                result.update(self._build_initial_child_map(state.children))\n        return result\n\n    @staticmethod\n    def _collect_parallel_descendants(\n        states: List[DiagramState],\n        inside_parallel: bool = False,\n    ) -> Set[str]:\n        \"\"\"Collect IDs of all states that are descendants of a parallel state.\"\"\"\n        result: Set[str] = set()\n        for state in states:\n            if inside_parallel:\n                result.add(state.id)\n            child_inside = inside_parallel or state.type == StateType.PARALLEL\n            result.update(\n                MermaidRenderer._collect_parallel_descendants(state.children, child_inside)\n            )\n        return result\n\n    def _build_all_descendants_map(self, states: List[DiagramState]) -> Dict[str, Set[str]]:\n        \"\"\"Map each compound state ID to the set of all its descendant IDs.\"\"\"\n        result: Dict[str, Set[str]] = {}\n        for state in states:\n            if state.children:\n                result[state.id] = self._collect_recursive_descendants(state.children)\n                result.update(self._build_all_descendants_map(state.children))\n        return result\n\n    @staticmethod\n    def _collect_recursive_descendants(states: List[DiagramState]) -> Set[str]:\n        \"\"\"Collect all state IDs in a subtree recursively.\"\"\"\n        ids: Set[str] = set()\n        for s in states:\n            ids.add(s.id)\n            ids.update(MermaidRenderer._collect_recursive_descendants(s.children))\n        return ids\n\n    def _resolve_endpoint(self, state_id: str) -> str:\n        \"\"\"Resolve a transition endpoint for Mermaid compatibility.\n\n        Only redirects compound states that are inside a parallel region —\n        this is where Mermaid's rendering bug (mermaid-js/mermaid#4052) occurs.\n        Compound states outside parallel regions are left unchanged.\n        \"\"\"\n        if (\n            state_id in self._compound_ids\n            and state_id in self._parallel_descendant_ids\n            and state_id in self._initial_child_map\n        ):\n            return self._initial_child_map[state_id]\n        return state_id\n\n    def _render_states(\n        self,\n        states: List[DiagramState],\n        transitions: List[DiagramTransition],\n        lines: List[str],\n        indent: int,\n    ) -> None:\n        for state in states:\n            if state.type in (StateType.HISTORY_SHALLOW, StateType.HISTORY_DEEP):\n                label = \"H*\" if state.type == StateType.HISTORY_DEEP else \"H\"\n                pad = \"    \" * indent\n                lines.append(f'{pad}state \"{label}\" as {state.id}')\n                continue\n\n            if state.type == StateType.CHOICE:\n                pad = \"    \" * indent\n                lines.append(f\"{pad}state {state.id} <<choice>>\")\n                continue\n\n            if state.type == StateType.FORK:\n                pad = \"    \" * indent\n                lines.append(f\"{pad}state {state.id} <<fork>>\")\n                continue\n\n            if state.type == StateType.JOIN:\n                pad = \"    \" * indent\n                lines.append(f\"{pad}state {state.id} <<join>>\")\n                continue\n\n            if state.children:\n                self._render_compound_state(state, transitions, lines, indent)\n            else:\n                self._render_atomic_state(state, lines, indent)\n\n    def _render_atomic_state(\n        self,\n        state: DiagramState,\n        lines: List[str],\n        indent: int,\n    ) -> None:\n        pad = \"    \" * indent\n\n        if state.name != state.id:\n            lines.append(f'{pad}state \"{state.name}\" as {state.id}')\n\n        actions = [a for a in state.actions if a.type != ActionType.INTERNAL or a.body]\n        if actions:\n            for action in actions:\n                lines.append(f\"{pad}{state.id} : {self._format_action(action)}\")\n\n        if state.is_active:\n            self._active_ids.append(state.id)\n\n    def _render_compound_state(\n        self,\n        state: DiagramState,\n        transitions: List[DiagramTransition],\n        lines: List[str],\n        indent: int,\n    ) -> None:\n        pad = \"    \" * indent\n\n        if state.type == StateType.PARALLEL:\n            lines.append(f'{pad}state \"{state.name}\" as {state.id} {{')\n            regions = [c for c in state.children if c.is_parallel_area or c.children]\n            for i, region in enumerate(regions):\n                if i > 0:\n                    lines.append(f\"{pad}    --\")\n                self._render_compound_state(region, transitions, lines, indent + 1)\n            lines.append(f\"{pad}}}\")\n        else:\n            label = state.name if state.name != state.id else \"\"\n            if label:\n                lines.append(f'{pad}state \"{label}\" as {state.id} {{')\n            else:\n                lines.append(f\"{pad}state {state.id} {{\")\n\n            initial_child = next((c for c in state.children if c.is_initial), None)\n            if initial_child:\n                lines.append(f\"{pad}    [*] --> {initial_child.id}\")\n\n            self._render_states(state.children, transitions, lines, indent + 1)\n\n            # Render transitions scoped to this compound\n            child_ids = self._collect_all_descendant_ids(state.children)\n            self._render_scope_transitions(transitions, child_ids, lines, indent + 1)\n\n            # Final state transitions\n            for child in state.children:\n                if child.type == StateType.FINAL:\n                    lines.append(f\"{pad}    {child.id} --> [*]\")\n\n            lines.append(f\"{pad}}}\")\n\n        if state.is_active:\n            self._active_ids.append(state.id)\n\n    def _collect_all_descendant_ids(self, states: List[DiagramState]) -> Set[str]:\n        \"\"\"Collect all state IDs in a subtree (direct children only for scope).\"\"\"\n        ids: Set[str] = set()\n        for s in states:\n            ids.add(s.id)\n        return ids\n\n    def _render_scope_transitions(\n        self,\n        transitions: List[DiagramTransition],\n        scope_ids: Set[str],\n        lines: List[str],\n        indent: int,\n    ) -> None:\n        \"\"\"Render transitions that belong to this scope level.\n\n        A transition belongs to scope S if all its endpoints are *reachable*\n        from S (either directly in S or descendants of a compound in S) **and**\n        the transition is not fully internal to a single compound in S (those\n        are rendered by the compound's inner scope).\n\n        This allows cross-boundary transitions (e.g., an outer state targeting\n        a history pseudo-state inside a compound) to be rendered at the correct\n        scope level — Mermaid draws the arrow crossing the compound border.\n\n        Mermaid crashes when the source or target is a compound state inside a\n        parallel region (mermaid-js/mermaid#4052).  For those cases, endpoints\n        are redirected to the compound's initial child via ``_resolve_endpoint``.\n        \"\"\"\n        # Build the descendant sets for compounds in this scope\n        compound_descendants: Dict[str, Set[str]] = {}\n        expanded: Set[str] = set(scope_ids)\n        for sid in scope_ids:\n            if sid in self._all_descendants_map:\n                compound_descendants[sid] = self._all_descendants_map[sid]\n                expanded |= self._all_descendants_map[sid]\n\n        for t in transitions:\n            if t.is_initial or t.is_internal:\n                continue\n\n            targets = t.targets if t.targets else [t.source]\n\n            # All endpoints must be reachable from this scope\n            if t.source not in expanded:\n                continue\n            if not all(target in expanded for target in targets):\n                continue\n\n            # Skip transitions fully internal to a single compound —\n            # those will be rendered by the compound's inner scope.\n            if self._is_fully_internal(t.source, targets, compound_descendants):\n                continue\n\n            # Resolve endpoints for rendering (redirect compound → initial child)\n            source = self._resolve_endpoint(t.source)\n            resolved_targets = [self._resolve_endpoint(tid) for tid in targets]\n\n            for target in resolved_targets:\n                key = (source, target, t.event)\n                if key in self._rendered_transitions:\n                    continue\n                self._rendered_transitions.add(key)\n                self._render_single_transition(t, source, target, lines, indent)\n\n    @staticmethod\n    def _is_fully_internal(\n        source: str,\n        targets: List[str],\n        compound_descendants: Dict[str, Set[str]],\n    ) -> bool:\n        \"\"\"Check if all endpoints belong to the same compound's descendants.\"\"\"\n        for descendants in compound_descendants.values():\n            if source in descendants and all(tgt in descendants for tgt in targets):\n                return True\n        return False\n\n    def _render_single_transition(\n        self,\n        transition: DiagramTransition,\n        source: str,\n        target: str,\n        lines: List[str],\n        indent: int,\n    ) -> None:\n        pad = \"    \" * indent\n        label_parts: List[str] = []\n        if transition.event:\n            label_parts.append(transition.event)\n        if transition.guards:\n            label_parts.append(f\"[{', '.join(transition.guards)}]\")\n\n        label = \" \".join(label_parts)\n        if label:\n            lines.append(f\"{pad}{source} --> {target} : {label}\")\n        else:\n            lines.append(f\"{pad}{source} --> {target}\")\n\n    @staticmethod\n    def _format_action(action: DiagramAction) -> str:\n        if action.type == ActionType.INTERNAL:\n            return action.body\n        return f\"{action.type.value} / {action.body}\"\n\n    def _render_initial_and_final(\n        self,\n        states: List[DiagramState],\n        lines: List[str],\n        indent: int,\n    ) -> None:\n        \"\"\"Render top-level [*] --> initial and final --> [*] arrows.\"\"\"\n        pad = \"    \" * indent\n        initial = next((s for s in states if s.is_initial), None)\n        if initial:\n            lines.append(f\"{pad}[*] --> {initial.id}\")\n\n        for state in states:\n            if state.type == StateType.FINAL:\n                lines.append(f\"{pad}{state.id} --> [*]\")\n"
  },
  {
    "path": "statemachine/contrib/diagram/renderers/table.py",
    "content": "from typing import List\n\nfrom ..model import DiagramGraph\nfrom ..model import DiagramState\nfrom ..model import DiagramTransition\n\n\nclass TransitionTableRenderer:\n    \"\"\"Renders a DiagramGraph as a transition table in markdown or RST format.\"\"\"\n\n    def render(self, graph: DiagramGraph, fmt: str = \"md\") -> str:\n        \"\"\"Render the transition table.\n\n        Args:\n            graph: The diagram IR to render.\n            fmt: Output format — ``\"md\"`` for markdown, ``\"rst\"`` for reStructuredText.\n\n        Returns:\n            The formatted transition table as a string.\n        \"\"\"\n        rows = self._collect_rows(graph.states, graph.transitions)\n\n        if fmt == \"rst\":\n            return self._render_rst(rows)\n        return self._render_md(rows)\n\n    def _collect_rows(\n        self,\n        states: List[DiagramState],\n        transitions: List[DiagramTransition],\n    ) -> \"List[tuple[str, str, str, str]]\":\n        \"\"\"Collect (State, Event, Guard, Target) tuples from the IR.\"\"\"\n        rows: List[tuple[str, str, str, str]] = []\n        state_names = self._build_state_name_map(states)\n\n        for t in transitions:\n            if t.is_initial or t.is_internal:\n                continue\n\n            source_name = state_names.get(t.source, t.source)\n            guard = \", \".join(t.guards) if t.guards else \"\"\n            event = t.event or \"\"\n\n            if t.targets:\n                for target_id in t.targets:\n                    target_name = state_names.get(target_id, target_id)\n                    rows.append((source_name, event, guard, target_name))\n            else:\n                rows.append((source_name, event, guard, source_name))\n\n        return rows\n\n    def _build_state_name_map(self, states: List[DiagramState]) -> dict:\n        \"\"\"Build a mapping from state ID to display name, recursively.\"\"\"\n        result: dict = {}\n        for state in states:\n            result[state.id] = state.name\n            if state.children:\n                result.update(self._build_state_name_map(state.children))\n        return result\n\n    def _render_md(self, rows: \"List[tuple[str, str, str, str]]\") -> str:\n        \"\"\"Render as a markdown table.\"\"\"\n        headers = (\"State\", \"Event\", \"Guard\", \"Target\")\n        col_widths = [len(h) for h in headers]\n\n        for row in rows:\n            for i, cell in enumerate(row):\n                col_widths[i] = max(col_widths[i], len(cell))\n\n        def _fmt_row(cells: \"tuple[str, ...]\") -> str:\n            parts = [cell.ljust(col_widths[i]) for i, cell in enumerate(cells)]\n            return \"| \" + \" | \".join(parts) + \" |\"\n\n        lines = [_fmt_row(headers)]\n        lines.append(\"| \" + \" | \".join(\"-\" * w for w in col_widths) + \" |\")\n        for row in rows:\n            lines.append(_fmt_row(row))\n\n        return \"\\n\".join(lines) + \"\\n\"\n\n    def _render_rst(self, rows: \"List[tuple[str, str, str, str]]\") -> str:\n        \"\"\"Render as an RST grid table.\"\"\"\n        headers = (\"State\", \"Event\", \"Guard\", \"Target\")\n        col_widths = [len(h) for h in headers]\n\n        for row in rows:\n            for i, cell in enumerate(row):\n                col_widths[i] = max(col_widths[i], len(cell))\n\n        def _border(char: str = \"-\") -> str:\n            return \"+\" + \"+\".join(char * (w + 2) for w in col_widths) + \"+\"\n\n        def _data_row(cells: \"tuple[str, ...]\") -> str:\n            parts = [f\" {cell.ljust(col_widths[i])} \" for i, cell in enumerate(cells)]\n            return \"|\" + \"|\".join(parts) + \"|\"\n\n        lines = [_border(\"-\")]\n        lines.append(_data_row(headers))\n        lines.append(_border(\"=\"))\n        for row in rows:\n            lines.append(_data_row(row))\n            lines.append(_border(\"-\"))\n\n        return \"\\n\".join(lines) + \"\\n\"\n"
  },
  {
    "path": "statemachine/contrib/diagram/sphinx_ext.py",
    "content": "\"\"\"Sphinx extension providing the ``statemachine-diagram`` directive.\n\nUsage in MyST Markdown::\n\n    ```{statemachine-diagram} mypackage.module.MyMachine\n    :events: start, ship\n    :caption: After shipping\n    ```\n\nThe directive imports the state machine class, optionally instantiates it and\nsends events, then renders an SVG diagram inline in the documentation.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport hashlib\nimport html as html_mod\nimport os\nimport re\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import ClassVar\n\nfrom docutils import nodes\nfrom docutils.parsers.rst import directives\nfrom sphinx.util.docutils import SphinxDirective\n\nif TYPE_CHECKING:\n    from sphinx.application import Sphinx\n\n\ndef _align_spec(argument: str) -> str:\n    return str(directives.choice(argument, (\"left\", \"center\", \"right\")))\n\n\ndef _parse_events(value: str) -> list[str]:\n    \"\"\"Parse a comma-separated list of event names.\"\"\"\n    return [e.strip() for e in value.split(\",\") if e.strip()]\n\n\n# Match the outer <svg ...>...</svg> element, stripping XML prologue/DOCTYPE.\n_SVG_TAG_RE = re.compile(r\"(<svg\\b.*</svg>)\", re.DOTALL)\n\n# Match fixed width/height attributes (e.g. width=\"702pt\" height=\"170pt\").\n_SVG_WIDTH_RE = re.compile(r'\\bwidth=\"([^\"]*(?:pt|px))\"')\n_SVG_HEIGHT_RE = re.compile(r'\\bheight=\"([^\"]*(?:pt|px))\"')\n\n\nclass StateMachineDiagram(SphinxDirective):\n    \"\"\"Render a state machine diagram from an importable class path.\n\n    Supports the same layout options as the standard ``image`` and ``figure``\n    directives (``width``, ``height``, ``scale``, ``align``, ``target``,\n    ``class``, ``name``), plus state-machine-specific options (``events``,\n    ``caption``, ``figclass``).\n    \"\"\"\n\n    has_content: ClassVar[bool] = False\n    required_arguments: ClassVar[int] = 1\n    optional_arguments: ClassVar[int] = 0\n    option_spec: ClassVar[dict[str, Any]] = {\n        # State-machine options\n        \"events\": directives.unchanged,\n        \"format\": directives.unchanged,\n        # Standard image/figure options\n        \"caption\": directives.unchanged,\n        \"alt\": directives.unchanged,\n        \"width\": directives.unchanged,\n        \"height\": directives.unchanged,\n        \"scale\": directives.unchanged,\n        \"align\": _align_spec,\n        \"target\": directives.unchanged,\n        \"class\": directives.class_option,\n        \"name\": directives.unchanged,\n        \"figclass\": directives.class_option,\n    }\n\n    def run(self) -> list[nodes.Node]:\n        qualname = self.arguments[0]\n\n        try:\n            from statemachine.contrib.diagram import formatter\n            from statemachine.contrib.diagram import import_sm\n\n            sm_class = import_sm(qualname)\n        except (ImportError, ValueError) as exc:\n            return [\n                self.state_machine.reporter.warning(\n                    f\"statemachine-diagram: could not import {qualname!r}: {exc}\",\n                    line=self.lineno,\n                )\n            ]\n\n        if \"events\" in self.options:\n            machine = sm_class()\n            for event_name in _parse_events(self.options[\"events\"]):\n                machine.send(event_name)\n        else:\n            machine = sm_class\n\n        output_format = self.options.get(\"format\", \"\").strip().lower()\n\n        if output_format == \"mermaid\":\n            return self._run_mermaid(machine, formatter, qualname)\n\n        try:\n            svg_text = formatter.render(machine, \"svg\")\n        except Exception as exc:\n            return [\n                self.state_machine.reporter.warning(\n                    f\"statemachine-diagram: failed to generate diagram for {qualname!r}: {exc}\",\n                    line=self.lineno,\n                )\n            ]\n\n        svg_tag, intrinsic_width, intrinsic_height = self._prepare_svg(svg_text)\n        svg_styles = self._build_svg_styles(intrinsic_width, intrinsic_height)\n        svg_tag = svg_tag.replace(\"<svg \", f\"<svg {svg_styles} \", 1)\n\n        alt_text = html_mod.escape(self.options.get(\"alt\", qualname.rsplit(\".\", 1)[-1]))\n        target = self._resolve_target(svg_text)\n\n        img_html = f'<div role=\"img\" aria-label=\"{alt_text}\">{svg_tag}</div>'\n        if target:\n            img_html = f'<a href=\"{target}\" target=\"_blank\" rel=\"noopener\">{img_html}</a>'\n\n        wrapper_classes = self._build_wrapper_classes()\n        class_attr = f' class=\"{\" \".join(wrapper_classes)}\"'\n\n        if \"caption\" in self.options:\n            caption = html_mod.escape(self.options[\"caption\"])\n            figclass = self.options.get(\"figclass\", [])\n            if figclass:\n                class_attr = f' class=\"{\" \".join(wrapper_classes + figclass)}\"'\n            html = (\n                f\"<figure{class_attr}>\\n\"\n                f\"  {img_html}\\n\"\n                f\"  <figcaption>{caption}</figcaption>\\n\"\n                f\"</figure>\"\n            )\n        else:\n            html = f\"<div{class_attr}>{img_html}</div>\"\n\n        raw_node = nodes.raw(\"\", html, format=\"html\")\n\n        if \"name\" in self.options:\n            self.add_name(raw_node)\n\n        return [raw_node]\n\n    def _run_mermaid(self, machine: object, formatter: Any, qualname: str) -> list[nodes.Node]:\n        \"\"\"Render a Mermaid diagram using sphinxcontrib-mermaid's node type.\"\"\"\n        try:\n            mermaid_src = formatter.render(machine, \"mermaid\")\n        except Exception as exc:\n            return [\n                self.state_machine.reporter.warning(\n                    f\"statemachine-diagram: failed to generate mermaid for {qualname!r}: {exc}\",\n                    line=self.lineno,\n                )\n            ]\n\n        try:\n            from sphinxcontrib.mermaid import (  # type: ignore[import-untyped]\n                mermaid as MermaidNode,\n            )\n        except ImportError:\n            # Fallback: emit a raw code block if sphinxcontrib-mermaid is not installed\n            code_node = nodes.literal_block(mermaid_src, mermaid_src)\n            code_node[\"language\"] = \"mermaid\"\n            return [code_node]\n\n        node = MermaidNode()\n        node[\"code\"] = mermaid_src\n        node[\"options\"] = {}\n\n        caption = self.options.get(\"caption\")\n        if caption:\n            figure_node = nodes.figure()\n            figure_node += node\n            figure_node += nodes.caption(caption, caption)\n            if \"name\" in self.options:\n                self.add_name(figure_node)\n            return [figure_node]\n\n        if \"name\" in self.options:\n            self.add_name(node)\n        return [node]\n\n    def _prepare_svg(self, svg_text: str) -> tuple[str, str, str]:\n        \"\"\"Extract the ``<svg>`` element and its intrinsic dimensions.\"\"\"\n        match = _SVG_TAG_RE.search(svg_text)\n        svg_tag = match.group(1) if match else svg_text\n\n        width_match = _SVG_WIDTH_RE.search(svg_tag)\n        height_match = _SVG_HEIGHT_RE.search(svg_tag)\n        intrinsic_width = width_match.group(1) if width_match else \"\"\n        intrinsic_height = height_match.group(1) if height_match else \"\"\n\n        # Remove fixed dimensions — sizing is controlled via inline styles.\n        svg_tag = _SVG_WIDTH_RE.sub(\"\", svg_tag)\n        svg_tag = _SVG_HEIGHT_RE.sub(\"\", svg_tag)\n\n        return svg_tag, intrinsic_width, intrinsic_height\n\n    def _build_svg_styles(self, intrinsic_width: str, intrinsic_height: str) -> str:\n        \"\"\"Build an inline ``style`` attribute for the ``<svg>`` element.\"\"\"\n        parts: list[str] = []\n\n        # Width: explicit > scaled intrinsic > intrinsic as max-width.\n        user_width = self.options.get(\"width\", \"\")\n        scale = self.options.get(\"scale\", \"\")\n        if user_width:\n            parts.append(f\"width: {user_width}\")\n        elif scale and intrinsic_width:\n            factor = int(scale.rstrip(\"%\")) / 100\n            value, unit = _split_length(intrinsic_width)\n            parts.append(f\"width: {value * factor:.1f}{unit}\")\n        elif intrinsic_width:\n            parts.append(f\"max-width: {intrinsic_width}\")\n\n        # Height: explicit > scaled intrinsic > auto.\n        user_height = self.options.get(\"height\", \"\")\n        if user_height:\n            parts.append(f\"height: {user_height}\")\n        elif scale and intrinsic_height:\n            factor = int(scale.rstrip(\"%\")) / 100\n            value, unit = _split_length(intrinsic_height)\n            parts.append(f\"height: {value * factor:.1f}{unit}\")\n        else:\n            parts.append(\"height: auto\")\n\n        return f'style=\"{\"; \".join(parts)}\"'\n\n    def _resolve_target(self, svg_text: str) -> str:\n        \"\"\"Return the href for the wrapper ``<a>`` tag, if any.\n\n        When ``:target:`` is given without a value (or as empty string), the\n        raw SVG is written to ``_images/`` and linked so the user can open\n        the full diagram in a new browser tab for zooming.\n        \"\"\"\n        if \"target\" not in self.options:\n            return \"\"\n        target = (self.options[\"target\"] or \"\").strip()\n        if target:\n            return target\n\n        # Auto-generate a standalone SVG file for zoom.\n        qualname = self.arguments[0]\n        events_key = self.options.get(\"events\", \"\")\n        identity = f\"{qualname}:{events_key}\"\n        digest = hashlib.sha1(identity.encode()).hexdigest()[:8]\n        filename = f\"statemachine-{digest}.svg\"\n\n        outdir = os.path.join(self.env.app.outdir, \"_images\")\n        os.makedirs(outdir, exist_ok=True)\n        outpath = os.path.join(outdir, filename)\n        with open(outpath, \"w\", encoding=\"utf-8\") as f:\n            f.write(svg_text)\n\n        return f\"/_images/{filename}\"\n\n    def _build_wrapper_classes(self) -> list[str]:\n        \"\"\"Build CSS class list for the outer wrapper element.\"\"\"\n        css_classes: list[str] = self.options.get(\"class\", [])\n        align = self.options.get(\"align\", \"center\")\n        return [\"statemachine-diagram\", f\"align-{align}\"] + css_classes\n\n\ndef _split_length(value: str) -> tuple[float, str]:\n    \"\"\"Split a CSS length like ``'702pt'`` into ``(702.0, 'pt')``.\"\"\"\n    match = re.match(r\"([0-9.]+)(.*)\", value)\n    if match:\n        return float(match.group(1)), match.group(2)\n    return 0.0, value\n\n\ndef setup(app: \"Sphinx\") -> dict[str, Any]:\n    app.add_directive(\"statemachine-diagram\", StateMachineDiagram)\n    return {\"version\": \"0.1\", \"parallel_read_safe\": True, \"parallel_write_safe\": True}\n"
  },
  {
    "path": "statemachine/contrib/timeout.py",
    "content": "\"\"\"Timeout helper for state invocations.\n\nProvides a ``timeout()`` function that returns an :class:`~statemachine.invoke.IInvoke`\nhandler. When a state is entered, the handler waits for the given duration; if the state\nis not exited before the timer expires, an event is sent to the machine.\n\nExample::\n\n    from statemachine.contrib.timeout import timeout\n\n    class MyMachine(StateChart):\n        waiting = State(initial=True, invoke=timeout(5, on=\"expired\"))\n        timed_out = State(final=True)\n        expired = waiting.to(timed_out)\n\"\"\"\n\nfrom typing import TYPE_CHECKING\nfrom typing import Any\n\nif TYPE_CHECKING:\n    from statemachine.invoke import InvokeContext\n\n\nclass _Timeout:\n    \"\"\"IInvoke handler that waits for a duration and optionally sends an event.\"\"\"\n\n    def __init__(self, duration: float, on: \"str | None\" = None):\n        self.duration = duration\n        self.on = on\n\n    def run(self, ctx: \"InvokeContext\") -> Any:\n        \"\"\"Wait for the timeout duration, then optionally send an event.\n\n        If the owning state is exited before the timer expires (``ctx.cancelled``\n        is set), the handler returns immediately without sending anything.\n        \"\"\"\n        fired = not ctx.cancelled.wait(timeout=self.duration)\n        if not fired:\n            # State was exited before the timeout — nothing to do.\n            return None\n        if self.on is not None:\n            ctx.send(self.on)\n        return None\n\n    def __repr__(self) -> str:\n        args = f\"{self.duration}\"\n        if self.on is not None:\n            args += f\", on={self.on!r}\"\n        return f\"timeout({args})\"\n\n\ndef timeout(duration: float, *, on: \"str | None\" = None) -> _Timeout:\n    \"\"\"Create a timeout invoke handler.\n\n    Args:\n        duration: Time in seconds to wait before firing.\n        on: Event name to send when the timeout expires. If ``None``, the\n            standard ``done.invoke.<state>`` event fires via invoke completion.\n\n    Returns:\n        An :class:`~statemachine.invoke.IInvoke`-compatible handler.\n\n    Raises:\n        ValueError: If *duration* is not positive.\n    \"\"\"\n    if duration <= 0:\n        raise ValueError(f\"timeout duration must be positive, got {duration}\")\n    return _Timeout(duration=duration, on=on)\n"
  },
  {
    "path": "statemachine/contrib/weighted.py",
    "content": "import random\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Tuple\nfrom typing import Union\n\nfrom statemachine.callbacks import CallbackPriority\nfrom statemachine.transition_list import TransitionList\n\nif TYPE_CHECKING:\n    from statemachine.state import State\n\n\nclass _WeightedGroup:\n    \"\"\"Holds weights and a shared random.Random instance for a group of weighted transitions.\n\n    When the first transition's cond (index 0) is evaluated, it rolls the dice and caches\n    the selected index. Subsequent conds check against the cache.\n    \"\"\"\n\n    def __init__(self, weights: List[float], seed: \"int | float | str | None\" = None):\n        self.weights = weights\n        self.rng = random.Random(seed)\n        self._selected: \"int | None\" = None\n        self._population = list(range(len(weights)))\n\n    def select(self) -> int:\n        \"\"\"Roll the dice and cache the selected index.\"\"\"\n        self._selected = self.rng.choices(self._population, weights=self.weights, k=1)[0]\n        return self._selected\n\n    @property\n    def selected(self) -> \"int | None\":\n        return self._selected\n\n\ndef _make_weighted_cond(index: int, group: _WeightedGroup, weight: float, total_weight: float):\n    \"\"\"Create a weighted condition callable for a specific transition index.\n\n    Returns a function that, when called, returns True only for the selected weighted\n    transition. Index 0 rolls the dice; other indices check against the cached selection.\n    \"\"\"\n    pct = weight / total_weight * 100\n\n    def weighted_cond() -> bool:\n        if index == 0:\n            selected = group.select()\n        elif group.selected is None:\n            selected = group.select()\n        else:\n            selected = group.selected\n        return selected == index\n\n    weighted_cond.__name__ = f\"weight={weight} ({pct:.0f}%)\"\n    weighted_cond.__qualname__ = f\"_weighted_cond_{index}_{id(group)}\"\n    return weighted_cond\n\n\n# Type alias for a weighted destination:\n#   (target, weight)  or  (target, weight, kwargs_dict)\n_WeightedDest = Union[\n    Tuple[\"State\", Union[int, float]],\n    Tuple[\"State\", Union[int, float], Dict[str, Any]],\n]\n\n\ndef to(target: \"State\", weight: \"int | float\", **kwargs: Any) -> _WeightedDest:\n    \"\"\"Build a weighted destination with transition keyword arguments.\n\n    Syntactic sugar that returns a ``(target, weight, kwargs)`` tuple, allowing\n    transition options (``cond``, ``unless``, ``before``, ``on``, ``after``, …) to be\n    passed as natural keyword arguments instead of a dict.\n\n    For simple cases without extra options, a plain ``(target, weight)`` tuple works\n    just as well — ``to()`` is only needed when you want to add transition kwargs.\n\n    Args:\n        target: The destination state.\n        weight: A positive number representing the relative weight.\n        **kwargs: Keyword arguments forwarded to ``source.to(target, **kwargs)``.\n\n    Returns:\n        A ``(target, weight, kwargs)`` tuple accepted by :func:`weighted_transitions`.\n\n    Example::\n\n        move = weighted_transitions(\n            standing,\n            to(walk, 70),\n            to(run, 30, cond=\"has_energy\", on=\"start_running\"),\n            seed=42,\n        )\n\n    \"\"\"\n    return (target, weight, kwargs)\n\n\ndef _validate_dest(i: int, item: Any) -> \"Tuple[State, float, Dict[str, Any]]\":\n    \"\"\"Validate and normalize a single ``(target, weight[, kwargs])`` tuple.\"\"\"\n    from statemachine.state import State\n\n    if not isinstance(item, tuple) or len(item) not in (2, 3):\n        raise TypeError(\n            f\"Destination {i} must be a (target_state, weight) or \"\n            f\"(target_state, weight, kwargs) tuple, got {type(item).__name__}\"\n        )\n\n    if len(item) == 2:\n        target, weight = item\n        kwargs: Dict[str, Any] = {}\n    else:\n        target, weight, kwargs = item\n        if not isinstance(kwargs, dict):\n            raise TypeError(\n                f\"Destination {i}: third element must be a dict of \"\n                f\"transition kwargs, got {type(kwargs).__name__}\"\n            )\n\n    if not isinstance(target, State):\n        raise TypeError(\n            f\"Destination {i}: first element must be a State, got {type(target).__name__}\"\n        )\n\n    if not isinstance(weight, (int, float)):\n        raise TypeError(\n            f\"Destination {i}: weight must be a positive number, got {type(weight).__name__}\"\n        )\n    if weight <= 0:\n        raise ValueError(f\"Destination {i}: weight must be positive, got {weight}\")\n\n    return target, float(weight), kwargs\n\n\ndef weighted_transitions(\n    source: \"State\",\n    *destinations: _WeightedDest,\n    seed: \"int | float | str | None\" = None,\n) -> TransitionList:\n    \"\"\"Create a :ref:`TransitionList` where transitions are selected probabilistically\n    based on weights.\n\n    Takes a ``source`` state and one or more ``(target, weight)`` tuples. For simple\n    cases a plain tuple is enough. When you need transition options (``cond``, ``on``,\n    etc.), use the :func:`to` helper to pass them as keyword arguments::\n\n        move = weighted_transitions(\n            standing,\n            (walk, 70),                                  # plain tuple\n            to(run, 30, cond=\"has_energy\", on=\"sprint\"), # with kwargs\n            seed=42,\n        )\n\n    The returned :ref:`TransitionList` can be assigned to a class attribute just like\n    any other event definition. At runtime, the engine evaluates the weighted conditions\n    and selects exactly one transition per event dispatch according to the weight\n    distribution.\n\n    Args:\n        source: The source state for all transitions.\n        *destinations: ``(target, weight)`` tuples or :func:`to` calls.\n        seed: Optional seed for the random number generator (for reproducibility).\n\n    Returns:\n        A :ref:`TransitionList` combining all transitions with weighted conditions.\n\n    \"\"\"\n    from statemachine.state import State\n\n    if not isinstance(source, State):\n        raise TypeError(f\"First argument must be a source State, got {type(source).__name__}\")\n\n    if not destinations:\n        raise ValueError(\n            \"weighted_transitions() requires at least one (target, weight) destination\"\n        )\n\n    validated = [_validate_dest(i, item) for i, item in enumerate(destinations)]\n\n    weights = [w for _, w, _ in validated]\n    total_weight = sum(weights)\n    group = _WeightedGroup(weights, seed=seed)\n\n    result = TransitionList()\n    for index, (target, weight, kwargs) in enumerate(validated):\n        trans = source.to(target, **kwargs)\n        cond_fn = _make_weighted_cond(index, group, weight, total_weight)\n        for transition in trans.transitions:\n            transition.cond.add(cond_fn, priority=CallbackPriority.GENERIC, expected_value=True)\n        result.add_transitions(trans)\n\n    return result\n"
  },
  {
    "path": "statemachine/dispatcher.py",
    "content": "from dataclasses import dataclass\nfrom functools import partial\nfrom functools import reduce\nfrom operator import attrgetter\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Callable\nfrom typing import Iterable\nfrom typing import List\nfrom typing import Set\nfrom typing import Tuple\n\nfrom .callbacks import SPECS_ALL\nfrom .callbacks import SpecReference\nfrom .callbacks import allways_true\nfrom .event import Event\nfrom .exceptions import InvalidDefinition\nfrom .i18n import _\nfrom .signature import SignatureAdapter\nfrom .spec_parser import custom_and\nfrom .spec_parser import operator_mapping\nfrom .spec_parser import parse_boolean_expr\n\nif TYPE_CHECKING:\n    from .callbacks import CallbackSpec\n    from .callbacks import CallbackSpecList\n    from .callbacks import CallbacksRegistry\n\n\n@dataclass\nclass Listener:\n    \"\"\"Object reference that provides attributes to be used as callbacks.\n\n    Args:\n        obj: Any object that will serve as lookup for attributes.\n        skip_attrs: Protected attrs that will be ignored on the search.\n    \"\"\"\n\n    obj: object\n    all_attrs: Set[str]\n    resolver_id: str\n\n    @classmethod\n    def from_obj(cls, obj, skip_attrs=None) -> \"Listener\":\n        if isinstance(obj, Listener):\n            return obj\n        else:\n            if skip_attrs is None:\n                skip_attrs = set()\n            all_attrs = set(dir(obj)) - skip_attrs\n            return cls(obj, all_attrs, str(id(obj)))\n\n    def build_key(self, attr_name) -> str:\n        return f\"{attr_name}@{self.resolver_id}\"\n\n\n@dataclass\nclass Listeners:\n    \"\"\"Listeners that provides attributes to be used as callbacks.\"\"\"\n\n    items: Tuple[Listener, ...]\n    all_attrs: Set[str]\n\n    @classmethod\n    def from_listeners(cls, listeners: Iterable[\"Listener\"]) -> \"Listeners\":\n        listeners = tuple(listeners)\n        all_attrs = set().union(*(listener.all_attrs for listener in listeners))\n        return cls(listeners, all_attrs)\n\n    def resolve(\n        self,\n        specs: \"CallbackSpecList\",\n        registry: \"CallbacksRegistry\",\n        allowed_references: SpecReference = SPECS_ALL,\n    ):\n        found_convention_specs = specs.conventional_specs & self.all_attrs\n\n        for spec in specs:\n            if (spec.reference not in allowed_references) or (\n                spec.is_convention and spec.func not in found_convention_specs\n            ):\n                continue\n\n            executor = registry[specs.grouper(spec.group).key]\n            for key, builder in self.build(spec):\n                executor.add(key, spec, builder)\n\n    def _take_callback(self, name: str, names_not_found_handler: Callable) -> Callable:\n        callbacks: List[Callable] = []\n        for key, builder in self.search_name(name):\n            callback = builder()\n            callback.unique_key = key  # type: ignore[attr-defined]\n            callbacks.append(callback)\n\n        if len(callbacks) == 0:\n            names_not_found_handler(name)\n            return allways_true\n        elif len(callbacks) == 1:\n            return callbacks[0]\n        else:\n            return reduce(custom_and, callbacks)\n\n    def build(self, spec: \"CallbackSpec\"):\n        \"\"\"\n        Resolves the `spec` into callables in the `registry`.\n\n        Args:\n            spec (CallbackSpec): A spec to be resolved.\n            registry (callable): A callable that will be used to store the resolved callables.\n        \"\"\"\n        if not spec.may_contain_boolean_expression:\n            yield from self.search(spec)\n            return\n\n        # Resolves boolean expressions\n\n        names_not_found: Set[str] = set()\n        take_callback_partial = partial(\n            self._take_callback, names_not_found_handler=names_not_found.add\n        )\n\n        try:\n            expression = parse_boolean_expr(spec.func, take_callback_partial, operator_mapping)\n        except SyntaxError as err:\n            raise InvalidDefinition(\n                _(\"Failed to parse boolean expression '{}'\").format(spec.func)\n            ) from err\n        if not expression or names_not_found:\n            spec.names_not_found = names_not_found\n            return\n\n        yield expression.unique_key, lambda: expression\n\n    def search(self, spec: \"CallbackSpec\"):\n        if spec.reference is SpecReference.NAME:\n            yield from self.search_name(spec.attr_name)\n        elif spec.reference is SpecReference.CALLABLE:\n            yield from self._search_callable(spec)\n        elif spec.reference is SpecReference.PROPERTY:\n            yield from self._search_property(spec)\n        else:  # never reached here from tests but put an exception for safety. pragma: no cover\n            raise ValueError(f\"Invalid reference {spec.reference}\")\n\n    def _search_property(self, spec):\n        # if the attr is a property, we'll try to find the object that has the\n        # property on the configs\n        attr_name = spec.attr_name\n        if attr_name not in self.all_attrs:\n            return\n        for listener in self.items:\n            func = getattr(type(listener.obj), attr_name, None)\n            if func is not None and func is spec.func:\n                yield (\n                    listener.build_key(attr_name),\n                    partial(attr_method, attr_name, listener.obj),\n                )\n                return\n\n    def _search_callable(self, spec):\n        # if the attr is an unbounded method, we'll try to find the bounded method\n        # on the self\n        if not spec.is_bounded:\n            for listener in self.items:\n                func = getattr(listener.obj, spec.attr_name, None)\n                if func is not None and func.__func__ is spec.func:\n                    yield listener.build_key(spec.attr_name), partial(callable_method, func)\n                    return\n\n        yield f\"{spec.attr_name}-{id(spec.func)}@None\", partial(callable_method, spec.func)\n\n    def search_name(self, name):\n        for listener in self.items:\n            if name not in listener.all_attrs:\n                continue\n\n            key = listener.build_key(name)\n            func = getattr(listener.obj, name)\n            if not callable(func):\n                yield key, partial(attr_method, name, listener.obj)\n                continue\n\n            if isinstance(func, Event):\n                yield key, partial(event_method, func)\n                continue\n\n            yield key, partial(callable_method, func)\n\n\ndef callable_method(a_callable) -> Callable:\n    sig = SignatureAdapter.from_callable(a_callable)\n    sig_bind_expected = sig.bind_expected\n\n    metadata_to_copy = a_callable.func if isinstance(a_callable, partial) else a_callable\n\n    if sig.is_coroutine:\n\n        async def signature_adapter(*args: Any, **kwargs: Any) -> Any:  # pyright: ignore[reportRedeclaration]\n            ba = sig_bind_expected(*args, **kwargs)\n            return await a_callable(*ba.args, **ba.kwargs)\n    else:\n\n        def signature_adapter(*args: Any, **kwargs: Any) -> Any:  # type: ignore[misc]\n            ba = sig_bind_expected(*args, **kwargs)\n            return a_callable(*ba.args, **ba.kwargs)\n\n    signature_adapter.__name__ = metadata_to_copy.__name__\n    signature_adapter.__doc__ = metadata_to_copy.__doc__\n    signature_adapter.is_coroutine = sig.is_coroutine  # type: ignore[attr-defined]\n\n    return signature_adapter\n\n\ndef attr_method(attribute, obj) -> Callable:\n    getter = attrgetter(attribute)\n\n    def method(*args, **kwargs):\n        return getter(obj)\n\n    method.__name__ = attribute\n    return method\n\n\ndef event_method(func) -> Callable:\n    def method(*args, **kwargs):\n        kwargs.pop(\"machine\", None)\n        return func(*args, **kwargs)\n\n    return method\n\n\ndef resolver_factory_from_objects(*objects: Tuple[Any, ...]):\n    return Listeners.from_listeners(Listener.from_obj(o) for o in objects)\n"
  },
  {
    "path": "statemachine/engines/__init__.py",
    "content": ""
  },
  {
    "path": "statemachine/engines/async_.py",
    "content": "import asyncio\nimport contextvars\nfrom itertools import chain\nfrom time import time\nfrom typing import TYPE_CHECKING\nfrom typing import Callable\nfrom typing import List\n\nfrom ..event_data import EventData\nfrom ..event_data import TriggerData\nfrom ..exceptions import InvalidDefinition\nfrom ..exceptions import TransitionNotAllowed\nfrom ..orderedset import OrderedSet\nfrom ..state import State\nfrom .base import _ERROR_EXECUTION\nfrom .base import BaseEngine\n\nif TYPE_CHECKING:\n    from ..transition import Transition\n\n# ContextVar to distinguish reentrant calls (from within callbacks) from\n# concurrent external calls. asyncio propagates context to child tasks\n# (e.g., those created by asyncio.gather in the callback system), so a\n# ContextVar set in the processing loop is visible in all callbacks.\n# Independent external coroutines have their own context where this is False.\n_in_processing_loop: contextvars.ContextVar[bool] = contextvars.ContextVar(\n    \"_in_processing_loop\", default=False\n)\n\n\nclass AsyncEngine(BaseEngine):\n    \"\"\"Async engine with full StateChart support.\n\n    Mirrors :class:`SyncEngine` algorithm but uses ``async``/``await`` for callback dispatch.\n    All pure-computation helpers are inherited from :class:`BaseEngine`.\n    \"\"\"\n\n    def put(self, trigger_data: TriggerData, internal: bool = False, _delayed: bool = False):\n        \"\"\"Override to attach an asyncio.Future for external events.\n\n        Futures are only created when:\n        - The event is external (not internal)\n        - No future is already attached\n        - There is a running asyncio loop\n        - The call is NOT from within the processing loop (reentrant calls\n          from callbacks must not get futures, as that would deadlock)\n        \"\"\"\n        if not internal and trigger_data.future is None and not _in_processing_loop.get():\n            try:\n                loop = asyncio.get_running_loop()\n                trigger_data.future = loop.create_future()\n            except RuntimeError:\n                pass  # No running loop — sync caller\n        super().put(trigger_data, internal=internal, _delayed=_delayed)\n\n    @staticmethod\n    def _resolve_future(future: \"asyncio.Future[object] | None\", result):\n        \"\"\"Resolve a future with the given result, if present and not yet done.\"\"\"\n        if future is not None and not future.done():\n            future.set_result(result)\n\n    @staticmethod\n    def _reject_future(future: \"asyncio.Future[object] | None\", exc: Exception):\n        \"\"\"Reject a future with the given exception, if present and not yet done.\"\"\"\n        if future is not None and not future.done():\n            future.set_exception(exc)\n\n    def _reject_pending_futures(self, exc: Exception):\n        \"\"\"Reject all unresolved futures in the external queue.\"\"\"\n        self.external_queue.reject_futures(exc)\n\n    # --- Callback dispatch overrides (async versions of BaseEngine methods) ---\n\n    async def _get_args_kwargs(\n        self, transition: \"Transition\", trigger_data: TriggerData, target: \"State | None\" = None\n    ):\n        cache_key = (id(transition), id(trigger_data), id(target))\n\n        if cache_key in self._cache:\n            return self._cache[cache_key]\n\n        event_data = EventData(trigger_data=trigger_data, transition=transition)\n        if target:\n            event_data.state = target\n            event_data.target = target\n\n        args, kwargs = event_data.args, event_data.extended_kwargs\n\n        result = await self.sm._callbacks.async_call(self.sm.prepare.key, *args, **kwargs)\n        for new_kwargs in result:\n            kwargs.update(new_kwargs)\n\n        self._cache[cache_key] = (args, kwargs)\n        return args, kwargs\n\n    async def _conditions_match(self, transition: \"Transition\", trigger_data: TriggerData):\n        args, kwargs = await self._get_args_kwargs(transition, trigger_data)\n        on_error = self._on_error_handler()\n\n        await self.sm._callbacks.async_call(\n            transition.validators.key, *args, on_error=None, **kwargs\n        )\n        return await self.sm._callbacks.async_all(\n            transition.cond.key, *args, on_error=on_error, **kwargs\n        )\n\n    async def _first_transition_that_matches(  # type: ignore[override]\n        self,\n        state: State,\n        trigger_data: TriggerData,\n        predicate: Callable,\n    ) -> \"Transition | None\":\n        for s in chain([state], state.ancestors()):\n            transition: \"Transition\"\n            for transition in s.transitions:\n                if (\n                    not transition.initial\n                    and predicate(transition, trigger_data.event)\n                    and await self._conditions_match(transition, trigger_data)\n                ):\n                    return transition\n        return None\n\n    async def _select_transitions(  # type: ignore[override]\n        self, trigger_data: TriggerData, predicate: Callable\n    ) -> \"OrderedSet[Transition]\":\n        enabled_transitions: \"OrderedSet[Transition]\" = OrderedSet()\n\n        atomic_states = (state for state in self.sm.configuration if state.is_atomic)\n\n        for state in atomic_states:\n            transition = await self._first_transition_that_matches(state, trigger_data, predicate)\n            if transition is not None:\n                enabled_transitions.add(transition)\n\n        return self._filter_conflicting_transitions(enabled_transitions)\n\n    async def select_eventless_transitions(self, trigger_data: TriggerData):\n        return await self._select_transitions(trigger_data, lambda t, _e: t.is_eventless)\n\n    async def select_transitions(self, trigger_data: TriggerData) -> \"OrderedSet[Transition]\":  # type: ignore[override]\n        return await self._select_transitions(trigger_data, lambda t, e: t.match(e))\n\n    async def _execute_transition_content(\n        self,\n        enabled_transitions: \"List[Transition]\",\n        trigger_data: TriggerData,\n        get_key: \"Callable[[Transition], str]\",\n        set_target_as_state: bool = False,\n        **kwargs_extra,\n    ):\n        result = []\n        for transition in enabled_transitions:\n            target = transition.target if set_target_as_state else None\n            args, kwargs = await self._get_args_kwargs(\n                transition,\n                trigger_data,\n                target=target,\n            )\n            kwargs.update(kwargs_extra)\n\n            result += await self.sm._callbacks.async_call(get_key(transition), *args, **kwargs)\n\n        return result\n\n    async def _exit_states(  # type: ignore[override]\n        self, enabled_transitions: \"List[Transition]\", trigger_data: TriggerData\n    ) -> \"OrderedSet[State]\":\n        ordered_states, result = self._prepare_exit_states(enabled_transitions)\n        on_error = self._on_error_handler()\n\n        for info in ordered_states:\n            # Cancel invocations for this state before executing exit handlers.\n            if info.state is not None:  # pragma: no branch\n                self._invoke_manager.cancel_for_state(info.state)\n\n            args, kwargs = await self._get_args_kwargs(info.transition, trigger_data)\n\n            if info.state is not None:  # pragma: no branch\n                self._debug(\"%s Exiting state: %s\", self._log_id, info.state)\n                await self.sm._callbacks.async_call(\n                    info.state.exit.key, *args, on_error=on_error, **kwargs\n                )\n\n            self._remove_state_from_configuration(info.state)\n\n        return result\n\n    async def _enter_states(  # noqa: C901\n        self,\n        enabled_transitions: \"List[Transition]\",\n        trigger_data: TriggerData,\n        states_to_exit: \"OrderedSet[State]\",\n        previous_configuration: \"OrderedSet[State]\",\n    ):\n        on_error = self._on_error_handler()\n        ordered_states, states_for_default_entry, default_history_content, new_configuration = (\n            self._prepare_entry_states(enabled_transitions, states_to_exit, previous_configuration)\n        )\n\n        # For transition 'on' content, use on_error only for non-error.execution\n        # events.  During error.execution processing, errors in transition content\n        # must propagate to microstep() where _send_error_execution's guard\n        # prevents infinite loops (per SCXML spec: errors during error event\n        # processing are ignored).\n        on_error_transition = on_error\n        if (\n            on_error is not None\n            and trigger_data.event\n            and str(trigger_data.event) == _ERROR_EXECUTION\n        ):\n            on_error_transition = None\n\n        result = await self._execute_transition_content(\n            enabled_transitions,\n            trigger_data,\n            lambda t: t.on.key,\n            on_error=on_error_transition,\n            previous_configuration=previous_configuration,\n            new_configuration=new_configuration,\n        )\n\n        if self.sm.atomic_configuration_update:\n            self.sm.configuration = new_configuration\n\n        for info in ordered_states:\n            target = info.state\n            transition = info.transition\n            args, kwargs = await self._get_args_kwargs(\n                transition,\n                trigger_data,\n                target=target,\n            )\n\n            self._debug(\"%s Entering state: %s\", self._log_id, target)\n            self._add_state_to_configuration(target)\n\n            on_entry_result = await self.sm._callbacks.async_call(\n                target.enter.key, *args, on_error=on_error, **kwargs\n            )\n\n            # Handle default initial states\n            if target.id in {t.state.id for t in states_for_default_entry if t.state}:\n                initial_transitions = [t for t in target.transitions if t.initial]\n                if len(initial_transitions) == 1:\n                    result += await self.sm._callbacks.async_call(\n                        initial_transitions[0].on.key, *args, **kwargs\n                    )\n\n            # Handle default history states\n            default_history_transitions = [\n                i.transition for i in default_history_content.get(target.id, [])\n            ]\n            if default_history_transitions:\n                await self._execute_transition_content(\n                    default_history_transitions,\n                    trigger_data,\n                    lambda t: t.on.key,\n                    previous_configuration=previous_configuration,\n                    new_configuration=new_configuration,\n                )\n\n            # Mark state for invocation if it has invoke callbacks registered\n            if target.invoke.key in self.sm._callbacks:\n                self._invoke_manager.mark_for_invoke(target, trigger_data.kwargs)\n\n            # Handle final states\n            if target.final:\n                self._handle_final_state(target, on_entry_result)\n\n        return result\n\n    async def microstep(self, transitions: \"List[Transition]\", trigger_data: TriggerData):\n        self._microstep_count += 1\n        self._debug(\n            \"%s macro:%d micro:%d transitions: %s\",\n            self._log_id,\n            self._macrostep_count,\n            self._microstep_count,\n            transitions,\n        )\n        previous_configuration = self.sm.configuration\n        try:\n            result = await self._execute_transition_content(\n                transitions, trigger_data, lambda t: t.before.key\n            )\n\n            states_to_exit = await self._exit_states(transitions, trigger_data)\n            result += await self._enter_states(\n                transitions, trigger_data, states_to_exit, previous_configuration\n            )\n        except InvalidDefinition:\n            self.sm.configuration = previous_configuration\n            raise\n        except Exception as e:\n            self.sm.configuration = previous_configuration\n            self._handle_error(e, trigger_data)\n            return None\n\n        try:\n            await self._execute_transition_content(\n                transitions,\n                trigger_data,\n                lambda t: t.after.key,\n                set_target_as_state=True,\n            )\n        except InvalidDefinition:\n            raise\n        except Exception as e:\n            self._handle_error(e, trigger_data)\n\n        if len(result) == 0:\n            result = None\n        elif len(result) == 1:\n            result = result[0]\n\n        return result\n\n    # --- Engine loop ---\n\n    async def _run_microstep(self, enabled_transitions, trigger_data):  # pragma: no cover\n        \"\"\"Run a microstep for internal/eventless transitions with error handling.\n\n        Note: microstep() handles its own errors internally, so this try/except\n        is a safety net that is not expected to be reached in normal operation.\n        \"\"\"\n        try:\n            await self.microstep(list(enabled_transitions), trigger_data)\n        except InvalidDefinition:\n            raise\n        except Exception as e:\n            self._handle_error(e, trigger_data)\n\n    async def activate_initial_state(self, **kwargs):\n        \"\"\"Activate the initial state.\n\n        In async code, the user must call this method explicitly (or it will be lazily\n        activated on the first event). There's no built-in way to call async code from\n        ``StateMachine.__init__``.\n\n        Any ``**kwargs`` are forwarded to initial state entry callbacks via dependency\n        injection, just like event kwargs on ``send()``.\n        \"\"\"\n        return await self.processing_loop()\n\n    async def processing_loop(  # noqa: C901\n        self, caller_future: \"asyncio.Future[object] | None\" = None\n    ):\n        \"\"\"Process event triggers with the 3-phase macrostep architecture.\n\n        Phase 1: Eventless transitions + internal queue until quiescence.\n        Phase 2: Remaining internal events (safety net for invoke-generated events).\n        Phase 3: External events.\n\n        When ``caller_future`` is provided, the caller can ``await`` it to\n        receive its own event's result — even if another coroutine holds the\n        processing lock.\n        \"\"\"\n        if not self._processing.acquire(blocking=False):\n            # Another coroutine holds the lock and will process our event.\n            # Await the caller's future so we get our own result back.\n            if caller_future is not None:\n                return await caller_future\n            return None\n\n        _ctx_token = _in_processing_loop.set(True)\n        self._debug(\"%s Processing loop started: %s\", self._log_id, self.sm.current_state_value)\n        first_result = self._sentinel\n        try:\n            took_events = True\n            while took_events and self.running:\n                self.clear_cache()\n                took_events = False\n                macrostep_done = False\n\n                # Phase 1: eventless transitions and internal events\n                while not macrostep_done:\n                    self._microstep_count = 0\n                    self._debug(\n                        \"%s Macrostep %d: eventless/internal queue\",\n                        self._log_id,\n                        self._macrostep_count,\n                    )\n\n                    self.clear_cache()\n                    internal_event = TriggerData(self.sm, event=None)  # null object for eventless\n                    enabled_transitions = await self.select_eventless_transitions(internal_event)\n                    if not enabled_transitions:\n                        if self.internal_queue.is_empty():\n                            macrostep_done = True\n                        else:\n                            internal_event = self.internal_queue.pop()\n                            enabled_transitions = await self.select_transitions(internal_event)\n                    if enabled_transitions:\n                        self._debug(\n                            \"%s Enabled transitions: %s\", self._log_id, enabled_transitions\n                        )\n                        took_events = True\n                        await self._run_microstep(enabled_transitions, internal_event)\n\n                # Spawn invoke handlers for states entered during this macrostep.\n                await self._invoke_manager.spawn_pending_async()\n                self._check_root_final_state()\n\n                # Phase 2: remaining internal events\n                while not self.internal_queue.is_empty():  # pragma: no cover\n                    internal_event = self.internal_queue.pop()\n                    enabled_transitions = await self.select_transitions(internal_event)\n                    if enabled_transitions:\n                        await self._run_microstep(enabled_transitions, internal_event)\n\n                # Phase 3: external events\n                self._debug(\"%s Macrostep %d: external queue\", self._log_id, self._macrostep_count)\n                while not self.external_queue.is_empty():\n                    self.clear_cache()\n                    took_events = True\n                    external_event = self.external_queue.pop()\n                    current_time = time()\n                    if external_event.execution_time > current_time:\n                        self.put(external_event, _delayed=True)\n                        await asyncio.sleep(self.sm._loop_sleep_in_ms)\n                        # Break to Phase 1 so internal events and eventless\n                        # transitions can be processed while we wait.\n                        break\n\n                    self._macrostep_count += 1\n                    self._microstep_count = 0\n                    self._debug(\n                        \"%s macrostep %d: event=%s\",\n                        self._log_id,\n                        self._macrostep_count,\n                        external_event.event,\n                    )\n\n                    # Handle lazy initial state activation.\n                    # Break out of phase 3 so the outer loop restarts from phase 1\n                    # (eventless/internal), ensuring internal events queued during\n                    # initial entry are processed before any external events.\n                    if external_event.event == \"__initial__\":\n                        transitions = self._initial_transitions(external_event)\n                        await self._enter_states(\n                            transitions, external_event, OrderedSet(), OrderedSet()\n                        )\n                        break\n\n                    # Finalize + autoforward for active invocations\n                    self._invoke_manager.handle_external_event(external_event)\n\n                    event_future = external_event.future\n                    try:\n                        enabled_transitions = await self.select_transitions(external_event)\n                        self._debug(\n                            \"%s Enabled transitions: %s\", self._log_id, enabled_transitions\n                        )\n                        if enabled_transitions:\n                            result = await self.microstep(\n                                list(enabled_transitions), external_event\n                            )\n                            self._resolve_future(event_future, result)\n                            if first_result is self._sentinel:\n                                first_result = result\n                        else:\n                            if not self.sm.allow_event_without_transition:\n                                tna = TransitionNotAllowed(\n                                    external_event.event, self.sm.configuration\n                                )\n                                self._reject_future(event_future, tna)\n                                self._reject_pending_futures(tna)\n                                raise tna\n                            # Event allowed but no transition — resolve with None\n                            self._resolve_future(event_future, None)\n                    except Exception as exc:\n                        self._reject_future(event_future, exc)\n                        self._reject_pending_futures(exc)\n                        self.clear()\n                        raise\n\n        except Exception as exc:\n            if caller_future is not None:\n                # Route the exception to the caller's future if still pending.\n                # If already resolved (caller's own event succeeded before a\n                # later event failed), suppress the exception — the caller will\n                # get their successful result via ``await future`` below, and\n                # the failing event's exception was already routed to *its*\n                # caller's future by ``_reject_future(event_future, ...)``.\n                self._reject_future(caller_future, exc)\n            else:\n                raise\n        finally:\n            _in_processing_loop.reset(_ctx_token)\n            self._processing.release()\n\n        self._debug(\"%s Processing loop ended\", self._log_id)\n        result = first_result if first_result is not self._sentinel else None\n        # If the caller has a future, await it (already resolved by now).\n        if caller_future is not None:\n            # Resolve the future if it wasn't processed (e.g. machine terminated).\n            self._resolve_future(caller_future, result)\n            return await caller_future\n        return result\n\n    async def enabled_events(self, *args, **kwargs):\n        sm = self.sm\n        enabled = {}\n        for state in sm.configuration:\n            for transition in state.transitions:\n                for event in transition.events:\n                    if event in enabled:\n                        continue\n                    extended_kwargs = kwargs.copy()\n                    extended_kwargs.update(\n                        {\n                            \"machine\": sm,\n                            \"model\": sm.model,\n                            \"event\": getattr(sm, event),\n                            \"source\": transition.source,\n                            \"target\": transition.target,\n                            \"state\": state,\n                            \"transition\": transition,\n                        }\n                    )\n                    try:\n                        if await sm._callbacks.async_all(\n                            transition.cond.key, *args, **extended_kwargs\n                        ):\n                            enabled[event] = getattr(sm, event)\n                    except Exception:\n                        enabled[event] = getattr(sm, event)\n        return list(enabled.values())\n"
  },
  {
    "path": "statemachine/engines/base.py",
    "content": "import logging\nfrom dataclasses import dataclass\nfrom dataclasses import field\nfrom itertools import chain\nfrom queue import PriorityQueue\nfrom queue import Queue\nfrom threading import Lock\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Callable\nfrom typing import Dict\nfrom typing import List\nfrom typing import cast\n\nfrom ..event import BoundEvent\nfrom ..event_data import EventData\nfrom ..event_data import TriggerData\nfrom ..exceptions import InvalidDefinition\nfrom ..exceptions import TransitionNotAllowed\nfrom ..invoke import InvokeManager\nfrom ..orderedset import OrderedSet\nfrom ..state import HistoryState\nfrom ..state import State\nfrom ..transition import Transition\n\nif TYPE_CHECKING:\n    from ..statemachine import StateChart\n\nlogger = logging.getLogger(__name__)\n\n\n@dataclass(frozen=True, unsafe_hash=True, eq=True)\nclass StateTransition:\n    transition: Transition = field(compare=False)\n    state: State\n\n\nclass EventQueue:\n    def __init__(self):\n        self.queue: Queue = PriorityQueue()\n\n    def __repr__(self):\n        return f\"EventQueue({self.queue.queue!r}, size={self.queue.qsize()})\"\n\n    def is_empty(self):\n        return self.queue.qsize() == 0\n\n    def put(self, trigger_data: TriggerData):\n        \"\"\"Put the trigger on the queue without blocking the caller.\"\"\"\n        self.queue.put(trigger_data)\n\n    def pop(self):\n        \"\"\"Pop a trigger from the queue without blocking the caller.\"\"\"\n        return self.queue.get(block=False)\n\n    def clear(self):\n        with self.queue.mutex:\n            self.queue.queue.clear()\n\n    def reject_futures(self, exc: Exception):\n        \"\"\"Reject all unresolved futures in the queue.\n\n        Called when the processing loop exits abnormally so that coroutines\n        awaiting their futures don't hang forever.\n        \"\"\"\n        with self.queue.mutex:\n            for trigger_data in self.queue.queue:\n                future = trigger_data.future\n                if future is not None and not future.done():\n                    future.set_exception(exc)\n\n    def remove(self, send_id: str):\n        # We use the internal `queue` to make thins faster as the mutex\n        # is protecting the block below\n        with self.queue.mutex:\n            self.queue.queue = [\n                trigger_data\n                for trigger_data in self.queue.queue\n                if trigger_data.send_id != send_id\n            ]\n\n\n_ERROR_EXECUTION = \"error.execution\"\n\n\nclass BaseEngine:\n    def __init__(self, sm: \"StateChart\"):\n        self.sm: \"StateChart\" = sm\n        self.external_queue = EventQueue()\n        self.internal_queue = EventQueue()\n        self._sentinel = object()\n        self.running = True\n        self._processing = Lock()\n        self._cache: Dict = {}  # Cache for _get_args_kwargs results\n        self._invoke_manager = InvokeManager(self)\n        self._macrostep_count: int = 0\n        self._microstep_count: int = 0\n        self._log_id = f\"[{type(sm).__name__}]\"\n        self._debug = logger.debug if logger.isEnabledFor(logging.DEBUG) else lambda *a, **k: None\n        self._root_parallel_final_pending: \"State | None\" = None\n\n    def empty(self):  # pragma: no cover\n        return self.external_queue.is_empty()\n\n    def clear_cache(self):\n        \"\"\"Clears the cache. Should be called at the start of each processing loop.\"\"\"\n        self._cache.clear()\n\n    def put(self, trigger_data: TriggerData, internal: bool = False, _delayed: bool = False):\n        \"\"\"Put the trigger on the queue without blocking the caller.\"\"\"\n        if not self.running and not self.sm.allow_event_without_transition:\n            raise TransitionNotAllowed(trigger_data.event, self.sm.configuration)\n\n        if internal:\n            self.internal_queue.put(trigger_data)\n        else:\n            self.external_queue.put(trigger_data)\n\n        if not _delayed:\n            self._debug(\n                \"%s New event '%s' put on the '%s' queue\",\n                self._log_id,\n                trigger_data.event,\n                \"internal\" if internal else \"external\",\n            )\n\n    def pop(self):  # pragma: no cover\n        return self.external_queue.pop()\n\n    def clear(self):\n        self.external_queue.clear()\n\n    def cancel_event(self, send_id: str):\n        \"\"\"Cancel the event with the given send_id.\"\"\"\n        self.external_queue.remove(send_id)\n\n    def _on_error_handler(self) -> \"Callable[[Exception], None] | None\":\n        \"\"\"Return a per-block error handler, or ``None``.\n\n        When ``catch_errors_as_events`` is enabled, returns a callable that queues\n        ``error.execution`` on the internal queue.  Otherwise returns ``None``\n        so that exceptions propagate normally.\n        \"\"\"\n        if not self.sm.catch_errors_as_events:\n            return None\n\n        def handler(error: Exception) -> None:\n            if isinstance(error, InvalidDefinition):\n                raise error\n            # Per-block errors always queue error.execution — even when the current\n            # event is itself error.execution.  The SCXML spec mandates that the\n            # new error.execution is a separate event that may trigger a different\n            # transition (see W3C test 152).  The infinite-loop guard lives at the\n            # *microstep* level (in ``_send_error_execution``), not here.\n            BoundEvent(_ERROR_EXECUTION, internal=True, _sm=self.sm).put(error=error)\n\n        return handler\n\n    def _handle_error(self, error: Exception, trigger_data: TriggerData):\n        \"\"\"Handle an execution error: send ``error.execution`` or re-raise.\n\n        Centralises the ``if catch_errors_as_events`` check so callers don't need\n        to know about the variation.\n        \"\"\"\n        if self.sm.catch_errors_as_events:\n            self._send_error_execution(error, trigger_data)\n        else:\n            raise error\n\n    def _send_error_execution(self, error: Exception, trigger_data: TriggerData):\n        \"\"\"Send error.execution to internal queue (SCXML spec).\n\n        If already processing an error.execution event, ignore to avoid infinite loops.\n        \"\"\"\n        self._debug(\n            \"%s Error %s captured while executing event=%s\",\n            self._log_id,\n            error,\n            trigger_data.event,\n        )\n        if trigger_data.event and str(trigger_data.event) == _ERROR_EXECUTION:\n            logger.warning(\"Error while processing error.execution, ignoring: %s\", error)\n            return\n        BoundEvent(_ERROR_EXECUTION, internal=True, _sm=self.sm).put(error=error)\n\n    def start(self, **kwargs):\n        if self.sm.current_state_value is not None:\n            return\n\n        BoundEvent(\"__initial__\", _sm=self.sm).put(**kwargs)\n\n    def _initial_transitions(self, trigger_data):\n        empty_state = State()\n        configuration = self.sm._get_initial_configuration()\n        transitions = [\n            Transition(empty_state, state, event=\"__initial__\") for state in configuration\n        ]\n        for transition in transitions:\n            transition._specs.clear()\n        return transitions\n\n    def _filter_conflicting_transitions(\n        self, transitions: OrderedSet[Transition]\n    ) -> OrderedSet[Transition]:\n        \"\"\"\n        Remove transições conflitantes, priorizando aquelas com estados de origem descendentes\n        ou que aparecem antes na ordem do documento.\n\n        Args:\n            transitions (OrderedSet[Transition]): Conjunto de transições habilitadas.\n\n        Returns:\n            OrderedSet[Transition]: Conjunto de transições sem conflitos.\n        \"\"\"\n        filtered_transitions = OrderedSet[Transition]()\n\n        # Ordena as transições na ordem dos estados que as selecionaram\n        for t1 in transitions:\n            t1_preempted = False\n            transitions_to_remove = OrderedSet[Transition]()\n\n            # Verifica conflitos com as transições já filtradas\n            for t2 in filtered_transitions:\n                # Calcula os conjuntos de saída (exit sets)\n                t1_exit_set = self._compute_exit_set([t1])\n                t2_exit_set = self._compute_exit_set([t2])\n\n                # Verifica interseção dos conjuntos de saída\n                if t1_exit_set & t2_exit_set:  # Há interseção\n                    if t1.source.is_descendant(t2.source):\n                        # t1 é preferido pois é descendente de t2\n                        transitions_to_remove.add(t2)\n                    else:\n                        # t2 é preferido pois foi selecionado antes na ordem do documento\n                        t1_preempted = True\n                        break\n\n            # Se t1 não foi preemptado, adiciona a lista filtrada e remove os conflitantes\n            if not t1_preempted:\n                for t3 in transitions_to_remove:\n                    filtered_transitions.discard(t3)\n                filtered_transitions.add(t1)\n\n        return filtered_transitions\n\n    def _compute_exit_set(self, transitions: List[Transition]) -> OrderedSet[StateTransition]:\n        \"\"\"Compute the exit set for a transition.\"\"\"\n\n        states_to_exit = OrderedSet[StateTransition]()\n\n        for transition in transitions:\n            if not transition.targets:\n                continue\n            domain = self.get_transition_domain(transition)\n            for state in self.sm.configuration:\n                if domain is None or state.is_descendant(domain):\n                    info = StateTransition(transition=transition, state=state)\n                    states_to_exit.add(info)\n\n        return states_to_exit\n\n    def get_transition_domain(self, transition: Transition) -> \"State | None\":\n        \"\"\"\n        Return the compound state such that\n        1) all states that are exited or entered as a result of taking 'transition' are\n           descendants of it\n        2) no descendant of it has this property.\n        \"\"\"\n        states = self.get_effective_target_states(transition)\n        if not states:\n            return None\n        elif (\n            transition.internal\n            and transition.source.is_compound\n            and all(state.is_descendant(transition.source) for state in states)\n        ):\n            return transition.source\n        elif (\n            transition.internal\n            and transition.is_self\n            and transition.target\n            and transition.target.is_atomic\n        ):\n            return transition.source\n        else:\n            return self.find_lcca([transition.source] + list(states))\n\n    @staticmethod\n    def find_lcca(states: List[State]) -> \"State | None\":\n        \"\"\"\n        Find the Least Common Compound Ancestor (LCCA) of the given list of states.\n\n        Args:\n            state_list: A list of states.\n\n        Returns:\n            The LCCA state, which is a proper ancestor of all states in the list,\n            or None if no such ancestor exists.\n        \"\"\"\n        # Get ancestors of the first state in the list, filtering for compound or SCXML elements\n        head, *tail = states\n        ancestors = [anc for anc in head.ancestors() if anc.is_compound]\n\n        # Find the first ancestor that is also an ancestor of all other states in the list\n        ancestor: State\n        for ancestor in ancestors:\n            if all(state.is_descendant(ancestor) for state in tail):\n                return ancestor\n\n        return None\n\n    def get_effective_target_states(self, transition: Transition) -> OrderedSet[State]:\n        targets = OrderedSet[State]()\n        for state in transition.targets:\n            if state.is_history:\n                if state.id in self.sm.history_values:\n                    targets.update(self.sm.history_values[state.id])\n                else:\n                    targets.update(\n                        state\n                        for t in state.transitions\n                        for state in self.get_effective_target_states(t)\n                    )\n            else:\n                targets.add(state)\n\n        return targets\n\n    def select_eventless_transitions(self, trigger_data: TriggerData):\n        \"\"\"\n        Select the eventless transitions that match the trigger data.\n        \"\"\"\n        return self._select_transitions(trigger_data, lambda t, _e: t.is_eventless)\n\n    def select_transitions(self, trigger_data: TriggerData) -> OrderedSet[Transition]:\n        \"\"\"\n        Select the transitions that match the trigger data.\n        \"\"\"\n        return self._select_transitions(trigger_data, lambda t, e: t.match(e))\n\n    def _first_transition_that_matches(\n        self,\n        state: State,\n        trigger_data: TriggerData,\n        predicate: Callable,\n    ) -> \"Transition | None\":\n        for s in chain([state], state.ancestors()):\n            transition: Transition\n            for transition in s.transitions:\n                if (\n                    not transition.initial\n                    and predicate(transition, trigger_data.event)\n                    and self._conditions_match(transition, trigger_data)\n                ):\n                    return transition\n        return None\n\n    def _select_transitions(\n        self, trigger_data: TriggerData, predicate: Callable\n    ) -> OrderedSet[Transition]:\n        \"\"\"Select the transitions that match the trigger data.\"\"\"\n        enabled_transitions = OrderedSet[Transition]()\n\n        # Get atomic states, TODO: sorted by document order\n        atomic_states = (state for state in self.sm.configuration if state.is_atomic)\n\n        for state in atomic_states:\n            transition = self._first_transition_that_matches(state, trigger_data, predicate)\n            if transition is not None:\n                enabled_transitions.add(transition)\n\n        return self._filter_conflicting_transitions(enabled_transitions)\n\n    def microstep(self, transitions: List[Transition], trigger_data: TriggerData):\n        \"\"\"Process a single set of transitions in a 'lock step'.\n        This includes exiting states, executing transition content, and entering states.\n        \"\"\"\n        self._microstep_count += 1\n        self._debug(\n            \"%s macro:%d micro:%d transitions: %s\",\n            self._log_id,\n            self._macrostep_count,\n            self._microstep_count,\n            transitions,\n        )\n        previous_configuration = self.sm.configuration\n        try:\n            result = self._execute_transition_content(\n                transitions, trigger_data, lambda t: t.before.key\n            )\n\n            states_to_exit = self._exit_states(transitions, trigger_data)\n            result += self._enter_states(\n                transitions, trigger_data, states_to_exit, previous_configuration\n            )\n        except InvalidDefinition:\n            self.sm.configuration = previous_configuration\n            raise\n        except Exception as e:\n            self.sm.configuration = previous_configuration\n            self._handle_error(e, trigger_data)\n            return None\n\n        try:\n            self._execute_transition_content(\n                transitions,\n                trigger_data,\n                lambda t: t.after.key,\n                set_target_as_state=True,\n            )\n        except InvalidDefinition:\n            raise\n        except Exception as e:\n            self._handle_error(e, trigger_data)\n\n        if len(result) == 0:\n            result = None\n        elif len(result) == 1:\n            result = result[0]\n\n        return result\n\n    def _get_args_kwargs(\n        self, transition: Transition, trigger_data: TriggerData, target: \"State | None\" = None\n    ):\n        # Generate a unique key for the cache, the cache is invalidated once per loop\n        cache_key = (id(transition), id(trigger_data), id(target))\n\n        # Check the cache for existing results\n        if cache_key in self._cache:\n            return self._cache[cache_key]\n\n        event_data = EventData(trigger_data=trigger_data, transition=transition)\n        if target:\n            event_data.state = target\n            event_data.target = target\n\n        args, kwargs = event_data.args, event_data.extended_kwargs\n\n        result = self.sm._callbacks.call(self.sm.prepare.key, *args, **kwargs)\n        for new_kwargs in result:\n            kwargs.update(new_kwargs)\n\n        # Store the result in the cache\n        self._cache[cache_key] = (args, kwargs)\n        return args, kwargs\n\n    def _conditions_match(self, transition: Transition, trigger_data: TriggerData):\n        args, kwargs = self._get_args_kwargs(transition, trigger_data)\n        on_error = self._on_error_handler()\n\n        self.sm._callbacks.call(transition.validators.key, *args, on_error=None, **kwargs)\n        return self.sm._callbacks.all(transition.cond.key, *args, on_error=on_error, **kwargs)\n\n    def _prepare_exit_states(\n        self,\n        enabled_transitions: List[Transition],\n    ) -> \"tuple[list[StateTransition], OrderedSet[State]]\":\n        \"\"\"Compute exit set, sort, and update history. Pure computation, no callbacks.\"\"\"\n        states_to_exit = self._compute_exit_set(enabled_transitions)\n\n        ordered_states = sorted(\n            states_to_exit, key=lambda x: x.state and x.state.document_order or 0, reverse=True\n        )\n        result = OrderedSet([info.state for info in ordered_states if info.state])\n        self._debug(\"%s States to exit: %s\", self._log_id, result)\n\n        # Update history\n        for info in ordered_states:\n            state = info.state\n            for history in state.history:\n                if history.type.is_deep:\n                    history_value = [s for s in self.sm.configuration if s.is_descendant(state)]  # noqa: E501\n                else:  # shallow history\n                    history_value = [s for s in self.sm.configuration if s.parent == state]\n\n                self._debug(\n                    \"%s Saving '%s.%s' history state: '%s'\",\n                    self._log_id,\n                    state,\n                    history,\n                    [s.id for s in history_value],\n                )\n                self.sm.history_values[history.id] = history_value\n\n        return ordered_states, result\n\n    def _remove_state_from_configuration(self, state: State):\n        \"\"\"Remove a state from the configuration if not using atomic updates.\"\"\"\n        if not self.sm.atomic_configuration_update:\n            self.sm._config.discard(state)\n\n    def _exit_states(\n        self, enabled_transitions: List[Transition], trigger_data: TriggerData\n    ) -> OrderedSet[State]:\n        \"\"\"Compute and process the states to exit for the given transitions.\"\"\"\n        ordered_states, result = self._prepare_exit_states(enabled_transitions)\n        on_error = self._on_error_handler()\n\n        for info in ordered_states:\n            # Cancel invocations for this state before executing exit handlers.\n            if info.state is not None:  # pragma: no branch\n                self._invoke_manager.cancel_for_state(info.state)\n\n            args, kwargs = self._get_args_kwargs(info.transition, trigger_data)\n\n            # Execute `onexit` handlers — same per-block error isolation as onentry.\n            if info.state is not None:  # pragma: no branch\n                self._debug(\"%s Exiting state: %s\", self._log_id, info.state)\n                self.sm._callbacks.call(info.state.exit.key, *args, on_error=on_error, **kwargs)\n\n            self._remove_state_from_configuration(info.state)\n\n        return result\n\n    def _execute_transition_content(\n        self,\n        enabled_transitions: List[Transition],\n        trigger_data: TriggerData,\n        get_key: Callable[[Transition], str],\n        set_target_as_state: bool = False,\n        **kwargs_extra,\n    ):\n        result = []\n        for transition in enabled_transitions:\n            target = transition.target if set_target_as_state else None\n            args, kwargs = self._get_args_kwargs(\n                transition,\n                trigger_data,\n                target=target,\n            )\n            kwargs.update(kwargs_extra)\n\n            result += self.sm._callbacks.call(get_key(transition), *args, **kwargs)\n\n        return result\n\n    def _prepare_entry_states(\n        self,\n        enabled_transitions: List[Transition],\n        states_to_exit: OrderedSet[State],\n        previous_configuration: OrderedSet[State],\n    ) -> \"tuple[list[StateTransition], OrderedSet[StateTransition], Dict[str, Any], OrderedSet[State]]\":  # noqa: E501\n        \"\"\"Compute entry set, ordering, and new configuration. Pure computation, no callbacks.\n\n        Returns:\n            (ordered_states, states_for_default_entry, default_history_content, new_configuration)\n        \"\"\"\n        states_to_enter = OrderedSet[StateTransition]()\n        states_for_default_entry = OrderedSet[StateTransition]()\n        default_history_content: Dict[str, Any] = {}\n\n        self.compute_entry_set(\n            enabled_transitions, states_to_enter, states_for_default_entry, default_history_content\n        )\n\n        ordered_states = sorted(\n            states_to_enter, key=lambda x: x.state and x.state.document_order or 0\n        )\n\n        states_targets_to_enter = OrderedSet(info.state for info in ordered_states if info.state)\n\n        # Build new configuration in a single pass instead of two set operations\n        # (- and |) that each allocate an intermediate OrderedSet.\n        new_configuration = OrderedSet(\n            s for s in previous_configuration if s not in states_to_exit\n        )\n        new_configuration.update(states_targets_to_enter)\n        self._debug(\"%s States to enter: %s\", self._log_id, states_targets_to_enter)\n\n        return ordered_states, states_for_default_entry, default_history_content, new_configuration\n\n    def _add_state_to_configuration(self, target: State):\n        \"\"\"Add a state to the configuration if not using atomic updates.\"\"\"\n        if not self.sm.atomic_configuration_update:\n            self.sm._config.add(target)\n\n    def stop(self):\n        \"\"\"Stop this engine externally (e.g. when a parent cancels a child invocation).\"\"\"\n        self._debug(\"%s Stopping engine\", self._log_id)\n        self.running = False\n        try:\n            self._invoke_manager.cancel_all()\n        except Exception:  # pragma: no cover\n            self._debug(\"%s Error stopping engine\", self._log_id, exc_info=True)\n\n    def __del__(self):\n        try:\n            self._invoke_manager.cancel_all()\n        except Exception:\n            pass\n\n    def _handle_final_state(self, target: State, on_entry_result: list):\n        \"\"\"Handle final state entry: queue done events. No direct callback dispatch.\"\"\"\n        self._debug(\"%s Reached final state: %s\", self._log_id, target)\n        if target.parent is None:\n            self._invoke_manager.cancel_all()\n            self.running = False\n        else:\n            parent = target.parent\n            grandparent = parent.parent\n\n            donedata_args: tuple = ()\n            donedata_kwargs: dict = {}\n            for item in on_entry_result:\n                if not item:\n                    continue\n                if isinstance(item, dict):\n                    donedata_kwargs.update(item)\n                else:\n                    donedata_args = (item,)\n\n            BoundEvent(\n                f\"done.state.{parent.id}\",\n                _sm=self.sm,\n                internal=True,\n            ).put(*donedata_args, **donedata_kwargs)\n\n            if grandparent and grandparent.parallel:\n                if all(self.is_in_final_state(child) for child in grandparent.states):\n                    BoundEvent(f\"done.state.{grandparent.id}\", _sm=self.sm, internal=True).put(\n                        *donedata_args, **donedata_kwargs\n                    )\n                    if grandparent.parent is None:\n                        self._root_parallel_final_pending = grandparent\n\n    def _enter_states(  # noqa: C901\n        self,\n        enabled_transitions: List[Transition],\n        trigger_data: TriggerData,\n        states_to_exit: OrderedSet[State],\n        previous_configuration: OrderedSet[State],\n    ):\n        \"\"\"Enter the states as determined by the given transitions.\"\"\"\n        on_error = self._on_error_handler()\n        ordered_states, states_for_default_entry, default_history_content, new_configuration = (\n            self._prepare_entry_states(enabled_transitions, states_to_exit, previous_configuration)\n        )\n\n        # For transition 'on' content, use on_error only for non-error.execution\n        # events.  During error.execution processing, errors in transition content\n        # must propagate to microstep() where _send_error_execution's guard\n        # prevents infinite loops (per SCXML spec: errors during error event\n        # processing are ignored).\n        on_error_transition = on_error\n        if (\n            on_error is not None\n            and trigger_data.event\n            and str(trigger_data.event) == _ERROR_EXECUTION\n        ):\n            on_error_transition = None\n\n        result = self._execute_transition_content(\n            enabled_transitions,\n            trigger_data,\n            lambda t: t.on.key,\n            on_error=on_error_transition,\n            previous_configuration=previous_configuration,\n            new_configuration=new_configuration,\n        )\n\n        if self.sm.atomic_configuration_update:\n            self.sm.configuration = new_configuration\n\n        for info in ordered_states:\n            target = info.state\n            transition = info.transition\n            args, kwargs = self._get_args_kwargs(\n                transition,\n                trigger_data,\n                target=target,\n            )\n\n            self._debug(\"%s Entering state: %s\", self._log_id, target)\n            self._add_state_to_configuration(target)\n\n            # Execute `onentry` handlers — each handler is a separate block per\n            # SCXML spec: errors in one block MUST NOT affect other blocks.\n            on_entry_result = self.sm._callbacks.call(\n                target.enter.key, *args, on_error=on_error, **kwargs\n            )\n\n            # Handle default initial states\n            if target.id in {t.state.id for t in states_for_default_entry if t.state}:\n                initial_transitions = [t for t in target.transitions if t.initial]\n                if len(initial_transitions) == 1:\n                    result += self.sm._callbacks.call(\n                        initial_transitions[0].on.key, *args, **kwargs\n                    )\n\n            # Handle default history states\n            default_history_transitions = [\n                i.transition for i in default_history_content.get(target.id, [])\n            ]\n            if default_history_transitions:\n                self._execute_transition_content(\n                    default_history_transitions,\n                    trigger_data,\n                    lambda t: t.on.key,\n                    previous_configuration=previous_configuration,\n                    new_configuration=new_configuration,\n                )\n\n            # Mark state for invocation if it has invoke callbacks registered\n            if target.invoke.key in self.sm._callbacks:\n                self._invoke_manager.mark_for_invoke(target, trigger_data.kwargs)\n\n            # Handle final states\n            if target.final:\n                self._handle_final_state(target, on_entry_result)\n\n        return result\n\n    def compute_entry_set(\n        self, transitions, states_to_enter, states_for_default_entry, default_history_content\n    ):\n        \"\"\"\n        Compute the set of states to be entered based on the given transitions.\n\n        Args:\n            transitions: A list of transitions.\n            states_to_enter: A set to store the states that need to be entered.\n            states_for_default_entry: A set to store compound states requiring default entry\n            processing.\n            default_history_content: A dictionary to hold temporary content for history states.\n        \"\"\"\n        for transition in transitions:\n            # Process each target state of the transition\n            for target_state in transition.targets:\n                info = StateTransition(transition=transition, state=target_state)\n                self.add_descendant_states_to_enter(\n                    info, states_to_enter, states_for_default_entry, default_history_content\n                )\n\n            # Determine the ancestor state (transition domain)\n            ancestor = self.get_transition_domain(transition)\n\n            # Add ancestor states to enter for each effective target state\n            for effective_target in self.get_effective_target_states(transition):\n                info = StateTransition(transition=transition, state=effective_target)\n                self.add_ancestor_states_to_enter(\n                    info,\n                    ancestor,\n                    states_to_enter,\n                    states_for_default_entry,\n                    default_history_content,\n                )\n\n    def add_descendant_states_to_enter(  # noqa: C901\n        self,\n        info: StateTransition,\n        states_to_enter,\n        states_for_default_entry,\n        default_history_content,\n    ):\n        \"\"\"\n        Add the given state and its descendants to the entry set.\n\n        Args:\n            state: The state to add to the entry set.\n            states_to_enter: A set to store the states that need to be entered.\n            states_for_default_entry: A set to track compound states requiring default entry\n            processing.\n            default_history_content: A dictionary to hold temporary content for history states.\n        \"\"\"\n        state = info.state\n\n        if state and state.is_history:\n            # Handle history state\n            state = cast(HistoryState, state)\n            parent_id = state.parent and state.parent.id\n            default_history_content[parent_id] = [info]\n            if state.id in self.sm.history_values:\n                self._debug(\n                    \"%s History state '%s.%s' %s restoring: '%s'\",\n                    self._log_id,\n                    state.parent,\n                    state,\n                    state.type.value,\n                    [s.id for s in self.sm.history_values[state.id]],\n                )\n                for history_state in self.sm.history_values[state.id]:\n                    info_to_add = StateTransition(transition=info.transition, state=history_state)\n                    if state.type.is_deep:\n                        states_to_enter.add(info_to_add)\n                    else:\n                        self.add_descendant_states_to_enter(\n                            info_to_add,\n                            states_to_enter,\n                            states_for_default_entry,\n                            default_history_content,\n                        )\n                for history_state in self.sm.history_values[state.id]:\n                    info_to_add = StateTransition(transition=info.transition, state=history_state)\n                    self.add_ancestor_states_to_enter(\n                        info_to_add,\n                        state.parent,\n                        states_to_enter,\n                        states_for_default_entry,\n                        default_history_content,\n                    )\n            else:\n                # Handle default history content\n                self._debug(\n                    \"%s History state '%s.%s' default content: %s\",\n                    self._log_id,\n                    state.parent,\n                    state,\n                    [t.target.id for t in state.transitions if t.target],\n                )\n\n                for transition in state.transitions:\n                    target = cast(State, transition.target)\n                    info_history = StateTransition(transition=transition, state=target)\n                    default_history_content[parent_id].append(info_history)\n                    self.add_descendant_states_to_enter(\n                        info_history,\n                        states_to_enter,\n                        states_for_default_entry,\n                        default_history_content,\n                    )  # noqa: E501\n                for transition in state.transitions:\n                    target = cast(State, transition.target)\n                    info_history = StateTransition(transition=transition, state=target)\n\n                    self.add_ancestor_states_to_enter(\n                        info_history,\n                        state.parent,\n                        states_to_enter,\n                        states_for_default_entry,\n                        default_history_content,\n                    )  # noqa: E501\n            return\n\n        # Add the state to the entry set\n        if (\n            self.sm.enable_self_transition_entries\n            or not info.transition.internal\n            or not (\n                info.transition.is_self\n                or (\n                    info.transition.target\n                    and info.transition.target.is_descendant(info.transition.source)\n                )\n            )\n        ):\n            states_to_enter.add(info)\n        state = info.state\n\n        if state.parallel:\n            for child_state in state.states:\n                if not any(  # pragma: no branch\n                    s.state.is_descendant(child_state) for s in states_to_enter\n                ):\n                    info_to_add = StateTransition(transition=info.transition, state=child_state)\n                    self.add_descendant_states_to_enter(\n                        info_to_add,\n                        states_to_enter,\n                        states_for_default_entry,\n                        default_history_content,\n                    )\n        elif state.is_compound:\n            states_for_default_entry.add(info)\n            transition = next(t for t in state.transitions if t.initial)\n            # Process all targets (supports multi-target initial transitions for parallel regions)\n            for initial_target in transition.targets:\n                info_initial = StateTransition(transition=transition, state=initial_target)\n                self.add_descendant_states_to_enter(\n                    info_initial,\n                    states_to_enter,\n                    states_for_default_entry,\n                    default_history_content,\n                )\n            for initial_target in transition.targets:\n                info_initial = StateTransition(transition=transition, state=initial_target)\n                self.add_ancestor_states_to_enter(\n                    info_initial,\n                    state,\n                    states_to_enter,\n                    states_for_default_entry,\n                    default_history_content,\n                )\n\n    def add_ancestor_states_to_enter(\n        self,\n        info: StateTransition,\n        ancestor,\n        states_to_enter,\n        states_for_default_entry,\n        default_history_content,\n    ):\n        \"\"\"\n        Add ancestors of the given state to the entry set.\n\n        Args:\n            state: The state whose ancestors are to be added.\n            ancestor: The upper bound ancestor (exclusive) to stop at.\n            states_to_enter: A set to store the states that need to be entered.\n            states_for_default_entry: A set to track compound states requiring default entry\n            processing.\n            default_history_content: A dictionary to hold temporary content for history states.\n        \"\"\"\n        state = info.state\n        assert state\n        for anc in state.ancestors(parent=ancestor):\n            # Add the ancestor to the entry set\n            info_to_add = StateTransition(transition=info.transition, state=anc)\n            states_to_enter.add(info_to_add)\n\n            if anc.parallel:\n                # Handle parallel states\n                for child in anc.states:\n                    if not any(s.state.is_descendant(child) for s in states_to_enter):\n                        info_to_add = StateTransition(transition=info.transition, state=child)\n                        self.add_descendant_states_to_enter(\n                            info_to_add,\n                            states_to_enter,\n                            states_for_default_entry,\n                            default_history_content,\n                        )\n\n    def _check_root_final_state(self):\n        \"\"\"SCXML spec: terminate when the root configuration is final.\n\n        For top-level parallel states, the machine terminates when all child\n        regions have reached their final states — equivalent to the SCXML\n        algorithm's ``isInFinalState(scxml_element)`` check.\n\n        Uses a flag set by ``_handle_final_state`` (Information Expert) to\n        avoid re-scanning top-level states on every macrostep.  The flag is\n        deferred because ``done.state`` events queued by ``_handle_final_state``\n        may trigger transitions that exit the parallel, so we verify the\n        parallel is still in the configuration before terminating.\n        \"\"\"\n        state = self._root_parallel_final_pending\n        if state is None:\n            return\n        self._root_parallel_final_pending = None\n        # A done.state transition may have exited the parallel; verify it's\n        # still in the configuration before terminating.\n        if state in self.sm.configuration and self.is_in_final_state(state):\n            self._invoke_manager.cancel_all()\n            self.running = False\n\n    def is_in_final_state(self, state: State) -> bool:\n        if state.is_compound:\n            return any(s.final and s in self.sm.configuration for s in state.states)\n        elif state.parallel:  # pragma: no cover — requires nested parallel-in-parallel\n            return all(self.is_in_final_state(s) for s in state.states)\n        else:  # pragma: no cover — atomic states are never \"in final state\"\n            return False\n"
  },
  {
    "path": "statemachine/engines/sync.py",
    "content": "from time import sleep\nfrom time import time\nfrom typing import TYPE_CHECKING\n\nfrom statemachine.event import BoundEvent\nfrom statemachine.orderedset import OrderedSet\n\nfrom ..event_data import TriggerData\nfrom ..exceptions import InvalidDefinition\nfrom ..exceptions import TransitionNotAllowed\nfrom .base import BaseEngine\n\nif TYPE_CHECKING:\n    from ..transition import Transition\n\n\nclass SyncEngine(BaseEngine):\n    def _run_microstep(self, enabled_transitions, trigger_data):\n        \"\"\"Run a microstep for internal/eventless transitions with error handling.\n\n        Note: microstep() handles its own errors internally, so this try/except\n        is a safety net that is not expected to be reached in normal operation.\n        \"\"\"\n        try:\n            self.microstep(list(enabled_transitions), trigger_data)\n        except InvalidDefinition:\n            raise\n        except Exception as e:  # pragma: no cover\n            self._handle_error(e, trigger_data)\n\n    def start(self, **kwargs):\n        if self.sm.current_state_value is not None:\n            return\n\n        self.activate_initial_state(**kwargs)\n\n    def activate_initial_state(self, **kwargs):\n        \"\"\"\n        Activate the initial state.\n\n        Called automatically on state machine creation from sync code, but in\n        async code, the user must call this method explicitly.\n\n        Given how async works on python, there's no built-in way to activate the initial state that\n        may depend on async code from the StateMachine.__init__ method.\n        \"\"\"\n        if self.sm.current_state_value is None:\n            trigger_data = BoundEvent(\"__initial__\", _sm=self.sm).build_trigger(\n                machine=self.sm, **kwargs\n            )\n            transitions = self._initial_transitions(trigger_data)\n            self._processing.acquire(blocking=False)\n            try:\n                self._enter_states(transitions, trigger_data, OrderedSet(), OrderedSet())\n            finally:\n                self._processing.release()\n        return self.processing_loop()\n\n    def processing_loop(self, caller_future=None):  # noqa: C901\n        \"\"\"Process event triggers.\n\n        The event is put on a queue, and only the first event will have the result collected.\n\n        .. note::\n            While processing the queue items, if others events are generated, they\n            will be processed sequentially (and not nested).\n\n        \"\"\"\n        # We make sure that only the first event enters the processing critical section,\n        # next events will only be put on the queue and processed by the same loop.\n        if not self._processing.acquire(blocking=False):\n            return None\n\n        # We will collect the first result as the processing result to keep backwards compatibility\n        # so we need to use a sentinel object instead of `None` because the first result may\n        # be also `None`, and on this case the `first_result` may be overridden by another result.\n        self._debug(\"%s Processing loop started: %s\", self._log_id, self.sm.current_state_value)\n        first_result = self._sentinel\n        try:\n            took_events = True\n            while took_events and self.running:\n                self.clear_cache()\n                took_events = False\n                # Execute the triggers in the queue in FIFO order until the queue is empty\n                # while self._running and not self.external_queue.is_empty():\n                macrostep_done = False\n                enabled_transitions: \"OrderedSet[Transition] | None\" = None\n\n                # handles eventless transitions and internal events\n                while not macrostep_done:\n                    self._microstep_count = 0\n                    self._debug(\n                        \"%s Macrostep %d: eventless/internal queue\",\n                        self._log_id,\n                        self._macrostep_count,\n                    )\n\n                    self.clear_cache()\n                    internal_event = TriggerData(\n                        self.sm, event=None\n                    )  # this one is a \"null object\"\n                    enabled_transitions = self.select_eventless_transitions(internal_event)\n                    if not enabled_transitions:\n                        if self.internal_queue.is_empty():\n                            macrostep_done = True\n                        else:\n                            internal_event = self.internal_queue.pop()\n                            enabled_transitions = self.select_transitions(internal_event)\n                    if enabled_transitions:\n                        self._debug(\n                            \"%s Enabled transitions: %s\", self._log_id, enabled_transitions\n                        )\n                        took_events = True\n                        self._run_microstep(enabled_transitions, internal_event)\n\n                # Spawn invoke handlers for states entered during this macrostep.\n                self._invoke_manager.spawn_pending_sync()\n                self._check_root_final_state()\n\n                # Process remaining internal events before external events.\n                # Note: the macrostep loop above already drains the internal queue,\n                # so this is a safety net per SCXML spec for invoke-generated events.\n                while not self.internal_queue.is_empty():  # pragma: no cover\n                    internal_event = self.internal_queue.pop()\n                    enabled_transitions = self.select_transitions(internal_event)\n                    if enabled_transitions:\n                        self._run_microstep(enabled_transitions, internal_event)\n\n                # Process external events\n                self._debug(\"%s Macrostep %d: external queue\", self._log_id, self._macrostep_count)\n                while not self.external_queue.is_empty():\n                    self.clear_cache()\n                    took_events = True\n                    external_event = self.external_queue.pop()\n                    current_time = time()\n                    if external_event.execution_time > current_time:\n                        self.put(external_event, _delayed=True)\n                        sleep(self.sm._loop_sleep_in_ms)\n                        # Break to Phase 1 so internal events and eventless\n                        # transitions can be processed while we wait.\n                        break\n\n                    self._macrostep_count += 1\n                    self._microstep_count = 0\n                    self._debug(\n                        \"%s macrostep %d: event=%s\",\n                        self._log_id,\n                        self._macrostep_count,\n                        external_event.event,\n                    )\n\n                    # Finalize + autoforward for active invocations\n                    self._invoke_manager.handle_external_event(external_event)\n\n                    enabled_transitions = self.select_transitions(external_event)\n                    self._debug(\"%s Enabled transitions: %s\", self._log_id, enabled_transitions)\n                    if enabled_transitions:\n                        try:\n                            result = self.microstep(list(enabled_transitions), external_event)\n                            if first_result is self._sentinel:\n                                first_result = result\n\n                        except Exception:\n                            # We clear the queue as we don't have an expected behavior\n                            # and cannot keep processing\n                            self.clear()\n                            raise\n\n                    else:\n                        if not self.sm.allow_event_without_transition:\n                            raise TransitionNotAllowed(external_event.event, self.sm.configuration)\n\n        finally:\n            self._processing.release()\n        self._debug(\"%s Processing loop ended\", self._log_id)\n        return first_result if first_result is not self._sentinel else None\n\n    def enabled_events(self, *args, **kwargs):\n        sm = self.sm\n        enabled = {}\n        for state in sm.configuration:\n            for transition in state.transitions:\n                for event in transition.events:\n                    if event in enabled:\n                        continue\n                    extended_kwargs = kwargs.copy()\n                    extended_kwargs.update(\n                        {\n                            \"machine\": sm,\n                            \"model\": sm.model,\n                            \"event\": getattr(sm, event),\n                            \"source\": transition.source,\n                            \"target\": transition.target,\n                            \"state\": state,\n                            \"transition\": transition,\n                        }\n                    )\n                    try:\n                        if sm._callbacks.all(transition.cond.key, *args, **extended_kwargs):\n                            enabled[event] = getattr(sm, event)\n                    except Exception:\n                        enabled[event] = getattr(sm, event)\n        return list(enabled.values())\n"
  },
  {
    "path": "statemachine/event.py",
    "content": "from typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import List\nfrom typing import cast\nfrom uuid import uuid4\n\nfrom .callbacks import CallbackGroup\nfrom .event_data import TriggerData\nfrom .exceptions import InvalidDefinition\nfrom .i18n import _\nfrom .transition_mixin import AddCallbacksMixin\nfrom .utils import humanize_id\n\nif TYPE_CHECKING:\n    from .statemachine import StateChart\n    from .transition import Transition\n    from .transition_list import TransitionList\n\n\ndef _expand_event_id(key: str) -> str:\n    \"\"\"Apply naming conventions for special event prefixes.\n\n    Converts underscore-based Python attribute names to their dot-separated\n    event equivalents. Returns a space-separated string so ``Events.add()``\n    registers both forms.\n    \"\"\"\n    if key.startswith(\"done_invoke_\"):\n        suffix = key[len(\"done_invoke_\") :]\n        return f\"{key} done.invoke.{suffix}\"\n    if key.startswith(\"done_state_\"):\n        suffix = key[len(\"done_state_\") :]\n        return f\"{key} done.state.{suffix}\"\n    if key.startswith(\"error_\"):\n        return f\"{key} {key.replace('_', '.')}\"\n    return key\n\n\n_event_data_kwargs = {\n    \"event_data\",\n    \"machine\",\n    \"event\",\n    \"model\",\n    \"transition\",\n    \"state\",\n    \"source\",\n    \"target\",\n}\n\n\nclass Event(AddCallbacksMixin, str):\n    \"\"\"An event triggers a signal that something has happened.\n\n    They are sent to a state machine and allow the state machine to react.\n\n    An event starts a :ref:`Transition`, which can be thought of as a “cause” that initiates a\n    change in the state of the system.\n\n    See also :ref:`events`.\n    \"\"\"\n\n    id: str\n    \"\"\"The event identifier.\"\"\"\n\n    name: str\n    \"\"\"The event name.\"\"\"\n\n    delay: float = 0\n    \"\"\"The delay in milliseconds before the event is triggered. Default is 0.\"\"\"\n\n    internal: bool = False\n    \"\"\"Indicates if the events should be placed on the internal event queue.\"\"\"\n\n    _sm: \"StateChart | None\" = None\n    \"\"\"The state machine instance.\"\"\"\n\n    _transitions: \"TransitionList | None\" = None\n    _has_real_id = False\n\n    def __new__(\n        cls,\n        transitions: \"str | Transition | TransitionList | None\" = None,\n        id: \"str | None\" = None,\n        name: \"str | None\" = None,\n        delay: float = 0,\n        internal: bool = False,\n        _sm: \"StateChart | None\" = None,\n    ):\n        if isinstance(transitions, str):\n            id = transitions\n            transitions = None\n\n        if id is not None and not isinstance(id, str):\n            raise InvalidDefinition(\n                _(\n                    \"Event() received a non-string 'id' ({cls_name}). \"\n                    \"To combine multiple transitions under one event, \"\n                    \"use the | operator: t1 | t2.\"\n                ).format(cls_name=type(id).__name__)\n            )\n\n        _has_real_id = id is not None\n        id = str(id) if _has_real_id else f\"__event__{uuid4().hex}\"\n\n        instance = super().__new__(cls, id)\n        instance.id = id\n        instance.delay = delay\n        instance.internal = internal\n        if name:\n            instance.name = name\n        elif _has_real_id:\n            instance.name = humanize_id(id)\n        else:\n            instance.name = \"\"\n        if transitions:\n            instance._transitions = transitions  # type: ignore[assignment]\n        instance._has_real_id = _has_real_id\n        instance._sm = _sm\n        return instance\n\n    def __repr__(self):\n        return (\n            f\"{type(self).__name__}({self.id!r}, delay={self.delay!r}, internal={self.internal!r})\"\n        )\n\n    def is_same_event(self, *_args, event: \"str | None\" = None, **_kwargs) -> bool:\n        return self == event\n\n    def _add_callback(self, callback, grouper: CallbackGroup, is_event=False, **kwargs):\n        if self._transitions is None:\n            raise InvalidDefinition(\n                _(\"Cannot add callback '{}' to an event with no transitions.\").format(callback)\n            )\n        return self._transitions._add_callback(\n            callback=callback,\n            grouper=grouper,\n            is_event=is_event,\n            **kwargs,\n        )\n\n    def __get__(self, instance, owner):\n        \"\"\"By implementing this method `Event` can be used as a property descriptor\n\n        When attached to a SM class, if the user tries to get the `Event` instance,\n        we intercept here and return a `BoundEvent` instance, so the user can call\n        it as a method with the correct SM instance.\n\n        \"\"\"\n        if instance is None:\n            return self\n        return BoundEvent(id=self.id, name=self.name, delay=self.delay, _sm=instance)\n\n    def put(self, *args, send_id: \"str | None\" = None, **kwargs):\n        # The `__call__` is declared here to help IDEs knowing that an `Event`\n        # can be called as a method. But it is not meant to be called without\n        # an SM instance. Such SM instance is provided by `__get__` method when\n        # used as a property descriptor.\n        assert self._sm is not None\n        trigger_data = self.build_trigger(*args, machine=self._sm, send_id=send_id, **kwargs)\n        self._sm._put_nonblocking(trigger_data, internal=self.internal)\n        return trigger_data\n\n    def build_trigger(self, *args, machine: \"StateChart\", send_id: \"str | None\" = None, **kwargs):\n        if machine is None:\n            raise RuntimeError(_(\"Event {} cannot be called without a SM instance\").format(self))\n\n        kwargs = {k: v for k, v in kwargs.items() if k not in _event_data_kwargs}\n        trigger_data = TriggerData(\n            machine=machine,\n            event=self,\n            send_id=send_id,\n            args=args,\n            kwargs=kwargs,\n        )\n\n        return trigger_data\n\n    def __call__(self, *args, **kwargs) -> Any:\n        \"\"\"Send this event to the current state machine.\n\n        Triggering an event on a state machine means invoking or sending a signal, initiating the\n        process that may result in executing a transition.\n        \"\"\"\n        # The `__call__` is declared here to help IDEs knowing that an `Event`\n        # can be called as a method. But it is not meant to be called without\n        # an SM instance. Such SM instance is provided by `__get__` method when\n        # used as a property descriptor.\n        trigger_data = self.put(*args, **kwargs)\n        return self._sm._processing_loop(trigger_data.future)  # type: ignore[union-attr]\n\n    def split(  # type: ignore[override]\n        self, sep: \"str | None\" = None, maxsplit: int = -1\n    ) -> List[\"Event\"]:\n        result = super().split(sep, maxsplit)\n        if len(result) == 1:\n            return [self]\n        return [Event(event) for event in result]\n\n    def match(self, event: str) -> bool:\n        if self == \"*\":\n            return True\n\n        # Normalize descriptor by removing trailing '.*' or '.'\n        # to handle cases like 'error', 'error.', 'error.*'\n        descriptor = cast(str, self)\n        if descriptor.endswith(\".*\"):\n            descriptor = descriptor[:-2]\n        elif descriptor.endswith(\".\"):\n            descriptor = descriptor[:-1]\n\n        # Check prefix match:\n        # The descriptor must be a prefix of the event.\n        # Split both descriptor and event into tokens\n        descriptor_tokens = descriptor.split(\".\") if descriptor else []\n        event_tokens = event.split(\".\") if event else []\n\n        if len(descriptor_tokens) > len(event_tokens):\n            return False\n\n        for d_token, e_token in zip(descriptor_tokens, event_tokens):  # noqa: B905\n            if d_token != e_token:\n                return False\n\n        return True\n\n\nclass BoundEvent(Event):\n    pass\n"
  },
  {
    "path": "statemachine/event_data.py",
    "content": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom time import time\nfrom typing import TYPE_CHECKING\nfrom typing import Any\n\nif TYPE_CHECKING:\n    from .event import Event\n    from .state import State\n    from .statemachine import StateChart\n    from .transition import Transition\n\n\n@dataclass(order=True)\nclass TriggerData:\n    machine: \"StateChart\" = field(compare=False)\n\n    event: \"Event | None\" = field(compare=False)\n    \"\"\"The Event that was triggered.\"\"\"\n\n    send_id: \"str | None\" = field(compare=False, default=None)\n    \"\"\"A string literal to be used as the id of this instance of :ref:`TriggerData`.\n\n    Allow revoking a delayed :ref:`TriggerData` instance.\n    \"\"\"\n\n    execution_time: float = field(default=0.0)\n    \"\"\"The time at which the :ref:`Event` should run.\"\"\"\n\n    model: Any = field(init=False, compare=False)\n    \"\"\"A reference to the underlying model that holds the current :ref:`State`.\"\"\"\n\n    args: tuple = field(default_factory=tuple, compare=False)\n    \"\"\"All positional arguments provided on the :ref:`Event`.\"\"\"\n\n    kwargs: dict = field(default_factory=dict, compare=False)\n    \"\"\"All keyword arguments provided on the :ref:`Event`.\"\"\"\n\n    future: Any = field(default=None, compare=False, repr=False, init=False)\n    \"\"\"An optional :class:`asyncio.Future` for async result routing.\n\n    When set, the processing loop will resolve this future with the microstep\n    result (or exception), allowing the caller to ``await`` it.\n    \"\"\"\n\n    def __post_init__(self):\n        self.model = self.machine.model\n        delay = self.event.delay if self.event and self.event.delay else 0\n        self.execution_time = time() + (delay / 1000)\n\n\n@dataclass\nclass EventData:\n    trigger_data: TriggerData\n    \"\"\"The :ref:`TriggerData` of the :ref:`event`.\"\"\"\n\n    transition: \"Transition\"\n    \"\"\"The :ref:`Transition` instance that was activated by the :ref:`Event`.\"\"\"\n\n    state: \"State\" = field(init=False)\n    \"\"\"The current :ref:`State` of the :ref:`statemachine`.\"\"\"\n\n    source: \"State\" = field(init=False)\n    \"\"\"The :ref:`State` which :ref:`statemachine` was in when the Event started.\"\"\"\n\n    target: \"State | None\" = field(init=False)\n    \"\"\"The destination :ref:`State` of the :ref:`transition`, or ``None`` for targetless.\"\"\"\n\n    def __post_init__(self):\n        self.state = self.transition.source\n        self.source = self.transition.source\n        self.target = self.transition.target\n        self.machine = self.trigger_data.machine\n\n    @property\n    def event(self):\n        return self.trigger_data.event\n\n    @property\n    def args(self):\n        return self.trigger_data.args\n\n    @property\n    def extended_kwargs(self):\n        kwargs = self.trigger_data.kwargs.copy()\n        kwargs[\"event_data\"] = self\n        kwargs[\"machine\"] = self.trigger_data.machine\n        kwargs[\"event\"] = self.trigger_data.event\n        kwargs[\"model\"] = self.trigger_data.model\n        kwargs[\"transition\"] = self.transition\n        kwargs[\"state\"] = self.state\n        kwargs[\"source\"] = self.source\n        kwargs[\"target\"] = self.target\n        return kwargs\n"
  },
  {
    "path": "statemachine/events.py",
    "content": "from .event import Event\nfrom .utils import ensure_iterable\n\n\nclass Events:\n    \"\"\"A collection of event names.\"\"\"\n\n    def __init__(self):\n        self._items: list[Event] = []\n\n    def __str__(self):\n        sep = \" \" if len(self._items) > 1 else \"\"\n        return sep.join(item for item in self._items)\n\n    def __repr__(self):\n        return f\"{self._items!r}\"\n\n    def __iter__(self):\n        return iter(self._items)\n\n    def add(self, events):\n        if events is None:\n            return self\n\n        unprepared = ensure_iterable(events)\n        for events in unprepared:\n            for event in events.split(\" \"):\n                if event in self._items:\n                    continue\n                if isinstance(event, Event):\n                    self._items.append(event)\n                else:\n                    self._items.append(Event(id=event))\n\n        return self\n\n    def match(self, event: \"str | None\"):\n        if event is None:\n            return self.is_empty\n        return any(e.match(event) for e in self)\n\n    def _replace(self, old, new):\n        self._items.remove(old)\n        self._items.append(new)\n\n    @property\n    def is_empty(self):\n        return len(self._items) == 0\n"
  },
  {
    "path": "statemachine/exceptions.py",
    "content": "from typing import TYPE_CHECKING\nfrom typing import MutableSet\n\nfrom .i18n import _\n\nif TYPE_CHECKING:\n    from .event import Event\n    from .state import State\n\n\nclass StateMachineError(Exception):\n    \"Base exception for this project, all exceptions that can be raised inherit from this class.\"\n\n\nclass InvalidDefinition(StateMachineError):\n    \"The state machine has a definition error\"\n\n\nclass InvalidStateValue(InvalidDefinition):\n    \"The current model state value is not mapped to a state definition.\"\n\n    def __init__(self, value, msg=None):\n        self.value = value\n        if msg is None:\n            msg = _(\"{!r} is not a valid state value.\").format(value)\n        super().__init__(msg)\n\n\nclass AttrNotFound(InvalidDefinition):\n    \"There's no method or property with the given name\"\n\n\nclass TransitionNotAllowed(StateMachineError):\n    \"Raised when there's no transition that can run from the current :ref:`configuration`.\"\n\n    def __init__(self, event: \"Event | None\", configuration: MutableSet[\"State\"]):\n        self.event = event\n        self.configuration = configuration\n        name = \", \".join([s.name for s in configuration])\n        msg = _(\"Can't {} when in {}.\").format(\n            self.event and self.event.name or \"transition\", name\n        )\n        super().__init__(msg)\n"
  },
  {
    "path": "statemachine/factory.py",
    "content": "import re\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Optional\nfrom typing import Tuple\n\nfrom . import registry\nfrom .callbacks import CallbackGroup\nfrom .callbacks import CallbackPriority\nfrom .callbacks import CallbackSpecList\nfrom .event import Event\nfrom .event import _expand_event_id\nfrom .exceptions import InvalidDefinition\nfrom .graph import disconnected_states\nfrom .graph import iterate_states\nfrom .graph import iterate_states_and_transitions\nfrom .graph import states_without_path_to_final_states\nfrom .i18n import _\nfrom .state import State\nfrom .states import States\nfrom .transition import Transition\nfrom .transition_list import TransitionList\n\n\nclass StateMachineMetaclass(type):\n    \"Metaclass for constructing StateMachine classes\"\n\n    validate_disconnected_states: bool = True\n    \"\"\"If `True`, the state machine will validate that there are no unreachable states.\"\"\"\n\n    validate_trap_states: bool = True\n    \"\"\"If ``True``, non-final states without outgoing transitions raise ``InvalidDefinition``.\"\"\"\n\n    validate_final_reachability: bool = True\n    \"\"\"If ``True`` and final states exist, non-final states without a path to any final\n    state raise ``InvalidDefinition``.\"\"\"\n\n    def __init__(\n        cls,\n        name: str,\n        bases: Tuple[type],\n        attrs: Dict[str, Any],\n    ) -> None:\n        super().__init__(name, bases, attrs)\n        registry.register(cls)\n        cls.name = cls.__name__\n        cls.id = cls.name.lower()\n        # TODO: Experiment with the IDEA of a root state\n        # cls.root = State(id=cls.id, name=cls.name)\n        cls.states: States = States()\n        cls.states_map: Dict[Any, State] = {}\n        \"\"\"Map of ``state.value`` to the corresponding :ref:`state`.\"\"\"\n\n        cls._abstract = True\n        cls._events: Dict[Event, None] = {}  # used Dict to preserve order and avoid duplicates\n        cls._protected_attrs: set = set()\n        cls._events_to_update: Dict[Event, Optional[Event]] = {}\n        cls._specs = CallbackSpecList()\n        cls.prepare = cls._specs.grouper(CallbackGroup.PREPARE).add(\n            \"prepare_event\", priority=CallbackPriority.GENERIC, is_convention=True\n        )\n        cls.add_inherited(bases)\n        cls.add_from_attributes(attrs)\n        cls._collect_class_listeners(attrs, bases)\n        cls._unpack_builders_callbacks()\n        cls._update_event_references()\n\n        if not cls.states:\n            return\n\n        cls._initials_by_document_order(list(cls.states), parent=None)\n\n        initials = [s for s in cls.states if s.initial]\n        parallels = [s.id for s in cls.states if s.parallel]\n        root_only_has_parallels = len(cls.states) == len(parallels)\n\n        if len(initials) != 1 and not root_only_has_parallels:\n            raise InvalidDefinition(\n                _(\n                    \"There should be one and only one initial state. \"\n                    \"Your currently have these: {0}\"\n                ).format(\", \".join(s.id for s in initials))\n            )\n\n        if initials:\n            cls.initial_state = initials[0]\n        else:  # pragma: no cover\n            cls.initial_state = None\n\n        cls.final_states: List[State] = [state for state in cls.states if state.final]\n\n        cls._check()\n        cls._setup()\n        cls._expand_docstring()\n\n    _STATECHART_RE = re.compile(r\"\\{statechart:(\\w+)\\}\")\n\n    def _expand_docstring(cls) -> None:\n        \"\"\"Replace ``{statechart:FORMAT}`` placeholders in the class docstring.\"\"\"\n        doc = cls.__doc__\n        if not doc:\n            return\n\n        from .contrib.diagram.formatter import formatter\n\n        def _replace(match: \"re.Match[str]\") -> str:\n            fmt = match.group(1)\n            rendered = formatter.render(cls, fmt)  # type: ignore[arg-type]\n\n            # Respect the indentation of the placeholder line.\n            line_start = doc.rfind(\"\\n\", 0, match.start())\n            if line_start == -1:\n                indent = \"\"\n            else:\n                indent_match = re.match(r\"[ \\t]*\", doc[line_start + 1 : match.start()])\n                indent = indent_match.group() if indent_match else \"\"\n\n            if indent:\n                lines = rendered.split(\"\\n\")\n                rendered = lines[0] + \"\\n\" + \"\\n\".join(indent + line for line in lines[1:])\n\n            return rendered\n\n        cls.__doc__ = cls._STATECHART_RE.sub(_replace, doc)\n\n    def __format__(cls, fmt: str) -> str:\n        from .contrib.diagram.formatter import formatter\n\n        return formatter.render(cls, fmt)  # type: ignore[arg-type]\n\n    def _initials_by_document_order(  # noqa: C901\n        cls, states: List[State], parent: \"State | None\" = None, order: int = 1\n    ):\n        \"\"\"Set initial state by document order if no explicit initial state is set\"\"\"\n        initials: List[State] = []\n        for s in states:\n            s.document_order = order\n            order += 1\n            if s.states:\n                cls._initials_by_document_order(s.states, s, order)\n            if s.initial:\n                initials.append(s)\n\n        if not initials and states:\n            initial = states[0]\n            initial._initial = True\n            initials.append(initial)\n\n        if not parent:\n            return\n\n        # If parent already has a multi-target initial transition (e.g., from SCXML initial\n        # attribute targeting multiple parallel regions), don't create default initial transitions.\n        if any(t for t in parent.transitions if t.initial and len(t.targets) > 1):\n            return\n\n        for initial in initials:\n            if not any(t for t in parent.transitions if t.initial and t.target == initial):\n                parent.to(initial, initial=True)\n\n        if not parent.parallel:\n            return\n\n        for state in states:\n            state._initial = True\n            if not any(t for t in parent.transitions if t.initial and t.target == state):\n                parent.to(state, initial=True)  # pragma: no cover\n\n    def _unpack_builders_callbacks(cls):\n        callbacks = {}\n        for state in iterate_states(cls.states):\n            if state._callbacks:\n                callbacks.update(state._callbacks)\n                del state._callbacks\n        for key, value in callbacks.items():\n            setattr(cls, key, value)\n\n    def _check(cls):\n        has_states = bool(cls.states)\n        cls._abstract = not has_states\n\n        # do not validate the base abstract classes\n        if cls._abstract:  # pragma: no cover\n            return\n\n        cls._check_initial_state()\n        cls._check_final_states()\n        cls._check_disconnected_state()\n        cls._check_trap_states()\n        cls._check_reachable_final_states()\n\n    def _check_initial_state(cls):\n        initials = [s for s in cls.states if s.initial]\n        if len(initials) != 1:  # pragma: no cover\n            raise InvalidDefinition(\n                _(\n                    \"There should be one and only one initial state. \"\n                    \"You currently have these: {!r}\"\n                ).format([s.id for s in initials])\n            )\n        # TODO: Check if this is still needed\n        # if not initials[0].transitions.transitions:\n        #     raise InvalidDefinition(_(\"There are no transitions.\"))\n\n    def _check_final_states(cls):\n        final_state_with_invalid_transitions = [\n            state for state in cls.final_states if state.transitions\n        ]\n\n        if final_state_with_invalid_transitions:\n            raise InvalidDefinition(\n                _(\"Cannot declare transitions from final state. Invalid state(s): {}\").format(\n                    [s.id for s in final_state_with_invalid_transitions]\n                )\n            )\n\n    def _check_trap_states(cls):\n        if not cls.validate_trap_states:\n            return\n        trap_states = [s for s in cls.states if not s.final and not s.transitions]\n        if trap_states:\n            raise InvalidDefinition(\n                _(\n                    \"All non-final states should have at least one outgoing transition. \"\n                    \"These states have no outgoing transition: {!r}\"\n                ).format([s.id for s in trap_states])\n            )\n\n    def _check_reachable_final_states(cls):\n        if not cls.validate_final_reachability:\n            return\n        if not any(s.final for s in cls.states):\n            return  # No need to check final reachability\n        disconnected_states = list(states_without_path_to_final_states(cls.states))\n        if disconnected_states:\n            raise InvalidDefinition(\n                _(\n                    \"All non-final states should have at least one path to a final state. \"\n                    \"These states have no path to a final state: {!r}\"\n                ).format([s.id for s in disconnected_states])\n            )\n\n    def _check_disconnected_state(cls):\n        if not cls.validate_disconnected_states:\n            return\n        assert cls.initial_state\n        states = disconnected_states(cls.initial_state, set(cls.states_map.values()))\n        if states:\n            raise InvalidDefinition(\n                _(\n                    \"There are unreachable states. \"\n                    \"The statemachine graph should have a single component. \"\n                    \"Disconnected states: {}\"\n                ).format([s.id for s in states])\n            )\n\n    def _setup(cls):\n        for visited in iterate_states_and_transitions(cls.states):\n            visited._setup()\n\n        cls._protected_attrs = {\n            \"_abstract\",\n            \"model\",\n            \"state_field\",\n            \"start_value\",\n            \"initial_state\",\n            \"final_states\",\n            \"states\",\n            \"_events\",\n            \"states_map\",\n            \"send\",\n        } | {s.id for s in cls.states}\n\n    def _collect_class_listeners(cls, attrs: Dict[str, Any], bases: Tuple[type]):\n        \"\"\"Collect class-level listener declarations from attrs and MRO.\n\n        Listeners declared on parent classes are prepended (MRO order),\n        unless the child sets ``listeners_inherit = False``.\n        \"\"\"\n        class_listeners: List[Any] = []\n        if attrs.get(\"listeners_inherit\", True):\n            for base in reversed(bases):\n                class_listeners.extend(getattr(base, \"_class_listeners\", []))\n        for entry in attrs.get(\"listeners\", []):\n            if entry is None or isinstance(entry, (str, int, float, bool)):\n                raise InvalidDefinition(\n                    _(\n                        \"Invalid entry in 'listeners': {!r}. \"\n                        \"Expected a class, callable, or listener instance.\"\n                    ).format(entry)\n                )\n            class_listeners.append(entry)\n        cls._class_listeners: List[Any] = class_listeners\n\n    def add_inherited(cls, bases):\n        for base in bases:\n            for state in getattr(base, \"states\", []):\n                cls.add_state(state.id, state)\n\n            events = getattr(base, \"_events\", {})\n            for event in events:\n                cls.add_event(event=Event(id=event.id, name=event.name))\n\n    def add_from_attributes(cls, attrs):  # noqa: C901\n        for key, value in attrs.items():\n            if isinstance(value, States):\n                cls._add_states_from_dict(value)\n            if isinstance(value, State):\n                cls.add_state(key, value)\n            elif isinstance(value, (Transition, TransitionList)):\n                event_id = _expand_event_id(key)\n                cls.add_event(event=Event(transitions=value, id=event_id))\n            elif isinstance(value, (Event,)):\n                if value._has_real_id:\n                    event_id = value.id\n                else:\n                    event_id = _expand_event_id(key)\n                new_event = Event(\n                    transitions=value._transitions,\n                    id=event_id,\n                    name=value.name,\n                )\n                cls.add_event(event=new_event, old_event=value)\n                # Ensure the event is accessible by the Python attribute name\n                if event_id != key:\n                    setattr(cls, key, new_event)\n            elif getattr(value, \"attr_name\", None):\n                cls._add_unbounded_callback(key, value)\n\n    def _add_states_from_dict(cls, states):\n        for state_id, state in states.items():\n            cls.add_state(state_id, state)\n\n    def _add_unbounded_callback(cls, attr_name, func):\n        # if func is an event, the `attr_name` will be replaced by an event trigger,\n        # so we'll also give the ``func`` a new unique name to be used by the callback\n        # machinery that is stored at ``func.attr_name``\n        setattr(cls, func.attr_name, func)\n        if func.is_event:\n            cls.add_event(event=Event(func._transitions, id=attr_name))\n\n    def add_state(cls, id, state: State):\n        state._set_id(id)\n        cls.states_map[state.value] = state\n        if not state.parent:\n            cls.states.append(state)\n            if not hasattr(cls, id):\n                setattr(cls, id, state)\n\n        # also register all events associated directly with transitions\n        for event in state.transitions.unique_events:\n            cls.add_event(event)\n\n        for substate in state.states:\n            cls.add_state(substate.id, substate)\n\n    def add_event(\n        cls,\n        event: Event,\n        old_event: \"Event | None\" = None,\n    ):\n        if not event._has_real_id:\n            if event not in cls._events_to_update:\n                cls._events_to_update[event] = None\n            return\n\n        transitions = event._transitions\n        if transitions is not None:\n            transitions._on_event_defined(event=event, states=list(cls.states))\n\n        if event not in cls._events:\n            cls._events[event] = None\n            setattr(cls, event.id, event)\n\n        if old_event is not None:\n            cls._events_to_update[old_event] = event\n\n        return cls._events[event]\n\n    def _update_event_references(cls):\n        for old_event, new_event in cls._events_to_update.items():\n            for state in cls.states:\n                for transition in state.transitions:\n                    if transition._events.match(old_event):\n                        if new_event is None:\n                            raise InvalidDefinition(\n                                _(\"An event in the '{}' has no id.\").format(transition)\n                            )\n                        transition.events._replace(old_event, new_event)\n\n        cls._events_to_update = {}\n\n    @property\n    def events(self):\n        return list(self._events)\n"
  },
  {
    "path": "statemachine/graph.py",
    "content": "from collections import deque\nfrom typing import TYPE_CHECKING\nfrom typing import Iterable\nfrom typing import MutableSet\n\nif TYPE_CHECKING:\n    from .state import State\n\n\ndef visit_connected_states(state: \"State\"):\n    visit = deque[\"State\"]()\n    already_visited = set()\n    visit.append(state)\n    while visit:\n        state = visit.popleft()\n        if state in already_visited:\n            continue\n        already_visited.add(state)\n        yield state\n        visit.extend(t.target for t in state.transitions if t.target)\n        # Traverse the state hierarchy: entering a compound/parallel state\n        # implicitly enters its initial children (all children for parallel).\n        for child in state.states:\n            if child.initial:\n                visit.append(child)\n        for child in state.history:\n            visit.append(child)\n        # Being in a child state implies being in all ancestor states.\n        if state.parent:\n            visit.append(state.parent)\n\n\ndef disconnected_states(starting_state: \"State\", all_states: MutableSet[\"State\"]):\n    visitable_states = set(visit_connected_states(starting_state))\n    return all_states - visitable_states\n\n\ndef iterate_states_and_transitions(states: Iterable[\"State\"]):\n    for state in states:\n        yield state\n        yield from state.transitions\n        if state.states:\n            yield from iterate_states_and_transitions(state.states)\n        if state.history:\n            yield from iterate_states_and_transitions(state.history)\n\n\ndef iterate_states(states: Iterable[\"State\"]):\n    for state in states:\n        yield state\n        if state.states:\n            yield from iterate_states(state.states)\n        if state.history:\n            yield from iterate_states(state.history)\n\n\ndef states_without_path_to_final_states(states: Iterable[\"State\"]):\n    return (\n        state\n        for state in states\n        if not state.final and not any(s.final for s in visit_connected_states(state))\n    )\n"
  },
  {
    "path": "statemachine/i18n.py",
    "content": "import gettext\nfrom pathlib import Path\n\nscript_dir = Path(__file__).resolve().parent\nlocale_dir = script_dir / \"locale\"\n\n\ndef setup_i18n():\n    translate = gettext.translation(\"statemachine\", locale_dir, fallback=True)\n    gettext.bindtextdomain(\"statemachine\", locale_dir)\n    gettext.textdomain(\"statemachine\")\n    return translate.gettext\n\n\n_ = setup_i18n()\n"
  },
  {
    "path": "statemachine/invoke.py",
    "content": "\"\"\"Invoke support for StateCharts.\n\nInvoke lets a state spawn external work (API calls, file I/O, child state machines)\nwhen entered, and cancel it when exited. Invoke is modelled as a callback group\n(``CallbackGroup.INVOKE``) so that convention naming (``on_invoke_<state>``),\ndecorators (``@state.invoke``), and inline callables all work out of the box.\n\"\"\"\n\nimport asyncio\nimport threading\nimport uuid\nfrom concurrent.futures import Future\nfrom concurrent.futures import ThreadPoolExecutor\nfrom dataclasses import dataclass\nfrom dataclasses import field\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Callable\nfrom typing import Dict\nfrom typing import List\nfrom typing import Tuple\nfrom typing import runtime_checkable\n\ntry:\n    from typing import Protocol\nexcept ImportError:  # pragma: no cover\n    from typing_extensions import Protocol  # type: ignore[assignment]\n\nif TYPE_CHECKING:\n    from .callbacks import CallbackWrapper\n    from .engines.base import BaseEngine\n    from .state import State\n    from .statemachine import StateChart\n\n\n@runtime_checkable\nclass IInvoke(Protocol):\n    \"\"\"Protocol for advanced invoke handlers.\n\n    Implement ``run(ctx)`` to execute work when a state is entered.\n    Optionally implement ``on_cancel()`` for cleanup when the state is exited.\n    \"\"\"\n\n    def run(self, ctx: \"InvokeContext\") -> Any: ...  # pragma: no branch\n\n\ndef _stop_child_machine(child: \"StateChart | None\") -> None:\n    \"\"\"Stop a child state machine and cancel all its invocations.\"\"\"\n    if child is None:\n        return\n    child._engine.stop()\n\n\nclass _InvokeCallableWrapper:\n    \"\"\"Wraps an IInvoke class/instance or StateChart class for the callback system.\n\n    The callback resolution system expects plain callables or strings. This wrapper\n    makes IInvoke classes, IInvoke instances, and StateChart classes look like regular\n    callables while preserving the original object for the InvokeManager to detect.\n\n    When ``_invoke_handler`` is a **class**, ``run()`` instantiates it on each call\n    so that each StateChart instance gets its own handler — avoiding shared mutable\n    state between machines.\n    \"\"\"\n\n    def __init__(self, handler: Any):\n        self._invoke_handler = handler\n        self._is_class = isinstance(handler, type)\n        self._instance: Any = None\n        name = getattr(handler, \"__name__\", type(handler).__name__)\n        self.__name__ = name\n        self.__qualname__ = getattr(handler, \"__qualname__\", name)\n        # The callback system inspects __code__ for caching (signature.py)\n        self.__code__ = self.__call__.__code__\n\n    def __call__(self, **kwargs):\n        return self._invoke_handler\n\n    def run(self, ctx: \"InvokeContext\") -> Any:\n        \"\"\"Create a fresh instance (if class) and delegate to its ``run()``.\"\"\"\n        handler = self._invoke_handler\n        if self._is_class:\n            handler = handler()\n        self._instance = handler\n        return handler.run(ctx)\n\n    def on_cancel(self):\n        \"\"\"Delegate to the live instance's ``on_cancel()`` if available.\"\"\"\n        if self._instance is not None:\n            target = self._instance\n        elif self._is_class:\n            return  # Handler hasn't been instantiated yet — nothing to cancel\n        else:\n            target = self._invoke_handler\n        if hasattr(target, \"on_cancel\"):\n            target.on_cancel()\n\n\ndef normalize_invoke_callbacks(invoke: Any) -> Any:\n    \"\"\"Wrap IInvoke instances and StateChart classes so the callback system can handle them.\n\n    Plain callables and strings pass through unchanged.\n    \"\"\"\n    if invoke is None:\n        return None\n\n    from .utils import ensure_iterable\n\n    items = ensure_iterable(invoke)\n    result = []\n    for item in items:\n        if _needs_wrapping(item):\n            result.append(_InvokeCallableWrapper(item))\n        else:\n            result.append(item)\n    return result\n\n\ndef _needs_wrapping(item: Any) -> bool:\n    \"\"\"Check if an item needs wrapping for the callback system.\"\"\"\n    if isinstance(item, str):\n        return False\n    if isinstance(item, _InvokeCallableWrapper):\n        return False\n    # IInvoke instance (already instantiated — kept for advanced use / SCXML adapter)\n    if isinstance(item, IInvoke):\n        return True\n    if isinstance(item, type):\n        from .statemachine import StateChart\n\n        # StateChart subclass → child machine invoker\n        if issubclass(item, StateChart):\n            return True\n    return False\n\n\n@dataclass\nclass InvokeContext:\n    \"\"\"Context passed to invoke handlers.\"\"\"\n\n    invokeid: str\n    \"\"\"Unique identifier for this invocation.\"\"\"\n\n    state_id: str\n    \"\"\"The id of the state that triggered this invocation.\"\"\"\n\n    send: \"Callable[..., None]\"\n    \"\"\"``send(event, **data)`` — enqueue an event on the parent machine's external queue.\"\"\"\n\n    machine: \"StateChart\"\n    \"\"\"Reference to the parent state machine.\"\"\"\n\n    cancelled: threading.Event = field(default_factory=threading.Event)\n    \"\"\"Set when the owning state is exited; handlers should check this to stop early.\"\"\"\n\n    kwargs: dict = field(default_factory=dict)\n    \"\"\"Keyword arguments from the event that triggered the state entry.\"\"\"\n\n\n@dataclass\nclass Invocation:\n    \"\"\"Tracks a single active invocation.\"\"\"\n\n    invokeid: str\n    state_id: str\n    ctx: InvokeContext\n    thread: \"threading.Thread | None\" = None\n    task: \"asyncio.Task[Any] | None\" = None\n    terminated: bool = False\n    _handler: Any = None\n\n\nclass StateChartInvoker:\n    \"\"\"Wraps a :class:`StateChart` subclass as an :class:`IInvoke` handler.\n\n    When ``run(ctx)`` is called, it instantiates and runs the child machine\n    synchronously. The child machine's final result (if any) becomes the\n    return value.\n    \"\"\"\n\n    def __init__(self, child_class: \"type[StateChart]\"):\n        self._child_class = child_class\n        self._child: \"StateChart | None\" = None\n\n    def run(self, _ctx: \"InvokeContext\") -> Any:\n        self._child = self._child_class()\n        # The child machine starts automatically in its constructor.\n        # If it has final states, it will terminate on its own.\n        return None\n\n    def on_cancel(self):\n        _stop_child_machine(self._child)\n        self._child = None\n\n\nclass InvokeGroup:\n    \"\"\"Runs multiple callables concurrently and returns their results as a list.\n\n    All callables are submitted to a :class:`~concurrent.futures.ThreadPoolExecutor`.\n    The handler blocks until every callable completes, then returns a list of results\n    in the same order as the input callables.\n\n    If the owning state is exited before all callables finish, the remaining futures\n    are cancelled.  If any callable raises, the remaining futures are cancelled and\n    the exception propagates (which causes an ``error.execution`` event).\n    \"\"\"\n\n    def __init__(self, callables: \"List[Callable[..., Any]]\"):\n        self._callables = list(callables)\n        self._futures: \"List[Future[Any]]\" = []\n        self._executor: \"ThreadPoolExecutor | None\" = None\n\n    def run(self, ctx: \"InvokeContext\") -> \"List[Any]\":\n        results: \"List[Any]\" = [None] * len(self._callables)\n        self._executor = ThreadPoolExecutor(max_workers=len(self._callables))\n        try:\n            self._futures = [self._executor.submit(fn) for fn in self._callables]\n            for idx, future in enumerate(self._futures):\n                # Poll so we can react to cancellation promptly.\n                while not future.done():\n                    if ctx.cancelled.is_set():\n                        self._cancel_remaining()\n                        return []\n                    ctx.cancelled.wait(timeout=0.05)\n                results[idx] = future.result()  # re-raises if the callable failed\n        except Exception:\n            self._cancel_remaining()\n            raise\n        finally:\n            # Normal exit: all futures completed, safe to shutdown without waiting.\n            self._executor.shutdown(wait=False)\n        return results\n\n    def on_cancel(self):\n        # Called from the engine thread — must not block. Cancel pending futures\n        # and signal shutdown; the invoke thread's run() will detect ctx.cancelled\n        # and exit, then _cancel()'s thread.join() waits for the actual cleanup.\n        self._cancel_remaining()\n        if self._executor is not None:\n            self._executor.shutdown(wait=False, cancel_futures=True)\n\n    def _cancel_remaining(self):\n        for future in self._futures:\n            if not future.done():\n                future.cancel()\n\n\ndef invoke_group(*callables: \"Callable[..., Any]\") -> InvokeGroup:\n    \"\"\"Group multiple callables into a single invoke that runs them concurrently.\n\n    Returns an :class:`InvokeGroup` instance (implements :class:`IInvoke`).\n    When all callables complete, a single ``done.invoke`` event is sent with\n    ``data`` set to a list of results in the same order as the input callables.\n\n    Example::\n\n        loading = State(initial=True, invoke=invoke_group(fetch_users, fetch_config))\n\n        def on_enter_ready(self, data=None, **kwargs):\n            users, config = data\n    \"\"\"\n    return InvokeGroup(list(callables))\n\n\nclass InvokeManager:\n    \"\"\"Manages the lifecycle of invoke handlers for a state machine engine.\n\n    Tracks which states need invocation after entry, spawns handlers\n    (in threads for sync, as tasks for async), and cancels them on exit.\n    \"\"\"\n\n    def __init__(self, engine: \"BaseEngine\"):\n        self._engine = engine\n        self._active: Dict[str, Invocation] = {}\n        self._pending: \"List[Tuple[State, dict]]\" = []\n\n    @property\n    def _debug(self):\n        return self._engine._debug\n\n    @property\n    def _log_id(self):\n        return self._engine._log_id\n\n    @property\n    def sm(self) -> \"StateChart\":\n        return self._engine.sm\n\n    # --- Engine hooks ---\n\n    def mark_for_invoke(self, state: \"State\", event_kwargs: \"dict | None\" = None):\n        \"\"\"Called by ``_enter_states()`` after entering a state with invoke callbacks.\n\n        Args:\n            state: The state that was entered.\n            event_kwargs: Keyword arguments from the event that triggered the\n                state entry.  These are forwarded to invoke handlers via\n                dependency injection (plain callables) and ``InvokeContext.kwargs``\n                (IInvoke handlers).\n        \"\"\"\n        self._pending.append((state, event_kwargs or {}))\n\n    def cancel_for_state(self, state: \"State\"):\n        \"\"\"Called by ``_exit_states()`` before exiting a state.\"\"\"\n        self._debug(\"%s invoke cancel_for_state: %s\", self._log_id, state.id)\n        for inv_id, inv in list(self._active.items()):\n            if inv.state_id == state.id and not inv.ctx.cancelled.is_set():\n                self._cancel(inv_id)\n        self._pending = [(s, kw) for s, kw in self._pending if s.id != state.id]\n        # Don't cleanup here — terminated invocations must stay in _active\n        # so that handle_external_event can still run finalize blocks for\n        # done.invoke events that are already queued.\n\n    def cancel_all(self):\n        \"\"\"Cancel all active invocations.\"\"\"\n        self._debug(\"%s invoke cancel_all: %d active\", self._log_id, len(self._active))\n        for inv_id in list(self._active.keys()):\n            self._cancel(inv_id)\n        self._cleanup_terminated()\n\n    def _cleanup_terminated(self):\n        \"\"\"Remove invocations whose threads/tasks have actually finished.\n\n        Only removes invocations that are both terminated AND cancelled.\n        A terminated-but-not-cancelled invocation means the handler's ``run()``\n        has returned but the owning state is still active — the invocation must\n        stay in ``_active`` so that ``send_to_child()`` can still forward events\n        to it (e.g. ``<send target=\"#_<invokeid>\">``).\n        \"\"\"\n        self._active = {\n            inv_id: inv\n            for inv_id, inv in self._active.items()\n            if not inv.terminated or not inv.ctx.cancelled.is_set()\n        }\n\n    # --- Sync spawning ---\n\n    def spawn_pending_sync(self):\n        \"\"\"Spawn invoke handlers for all states marked for invocation (sync engine).\"\"\"\n        # Opportunistically clean up finished invocations before spawning new ones.\n        self._cleanup_terminated()\n\n        pending = sorted(self._pending, key=lambda p: p[0].document_order)\n        self._pending.clear()\n        for state, event_kwargs in pending:\n            self.sm._callbacks.visit(\n                state.invoke.key,\n                self._spawn_one_sync,\n                state=state,\n                event_kwargs=event_kwargs,\n            )\n\n    def _spawn_one_sync(self, callback: \"CallbackWrapper\", **kwargs):\n        state: \"State\" = kwargs[\"state\"]\n        event_kwargs: dict = kwargs.get(\"event_kwargs\", {})\n\n        # Use meta.func to find the original (unwrapped) handler; the callback\n        # system wraps everything in a signature_adapter closure.\n        handler = self._resolve_handler(callback.meta.func)\n        ctx = self._make_context(state, event_kwargs, handler=handler)\n        invocation = Invocation(invokeid=ctx.invokeid, state_id=state.id, ctx=ctx)\n\n        invocation._handler = handler\n        self._active[ctx.invokeid] = invocation\n        self._debug(\"%s invoke spawn sync: %s on state %s\", self._log_id, ctx.invokeid, state.id)\n\n        thread = threading.Thread(\n            target=self._run_sync_handler,\n            args=(callback, handler, ctx, invocation),\n            daemon=True,\n        )\n        invocation.thread = thread\n        thread.start()\n\n    def _run_sync_handler(\n        self,\n        callback: \"CallbackWrapper\",\n        handler: \"Any | None\",\n        ctx: InvokeContext,\n        invocation: Invocation,\n    ):\n        try:\n            if handler is not None:\n                result = handler.run(ctx)\n            else:\n                result = callback.call(ctx=ctx, machine=ctx.machine, **ctx.kwargs)\n            if not ctx.cancelled.is_set():\n                self.sm.send(\n                    f\"done.invoke.{ctx.invokeid}\",\n                    data=result,\n                )\n        except Exception as e:\n            if not ctx.cancelled.is_set():\n                # Intentionally using the external queue (no internal=True):\n                # This handler runs in a background thread, outside the processing\n                # loop. Using the internal queue would either contaminate an\n                # unrelated macrostep in progress, or stall if no macrostep is\n                # active (the internal queue is only drained within a macrostep).\n                # This matches done.invoke, which also uses the external queue.\n                self.sm.send(\"error.execution\", error=e)\n        finally:\n            invocation.terminated = True\n            self._debug(\n                \"%s invoke %s: completed (cancelled=%s)\",\n                self._log_id,\n                ctx.invokeid,\n                ctx.cancelled.is_set(),\n            )\n\n    # --- Async spawning ---\n\n    async def spawn_pending_async(self):\n        \"\"\"Spawn invoke handlers for all states marked for invocation (async engine).\"\"\"\n        # Opportunistically clean up finished invocations before spawning new ones.\n        self._cleanup_terminated()\n\n        pending = sorted(self._pending, key=lambda p: p[0].document_order)\n        self._pending.clear()\n        for state, event_kwargs in pending:\n            await self.sm._callbacks.async_visit(\n                state.invoke.key,\n                self._spawn_one_async,\n                state=state,\n                event_kwargs=event_kwargs,\n            )\n\n    def _spawn_one_async(self, callback: \"CallbackWrapper\", **kwargs):\n        state: \"State\" = kwargs[\"state\"]\n        event_kwargs: dict = kwargs.get(\"event_kwargs\", {})\n\n        handler = self._resolve_handler(callback.meta.func)\n        ctx = self._make_context(state, event_kwargs, handler=handler)\n        invocation = Invocation(invokeid=ctx.invokeid, state_id=state.id, ctx=ctx)\n\n        invocation._handler = handler\n        self._active[ctx.invokeid] = invocation\n        self._debug(\"%s invoke spawn async: %s on state %s\", self._log_id, ctx.invokeid, state.id)\n\n        loop = asyncio.get_running_loop()\n        task = loop.create_task(self._run_async_handler(callback, handler, ctx, invocation))\n        invocation.task = task\n\n    async def _run_async_handler(\n        self,\n        callback: \"CallbackWrapper\",\n        handler: \"Any | None\",\n        ctx: InvokeContext,\n        invocation: Invocation,\n    ):\n        try:\n            loop = asyncio.get_running_loop()\n            if handler is not None:\n                # Run handler.run(ctx) in a thread executor so blocking I/O\n                # doesn't freeze the event loop.\n                result = await loop.run_in_executor(None, handler.run, ctx)\n            else:\n                result = await loop.run_in_executor(\n                    None, lambda: callback.call(ctx=ctx, machine=ctx.machine, **ctx.kwargs)\n                )\n            if not ctx.cancelled.is_set():\n                await self.sm.send(\n                    f\"done.invoke.{ctx.invokeid}\",\n                    data=result,\n                )\n        except asyncio.CancelledError:\n            # Intentionally swallowed: the owning state was exited, so this\n            # invocation was cancelled — there is nothing to propagate.\n            return\n        except Exception as e:\n            if not ctx.cancelled.is_set():\n                # External queue — see comment in _run_sync_handler.\n                await self.sm.send(\"error.execution\", error=e)\n        finally:\n            invocation.terminated = True\n            self._debug(\n                \"%s invoke %s: completed (cancelled=%s)\",\n                self._log_id,\n                ctx.invokeid,\n                ctx.cancelled.is_set(),\n            )\n\n    # --- Cancel ---\n\n    def _cancel(self, invokeid: str):\n        invocation = self._active.get(invokeid)\n        if not invocation or invocation.ctx.cancelled.is_set():\n            return\n\n        self._debug(\"%s invoke cancel: %s\", self._log_id, invokeid)\n        # 1) Signal cancellation so the handler can check and stop early.\n        invocation.ctx.cancelled.set()\n\n        # 2) Notify the handler (may stop child SMs, cancel futures, etc.).\n        handler = invocation._handler\n        if handler is not None and hasattr(handler, \"on_cancel\"):\n            try:\n                handler.on_cancel()\n            except Exception:\n                self._debug(\"%s Error in on_cancel for %s\", self._log_id, invokeid, exc_info=True)\n\n        # 3) Cancel the async task (raises CancelledError at next await).\n        if invocation.task is not None and not invocation.task.done():\n            invocation.task.cancel()\n\n        # 4) Wait for the sync thread to actually finish (skip if we ARE\n        #    that thread — e.g. done.invoke processed from within the handler).\n        if (\n            invocation.thread is not None\n            and invocation.thread is not threading.current_thread()\n            and invocation.thread.is_alive()\n        ):\n            invocation.thread.join(timeout=2.0)\n\n    def send_to_child(self, invokeid: str, event: str, **data) -> bool:\n        \"\"\"Send an event to an invoked child session by its invokeid.\n\n        Returns True if the event was forwarded, False if the invocation was\n        not found or doesn't support event forwarding.\n        \"\"\"\n        invocation = self._active.get(invokeid)\n        if invocation is None:\n            return False\n        handler = invocation._handler\n        if handler is not None and hasattr(handler, \"on_event\"):\n            handler.on_event(event, **data)\n            return True\n        return False\n\n    # --- Helpers ---\n\n    def handle_external_event(self, trigger_data) -> None:\n        \"\"\"Run finalize blocks and autoforward for active invocations.\n\n        Called by the engine before processing each external event.\n        For each active invocation whose handler has ``on_finalize`` or\n        ``on_event`` (autoforward), delegate accordingly.\n        \"\"\"\n        event_name = str(trigger_data.event) if trigger_data.event else None\n        if event_name is None:\n            return\n\n        # Tag done.invoke events with the invokeid\n        if event_name.startswith(\"done.invoke.\"):\n            invokeid = event_name[len(\"done.invoke.\") :]\n            trigger_data.kwargs.setdefault(\"_invokeid\", invokeid)\n\n        for inv in list(self._active.values()):\n            handler = inv._handler\n            if handler is None:\n                continue\n\n            # Check if event originates from this invocation\n            is_from_child = trigger_data.kwargs.get(\n                \"_invokeid\"\n            ) == inv.invokeid or event_name.startswith(f\"done.invoke.{inv.invokeid}\")\n\n            # Finalize: run the finalize block if the event came from this invocation.\n            # Note: finalize must run even after the invocation terminates, because\n            # child events may still be queued when the handler thread completes.\n            if is_from_child and hasattr(handler, \"on_finalize\"):\n                handler.on_finalize(trigger_data)\n\n            # Autoforward: forward parent events to child (not events from child itself).\n            # Only forward if the invocation is still running.\n            if (\n                not inv.terminated\n                and not inv.ctx.cancelled.is_set()\n                and not is_from_child\n                and hasattr(handler, \"autoforward\")\n                and handler.autoforward\n                and hasattr(handler, \"on_event\")\n            ):\n                self._debug(\n                    \"%s invoke autoforward: %s -> %s\", self._log_id, event_name, inv.invokeid\n                )\n                handler.on_event(event_name, **trigger_data.kwargs)\n\n    def _make_context(\n        self, state: \"State\", event_kwargs: \"dict | None\" = None, handler: Any = None\n    ) -> InvokeContext:\n        # Use static invoke_id from handler if available (SCXML id= attribute)\n        static_id = getattr(handler, \"invoke_id\", None) if handler else None\n        invokeid = static_id or f\"{state.id}.{uuid.uuid4().hex[:8]}\"\n        return InvokeContext(\n            invokeid=invokeid,\n            state_id=state.id,\n            send=self.sm.send,\n            machine=self.sm,\n            kwargs=event_kwargs or {},\n        )\n\n    @staticmethod\n    def _resolve_handler(underlying: Any) -> \"Any | None\":\n        \"\"\"Determine the handler type from the resolved callable.\"\"\"\n        from .statemachine import StateChart\n\n        if isinstance(underlying, _InvokeCallableWrapper):\n            inner = underlying._invoke_handler\n            if isinstance(inner, type) and issubclass(inner, StateChart):\n                return StateChartInvoker(inner)\n            # Return the inner handler directly if it's an IInvoke instance\n            # (e.g., SCXMLInvoker) so duck-typed attributes like invoke_id are accessible.\n            # Exclude classes — @runtime_checkable matches classes that define run().\n            if not isinstance(inner, type) and isinstance(inner, IInvoke):\n                return inner\n            return underlying\n        if isinstance(underlying, IInvoke):\n            return underlying\n        if isinstance(underlying, type) and issubclass(underlying, StateChart):\n            return StateChartInvoker(underlying)\n        return None\n"
  },
  {
    "path": "statemachine/io/__init__.py",
    "content": "from typing import Any\nfrom typing import Dict\nfrom typing import List\nfrom typing import Mapping\nfrom typing import Protocol\nfrom typing import Sequence\nfrom typing import Tuple\nfrom typing import TypedDict\nfrom typing import cast\n\nfrom ..factory import StateMachineMetaclass\nfrom ..state import HistoryState\nfrom ..state import State\nfrom ..statemachine import StateChart\nfrom ..transition import Transition\nfrom ..transition_list import TransitionList\n\n\nclass ActionProtocol(Protocol):  # pragma: no cover\n    def __call__(self, *args, **kwargs) -> Any: ...\n\n\nclass TransitionDict(TypedDict, total=False):\n    target: \"str | None\"\n    event: \"str | None\"\n    internal: bool\n    initial: bool\n    validators: bool\n    cond: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n    unless: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n    on: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n    before: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n    after: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n\n\nTransitionsDict = Dict[\"str | None\", List[TransitionDict]]\nTransitionsList = List[TransitionDict]\n\n\nclass BaseStateKwargs(TypedDict, total=False):\n    name: str\n    value: Any\n    initial: bool\n    final: bool\n    parallel: bool\n    enter: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n    exit: \"str | ActionProtocol | Sequence[str] | Sequence[ActionProtocol]\"\n    donedata: \"ActionProtocol | None\"\n\n\nclass StateKwargs(BaseStateKwargs, total=False):\n    states: List[State]\n    history: List[HistoryState]\n\n\nclass HistoryKwargs(TypedDict, total=False):\n    name: str\n    value: Any\n    type: str\n\n\nclass HistoryDefinition(HistoryKwargs, total=False):\n    on: TransitionsDict\n    transitions: TransitionsList\n\n\nclass StateDefinition(BaseStateKwargs, total=False):\n    states: Dict[str, \"StateDefinition\"]\n    history: Dict[str, \"HistoryDefinition\"]\n    on: TransitionsDict\n    transitions: TransitionsList\n\n\ndef _parse_history(\n    states: Mapping[str, \"HistoryKwargs |HistoryDefinition\"],\n) -> Tuple[Dict[str, HistoryState], Dict[str, dict]]:\n    states_instances: Dict[str, HistoryState] = {}\n    events_definitions: Dict[str, dict] = {}\n    for state_id, state_definition in states.items():\n        state_definition = cast(HistoryDefinition, state_definition)\n        transition_defs = state_definition.pop(\"on\", {})\n        transition_list = state_definition.pop(\"transitions\", [])\n        if transition_list:\n            transition_defs[None] = transition_list\n\n        if transition_defs:\n            events_definitions[state_id] = transition_defs\n\n        state_definition = cast(HistoryKwargs, state_definition)\n        states_instances[state_id] = HistoryState(**state_definition)\n\n    return (states_instances, events_definitions)\n\n\ndef _parse_states(\n    states: Mapping[str, \"BaseStateKwargs | StateDefinition\"],\n) -> Tuple[Dict[str, State], Dict[str, dict]]:\n    states_instances: Dict[str, State] = {}\n    events_definitions: Dict[str, dict] = {}\n\n    for state_id, state_definition in states.items():\n        # Process nested states. Replaces `states` as a definition by a list of `State` instances.\n        state_definition = cast(StateDefinition, state_definition)\n\n        # pop the nested states, history and transitions definitions\n        inner_states_defs: Dict[str, StateDefinition] = state_definition.pop(\"states\", {})\n        inner_history_defs: Dict[str, HistoryDefinition] = state_definition.pop(\"history\", {})\n        transition_defs = state_definition.pop(\"on\", {})\n        transition_list = state_definition.pop(\"transitions\", [])\n        if transition_list:\n            transition_defs[None] = transition_list\n\n        if inner_states_defs:\n            inner_states, inner_events = _parse_states(inner_states_defs)\n\n            top_level_states = [\n                state._set_id(state_id)\n                for state_id, state in inner_states.items()\n                if not state.parent\n            ]\n            state_definition[\"states\"] = top_level_states  # type: ignore\n            states_instances.update(inner_states)\n            events_definitions.update(inner_events)\n\n        if inner_history_defs:\n            inner_history, inner_events = _parse_history(inner_history_defs)\n\n            top_level_history = [\n                state._set_id(state_id)\n                for state_id, state in inner_history.items()\n                if not state.parent\n            ]\n            state_definition[\"history\"] = top_level_history  # type: ignore\n            states_instances.update(inner_history)\n            events_definitions.update(inner_events)\n\n        if transition_defs:\n            events_definitions[state_id] = transition_defs\n\n        state_definition = cast(BaseStateKwargs, state_definition)\n        states_instances[state_id] = State(**state_definition)\n\n    return (states_instances, events_definitions)\n\n\ndef create_machine_class_from_definition(\n    name: str, states: Mapping[str, \"StateKwargs | StateDefinition\"], **definition\n) -> \"type[StateChart]\":  # noqa: C901\n    \"\"\"Create a StateChart class dynamically from a dictionary definition.\n\n    Args:\n        name: The class name for the generated state machine.\n        states: A mapping of state IDs to state definitions. Each state definition\n            can include ``initial``, ``final``, ``parallel``, ``name``, ``value``,\n            ``enter``/``exit`` callbacks, ``donedata``, nested ``states``,\n            ``history``, and transitions via ``on`` (event-triggered) or\n            ``transitions`` (eventless).\n        **definition: Additional keyword arguments passed to the metaclass\n            (e.g., ``validate_final_reachability=False``).\n\n    Returns:\n        A new StateChart subclass configured with the given states and transitions.\n\n    Example:\n\n    >>> machine = create_machine_class_from_definition(\n    ...     \"TrafficLightMachine\",\n    ...     **{\n    ...         \"states\": {\n    ...             \"green\": {\"initial\": True, \"on\": {\"change\": [{\"target\": \"yellow\"}]}},\n    ...             \"yellow\": {\"on\": {\"change\": [{\"target\": \"red\"}]}},\n    ...             \"red\": {\"on\": {\"change\": [{\"target\": \"green\"}]}},\n    ...         },\n    ...     }\n    ... )\n\n    \"\"\"\n    states_instances, events_definitions = _parse_states(states)\n\n    events: Dict[str, TransitionList] = {}\n    for state_id, state_events in events_definitions.items():\n        for event_name, transitions_data in state_events.items():\n            for transition_data in transitions_data:\n                source = states_instances[state_id]\n\n                target_state_id = transition_data[\"target\"]\n                transition_event_name = transition_data.get(\"event\")\n                if event_name is not None and transition_event_name is not None:\n                    transition_event_name = f\"{event_name} {transition_event_name}\"\n                elif event_name is not None:\n                    transition_event_name = event_name\n\n                transition_kwargs = {\n                    \"event\": transition_event_name,\n                    \"internal\": transition_data.get(\"internal\"),\n                    \"initial\": transition_data.get(\"initial\"),\n                    \"cond\": transition_data.get(\"cond\"),\n                    \"unless\": transition_data.get(\"unless\"),\n                    \"on\": transition_data.get(\"on\"),\n                    \"before\": transition_data.get(\"before\"),\n                    \"after\": transition_data.get(\"after\"),\n                }\n\n                # Handle multi-target transitions (space-separated target IDs)\n                if target_state_id and isinstance(target_state_id, str) and \" \" in target_state_id:\n                    target_ids = target_state_id.split()\n                    targets = [states_instances[tid] for tid in target_ids]\n                    t = Transition(source, target=targets, **transition_kwargs)\n                    source.transitions.add_transitions(t)\n                    transition = TransitionList([t])\n                else:\n                    target = states_instances[target_state_id] if target_state_id else None\n                    transition = source.to(target, **transition_kwargs)\n\n                if event_name in events:\n                    events[event_name] |= transition\n                elif event_name is not None:\n                    events[event_name] = transition\n\n    top_level_states = {\n        state_id: state for state_id, state in states_instances.items() if not state.parent\n    }\n\n    attrs_mapper = {**definition, **top_level_states, **events}\n    return StateMachineMetaclass(name, (StateChart,), attrs_mapper)  # type: ignore[return-value]\n"
  },
  {
    "path": "statemachine/io/scxml/__init__.py",
    "content": ""
  },
  {
    "path": "statemachine/io/scxml/actions.py",
    "content": "import html\nimport logging\nimport re\nfrom dataclasses import dataclass\nfrom itertools import chain\nfrom typing import Any\nfrom typing import Callable\nfrom uuid import uuid4\n\nfrom ...event import BoundEvent\nfrom ...event import Event\nfrom ...event import _event_data_kwargs\nfrom ...spec_parser import InState\nfrom ...statemachine import StateChart\nfrom .parser import Action\nfrom .parser import AssignAction\nfrom .parser import IfAction\nfrom .parser import LogAction\nfrom .parser import RaiseAction\nfrom .parser import SendAction\nfrom .schema import CancelAction\nfrom .schema import DataItem\nfrom .schema import DataModel\nfrom .schema import DoneData\nfrom .schema import ExecutableContent\nfrom .schema import ForeachAction\nfrom .schema import Param\nfrom .schema import ScriptAction\n\nlogger = logging.getLogger(__name__)\n_debug = logger.debug if logger.isEnabledFor(logging.DEBUG) else lambda *a, **k: None\nprotected_attrs = _event_data_kwargs | {\"_sessionid\", \"_ioprocessors\", \"_name\", \"_event\"}\n\n\nclass ParseTime:\n    pattern = re.compile(r\"(\\d+)?(\\.\\d+)?(s|ms)\")\n\n    @classmethod\n    def parse_delay(cls, delay: \"str | None\", delayexpr: \"str | None\", **kwargs):\n        if delay:\n            return cls.time_in_ms(delay)\n        elif delayexpr:\n            delay_expr_expanded = cls.replace(delayexpr)\n            return cls.time_in_ms(_eval(delay_expr_expanded, **kwargs))\n\n        return 0\n\n    @classmethod\n    def replace(cls, expr: str) -> str:\n        def rep(match):\n            return str(cls.time_in_ms(match.group(0)))\n\n        return cls.pattern.sub(rep, expr)\n\n    @classmethod\n    def time_in_ms(cls, expr: str) -> float:\n        \"\"\"\n        Convert a CSS2 time expression to milliseconds.\n\n        Args:\n            time (str): A string representing the time, e.g., '1.5s' or '150ms'.\n\n        Returns:\n            float: The time in milliseconds.\n\n        Raises:\n            ValueError: If the input is not a valid CSS2 time expression.\n        \"\"\"\n        if expr.endswith(\"ms\"):\n            try:\n                return float(expr[:-2])\n            except ValueError as e:\n                raise ValueError(f\"Invalid time value: {expr}\") from e\n        elif expr.endswith(\"s\"):\n            try:\n                return float(expr[:-1]) * 1000\n            except ValueError as e:\n                raise ValueError(f\"Invalid time value: {expr}\") from e\n        else:\n            try:\n                return float(expr)\n            except ValueError as e:\n                raise ValueError(f\"Invalid time unit in: {expr}\") from e\n\n\n@dataclass\nclass _Data:\n    kwargs: dict\n\n    def __getattr__(self, name):\n        return self.kwargs.get(name, None)\n\n    def get(self, name, default=None):\n        return self.kwargs.get(name, default)\n\n\nclass OriginTypeSCXML(str):\n    \"\"\"The origintype of the :ref:`Event` as specified by the SCXML namespace.\"\"\"\n\n    def __eq__(self, other):\n        return other == \"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" or other == \"scxml\"\n\n\nclass EventDataWrapper:\n    origin: str = \"\"\n    origintype: str = OriginTypeSCXML(\"scxml\")\n    \"\"\"The origintype of the :ref:`Event` as specified by the SCXML namespace.\"\"\"\n    invokeid: str = \"\"\n    \"\"\"If this event is generated from an invoked child process, the SCXML Processor MUST set\n    this field to the invoke id of the invocation that triggered the child process.\n    Otherwise it MUST leave it blank.\n    \"\"\"\n\n    def __init__(self, event_data=None, *, trigger_data=None):\n        self.event_data = event_data\n        if trigger_data is not None:\n            self.trigger_data = trigger_data\n        elif event_data is not None:\n            self.trigger_data = event_data.trigger_data\n        else:\n            raise ValueError(\"Either event_data or trigger_data must be provided\")\n\n        td = self.trigger_data\n        self.sendid = td.send_id\n        self.invokeid = td.kwargs.get(\"_invokeid\", \"\")\n        if td.event is None or td.event.internal:\n            if \"error.execution\" == td.event:\n                self.type = \"platform\"\n            else:\n                self.type = \"internal\"\n                self.origintype = \"\"\n        else:\n            self.type = \"external\"\n\n    @classmethod\n    def from_trigger_data(cls, trigger_data):\n        \"\"\"Create an EventDataWrapper directly from a TriggerData (no EventData needed).\"\"\"\n        return cls(trigger_data=trigger_data)\n\n    def __getattr__(self, name):\n        if self.event_data is not None:\n            return getattr(self.event_data, name)\n        raise AttributeError(f\"'{type(self).__name__}' object has no attribute '{name}'\")\n\n    def __eq__(self, value):\n        \"This makes SCXML test 329 pass. It assumes that the event is the same instance\"\n        return isinstance(value, EventDataWrapper)\n\n    @property\n    def name(self):\n        if self.event_data is not None:\n            return self.event_data.event\n        return str(self.trigger_data.event) if self.trigger_data.event else None\n\n    @property\n    def data(self):\n        \"Property used by the SCXML namespace\"\n        td = self.trigger_data\n        if td.kwargs:\n            return _Data(td.kwargs)\n        elif td.args and len(td.args) == 1:\n            return td.args[0]\n        elif td.args:\n            return td.args\n        else:\n            return None\n\n\ndef _eval(expr: str, **kwargs) -> Any:\n    if \"machine\" in kwargs:\n        kwargs.update(\n            **{\n                k: v\n                for k, v in kwargs[\"machine\"].model.__dict__.items()\n                if k not in protected_attrs\n            }\n        )\n        kwargs[\"In\"] = InState(kwargs[\"machine\"])\n    return eval(expr, {}, kwargs)\n\n\nclass CallableAction:\n    action: Any\n\n    def __init__(self):\n        self.__qualname__ = f\"{self.__class__.__module__}.{self.__class__.__name__}\"\n\n    def __call__(self, *args, **kwargs):\n        raise NotImplementedError\n\n    def __str__(self):\n        return f\"{self.action}\"\n\n    def __repr__(self):\n        return f\"{self.__class__.__name__}({self.action!r})\"\n\n    @property\n    def __name__(self):\n        return str(self)\n\n    @property\n    def __code__(self):\n        return self.__call__.__code__\n\n\nclass Cond(CallableAction):\n    \"\"\"Evaluates a condition like a predicate and returns True or False.\"\"\"\n\n    @classmethod\n    def create(cls, cond: \"str | None\", processor=None):\n        cond = cls._normalize(cond)\n        if cond is None:\n            return None\n\n        return cls(cond, processor)\n\n    def __init__(self, cond: str, processor=None):\n        super().__init__()\n        self.action = cond\n        self.processor = processor\n\n    def __call__(self, *args, **kwargs):\n        result = _eval(self.action, **kwargs)\n        _debug(\"Cond %s -> %s\", self.action, result)\n        return result\n\n    @staticmethod\n    def _normalize(cond: \"str | None\") -> \"str | None\":\n        \"\"\"\n        Normalizes a JavaScript-like condition string to be compatible with Python's eval.\n        \"\"\"\n        if cond is None:\n            return None\n\n        # Decode HTML entities, to allow XML syntax like `Var1&lt;Var2`\n        cond = html.unescape(cond)\n\n        replacements = {\n            \"true\": \"True\",\n            \"false\": \"False\",\n            \"null\": \"None\",\n            \"===\": \"==\",\n            \"!==\": \"!=\",\n            \"&&\": \"and\",\n            \"||\": \"or\",\n        }\n\n        # Use regex to replace each JavaScript-like token with its Python equivalent\n        pattern = re.compile(r\"\\b(?:true|false|null)\\b|===|!==|&&|\\|\\|\")\n        return pattern.sub(lambda match: replacements[match.group(0)], cond)\n\n\ndef create_action_callable(action: Action) -> Callable:\n    if isinstance(action, RaiseAction):\n        return create_raise_action_callable(action)\n    elif isinstance(action, AssignAction):\n        return Assign(action)\n    elif isinstance(action, LogAction):\n        return Log(action)\n    elif isinstance(action, IfAction):\n        return create_if_action_callable(action)\n    elif isinstance(action, ForeachAction):\n        return create_foreach_action_callable(action)\n    elif isinstance(action, SendAction):\n        return create_send_action_callable(action)\n    elif isinstance(action, CancelAction):\n        return create_cancel_action_callable(action)\n    elif isinstance(action, ScriptAction):\n        return create_script_action_callable(action)\n    else:\n        raise ValueError(f\"Unknown action type: {type(action)}\")\n\n\nclass Assign(CallableAction):\n    def __init__(self, action: AssignAction):\n        super().__init__()\n        self.action = action\n\n    def __call__(self, *args, **kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n        if self.action.child_xml is not None:\n            value = self.action.child_xml\n        else:\n            value = _eval(self.action.expr, **kwargs)\n\n        *path, attr = self.action.location.split(\".\")\n        obj = machine.model\n        for p in path:\n            obj = getattr(obj, p)\n\n        if not attr.isidentifier() or not (hasattr(obj, attr) or attr in kwargs):\n            raise ValueError(\n                f\"<assign> 'location' must be a valid Python attribute name and must be declared, \"\n                f\"got: {self.action.location}\"\n            )\n        if attr in protected_attrs:\n            raise ValueError(\n                f\"<assign> 'location' cannot assign to a protected attribute: \"\n                f\"{self.action.location}\"\n            )\n        setattr(obj, attr, value)\n        _debug(\"Assign: %s = %r\", self.action.location, value)\n\n\nclass Log(CallableAction):\n    def __init__(self, action: LogAction):\n        super().__init__()\n        self.action = action\n\n    def __call__(self, *args, **kwargs):\n        value = _eval(self.action.expr, **kwargs) if self.action.expr else None\n\n        if self.action.label and self.action.expr is not None:\n            msg = f\"{self.action.label}: {value!r}\"\n        elif self.action.label:\n            msg = f\"{self.action.label}\"\n        else:\n            msg = f\"{value!r}\"\n        print(msg)\n\n\ndef create_if_action_callable(action: IfAction) -> Callable:\n    branches = [\n        (\n            Cond.create(branch.cond),\n            [create_action_callable(action) for action in branch.actions],\n        )\n        for branch in action.branches\n    ]\n\n    def if_action(*args, **kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n        for cond, actions in branches:\n            try:\n                cond_result = not cond or cond(*args, **kwargs)\n            except Exception as e:\n                # SCXML spec: condition error → treat as false, queue error.execution.\n                if machine.catch_errors_as_events:\n                    machine.send(\"error.execution\", error=e, internal=True)\n                    cond_result = False\n                else:\n                    raise\n            if cond_result:\n                for action in actions:\n                    action(*args, **kwargs)\n                return\n\n    if_action.action = action  # type: ignore[attr-defined]\n    return if_action\n\n\ndef create_foreach_action_callable(action: ForeachAction) -> Callable:\n    child_actions = [create_action_callable(act) for act in action.content.actions]\n\n    def foreach_action(*args, **kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n        try:\n            # Evaluate the array expression to get the iterable\n            array = _eval(action.array, **kwargs)\n        except Exception as e:\n            raise ValueError(f\"Error evaluating <foreach> 'array' expression: {e}\") from e\n\n        if not action.item.isidentifier():\n            raise ValueError(\n                f\"<foreach> 'item' must be a valid Python attribute name, got: {action.item}\"\n            )\n        for index, item in enumerate(array):\n            # Assign the item and optionally the index\n            setattr(machine.model, action.item, item)\n            if action.index:\n                setattr(machine.model, action.index, index)\n\n            # Execute child actions\n            for act in child_actions:\n                act(*args, **kwargs)\n\n    foreach_action.action = action  # type: ignore[attr-defined]\n    return foreach_action\n\n\ndef create_raise_action_callable(action: RaiseAction) -> Callable:\n    def raise_action(*args, **kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n\n        Event(id=action.event, internal=True, _sm=machine).put()\n\n    raise_action.action = action  # type: ignore[attr-defined]\n    return raise_action\n\n\ndef _send_to_parent(action: SendAction, **kwargs):\n    \"\"\"Route a <send target=\"#_parent\"> to the parent machine via _invoke_session.\"\"\"\n    machine = kwargs[\"machine\"]\n    session = getattr(machine, \"_invoke_session\", None)\n    if session is None:\n        logger.warning(\n            \"<send target='#_parent'> ignored: machine %r has no _invoke_session\",\n            machine.name,\n        )\n        return\n    event = action.event or _eval(action.eventexpr, **kwargs)  # type: ignore[arg-type]\n    names = []\n    for name in (action.namelist or \"\").strip().split():\n        if not hasattr(machine.model, name):\n            raise NameError(f\"Namelist variable '{name}' not found on model\")\n        names.append(Param(name=name, expr=name))\n    params_values = {}\n    for param in chain(names, action.params):\n        if param.expr is None:\n            continue\n        params_values[param.name] = _eval(param.expr, **kwargs)\n    session.send_to_parent(event, **params_values)\n\n\ndef _send_to_invoke(action: SendAction, invokeid: str, **kwargs):\n    \"\"\"Route a <send target=\"#_<invokeid>\"> to the invoked child session.\"\"\"\n    machine: StateChart = kwargs[\"machine\"]\n    event = action.event or _eval(action.eventexpr, **kwargs)  # type: ignore[arg-type]\n    names = []\n    for name in (action.namelist or \"\").strip().split():\n        if not hasattr(machine.model, name):\n            raise NameError(f\"Namelist variable '{name}' not found on model\")\n        names.append(Param(name=name, expr=name))\n    params_values = {}\n    for param in chain(names, action.params):\n        if param.expr is None:\n            continue\n        params_values[param.name] = _eval(param.expr, **kwargs)\n    if not machine._engine._invoke_manager.send_to_child(invokeid, event, **params_values):\n        # Per SCXML spec: if target is not reachable → error.communication\n        BoundEvent(\"error.communication\", internal=True, _sm=machine).put()\n\n\ndef create_send_action_callable(action: SendAction) -> Callable:  # noqa: C901\n    content: Any = ()\n    _valid_targets = (None, \"#_internal\", \"internal\", \"#_parent\", \"parent\")\n    if action.content:\n        try:\n            content = (eval(action.content, {}, {}),)\n        except (NameError, SyntaxError, TypeError):\n            content = (action.content,)\n\n    def send_action(*args, **kwargs):  # noqa: C901\n        machine: StateChart = kwargs[\"machine\"]\n        event = action.event or _eval(action.eventexpr, **kwargs)  # type: ignore[arg-type]\n        target = action.target if action.target else None\n\n        if action.type and action.type != \"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\":\n            # Per SCXML spec 6.2.3, unsupported type raises error.execution\n            raise ValueError(\n                f\"Unsupported send type: {action.type}. \"\n                \"Only 'http://www.w3.org/TR/scxml/#SCXMLEventProcessor' is supported\"\n            )\n        if target not in _valid_targets:\n            if target and target.startswith(\"#_scxml_\"):\n                # Valid SCXML session reference but undispatchable → error.communication\n                BoundEvent(\"error.communication\", internal=True, _sm=machine).put()\n            elif target and target.startswith(\"#_\"):\n                # #_<invokeid> → route to invoked child session\n                _send_to_invoke(action, target[2:], **kwargs)\n            else:\n                # Invalid target expression → error.execution (raised as exception)\n                raise ValueError(f\"Invalid target: {target}. Must be one of {_valid_targets}\")\n            return\n\n        # Handle #_parent target — route to parent via _invoke_session\n        if target == \"#_parent\":\n            _send_to_parent(action, **kwargs)\n            return\n\n        internal = target in (\"#_internal\", \"internal\")\n\n        send_id = None\n        if action.id:\n            send_id = action.id\n        elif action.idlocation:\n            send_id = uuid4().hex\n            setattr(machine.model, action.idlocation, send_id)\n\n        delay = ParseTime.parse_delay(action.delay, action.delayexpr, **kwargs)\n\n        # Per SCXML spec, if namelist evaluation causes an error (e.g., variable not found),\n        # the send MUST NOT be dispatched and error.execution is raised.\n        names = []\n        for name in (action.namelist or \"\").strip().split():\n            if not hasattr(machine.model, name):\n                raise NameError(f\"Namelist variable '{name}' not found on model\")\n            names.append(Param(name=name, expr=name))\n        params_values = {}\n        for param in chain(names, action.params):\n            if param.expr is None:\n                continue\n            params_values[param.name] = _eval(param.expr, **kwargs)\n\n        Event(id=event, delay=delay, internal=internal, _sm=machine).put(\n            *content,\n            send_id=send_id,\n            **params_values,\n        )\n\n    send_action.action = action  # type: ignore[attr-defined]\n    return send_action\n\n\ndef create_cancel_action_callable(action: CancelAction) -> Callable:\n    def cancel_action(*args, **kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n        if action.sendid:\n            send_id = action.sendid\n        elif action.sendidexpr:\n            send_id = _eval(action.sendidexpr, **kwargs)\n        else:\n            raise ValueError(\"CancelAction must have either 'sendid' or 'sendidexpr'\")\n        # Implement cancel logic if necessary\n        # For now, we can just print that the event is canceled\n        machine.cancel_event(send_id)\n\n    cancel_action.action = action  # type: ignore[attr-defined]\n    return cancel_action\n\n\ndef create_script_action_callable(action: ScriptAction) -> Callable:\n    def script_action(*args, **kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n        local_vars = {\n            **machine.model.__dict__,\n        }\n        exec(action.content, {}, local_vars)\n\n        # Assign the resulting variables to the state machine's model\n        for var_name, value in local_vars.items():\n            setattr(machine.model, var_name, value)\n\n    script_action.action = action  # type: ignore[attr-defined]\n    return script_action\n\n\ndef create_invoke_init_callable() -> Callable:\n    \"\"\"Create a callback that extracts invoke-specific kwargs and stores them on the machine.\n\n    This is always inserted at position 0 in the initial state's onentry list by the\n    SCXML processor, so that ``_invoke_session`` and ``_invoke_params`` are handled\n    before any other callbacks run — even for SMs without a ``<datamodel>``.\n    \"\"\"\n    initialized = False\n\n    def invoke_init(*args, **kwargs):\n        nonlocal initialized\n        if initialized:\n            return\n        initialized = True\n        machine = kwargs.get(\"machine\")\n        if machine is not None:\n            # Use get() not pop(): each callback receives a copy of kwargs\n            # (via EventData.extended_kwargs), so pop would be misleading.\n            machine._invoke_params = kwargs.get(\"_invoke_params\")\n            machine._invoke_session = kwargs.get(\"_invoke_session\")\n\n    return invoke_init\n\n\ndef _create_dataitem_callable(action: DataItem) -> Callable:\n    def data_initializer(**kwargs):\n        machine: StateChart = kwargs[\"machine\"]\n\n        # Check for invoke param overrides — params from parent override child defaults\n        invoke_params = getattr(machine, \"_invoke_params\", None)\n        if invoke_params and action.id in invoke_params:\n            setattr(machine.model, action.id, invoke_params[action.id])\n            return\n\n        if action.expr:\n            try:\n                value = _eval(action.expr, **kwargs)\n            except Exception:\n                setattr(machine.model, action.id, None)\n                raise\n\n        elif action.content:\n            try:\n                value = _eval(action.content, **kwargs)\n            except Exception:\n                value = action.content\n        else:\n            value = None\n\n        setattr(machine.model, action.id, value)\n\n    return data_initializer\n\n\ndef create_datamodel_action_callable(action: DataModel) -> \"Callable | None\":\n    data_elements = [_create_dataitem_callable(item) for item in action.data]\n    data_elements.extend([create_script_action_callable(script) for script in action.scripts])\n\n    if not data_elements:\n        return None\n\n    initialized = False\n\n    def datamodel(*args, **kwargs):\n        nonlocal initialized\n        if initialized:\n            return\n        initialized = True\n\n        for act in data_elements:\n            act(**kwargs)\n\n    return datamodel\n\n\nclass ExecuteBlock(CallableAction):\n    \"\"\"Parses the children as <executable> content XML into a callable.\"\"\"\n\n    def __init__(self, content: ExecutableContent):\n        super().__init__()\n        self.action = content\n        self.action_callables = [create_action_callable(action) for action in content.actions]\n\n    def __call__(self, *args, **kwargs):\n        for action in self.action_callables:\n            action(*args, **kwargs)\n\n\nclass DoneDataCallable(CallableAction):\n    \"\"\"Evaluates <donedata> params/content and returns the data for done events.\"\"\"\n\n    def __init__(self, donedata: DoneData):\n        super().__init__()\n        self.action = donedata\n        self.donedata = donedata\n\n    def __call__(self, *args, **kwargs):\n        if self.donedata.content_expr is not None:\n            return _eval(self.donedata.content_expr, **kwargs)\n\n        result = {}\n        for param in self.donedata.params:\n            if param.expr is not None:\n                result[param.name] = _eval(param.expr, **kwargs)\n            elif param.location is not None:  # pragma: no branch\n                location = param.location.strip()\n                try:\n                    result[param.name] = _eval(location, **kwargs)\n                except Exception as e:\n                    raise ValueError(\n                        f\"<param> location '{location}' does not resolve to a valid value\"\n                    ) from e\n        return result\n"
  },
  {
    "path": "statemachine/io/scxml/invoke.py",
    "content": "\"\"\"SCXML-specific invoke handler.\n\nImplements the IInvoke protocol by resolving child SCXML content (inline or\nvia src/srcexpr), evaluating params/namelist in the parent context, and managing\nthe child machine lifecycle including ``#_parent`` routing, autoforward, and\nfinalize.\n\"\"\"\n\nimport asyncio\nimport logging\nfrom inspect import isawaitable\nfrom pathlib import Path\nfrom typing import Any\nfrom typing import Callable\n\nfrom ...invoke import IInvoke\nfrom ...invoke import InvokeContext\nfrom .actions import ExecuteBlock\nfrom .actions import _eval\nfrom .schema import InvokeDefinition\n\nlogger = logging.getLogger(__name__)\n\n_VALID_INVOKE_TYPES = {\n    None,\n    \"scxml\",\n    \"http://www.w3.org/TR/scxml\",\n    \"http://www.w3.org/TR/scxml/\",\n    \"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\",\n}\n\n\nclass SCXMLInvoker:\n    \"\"\"SCXML-specific invoke handler implementing the IInvoke protocol.\n\n    Resolves the child SCXML from inline content, src file, or srcexpr,\n    evaluates params/namelist, and manages the child machine lifecycle.\n    \"\"\"\n\n    def __init__(\n        self,\n        definition: InvokeDefinition,\n        base_dir: str,\n        register_child: \"Callable[[str, str], type]\",\n    ):\n        self._definition = definition\n        self._register_child = register_child\n        self._child: Any = None\n        self._base_dir: str = base_dir\n\n        # Duck-typed attributes for InvokeManager\n        self.invoke_id: \"str | None\" = definition.id\n        self.idlocation: \"str | None\" = definition.idlocation\n        self.autoforward: bool = definition.autoforward\n\n        # Pre-compile finalize block\n        self._finalize_block: \"ExecuteBlock | None\" = None\n        if definition.finalize and not definition.finalize.is_empty:\n            self._finalize_block = ExecuteBlock(definition.finalize)\n\n    def run(self, ctx: InvokeContext) -> Any:\n        \"\"\"Create and run the child state machine.\"\"\"\n        machine = ctx.machine\n\n        # Store invokeid in idlocation if specified\n        if self.idlocation:\n            setattr(machine.model, self.idlocation, ctx.invokeid)\n\n        # Resolve invoke type\n        invoke_type = self._definition.type\n        if self._definition.typeexpr:\n            invoke_type = _eval(self._definition.typeexpr, machine=machine)\n\n        if invoke_type not in _VALID_INVOKE_TYPES:\n            raise ValueError(\n                f\"Unsupported invoke type: {invoke_type}. Supported types: {_VALID_INVOKE_TYPES}\"\n            )\n\n        # Resolve child SCXML content\n        scxml_content = self._resolve_content(machine)\n        if scxml_content is None:\n            raise ValueError(\"No content resolved for <invoke>\")\n\n        # Evaluate params and namelist\n        invoke_params = self._evaluate_params(machine)\n\n        # Parse and create the child machine\n        child_cls = self._create_child_class(scxml_content, ctx.invokeid)\n\n        # _invoke_session and _invoke_params are passed as kwargs so that the\n        # invoke_init callback (inserted at position 0 in the initial state's onentry\n        # by the processor) can pop them and store them on the machine instance.\n        #\n        # The _ChildRefSetter listener captures ``self._child`` during the first\n        # state entry, before the processing loop blocks.  This is necessary\n        # because the child's ``__init__`` may block for an extended time when\n        # there are delayed events, and ``on_event()`` needs access to the child\n        # to forward events from the parent session.\n        session = _InvokeSession(parent=machine, invokeid=ctx.invokeid)\n        ref_setter = _ChildRefSetter(self)\n        self._child = child_cls(\n            _invoke_params=invoke_params,\n            _invoke_session=session,\n            listeners=[ref_setter],\n        )\n\n        return None\n\n    def on_cancel(self):\n        \"\"\"Cancel the child machine and all its invocations.\"\"\"\n        from ...invoke import _stop_child_machine\n\n        _stop_child_machine(self._child)\n        self._child = None\n\n    def on_event(self, event_name: str, **data):\n        \"\"\"Forward an event to the child machine (autoforward).\"\"\"\n        if self._child is not None and not self._child.is_terminated:\n            try:\n                self._child.send(event_name, **data)\n            except Exception:\n                logger.debug(\"Error forwarding event %s to child\", event_name, exc_info=True)\n\n    def on_finalize(self, trigger_data):\n        \"\"\"Execute the finalize block before the parent processes the event.\"\"\"\n        if self._finalize_block is not None:\n            machine = trigger_data.machine\n            kwargs = {\n                \"machine\": machine,\n                \"model\": machine.model,\n            }\n            # Inject SCXML context variables\n            from .actions import EventDataWrapper\n\n            kwargs.update(\n                {k: v for k, v in machine.model.__dict__.items() if not k.startswith(\"_\")}\n            )\n            # Build EventDataWrapper from trigger_data's kwargs\n            kwargs[\"_event\"] = EventDataWrapper.from_trigger_data(trigger_data)\n            self._finalize_block(**kwargs)\n\n    def _resolve_content(self, machine) -> \"str | None\":\n        \"\"\"Resolve the child SCXML content from content/src/srcexpr.\"\"\"\n        defn = self._definition\n\n        if defn.content:\n            # Content could be an expr to evaluate or inline SCXML\n            if defn.content.lstrip().startswith(\"<\"):\n                return defn.content\n            # It's an expression — evaluate it\n            result = _eval(defn.content, machine=machine)\n            if isinstance(result, str):\n                return result\n            return str(result)\n\n        if defn.srcexpr:\n            src = _eval(defn.srcexpr, machine=machine)\n        elif defn.src:\n            src = defn.src\n        else:\n            return None\n\n        # Handle file: URIs and relative paths\n        if src.startswith(\"file:\"):\n            path = Path(src.removeprefix(\"file:\"))\n        else:\n            path = Path(src)\n\n        # Resolve relative to the base directory of the parent SCXML file\n        if not path.is_absolute():\n            path = Path(self._base_dir) / path\n\n        return path.read_text()\n\n    def _evaluate_params(self, machine) -> dict:\n        \"\"\"Evaluate params and namelist into a dict of values.\"\"\"\n        defn = self._definition\n        result = {}\n\n        # Evaluate namelist\n        if defn.namelist:\n            for name in defn.namelist.strip().split():\n                if hasattr(machine.model, name):\n                    result[name] = getattr(machine.model, name)\n\n        # Evaluate param elements\n        for param in defn.params:\n            if param.expr is not None:\n                result[param.name] = _eval(param.expr, machine=machine)\n            elif param.location is not None:\n                result[param.name] = _eval(param.location, machine=machine)\n\n        return result\n\n    def _create_child_class(self, scxml_content: str, invokeid: str):\n        \"\"\"Parse the child SCXML and create a machine class.\"\"\"\n        child_name = f\"invoke_{invokeid}\"\n        return self._register_child(scxml_content, child_name)\n\n\nclass _ChildRefSetter:\n    \"\"\"Listener that captures the child machine reference during initialization.\n\n    The child's ``__init__`` blocks inside the processing loop (e.g. when there\n    are delayed events).  By using this listener, ``SCXMLInvoker._child`` is set\n    during the first state entry — *before* the processing loop starts spinning —\n    so that ``on_event()`` can forward events to the child immediately.\n    \"\"\"\n\n    def __init__(self, invoker: \"SCXMLInvoker\"):\n        self._invoker = invoker\n\n    def on_enter_state(self, machine=None, **kwargs):\n        if self._invoker._child is None and machine is not None:\n            self._invoker._child = machine\n\n\nclass _InvokeSession:\n    \"\"\"Holds the reference to the parent machine for ``#_parent`` routing.\"\"\"\n\n    def __init__(self, parent, invokeid: str):\n        self.parent = parent\n        self.invokeid = invokeid\n\n    def send_to_parent(self, event: str, **data):\n        \"\"\"Send an event to the parent machine's external queue.\"\"\"\n        result = self.parent.send(event, _invokeid=self.invokeid, **data)\n        if isawaitable(result):\n            asyncio.ensure_future(result)\n\n\n# Verify protocol compliance at import time\nassert isinstance(SCXMLInvoker.__new__(SCXMLInvoker), IInvoke)\n"
  },
  {
    "path": "statemachine/io/scxml/parser.py",
    "content": "import re\nimport xml.etree.ElementTree as ET\nfrom typing import List\nfrom typing import Literal\nfrom typing import Set\nfrom typing import cast\nfrom urllib.parse import urlparse\n\nfrom .schema import Action\nfrom .schema import AssignAction\nfrom .schema import CancelAction\nfrom .schema import DataItem\nfrom .schema import DataModel\nfrom .schema import DoneData\nfrom .schema import ExecutableContent\nfrom .schema import ForeachAction\nfrom .schema import HistoryState\nfrom .schema import IfAction\nfrom .schema import IfBranch\nfrom .schema import InvokeDefinition\nfrom .schema import LogAction\nfrom .schema import Param\nfrom .schema import RaiseAction\nfrom .schema import ScriptAction\nfrom .schema import SendAction\nfrom .schema import State\nfrom .schema import StateMachineDefinition\nfrom .schema import Transition\n\n\ndef strip_namespaces(tree: ET.Element):\n    \"\"\"Remove all namespaces from tags and attributes in place.\"\"\"\n    for el in tree.iter():\n        if \"}\" in el.tag:\n            el.tag = el.tag.split(\"}\", 1)[1]\n        attrib = el.attrib\n        for name in list(attrib.keys()):  # list() needed: loop mutates attrib\n            if \"}\" in name:\n                new_name = name.split(\"}\", 1)[1]\n                attrib[new_name] = attrib.pop(name)\n\n\ndef _parse_initial(initial_content: \"str | None\") -> List[str]:\n    if initial_content is None:\n        return []\n    return initial_content.split()\n\n\ndef parse_scxml(scxml_content: str) -> StateMachineDefinition:  # noqa: C901\n    root = ET.fromstring(scxml_content)\n    strip_namespaces(root)\n\n    scxml = root if root.tag == \"scxml\" else root.find(\".//scxml\")\n    if scxml is None:\n        raise ValueError(\"No scxml element found in document\")\n\n    name = scxml.get(\"name\")\n\n    initial_states = _parse_initial(scxml.get(\"initial\"))\n    all_initial_states = set(initial_states)\n\n    definition = StateMachineDefinition(name=name, initial_states=initial_states)\n\n    # Parse datamodel\n    datamodel = parse_datamodel(scxml)\n    if datamodel:\n        definition.datamodel = datamodel\n\n    # Parse states\n    for state_elem in scxml:\n        if state_elem.tag == \"state\":\n            state = parse_state(state_elem, all_initial_states)\n            definition.states[state.id] = state\n        elif state_elem.tag == \"final\":\n            state = parse_state(state_elem, all_initial_states, is_final=True)\n            definition.states[state.id] = state\n        elif state_elem.tag == \"parallel\":\n            state = parse_state(state_elem, all_initial_states, is_parallel=True)\n            definition.states[state.id] = state\n\n    # If no initial state was specified, pick the first state\n    if not all_initial_states and definition.states:\n        first_state = next(iter(definition.states.keys()))\n        definition.initial_states = [first_state]\n        definition.states[first_state].initial = True\n\n    return definition\n\n\ndef _find_own_datamodel_elements(root: ET.Element) -> List[ET.Element]:\n    \"\"\"Find <datamodel> elements that belong to this SCXML document, not to inline children.\n\n    Skips any <datamodel> nested inside <content> elements (which contain inline\n    child SCXML documents for <invoke>).\n    \"\"\"\n    result: List[ET.Element] = []\n\n    def _walk(elem: ET.Element):\n        for child in elem:\n            if child.tag == \"content\":\n                continue  # Skip inline SCXML content\n            if child.tag == \"datamodel\":\n                result.append(child)\n            _walk(child)\n\n    _walk(root)\n    return result\n\n\ndef parse_datamodel(root: ET.Element) -> \"DataModel | None\":\n    data_model = DataModel()\n\n    for datamodel_elem in _find_own_datamodel_elements(root):\n        for data_elem in datamodel_elem.findall(\"data\"):\n            content = data_elem.text and re.sub(r\"\\s+\", \" \", data_elem.text).strip() or None\n            src = data_elem.attrib.get(\"src\")\n            src_parsed = urlparse(src) if src else None\n            if src_parsed and src_parsed.scheme == \"file\" and content is None:\n                with open(src_parsed.path) as f:\n                    content = f.read()\n\n            data_model.data.append(\n                DataItem(\n                    id=data_elem.attrib[\"id\"],\n                    src=src_parsed,\n                    expr=data_elem.attrib.get(\"expr\"),\n                    content=content,\n                )\n            )\n\n    # Parse <script> elements outside of <datamodel>\n    for script_elem in root.findall(\"script\"):\n        script_content = ScriptAction(\n            content=script_elem.text.strip() if script_elem.text else \"\",\n        )\n        data_model.scripts.append(script_content)\n\n    return data_model if data_model.data or data_model.scripts else None\n\n\ndef parse_history(state_elem: ET.Element) -> HistoryState:\n    state_id = state_elem.get(\"id\")\n    if not state_id:\n        raise ValueError(\"History must have an 'id' attribute\")\n\n    history_type = cast(\"Literal['shallow', 'deep']\", state_elem.get(\"type\", \"shallow\"))\n    state = HistoryState(\n        id=state_id,\n        type=history_type,\n    )\n    for trans_elem in state_elem.findall(\"transition\"):\n        transition = parse_transition(trans_elem)\n        state.transitions.append(transition)\n\n    return state\n\n\ndef parse_state(  # noqa: C901\n    state_elem: ET.Element,\n    initial_states: Set[str],\n    is_final: bool = False,\n    is_parallel: bool = False,\n) -> State:\n    state_id = state_elem.get(\"id\")\n    if not state_id:\n        # Per SCXML spec, if no id is specified, the processor auto-generates one.\n        from uuid import uuid4\n\n        state_id = f\"__auto_{uuid4().hex[:8]}\"\n\n    initial = state_id in initial_states\n    state = State(id=state_id, initial=initial, final=is_final, parallel=is_parallel)\n\n    # Parse onentry actions\n    for onentry_elem in state_elem.findall(\"onentry\"):\n        content = parse_executable_content(onentry_elem)\n        state.onentry.append(content)\n\n    # Parse onexit actions\n    for onexit_elem in state_elem.findall(\"onexit\"):\n        content = parse_executable_content(onexit_elem)\n        state.onexit.append(content)\n\n    # Parse transitions\n    for trans_elem in state_elem.findall(\"transition\"):\n        transition = parse_transition(trans_elem)\n        state.transitions.append(transition)\n\n    # Parse child states — handle initial attribute and <initial> element\n    # Per SCXML spec, the initial attribute is equivalent to an <initial> element\n    # with a <transition> whose target is the attribute value.\n    initial_attr = state_elem.get(\"initial\")\n    if initial_attr:\n        initial_states.update(_parse_initial(initial_attr))\n\n    initial_elem = state_elem.find(\"initial\")\n    if initial_elem is not None:\n        for trans_elem in initial_elem.findall(\"transition\"):\n            transition = parse_transition(trans_elem, initial=True)\n            state.transitions.append(transition)\n            initial_states.update(_parse_initial(trans_elem.get(\"target\")))\n    elif initial_attr:\n        # Convert initial attribute to an initial transition\n        transition = Transition(target=initial_attr, initial=True)\n        state.transitions.append(transition)\n\n    for child_state_elem in state_elem.findall(\"state\"):\n        child_state = parse_state(child_state_elem, initial_states=initial_states)\n        state.states[child_state.id] = child_state\n    for child_state_elem in state_elem.findall(\"final\"):\n        child_state = parse_state(child_state_elem, initial_states=initial_states, is_final=True)\n        state.states[child_state.id] = child_state\n    for child_state_elem in state_elem.findall(\"parallel\"):\n        child_state = parse_state(\n            child_state_elem, initial_states=initial_states, is_parallel=True\n        )\n        state.states[child_state.id] = child_state\n    for child_state_elem in state_elem.findall(\"history\"):\n        child_history_state = parse_history(child_state_elem)\n        state.history[child_history_state.id] = child_history_state\n\n    # Parse invoke elements\n    for invoke_elem in state_elem.findall(\"invoke\"):\n        state.invocations.append(parse_invoke(invoke_elem))\n\n    # Parse donedata (only valid on final states)\n    if is_final:\n        donedata_elem = state_elem.find(\"donedata\")\n        if donedata_elem is not None:\n            state.donedata = parse_donedata(donedata_elem)\n\n    return state\n\n\ndef parse_donedata(element: ET.Element) -> DoneData:\n    \"\"\"Parse a <donedata> element containing <param> and/or <content> children.\"\"\"\n    params = []\n    content_expr = None\n    for child in element:\n        if child.tag == \"param\":\n            name = child.attrib[\"name\"]\n            expr = child.attrib.get(\"expr\")\n            location = child.attrib.get(\"location\")\n            params.append(Param(name=name, expr=expr, location=location))\n        elif child.tag == \"content\":  # pragma: no branch\n            content_expr = child.attrib.get(\"expr\")\n            if content_expr is None and child.text:\n                content_expr = re.sub(r\"\\s+\", \" \", child.text).strip()\n    return DoneData(params=params, content_expr=content_expr)\n\n\ndef parse_transition(trans_elem: ET.Element, initial: bool = False) -> Transition:\n    target = trans_elem.get(\"target\")\n\n    event = trans_elem.get(\"event\")\n    cond = trans_elem.get(\"cond\")\n    internal = trans_elem.get(\"type\") == \"internal\"\n\n    executable_content = parse_executable_content(trans_elem)\n\n    return Transition(\n        target=target,\n        internal=internal,\n        initial=initial,\n        event=event,\n        cond=cond,\n        on=executable_content,\n    )\n\n\ndef parse_executable_content(element: ET.Element) -> ExecutableContent:\n    \"\"\"Parses the children as <executable> content XML into a list of Action instances.\"\"\"\n    actions = []\n    for child in element:\n        action = parse_element(child)\n        if action:  # pragma: no branch\n            actions.append(action)\n    return ExecutableContent(actions=actions)\n\n\ndef parse_element(element: ET.Element) -> Action:\n    tag = element.tag\n    if tag == \"raise\":\n        return parse_raise(element)\n    elif tag == \"assign\":\n        return parse_assign(element)\n    elif tag == \"log\":\n        return parse_log(element)\n    elif tag == \"if\":\n        return parse_if(element)\n    elif tag == \"send\":\n        return parse_send(element)\n    elif tag == \"script\":\n        return parse_script(element)\n    elif tag == \"foreach\":\n        return parse_foreach(element)\n    elif tag == \"cancel\":\n        return parse_cancel(element)\n\n    raise ValueError(f\"Unknown tag: {tag}\")\n\n\ndef parse_raise(element: ET.Element) -> RaiseAction:\n    event = element.attrib[\"event\"]\n    return RaiseAction(event=event)\n\n\ndef parse_assign(element: ET.Element) -> AssignAction:\n    location = element.attrib[\"location\"]\n    expr = element.attrib.get(\"expr\")\n    child_xml: \"str | None\" = None\n    if expr is None:\n        # Per SCXML spec, <assign> can have child content instead of expr\n        children = list(element)\n        if children:\n            child_xml = ET.tostring(children[0], encoding=\"unicode\")\n        elif element.text:\n            expr = element.text.strip()\n    return AssignAction(location=location, expr=expr, child_xml=child_xml)\n\n\ndef parse_log(element: ET.Element) -> LogAction:\n    label = element.attrib.get(\"label\")\n    expr = element.attrib.get(\"expr\")\n    return LogAction(label=label, expr=expr)\n\n\ndef parse_if(element: ET.Element) -> IfAction:\n    current_branch = IfBranch(cond=element.attrib[\"cond\"])\n    branches = [current_branch]\n    for child in element:\n        tag = child.tag\n        if tag in (\"elseif\", \"else\"):\n            current_branch = IfBranch(cond=child.attrib.get(\"cond\"))\n            branches.append(current_branch)\n        else:\n            # Add the action to the current branch\n            action = parse_element(child)\n            current_branch.append(action)\n\n    return IfAction(branches=branches)\n\n\ndef parse_foreach(element: ET.Element) -> ForeachAction:\n    array = element.attrib[\"array\"]\n    item = element.attrib[\"item\"]\n    index = element.attrib.get(\"index\")\n    content = parse_executable_content(element)\n    return ForeachAction(array=array, item=item, index=index, content=content)\n\n\ndef parse_send(element: ET.Element) -> SendAction:\n    \"\"\"\n    Parses the <send> element into SendAction.\n\n    Attributes:\n    - `event`: The name of the event to send (required).\n    - `target`: The target to which the event is sent (optional).\n    - `type`: The type of the event (optional).\n    - `id`: A unique identifier for this send action (optional).\n    - `delay`: The delay before sending the event (optional).\n    - `namelist`: A space-separated list of data model variables to include in the event (optional)\n    - `params`: A dictionary of parameters to include in the event (optional).\n    - `content`: Content to include in the event (optional).\n    \"\"\"\n\n    event = element.attrib.get(\"event\")\n    eventexpr = element.attrib.get(\"eventexpr\")\n\n    if not (event or eventexpr):\n        raise ValueError(\"<send> must have an 'event' or `eventexpr` attribute\")\n\n    target = element.attrib.get(\"target\")\n    type_attr = element.attrib.get(\"type\")\n    id_attr = element.attrib.get(\"id\")\n    idlocation = element.attrib.get(\"idlocation\")\n    delay = element.attrib.get(\"delay\")\n    delayexpr = element.attrib.get(\"delayexpr\")\n    namelist = element.attrib.get(\"namelist\")\n\n    params = []\n    content = None\n    for child in element:\n        if child.tag == \"param\":\n            name = child.attrib[\"name\"]\n            expr = child.attrib.get(\"expr\")\n            location = child.attrib.get(\"location\")\n            if not (expr or location):\n                raise ValueError(\"Must specify \")\n            params.append(\n                Param(\n                    name=name,\n                    expr=expr,\n                    location=location,\n                )\n            )\n        elif child.tag == \"content\":  # pragma: no branch\n            content = re.sub(r\"\\s+\", \" \", child.text).strip() if child.text else None\n\n    return SendAction(\n        event=event,\n        eventexpr=eventexpr,\n        target=target,\n        type=type_attr,\n        id=id_attr,\n        idlocation=idlocation,\n        delay=delay,\n        delayexpr=delayexpr,\n        namelist=namelist,\n        params=params,\n        content=content,\n    )\n\n\ndef parse_cancel(element: ET.Element) -> CancelAction:\n    sendid = element.attrib.get(\"sendid\")\n    sendidexpr = element.attrib.get(\"sendidexpr\")\n    return CancelAction(sendid=sendid, sendidexpr=sendidexpr)\n\n\ndef parse_script(element: ET.Element) -> ScriptAction:\n    content = element.text.strip() if element.text else \"\"\n    return ScriptAction(content=content)\n\n\ndef parse_invoke(element: ET.Element) -> InvokeDefinition:\n    \"\"\"Parse an <invoke> element into an InvokeDefinition.\"\"\"\n    invoke_type = element.attrib.get(\"type\")\n    typeexpr = element.attrib.get(\"typeexpr\")\n    src = element.attrib.get(\"src\")\n    srcexpr = element.attrib.get(\"srcexpr\")\n    invoke_id = element.attrib.get(\"id\")\n    idlocation = element.attrib.get(\"idlocation\")\n    autoforward = element.attrib.get(\"autoforward\", \"false\").lower() == \"true\"\n    namelist = element.attrib.get(\"namelist\")\n\n    params: List[Param] = []\n    content: \"str | None\" = None\n    finalize: \"ExecutableContent | None\" = None\n\n    for child in element:\n        if child.tag == \"param\":\n            name = child.attrib[\"name\"]\n            expr = child.attrib.get(\"expr\")\n            location = child.attrib.get(\"location\")\n            params.append(Param(name=name, expr=expr, location=location))\n        elif child.tag == \"content\":\n            # Check for inline <scxml> element (namespaces already stripped)\n            scxml_child = child.find(\"scxml\")\n            if scxml_child is not None:\n                # Serialize the inline SCXML back to string for later parsing\n                content = ET.tostring(scxml_child, encoding=\"unicode\")\n            elif child.attrib.get(\"expr\"):\n                # Dynamic content via expr attribute\n                content = child.attrib[\"expr\"]\n            elif child.text:\n                content = re.sub(r\"\\s+\", \" \", child.text).strip()\n        elif child.tag == \"finalize\":\n            finalize = parse_executable_content(child)\n\n    return InvokeDefinition(\n        type=invoke_type,\n        typeexpr=typeexpr,\n        src=src,\n        srcexpr=srcexpr,\n        id=invoke_id,\n        idlocation=idlocation,\n        autoforward=autoforward,\n        namelist=namelist,\n        params=params,\n        content=content,\n        finalize=finalize,\n    )\n"
  },
  {
    "path": "statemachine/io/scxml/processor.py",
    "content": "import os\nfrom contextlib import contextmanager\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Any\nfrom typing import Dict\nfrom typing import List\n\nfrom ...event import Event\nfrom ...exceptions import InvalidDefinition\nfrom ...statemachine import StateChart\nfrom .. import HistoryDefinition\nfrom .. import StateDefinition\nfrom .. import TransitionDict\nfrom .. import TransitionsList\nfrom .. import create_machine_class_from_definition\nfrom .actions import Cond\nfrom .actions import DoneDataCallable\nfrom .actions import EventDataWrapper\nfrom .actions import ExecuteBlock\nfrom .actions import create_datamodel_action_callable\nfrom .actions import create_invoke_init_callable\nfrom .invoke import SCXMLInvoker\nfrom .parser import parse_scxml\nfrom .schema import HistoryState\nfrom .schema import InvokeDefinition\nfrom .schema import State\nfrom .schema import Transition\n\n\n@contextmanager\ndef temporary_directory(new_current_dir):\n    original_dir = os.getcwd()\n    try:\n        os.chdir(new_current_dir)\n        yield\n    finally:\n        os.chdir(original_dir)\n\n\nclass IOProcessor:\n    def __init__(self, processor: \"SCXMLProcessor\", machine: StateChart):\n        self.scxml_processor = processor\n        self.machine = machine\n\n    def __getitem__(self, name: str):\n        return self\n\n    @property\n    def location(self):\n        return self.machine.name\n\n    def get(self, name: str):\n        return getattr(self, name)\n\n\n@dataclass\nclass SessionData:\n    machine: StateChart\n    processor: IOProcessor\n    first_event_raised: bool = False\n\n    def __post_init__(self):\n        self.session_id = f\"{self.machine.name}:{id(self.machine)}\"\n\n\nclass SCXMLProcessor:\n    def __init__(self):\n        self.scs: \"Dict[str, type[StateChart]]\" = {}\n        self.sessions: Dict[str, SessionData] = {}\n        self._ioprocessors = {\n            \"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\": self,\n            \"scxml\": self,\n        }\n\n    def parse_scxml_file(self, path: Path):\n        scxml_content = path.read_text()\n        with temporary_directory(path.parent):\n            return self.parse_scxml(path.stem, scxml_content)\n\n    def parse_scxml(self, sm_name: str, scxml_content: str):\n        definition = parse_scxml(scxml_content)\n        self.process_definition(definition, location=definition.name or sm_name)\n\n    def process_definition(self, definition, location: str, is_invoked: bool = False):\n        states_dict = self._process_states(definition.states)\n\n        # Find the initial state for inserting init callbacks\n        try:\n            initial_state = next(s for s in iter(states_dict.values()) if s.get(\"initial\"))\n        except StopIteration:\n            initial_state = next(iter(states_dict.values()))\n\n        if \"enter\" not in initial_state:\n            initial_state[\"enter\"] = []\n\n        insert_pos = 0\n\n        # For invoked children, insert invoke_init to pop _invoke_session/_invoke_params\n        # from kwargs and store them on the machine instance before any other callbacks.\n        if is_invoked:\n            initial_state[\"enter\"].insert(0, create_invoke_init_callable())  # type: ignore[union-attr]\n            insert_pos = 1\n\n        # Process datamodel (initial variables)\n        if definition.datamodel:\n            datamodel = create_datamodel_action_callable(definition.datamodel)\n            if datamodel:  # pragma: no branch – parse_datamodel guarantees non-empty\n                if isinstance(  # pragma: no branch – always a list from lines above\n                    initial_state[\"enter\"], list\n                ):\n                    initial_state[\"enter\"].insert(insert_pos, datamodel)  # type: ignore[arg-type]\n\n        self._add(\n            location,\n            {\n                \"states\": states_dict,\n                \"prepare_event\": self._prepare_event,\n                \"validate_disconnected_states\": False,\n                \"validate_trap_states\": False,\n                \"validate_final_reachability\": False,\n                \"start_configuration_values\": list(definition.initial_states),\n            },\n        )\n\n    def _prepare_event(self, *args, event: Event, **kwargs):\n        machine = kwargs[\"machine\"]\n        session_data = self._get_session(machine)\n\n        if not session_data.first_event_raised and event and event != \"__initial__\":\n            session_data.first_event_raised = True\n\n        _event: \"EventDataWrapper | None\" = None\n        if session_data.first_event_raised:\n            _event = EventDataWrapper(kwargs[\"event_data\"])\n\n        return {\n            \"_name\": machine.name,\n            \"_sessionid\": session_data.session_id,\n            \"_ioprocessors\": session_data.processor,\n            \"_event\": _event,\n        }\n\n    def _get_session(self, machine: StateChart):\n        if machine.name not in self.sessions:\n            self.sessions[machine.name] = SessionData(\n                processor=IOProcessor(self, machine=machine), machine=machine\n            )\n        return self.sessions[machine.name]\n\n    def _process_history(self, history: Dict[str, HistoryState]) -> Dict[str, HistoryDefinition]:\n        states_dict: Dict[str, HistoryDefinition] = {}\n        for state_id, state in history.items():\n            state_dict = HistoryDefinition()\n\n            state_dict[\"type\"] = state.type\n\n            # Process transitions\n            if state.transitions:\n                state_dict[\"transitions\"] = self._process_transitions(state.transitions)\n\n            states_dict[state_id] = state_dict\n\n        return states_dict\n\n    def _process_states(self, states: Dict[str, State]) -> Dict[str, StateDefinition]:\n        states_dict: Dict[str, StateDefinition] = {}\n        for state_id, state in states.items():\n            states_dict[state_id] = self._process_state(state)\n        return states_dict\n\n    def _process_state(self, state: State) -> StateDefinition:  # noqa: C901\n        state_dict = StateDefinition()\n        if state.initial:\n            state_dict[\"initial\"] = True\n        if state.final:\n            state_dict[\"final\"] = True\n        if state.parallel:\n            state_dict[\"parallel\"] = True\n\n        # Process enter actions\n        enter_callables: list = [\n            ExecuteBlock(content) for content in state.onentry if not content.is_empty\n        ]\n        if enter_callables:\n            state_dict[\"enter\"] = enter_callables\n        if state.final and state.donedata:\n            state_dict[\"donedata\"] = DoneDataCallable(state.donedata)\n\n        # Process exit actions\n        if state.onexit:\n            callables = [ExecuteBlock(content) for content in state.onexit if not content.is_empty]\n            state_dict[\"exit\"] = callables\n\n        # Process transitions\n        if state.transitions:\n            state_dict[\"transitions\"] = self._process_transitions(state.transitions)\n\n        # Process invoke elements\n        if state.invocations:\n            invokers = [self._process_invocation(inv) for inv in state.invocations]\n            state_dict[\"invoke\"] = invokers  # type: ignore[typeddict-unknown-key]\n\n        if state.states:\n            state_dict[\"states\"] = self._process_states(state.states)\n\n        if state.history:\n            state_dict[\"history\"] = self._process_history(state.history)\n\n        return state_dict\n\n    def _process_invocation(self, invoke_def: InvokeDefinition) -> SCXMLInvoker:\n        \"\"\"Convert an InvokeDefinition into an SCXMLInvoker.\"\"\"\n        return SCXMLInvoker(\n            definition=invoke_def,\n            base_dir=os.getcwd(),\n            register_child=self._register_child,\n        )\n\n    def _register_child(self, scxml_content: str, child_name: str) -> type:\n        \"\"\"Parse SCXML content, register it as a child machine, and return its class.\"\"\"\n        definition = parse_scxml(scxml_content)\n        self.process_definition(definition, location=child_name, is_invoked=True)\n        return self.scs[child_name]\n\n    def _process_transitions(self, transitions: List[Transition]):\n        result: TransitionsList = []\n        for transition in transitions:\n            event = transition.event or None\n            transition_dict: TransitionDict = {\n                \"event\": event,\n                \"target\": transition.target,\n                \"internal\": transition.internal,\n                \"initial\": transition.initial,\n            }\n\n            # Process cond\n            if transition.cond:\n                cond_callable = Cond.create(transition.cond, processor=self)\n                if cond_callable is not None:  # pragma: no branch – cond already truthy\n                    transition_dict[\"cond\"] = cond_callable\n\n                # Process actions\n            if transition.on and not transition.on.is_empty:\n                transition_dict[\"on\"] = ExecuteBlock(transition.on)\n\n            result.append(transition_dict)\n        return result\n\n    def _add(self, location: str, definition: Dict[str, Any]):\n        try:\n            sc_class = create_machine_class_from_definition(location, **definition)\n            self.scs[location] = sc_class\n            return sc_class\n        except Exception as e:  # pragma: no cover\n            raise InvalidDefinition(\n                f\"Failed to create state machine class: {e} from definition: {definition}\"\n            ) from e\n\n    def start(self, **kwargs):\n        self.root_cls = next(iter(self.scs.values()))\n        self.root = self.root_cls(**kwargs)\n        return self.root\n"
  },
  {
    "path": "statemachine/io/scxml/schema.py",
    "content": "from dataclasses import dataclass\nfrom dataclasses import field\nfrom typing import Dict\nfrom typing import List\nfrom typing import Literal\nfrom urllib.parse import ParseResult\n\n\n@dataclass\nclass Action:\n    def __str__(self):\n        return f\"{self.__class__.__name__}\"\n\n\n@dataclass\nclass ExecutableContent:\n    actions: List[Action] = field(default_factory=list)\n\n    def __str__(self):\n        return \", \".join(str(action) for action in self.actions)\n\n    @property\n    def is_empty(self):\n        return not self.actions\n\n\n@dataclass\nclass RaiseAction(Action):\n    event: str\n\n\n@dataclass\nclass AssignAction(Action):\n    location: str\n    expr: \"str | None\" = None\n    child_xml: \"str | None\" = None\n\n\n@dataclass\nclass LogAction(Action):\n    label: \"str | None\"\n    expr: \"str | None\"\n\n\n@dataclass\nclass IfBranch(Action):\n    cond: \"str | None\"\n    actions: List[Action] = field(default_factory=list)\n\n    def __str__(self):\n        return self.cond or \"<empty cond>\"\n\n    def append(self, action: Action):\n        self.actions.append(action)\n\n\n@dataclass\nclass IfAction(Action):\n    branches: List[IfBranch] = field(default_factory=list)\n\n\n@dataclass\nclass ForeachAction(Action):\n    array: str\n    item: str\n    index: \"str | None\"\n    content: ExecutableContent\n\n\n@dataclass\nclass Param:\n    name: str\n    expr: \"str | None\"\n    location: \"str | None\" = None\n\n\n@dataclass\nclass SendAction(Action):\n    event: \"str | None\" = None\n    eventexpr: \"str | None\" = None\n    target: \"str | None\" = None\n    type: \"str | None\" = None\n    id: \"str | None\" = None\n    idlocation: \"str | None\" = None\n    delay: \"str | None\" = None\n    delayexpr: \"str | None\" = None\n    namelist: \"str | None\" = None\n    params: List[Param] = field(default_factory=list)\n    content: \"str | None\" = None\n\n\n@dataclass\nclass CancelAction(Action):\n    sendid: \"str | None\" = None\n    sendidexpr: \"str | None\" = None\n\n\n@dataclass\nclass ScriptAction(Action):\n    content: str\n\n\n@dataclass\nclass Transition:\n    target: \"str | None\" = None\n    internal: bool = False\n    initial: bool = False\n    event: \"str | None\" = None\n    cond: \"str | None\" = None\n    on: \"ExecutableContent | None\" = None\n\n\n@dataclass\nclass DoneData:\n    params: List[Param] = field(default_factory=list)\n    content_expr: \"str | None\" = None\n\n\n@dataclass\nclass InvokeDefinition:\n    type: \"str | None\" = None\n    typeexpr: \"str | None\" = None\n    src: \"str | None\" = None\n    srcexpr: \"str | None\" = None\n    id: \"str | None\" = None\n    idlocation: \"str | None\" = None\n    autoforward: bool = False\n    namelist: \"str | None\" = None\n    params: List[Param] = field(default_factory=list)\n    content: \"str | None\" = None\n    finalize: \"ExecutableContent | None\" = None\n\n\n@dataclass\nclass State:\n    id: str\n    initial: bool = False\n    final: bool = False\n    parallel: bool = False\n    transitions: List[Transition] = field(default_factory=list)\n    onentry: List[ExecutableContent] = field(default_factory=list)\n    onexit: List[ExecutableContent] = field(default_factory=list)\n    states: Dict[str, \"State\"] = field(default_factory=dict)\n    history: Dict[str, \"HistoryState\"] = field(default_factory=dict)\n    donedata: \"DoneData | None\" = None\n    invocations: List[InvokeDefinition] = field(default_factory=list)\n\n\n@dataclass\nclass HistoryState:\n    id: str\n    type: \"Literal['shallow', 'deep']\" = \"shallow\"\n    transitions: List[Transition] = field(default_factory=list)\n\n\n@dataclass\nclass DataItem:\n    id: str\n    src: \"ParseResult | None\"\n    expr: \"str | None\"\n    content: \"str | None\"\n\n\n@dataclass\nclass DataModel:\n    data: List[DataItem] = field(default_factory=list)\n    scripts: List[ScriptAction] = field(default_factory=list)\n\n\n@dataclass\nclass StateMachineDefinition:\n    name: \"str | None\" = None\n    states: Dict[str, State] = field(default_factory=dict)\n    initial_states: List[str] = field(default_factory=list)\n    datamodel: \"DataModel | None\" = None\n"
  },
  {
    "path": "statemachine/locale/en/LC_MESSAGES/statemachine.po",
    "content": "# This file is distributed under the same license as the  project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version:  3.0.0\\n\"\n\"Report-Msgid-Bugs-To: fgmacedo@gmail.com\\n\"\n\"POT-Creation-Date: 2026-02-24 14:31-0300\\n\"\n\"PO-Revision-Date: 2026-02-24 14:31-0300\\n\"\n\"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\\n\"\n\"Language: en\\n\"\n\"Language-Team: en <LL@li.org>\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Generated-By: Babel 2.16.0\\n\"\n\n#: statemachine/callbacks.py:404 statemachine/callbacks.py:409\nmsgid \"Did not found name '{}' from model or statemachine\"\nmsgstr \"Did not found name '{}' from model or statemachine\"\n\n#: statemachine/dispatcher.py:126\nmsgid \"Failed to parse boolean expression '{}'\"\nmsgstr \"Failed to parse boolean expression '{}'\"\n\n#: statemachine/event.py:121\nmsgid \"Cannot add callback '{}' to an event with no transitions.\"\nmsgstr \"Cannot add callback '{}' to an event with no transitions.\"\n\n#: statemachine/event.py:154\nmsgid \"Event {} cannot be called without a SM instance\"\nmsgstr \"Event {} cannot be called without a SM instance\"\n\n#: statemachine/exceptions.py:25\nmsgid \"{!r} is not a valid state value.\"\nmsgstr \"{!r} is not a valid state value.\"\n\n#: statemachine/exceptions.py:40\nmsgid \"Can't {} when in {}.\"\nmsgstr \"Can't {} when in {}.\"\n\n#: statemachine/factory.py:79\nmsgid \"\"\n\"There should be one and only one initial state. Your currently have \"\n\"these: {0}\"\nmsgstr \"\"\n\"There should be one and only one initial state. Your currently have \"\n\"these: {0}\"\n\n#: statemachine/factory.py:160\nmsgid \"\"\n\"There should be one and only one initial state. You currently have these:\"\n\" {!r}\"\nmsgstr \"\"\n\"There should be one and only one initial state. You currently have these:\"\n\" {!r}\"\n\n#: statemachine/factory.py:176\nmsgid \"Cannot declare transitions from final state. Invalid state(s): {}\"\nmsgstr \"Cannot declare transitions from final state. Invalid state(s): {}\"\n\n#: statemachine/factory.py:187\nmsgid \"\"\n\"All non-final states should have at least one outgoing transition. These \"\n\"states have no outgoing transition: {!r}\"\nmsgstr \"\"\n\"All non-final states should have at least one outgoing transition. These \"\n\"states have no outgoing transition: {!r}\"\n\n#: statemachine/factory.py:201\nmsgid \"\"\n\"All non-final states should have at least one path to a final state. \"\n\"These states have no path to a final state: {!r}\"\nmsgstr \"\"\n\"All non-final states should have at least one path to a final state. \"\n\"These states have no path to a final state: {!r}\"\n\n#: statemachine/factory.py:214\nmsgid \"\"\n\"There are unreachable states. The statemachine graph should have a single\"\n\" component. Disconnected states: {}\"\nmsgstr \"\"\n\"There are unreachable states. The statemachine graph should have a single\"\n\" component. Disconnected states: {}\"\n\n#: statemachine/factory.py:251\nmsgid \"\"\n\"Invalid entry in 'listeners': {!r}. Expected a class, callable, or \"\n\"listener instance.\"\nmsgstr \"\"\n\"Invalid entry in 'listeners': {!r}. Expected a class, callable, or \"\n\"listener instance.\"\n\n#: statemachine/factory.py:351\nmsgid \"An event in the '{}' has no id.\"\nmsgstr \"An event in the '{}' has no id.\"\n\n#: statemachine/mixins.py:28\nmsgid \"{!r} is not a valid state machine name.\"\nmsgstr \"{!r} is not a valid state machine name.\"\n\n#: statemachine/state.py:246\nmsgid \"'donedata' can only be specified on final states.\"\nmsgstr \"'donedata' can only be specified on final states.\"\n\n#: statemachine/state.py:304\nmsgid \"State overriding is not allowed. Trying to add '{}' to {}\"\nmsgstr \"State overriding is not allowed. Trying to add '{}' to {}\"\n\n#: statemachine/statemachine.py:158\nmsgid \"There are no states or transitions.\"\nmsgstr \"There are no states or transitions.\"\n\n#: statemachine/statemachine.py:431\nmsgid \"\"\n\"There's no current state set. In async code, did you activate the initial\"\n\" state? (e.g., `await sm.activate_initial_state()`)\"\nmsgstr \"\"\n\"There's no current state set. In async code, did you activate the initial\"\n\" state? (e.g., `await sm.activate_initial_state()`)\"\n\n#: statemachine/transition.py:74\nmsgid \"\"\n\"Not a valid internal transition from source {source!r}, target {target!r}\"\n\" should be self or a descendant.\"\nmsgstr \"\"\n\"Not a valid internal transition from source {source!r}, target {target!r}\"\n\" should be self or a descendant.\"\n\n#: statemachine/transition_mixin.py:19\nmsgid \"{} only supports the decorator syntax to register callbacks.\"\nmsgstr \"{} only supports the decorator syntax to register callbacks.\"\n"
  },
  {
    "path": "statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po",
    "content": "# This file is distributed under the same license as the  project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version:  2.4.0\\n\"\n\"Report-Msgid-Bugs-To: fgmacedo@gmail.com\\n\"\n\"POT-Creation-Date: 2026-02-24 14:31-0300\\n\"\n\"PO-Revision-Date: 2024-06-07 17:41-0300\\n\"\n\"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\\n\"\n\"Language: hi_IN\\n\"\n\"Language-Team: hi_IN <LL@li.org>\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Generated-By: Babel 2.16.0\\n\"\n\n#: statemachine/callbacks.py:404 statemachine/callbacks.py:409\nmsgid \"Did not found name '{}' from model or statemachine\"\nmsgstr \"मॉडल या स्टेटमशीन में नाम '{}' नहीं मिला\"\n\n#: statemachine/dispatcher.py:126\nmsgid \"Failed to parse boolean expression '{}'\"\nmsgstr \"बूलियन अभिव्यक्ति '{}' को पार्स करने में विफल रहा\"\n\n#: statemachine/event.py:121\nmsgid \"Cannot add callback '{}' to an event with no transitions.\"\nmsgstr \"बिना ट्रांज़िशन वाले इवेंट में कॉलबैक '{}' नहीं जोड़ सकते।\"\n\n#: statemachine/event.py:154\nmsgid \"Event {} cannot be called without a SM instance\"\nmsgstr \"इवेंट {} को SM इंस्टेंस के बिना कॉल नहीं किया जा सकता\"\n\n#: statemachine/exceptions.py:25\nmsgid \"{!r} is not a valid state value.\"\nmsgstr \"{!r} एक मान्य स्टेट मान नहीं है।\"\n\n#: statemachine/exceptions.py:40\nmsgid \"Can't {} when in {}.\"\nmsgstr \"{} स्थिति में {} नहीं कर सकते।\"\n\n#: statemachine/factory.py:79\nmsgid \"\"\n\"There should be one and only one initial state. Your currently have \"\n\"these: {0}\"\nmsgstr \"\"\n\"एक और केवल एक प्रारंभिक स्टेट होना चाहिए। वर्तमान में आपके पास ये हैं: \"\n\"{0}\"\n\n#: statemachine/factory.py:160\nmsgid \"\"\n\"There should be one and only one initial state. You currently have these:\"\n\" {!r}\"\nmsgstr \"\"\n\"एक और केवल एक प्रारंभिक स्टेट होना चाहिए। वर्तमान में आपके पास ये हैं: \"\n\"{!r}\"\n\n#: statemachine/factory.py:176\nmsgid \"Cannot declare transitions from final state. Invalid state(s): {}\"\nmsgstr \"अंतिम स्टेट से ट्रांज़िशन घोषित नहीं कर सकते। अमान्य स्टेट: {}\"\n\n#: statemachine/factory.py:187\nmsgid \"\"\n\"All non-final states should have at least one outgoing transition. These \"\n\"states have no outgoing transition: {!r}\"\nmsgstr \"\"\n\"सभी गैर-अंतिम स्टेट में कम से कम एक आउटगोइंग ट्रांज़िशन होना चाहिए। इन \"\n\"स्टेट में कोई आउटगोइंग ट्रांज़िशन नहीं है: {!r}\"\n\n#: statemachine/factory.py:201\nmsgid \"\"\n\"All non-final states should have at least one path to a final state. \"\n\"These states have no path to a final state: {!r}\"\nmsgstr \"\"\n\"सभी गैर-अंतिम स्टेट में अंतिम स्टेट तक कम से कम एक पथ होना चाहिए। इन \"\n\"स्टेट में अंतिम स्टेट तक कोई पथ नहीं है: {!r}\"\n\n#: statemachine/factory.py:214\nmsgid \"\"\n\"There are unreachable states. The statemachine graph should have a single\"\n\" component. Disconnected states: {}\"\nmsgstr \"\"\n\"कुछ स्टेट पहुंच योग्य नहीं हैं। स्टेटमशीन ग्राफ में एक ही घटक होना चाहिए।\"\n\" डिस्कनेक्टेड स्टेट: {}\"\n\n#: statemachine/factory.py:251\nmsgid \"\"\n\"Invalid entry in 'listeners': {!r}. Expected a class, callable, or \"\n\"listener instance.\"\nmsgstr \"\"\n\"'listeners' में अमान्य प्रविष्टि: {!r}। एक क्लास, कॉलेबल, या लिसनर \"\n\"इंस्टेंस अपेक्षित है।\"\n\n#: statemachine/factory.py:351\nmsgid \"An event in the '{}' has no id.\"\nmsgstr \"'{}' में एक इवेंट का आईडी नहीं है।\"\n\n#: statemachine/mixins.py:28\nmsgid \"{!r} is not a valid state machine name.\"\nmsgstr \"{!r} एक मान्य स्टेटमशीन नाम नहीं है।\"\n\n#: statemachine/state.py:246\nmsgid \"'donedata' can only be specified on final states.\"\nmsgstr \"'donedata' केवल अंतिम स्टेट पर निर्दिष्ट किया जा सकता है।\"\n\n#: statemachine/state.py:304\nmsgid \"State overriding is not allowed. Trying to add '{}' to {}\"\nmsgstr \"\"\n\"स्टेट ओवरराइड करना अनुमति नहीं है। '{}' को {} में जोड़ने की कोशिश कर रहे \"\n\"हैं\"\n\n#: statemachine/statemachine.py:158\nmsgid \"There are no states or transitions.\"\nmsgstr \"कोई स्टेट या ट्रांज़िशन नहीं हैं।\"\n\n#: statemachine/statemachine.py:431\nmsgid \"\"\n\"There's no current state set. In async code, did you activate the initial\"\n\" state? (e.g., `await sm.activate_initial_state()`)\"\nmsgstr \"\"\n\"कोई वर्तमान स्टेट सेट नहीं है। असिंक्रोनस कोड में, क्या आपने प्रारंभिक \"\n\"स्टेट को सक्रिय किया? (उदाहरण: `await sm.activate_initial_state()`)\"\n\n#: statemachine/transition.py:74\nmsgid \"\"\n\"Not a valid internal transition from source {source!r}, target {target!r}\"\n\" should be self or a descendant.\"\nmsgstr \"\"\n\"स्रोत {source!r} से अमान्य आंतरिक ट्रांज़िशन, लक्ष्य {target!r} स्वयं या \"\n\"एक वंशज होना चाहिए।\"\n\n#: statemachine/transition_mixin.py:19\nmsgid \"{} only supports the decorator syntax to register callbacks.\"\nmsgstr \"{} केवल कॉलबैक रजिस्टर करने के लिए डेकोरेटर सिंटैक्स का समर्थन करता है।\"\n\n#~ msgid \"There are no states.\"\n#~ msgstr \"कोई स्टेट नहीं है।\"\n\n#~ msgid \"There are no events.\"\n#~ msgstr \"कोई इवेंट नहीं है।\"\n\n#~ msgid \"Only RTC is supported on async engine\"\n#~ msgstr \"असिंक्रोनस इंजन पर केवल RTC समर्थित है\"\n"
  },
  {
    "path": "statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po",
    "content": "# This file is distributed under the same license as the  project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version:  2.4.0\\n\"\n\"Report-Msgid-Bugs-To: fgmacedo@gmail.com\\n\"\n\"POT-Creation-Date: 2026-02-24 14:31-0300\\n\"\n\"PO-Revision-Date: 2024-06-07 17:41-0300\\n\"\n\"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\\n\"\n\"Language: pt_BR\\n\"\n\"Language-Team: pt_BR <LL@li.org>\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Generated-By: Babel 2.16.0\\n\"\n\n#: statemachine/callbacks.py:404 statemachine/callbacks.py:409\nmsgid \"Did not found name '{}' from model or statemachine\"\nmsgstr \"Nome '{}' não encontrado no modelo ou na máquina de estados\"\n\n#: statemachine/dispatcher.py:126\nmsgid \"Failed to parse boolean expression '{}'\"\nmsgstr \"Falha ao interpretar a expressão booleana '{}'\"\n\n#: statemachine/event.py:121\nmsgid \"Cannot add callback '{}' to an event with no transitions.\"\nmsgstr \"Não é possível adicionar callback '{}' a um evento sem transições.\"\n\n#: statemachine/event.py:154\nmsgid \"Event {} cannot be called without a SM instance\"\nmsgstr \"O evento {} não pode ser chamado sem uma instância de SM\"\n\n#: statemachine/exceptions.py:25\nmsgid \"{!r} is not a valid state value.\"\nmsgstr \"{!r} não é um valor de estado válido.\"\n\n#: statemachine/exceptions.py:40\nmsgid \"Can't {} when in {}.\"\nmsgstr \"Não é possível {} quando em {}.\"\n\n#: statemachine/factory.py:79\nmsgid \"\"\n\"There should be one and only one initial state. Your currently have \"\n\"these: {0}\"\nmsgstr \"\"\n\"Deve haver um e apenas um estado inicial. Atualmente, você possui estes: \"\n\"{0}\"\n\n#: statemachine/factory.py:160\nmsgid \"\"\n\"There should be one and only one initial state. You currently have these:\"\n\" {!r}\"\nmsgstr \"\"\n\"Deve haver um e apenas um estado inicial. Atualmente, você possui estes: \"\n\"{!r}\"\n\n#: statemachine/factory.py:176\nmsgid \"Cannot declare transitions from final state. Invalid state(s): {}\"\nmsgstr \"\"\n\"Não é possível declarar transições a partir de um estado final. Estado(s)\"\n\" inválido(s): {}\"\n\n#: statemachine/factory.py:187\nmsgid \"\"\n\"All non-final states should have at least one outgoing transition. These \"\n\"states have no outgoing transition: {!r}\"\nmsgstr \"\"\n\"Todos os estados não finais devem ter pelo menos uma transição de saída. \"\n\"Estes estados não possuem transição de saída: {!r}\"\n\n#: statemachine/factory.py:201\nmsgid \"\"\n\"All non-final states should have at least one path to a final state. \"\n\"These states have no path to a final state: {!r}\"\nmsgstr \"\"\n\"Todos os estados não finais devem ter pelo menos um caminho para um \"\n\"estado final. Estes estados não possuem caminho para um estado final: \"\n\"{!r}\"\n\n#: statemachine/factory.py:214\nmsgid \"\"\n\"There are unreachable states. The statemachine graph should have a single\"\n\" component. Disconnected states: {}\"\nmsgstr \"\"\n\"Há estados inacessíveis. O grafo da máquina de estados deve ter um único \"\n\"componente. Estados desconectados: {}\"\n\n#: statemachine/factory.py:251\nmsgid \"\"\n\"Invalid entry in 'listeners': {!r}. Expected a class, callable, or \"\n\"listener instance.\"\nmsgstr \"\"\n\"Entrada inválida em 'listeners': {!r}. Esperado uma classe, callable ou \"\n\"instância de listener.\"\n\n#: statemachine/factory.py:351\nmsgid \"An event in the '{}' has no id.\"\nmsgstr \"Um evento em '{}' não possui id.\"\n\n#: statemachine/mixins.py:28\nmsgid \"{!r} is not a valid state machine name.\"\nmsgstr \"{!r} não é um nome de máquina de estados válido.\"\n\n#: statemachine/state.py:246\nmsgid \"'donedata' can only be specified on final states.\"\nmsgstr \"'donedata' só pode ser especificado em estados finais.\"\n\n#: statemachine/state.py:304\nmsgid \"State overriding is not allowed. Trying to add '{}' to {}\"\nmsgstr \"Sobrescrever estados não é permitido. Tentando adicionar '{}' a {}\"\n\n#: statemachine/statemachine.py:158\nmsgid \"There are no states or transitions.\"\nmsgstr \"Não há estados ou transições.\"\n\n#: statemachine/statemachine.py:431\nmsgid \"\"\n\"There's no current state set. In async code, did you activate the initial\"\n\" state? (e.g., `await sm.activate_initial_state()`)\"\nmsgstr \"\"\n\"Nenhum estado atual definido. Em código assíncrono, você ativou o estado \"\n\"inicial? (ex.: `await sm.activate_initial_state()`)\"\n\n#: statemachine/transition.py:74\nmsgid \"\"\n\"Not a valid internal transition from source {source!r}, target {target!r}\"\n\" should be self or a descendant.\"\nmsgstr \"\"\n\"Transição interna inválida da origem {source!r}, o destino {target!r} \"\n\"deve ser o próprio estado ou um descendente.\"\n\n#: statemachine/transition_mixin.py:19\nmsgid \"{} only supports the decorator syntax to register callbacks.\"\nmsgstr \"{} suporta apenas a sintaxe de decorator para registrar callbacks.\"\n\n#~ msgid \"There are no states.\"\n#~ msgstr \"Não há estados.\"\n\n#~ msgid \"There are no events.\"\n#~ msgstr \"Não há eventos.\"\n\n#~ msgid \"Only RTC is supported on async engine\"\n#~ msgstr \"Apenas RTC é suportado no motor assíncrono\"\n"
  },
  {
    "path": "statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po",
    "content": "# This file is distributed under the same license as the  project.\n# Fernando Macedo <fgmacedo@gmail.com>, 2024.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version:  2.4.0\\n\"\n\"Report-Msgid-Bugs-To: fgmacedo@gmail.com\\n\"\n\"POT-Creation-Date: 2026-02-24 14:31-0300\\n\"\n\"PO-Revision-Date: 2024-06-07 17:41-0300\\n\"\n\"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\\n\"\n\"Language: zh_CN\\n\"\n\"Language-Team: zh_CN <LL@li.org>\\n\"\n\"Plural-Forms: nplurals=1; plural=0;\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=utf-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Generated-By: Babel 2.16.0\\n\"\n\n#: statemachine/callbacks.py:404 statemachine/callbacks.py:409\nmsgid \"Did not found name '{}' from model or statemachine\"\nmsgstr \"在模型或状态机中未找到名称 '{}'\"\n\n#: statemachine/dispatcher.py:126\nmsgid \"Failed to parse boolean expression '{}'\"\nmsgstr \"无法解析布尔表达式 '{}'\"\n\n#: statemachine/event.py:121\nmsgid \"Cannot add callback '{}' to an event with no transitions.\"\nmsgstr \"无法将回调 '{}' 添加到没有转换的事件。\"\n\n#: statemachine/event.py:154\nmsgid \"Event {} cannot be called without a SM instance\"\nmsgstr \"事件 {} 不能在没有 SM 实例的情况下调用\"\n\n#: statemachine/exceptions.py:25\nmsgid \"{!r} is not a valid state value.\"\nmsgstr \"{!r} 不是有效的状态值。\"\n\n#: statemachine/exceptions.py:40\nmsgid \"Can't {} when in {}.\"\nmsgstr \"在 {} 时无法 {}。\"\n\n#: statemachine/factory.py:79\nmsgid \"\"\n\"There should be one and only one initial state. Your currently have \"\n\"these: {0}\"\nmsgstr \"应有且仅有一个初始状态。当前您有这些：{0}\"\n\n#: statemachine/factory.py:160\nmsgid \"\"\n\"There should be one and only one initial state. You currently have these:\"\n\" {!r}\"\nmsgstr \"应有且仅有一个初始状态。当前您有这些：{!r}\"\n\n#: statemachine/factory.py:176\nmsgid \"Cannot declare transitions from final state. Invalid state(s): {}\"\nmsgstr \"无法从终止状态声明转换。无效状态：{}\"\n\n#: statemachine/factory.py:187\nmsgid \"\"\n\"All non-final states should have at least one outgoing transition. These \"\n\"states have no outgoing transition: {!r}\"\nmsgstr \"所有非终止状态都应至少有一个外部转换。这些状态没有外部转换：{!r}\"\n\n#: statemachine/factory.py:201\nmsgid \"\"\n\"All non-final states should have at least one path to a final state. \"\n\"These states have no path to a final state: {!r}\"\nmsgstr \"所有非终止状态应至少有一个到终止状态的路径。这些状态没有到终止状态的路径：{!r}\"\n\n#: statemachine/factory.py:214\nmsgid \"\"\n\"There are unreachable states. The statemachine graph should have a single\"\n\" component. Disconnected states: {}\"\nmsgstr \"存在不可到达的状态。状态机图应具有单个组件。断开的状态：{}\"\n\n#: statemachine/factory.py:251\nmsgid \"\"\n\"Invalid entry in 'listeners': {!r}. Expected a class, callable, or \"\n\"listener instance.\"\nmsgstr \"\"\n\"'listeners' 中的条目无效：{!r}。期望为类、可调用对象或监听器实例。\"\n\n#: statemachine/factory.py:351\nmsgid \"An event in the '{}' has no id.\"\nmsgstr \"'{}' 中的事件没有 ID。\"\n\n#: statemachine/mixins.py:28\nmsgid \"{!r} is not a valid state machine name.\"\nmsgstr \"{!r} 不是有效的状态机名称。\"\n\n#: statemachine/state.py:246\nmsgid \"'donedata' can only be specified on final states.\"\nmsgstr \"'donedata' 只能在终止状态上指定。\"\n\n#: statemachine/state.py:304\nmsgid \"State overriding is not allowed. Trying to add '{}' to {}\"\nmsgstr \"不允许覆盖状态。尝试将 '{}' 添加到 {}\"\n\n#: statemachine/statemachine.py:158\nmsgid \"There are no states or transitions.\"\nmsgstr \"没有状态或转换。\"\n\n#: statemachine/statemachine.py:431\nmsgid \"\"\n\"There's no current state set. In async code, did you activate the initial\"\n\" state? (e.g., `await sm.activate_initial_state()`)\"\nmsgstr \"没有设置当前状态。在异步代码中，您是否激活了初始状态？（例如，`await sm.activate_initial_state()`）\"\n\n#: statemachine/transition.py:74\nmsgid \"\"\n\"Not a valid internal transition from source {source!r}, target {target!r}\"\n\" should be self or a descendant.\"\nmsgstr \"\"\n\"从源 {source!r} 的内部转换无效，目标 {target!r} 应为自身或后代。\"\n\n#: statemachine/transition_mixin.py:19\nmsgid \"{} only supports the decorator syntax to register callbacks.\"\nmsgstr \"{} 仅支持使用装饰器语法注册回调。\"\n\n#~ msgid \"There are no states.\"\n#~ msgstr \"没有状态。\"\n\n#~ msgid \"There are no events.\"\n#~ msgstr \"没有事件。\"\n\n#~ msgid \"Only RTC is supported on async engine\"\n#~ msgstr \"异步引擎仅支持 RTC\"\n"
  },
  {
    "path": "statemachine/mixins.py",
    "content": "from . import registry\nfrom .i18n import _\n\n\nclass MachineMixin:\n    \"\"\"This mixing allows a model to automatically instantiate and assign an\n    ``StateMachine``.\n    \"\"\"\n\n    state_field_name: str = \"state\"\n    \"\"\"The model's state field name that will hold the state value.\"\"\"\n\n    state_machine_name: \"str | None\" = None\n    \"\"\"A fully qualified name of the class, where it can be imported.\"\"\"\n\n    state_machine_attr: str = \"statemachine\"\n    \"\"\"Name of the model's attribute that will hold the machine instance.\"\"\"\n\n    bind_events_as_methods: bool = False\n    \"\"\"If ``True`` the state machine events triggers will be bound to the model as methods.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if not self.state_machine_name:\n            if self._is_django_historical_model():\n                return\n            raise ValueError(\n                _(\"{!r} is not a valid state machine name.\").format(self.state_machine_name)\n            )\n        machine_cls = registry.get_machine_cls(self.state_machine_name)\n        sm = machine_cls(self, state_field=self.state_field_name)\n        setattr(\n            self,\n            self.state_machine_attr,\n            sm,\n        )\n        if self.bind_events_as_methods:\n            sm.bind_events_to(self)\n\n    @classmethod\n    def _is_django_historical_model(cls) -> bool:\n        \"\"\"Detect Django historical models created by ``apps.get_model()`` in migrations.\n\n        Django sets ``__module__ = '__fake__'`` on these dynamically-created classes,\n        which lack the user-defined class attributes like ``state_machine_name``.\n        \"\"\"\n        return getattr(cls, \"__module__\", None) == \"__fake__\"\n"
  },
  {
    "path": "statemachine/model.py",
    "content": "class Model:\n    def __init__(self):\n        self.state = None\n        \"\"\"Holds the current :ref:`state` value of the :ref:`StateMachine`.\"\"\"\n\n    def __repr__(self):\n        return f\"Model(state={self.state})\"\n"
  },
  {
    "path": "statemachine/orderedset.py",
    "content": "import itertools\nfrom typing import Iterable\nfrom typing import Iterator\nfrom typing import MutableSet\nfrom typing import TypeVar\n\nT = TypeVar(\"T\")\n\n\nclass OrderedSet(MutableSet[T]):\n    \"\"\"A set that preserves insertion order by internally using a dict.\n\n    >>> OrderedSet([1, 2, \"foo\"])\n    OrderedSet([1, 2, 'foo'])\n\n\n    >>> OrderedSet([1, 2, 3, 3, 2, 1, 'a', 'b', 'a', 'c'])\n    OrderedSet([1, 2, 3, 'a', 'b', 'c'])\n\n    >>> s = OrderedSet([1, 2, 3])\n    >>> s.add(4)\n    >>> s\n    OrderedSet([1, 2, 3, 4])\n\n    >>> s = OrderedSet([1, 2, 3])\n    >>> \"foo\" in s\n    False\n\n    >>> 1 in s\n    True\n\n    >>> list(s)\n    [1, 2, 3]\n\n    >>> s == OrderedSet([1, 2, 3])\n    True\n\n    >>> s > OrderedSet([1, 2])  # set is a superset of other\n    True\n\n    >>> s & {2}\n    OrderedSet([2])\n\n    >>> s | {4}\n    OrderedSet([1, 2, 3, 4])\n\n    >>> s - {2}\n    OrderedSet([1, 3])\n\n    >>> s - {1}\n    OrderedSet([2, 3])\n\n    >>> {1} - s\n    OrderedSet([])\n\n    >>> s ^ {2}\n    OrderedSet([1, 3])\n\n    >>> s[1]\n    2\n\n    >>> s[2]\n    3\n\n    >>> eval(repr(OrderedSet(['a', 'b', 'c'])))\n    OrderedSet(['a', 'b', 'c'])\n\n\n\n    \"\"\"\n\n    __slots__ = (\"_d\",)\n\n    def __init__(self, iterable: \"Iterable[T] | None\" = None):\n        self._d = dict.fromkeys(iterable) if iterable else {}\n\n    def add(self, x: T) -> None:\n        self._d[x] = None\n\n    def clear(self) -> None:\n        self._d.clear()\n\n    def discard(self, x: T) -> None:\n        self._d.pop(x, None)\n\n    def __getitem__(self, index) -> T:\n        try:\n            return next(itertools.islice(self._d, index, index + 1))\n        except StopIteration as e:\n            raise IndexError(f\"index {index} out of range\") from e\n\n    def __contains__(self, x: object) -> bool:\n        return self._d.__contains__(x)\n\n    def __len__(self) -> int:\n        return self._d.__len__()\n\n    def __iter__(self) -> Iterator[T]:\n        return self._d.__iter__()\n\n    def __str__(self):\n        return f\"{{{', '.join(str(i) for i in self)}}}\"\n\n    def __repr__(self):\n        return f\"OrderedSet({list(self._d.keys())})\"\n\n    def union(self, *others: Iterable[T]) -> \"OrderedSet[T]\":\n        \"\"\"Return a new OrderedSet containing elements from the set and all others.\"\"\"\n        new_set = OrderedSet(self)\n        for other in others:\n            new_set.update(other)\n        return new_set\n\n    def update(self, *others: Iterable[T]) -> None:\n        \"\"\"Update the set, adding elements from all others.\"\"\"\n        for other in others:\n            for element in other:\n                self.add(element)\n"
  },
  {
    "path": "statemachine/py.typed",
    "content": ""
  },
  {
    "path": "statemachine/registry.py",
    "content": "from typing import List\nfrom typing import Optional\n\nfrom .utils import qualname\n\ntry:\n    from django.utils.module_loading import autodiscover_modules\nexcept ImportError:  # pragma: no cover\n    # Not a django project\n    def autodiscover_modules(module_name: str):\n        pass\n\n\n_REGISTRY = {}\n_initialized = False\n\n\ndef register(cls):\n    _REGISTRY[qualname(cls)] = cls\n    return cls\n\n\ndef get_machine_cls(name):\n    init_registry()\n    return _REGISTRY[name]\n\n\ndef init_registry():\n    global _initialized\n    if not _initialized:\n        load_modules([\"statemachine\", \"statemachines\"])\n        _initialized = True\n\n\ndef load_modules(modules: Optional[List[str]] = None) -> None:\n    for module in modules or []:\n        autodiscover_modules(module)\n"
  },
  {
    "path": "statemachine/signature.py",
    "content": "from __future__ import annotations\n\nfrom functools import partial\nfrom inspect import BoundArguments\nfrom inspect import Parameter\nfrom inspect import Signature\nfrom inspect import iscoroutinefunction\nfrom itertools import chain\nfrom types import MethodType\nfrom typing import Any\nfrom typing import FrozenSet\nfrom typing import Optional\nfrom typing import Tuple\n\nBindCacheKey = Tuple[int, FrozenSet[str]]\nBindTemplate = Tuple[Tuple[str, ...], Optional[str], Optional[str]]  # noqa: UP007\n\n\ndef _make_key(method):\n    method = method.func if isinstance(method, partial) else method\n    if isinstance(method, property):  # pragma: no cover\n        assert method.fget is not None\n        method = method.fget\n    if isinstance(method, MethodType):\n        return hash(\n            (\n                method.__qualname__,\n                method.__self__.__class__.__name__,\n                method.__code__.co_varnames,\n            )\n        )\n    else:\n        return hash((method.__qualname__, method.__code__.co_varnames))\n\n\ndef signature_cache(user_function):\n    cache = {}\n    cache_get = cache.get\n\n    def cached_function(cls, method):\n        key = _make_key(method)\n        sig = cache_get(key)\n        if sig is None:\n            sig = user_function(cls, method)\n            cache[key] = sig\n\n        return sig\n\n    cached_function.clear_cache = cache.clear\n    cached_function.make_key = _make_key\n\n    return cached_function\n\n\nclass SignatureAdapter(Signature):\n    is_coroutine: bool = False\n    _bind_cache: dict[BindCacheKey, BindTemplate]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._bind_cache = {}\n\n    @classmethod\n    @signature_cache\n    def from_callable(cls, method):\n        if hasattr(method, \"__signature__\"):\n            sig = method.__signature__\n            adapter = SignatureAdapter(\n                sig.parameters.values(),\n                return_annotation=sig.return_annotation,\n            )\n        else:\n            adapter = super().from_callable(method)\n\n        adapter.is_coroutine = iscoroutinefunction(method)\n        return adapter\n\n    def bind_expected(self, *args: Any, **kwargs: Any) -> BoundArguments:\n        cache_key: BindCacheKey = (len(args), frozenset(kwargs.keys()))\n        template = self._bind_cache.get(cache_key)\n\n        if template is not None:\n            return self._fast_bind(args, kwargs, template)\n\n        result = self._full_bind(cache_key, *args, **kwargs)\n        return result\n\n    def _fast_bind(\n        self,\n        args: tuple[Any, ...],\n        kwargs: dict[str, Any],\n        template: BindTemplate,\n    ) -> BoundArguments:\n        param_names, kwargs_param_name, var_positional_name = template\n        arguments: dict[str, Any] = {}\n        past_var_positional = False\n\n        for i, name in enumerate(param_names):\n            if name == var_positional_name:\n                # Collect all remaining positional args into a tuple\n                arguments[name] = args[i:]\n                past_var_positional = True\n            elif past_var_positional:\n                # After *args, remaining params are keyword-only\n                arguments[name] = kwargs.get(name)\n            elif i < len(args):\n                # Match _full_bind: if param is also in kwargs, kwargs wins\n                # (POSITIONAL_OR_KEYWORD params prefer kwargs over positional args)\n                if name in kwargs:\n                    arguments[name] = kwargs[name]\n                else:\n                    arguments[name] = args[i]\n            else:\n                arguments[name] = kwargs.get(name)\n\n        if kwargs_param_name is not None:\n            matched = set(param_names)\n            arguments[kwargs_param_name] = {k: v for k, v in kwargs.items() if k not in matched}\n\n        return BoundArguments(self, arguments)  # type: ignore[arg-type]\n\n    def _full_bind(  # noqa: C901\n        self,\n        cache_key: BindCacheKey,\n        *args: Any,\n        **kwargs: Any,\n    ) -> BoundArguments:\n        \"\"\"Get a BoundArguments object, that maps the passed `args`\n        and `kwargs` to the function's signature.  It avoids to raise `TypeError`\n        trying to fill all the required arguments and ignoring the unknown ones.\n\n        Adapted from the internal `inspect.Signature._bind`.\n        \"\"\"\n        arguments: dict[str, Any] = {}\n        param_names_used: list[str] = []\n\n        parameters = iter(self.parameters.values())\n        arg_vals = iter(args)\n        parameters_ex: Any = ()\n        kwargs_param = None\n        kwargs_param_name: str | None = None\n        var_positional_name: str | None = None\n\n        while True:\n            # Let's iterate through the positional arguments and corresponding\n            # parameters\n            try:\n                arg_val = next(arg_vals)\n            except StopIteration:\n                # No more positional arguments\n                try:\n                    param = next(parameters)\n                except StopIteration:\n                    # No more parameters. That's it. Just need to check that\n                    # we have no `kwargs` after this while loop\n                    break\n                else:\n                    if param.kind == Parameter.VAR_POSITIONAL:\n                        # That's OK, just empty *args.  Let's start parsing\n                        # kwargs\n                        break\n                    elif param.name in kwargs:\n                        if param.kind == Parameter.POSITIONAL_ONLY:\n                            msg = (\n                                \"{arg!r} parameter is positional only, but was passed as a keyword\"\n                            )\n                            msg = msg.format(arg=param.name)\n                            raise TypeError(msg) from None\n                        parameters_ex = (param,)\n                        break\n                    elif (\n                        param.kind == Parameter.VAR_KEYWORD or param.default is not Parameter.empty\n                    ):\n                        # That's fine too - we have a default value for this\n                        # parameter.  So, lets start parsing `kwargs`, starting\n                        # with the current parameter\n                        parameters_ex = (param,)\n                        break\n                    else:\n                        # No default, not VAR_KEYWORD, not VAR_POSITIONAL,\n                        # not in `kwargs`\n                        parameters_ex = (param,)\n                        break\n            else:\n                # We have a positional argument to process\n                try:\n                    param = next(parameters)\n                except StopIteration:\n                    # raise TypeError('too many positional arguments') from None\n                    break\n                else:\n                    if param.kind == Parameter.VAR_KEYWORD:\n                        # Memorize that we have a '**kwargs'-like parameter\n                        kwargs_param = param\n                        break\n\n                    if param.kind == Parameter.KEYWORD_ONLY:\n                        # Looks like we have no parameter for this positional\n                        # argument\n                        # 'too many positional arguments' forgiven\n                        break\n\n                    if param.kind == Parameter.VAR_POSITIONAL:\n                        # We have an '*args'-like argument, let's fill it with\n                        # all positional arguments we have left and move on to\n                        # the next phase\n                        values = [arg_val]\n                        values.extend(arg_vals)\n                        arguments[param.name] = tuple(values)\n                        param_names_used.append(param.name)\n                        var_positional_name = param.name\n                        break\n\n                    if param.name in kwargs and param.kind != Parameter.POSITIONAL_ONLY:\n                        arguments[param.name] = kwargs.pop(param.name)\n                    else:\n                        arguments[param.name] = arg_val\n                    param_names_used.append(param.name)\n\n        # Now, we iterate through the remaining parameters to process\n        # keyword arguments\n        for param in chain(parameters_ex, parameters):\n            if param.kind == Parameter.VAR_KEYWORD:\n                # Memorize that we have a '**kwargs'-like parameter\n                kwargs_param = param\n                continue\n\n            if param.kind == Parameter.VAR_POSITIONAL:\n                # Named arguments don't refer to '*args'-like parameters.\n                # We only arrive here if the positional arguments ended\n                # before reaching the last parameter before *args.\n                continue\n\n            param_name = param.name\n            try:\n                arg_val = kwargs.pop(param_name)\n            except KeyError:\n                # We have no value for this parameter.  It's fine though,\n                # if it has a default value, or it is an '*args'-like\n                # parameter, left alone by the processing of positional\n                # arguments.\n                pass\n            else:\n                arguments[param_name] = arg_val\n                param_names_used.append(param_name)\n\n        if kwargs:\n            if kwargs_param is not None:\n                # Process our '**kwargs'-like parameter\n                arguments[kwargs_param.name] = kwargs  # type: ignore[assignment]\n                kwargs_param_name = kwargs_param.name\n            else:\n                # 'ignoring we got an unexpected keyword argument'\n                pass\n\n        template: BindTemplate = (tuple(param_names_used), kwargs_param_name, var_positional_name)\n        self._bind_cache[cache_key] = template\n\n        return BoundArguments(self, arguments)  # type: ignore[arg-type]\n"
  },
  {
    "path": "statemachine/spec_parser.py",
    "content": "import ast\nimport operator\nimport re\nfrom functools import reduce\nfrom inspect import isawaitable\nfrom typing import Callable\nfrom typing import Dict\n\nreplacements = {\"!\": \"not \", \"^\": \" and \", \"v\": \" or \"}\n\npattern = re.compile(r\"\\!(?!=)|\\^|\\bv\\b\")\n\ncomparison_repr = {\n    operator.eq: \"==\",\n    operator.ne: \"!=\",\n    operator.gt: \">\",\n    operator.ge: \">=\",\n    operator.lt: \"<\",\n    operator.le: \"<=\",\n}\n\n\ndef _unique_key(left, right, operator) -> str:\n    left_key = getattr(left, \"unique_key\", \"\")\n    right_key = getattr(right, \"unique_key\", \"\")\n    return f\"{left_key} {operator} {right_key}\"\n\n\ndef replace_operators(expr: str) -> str:\n    # preprocess the expression adding support for classical logical operators\n    def match_func(match):\n        return replacements[match.group(0)]\n\n    return pattern.sub(match_func, expr)\n\n\ndef custom_not(predicate: Callable) -> Callable:\n    def decorated(*args, **kwargs):\n        result = predicate(*args, **kwargs)\n        if isawaitable(result):\n\n            async def _negate():\n                return not await result\n\n            return _negate()\n        return not result\n\n    decorated.__name__ = f\"not({predicate.__name__})\"\n    unique_key = getattr(predicate, \"unique_key\", \"\")\n    decorated.unique_key = f\"not({unique_key})\"  # type: ignore[attr-defined]\n    return decorated\n\n\ndef custom_and(left: Callable, right: Callable) -> Callable:\n    def decorated(*args, **kwargs):\n        left_result = left(*args, **kwargs)\n        if isawaitable(left_result):\n\n            async def _async_and():\n                lr = await left_result\n                if not lr:\n                    return lr\n                rr = right(*args, **kwargs)\n                if isawaitable(rr):\n                    return await rr\n                return rr\n\n            return _async_and()\n        if not left_result:\n            return left_result\n        right_result = right(*args, **kwargs)\n        if isawaitable(right_result):\n            return right_result\n        return right_result\n\n    decorated.__name__ = f\"({left.__name__} and {right.__name__})\"\n    decorated.unique_key = _unique_key(left, right, \"and\")  # type: ignore[attr-defined]\n    return decorated\n\n\ndef custom_or(left: Callable, right: Callable) -> Callable:\n    def decorated(*args, **kwargs):\n        left_result = left(*args, **kwargs)\n        if isawaitable(left_result):\n\n            async def _async_or():\n                lr = await left_result\n                if lr:\n                    return lr\n                rr = right(*args, **kwargs)\n                if isawaitable(rr):\n                    return await rr\n                return rr\n\n            return _async_or()\n        if left_result:\n            return left_result\n        right_result = right(*args, **kwargs)\n        if isawaitable(right_result):\n            return right_result\n        return right_result\n\n    decorated.__name__ = f\"({left.__name__} or {right.__name__})\"\n    decorated.unique_key = _unique_key(left, right, \"or\")  # type: ignore[attr-defined]\n    return decorated\n\n\ndef build_constant(constant) -> Callable:\n    def decorated(*args, **kwargs):\n        return constant\n\n    decorated.__name__ = str(constant)\n    decorated.unique_key = str(constant)  # type: ignore[attr-defined]\n    return decorated\n\n\nclass Functions:\n    registry: Dict[str, Callable] = {}\n\n    @classmethod\n    def register(cls, id) -> Callable:\n        def register(func):\n            cls.registry[id] = func\n            return func\n\n        return register\n\n    @classmethod\n    def get(cls, func_id):\n        func_id = func_id.lower()\n        if func_id not in cls.registry:\n            raise ValueError(f\"Unsupported function: {func_id}\")\n        return cls.registry[func_id]\n\n\nclass InState:\n    def __init__(self, machine):\n        self.machine = machine\n\n    def __call__(self, *state_ids: str):\n        return set(state_ids).issubset({s.id for s in self.machine.configuration})\n\n\n@Functions.register(\"in\")\ndef build_in_call(*state_ids: str) -> Callable:\n    state_ids_set = set(state_ids)\n\n    def decorated(*args, **kwargs):\n        machine = kwargs[\"machine\"]\n        return InState(machine)(*state_ids)\n\n    decorated.__name__ = f\"in({state_ids_set})\"\n    decorated.unique_key = f\"in({state_ids_set})\"  # type: ignore[attr-defined]\n    return decorated\n\n\ndef build_custom_operator(operator) -> Callable:\n    operator_repr = comparison_repr[operator]\n\n    def custom_comparator(left: Callable, right: Callable) -> Callable:\n        def decorated(*args, **kwargs):\n            left_result = left(*args, **kwargs)\n            right_result = right(*args, **kwargs)\n            if isawaitable(left_result) or isawaitable(right_result):\n\n                async def _async_compare():\n                    lr = (await left_result) if isawaitable(left_result) else left_result\n                    rr = (await right_result) if isawaitable(right_result) else right_result\n                    return bool(operator(lr, rr))\n\n                return _async_compare()\n            return bool(operator(left_result, right_result))\n\n        decorated.__name__ = f\"({left.__name__} {operator_repr} {right.__name__})\"\n        decorated.unique_key = _unique_key(left, right, operator_repr)  # type: ignore[attr-defined]\n        return decorated\n\n    return custom_comparator\n\n\ndef build_expression(node, variable_hook, operator_mapping):  # noqa: C901\n    if isinstance(node, ast.BoolOp):\n        # Handle `and` / `or` operations\n        operator_fn = operator_mapping[type(node.op)]\n        left_expr = build_expression(node.values[0], variable_hook, operator_mapping)\n        for right in node.values[1:]:\n            right_expr = build_expression(right, variable_hook, operator_mapping)\n            left_expr = operator_fn(left_expr, right_expr)\n        return left_expr\n    elif isinstance(node, ast.Compare):\n        # Handle `==` / `!=` / `>` / `<` / `>=` / `<=` operations\n        expressions = []\n        left_expr = build_expression(node.left, variable_hook, operator_mapping)\n        for right_op, right in zip(node.ops, node.comparators):  # noqa: B905  # strict=True requires 3.10+\n            right_expr = build_expression(right, variable_hook, operator_mapping)\n            operator_fn = operator_mapping[type(right_op)]\n            expression = operator_fn(left_expr, right_expr)\n            left_expr = right_expr\n            expressions.append(expression)\n\n        return reduce(custom_and, expressions)\n    elif isinstance(node, ast.Call):\n        # Handle function calls\n        assert isinstance(node.func, ast.Name)\n        constructor = Functions.get(node.func.id)\n        params = [arg.value for arg in node.args if isinstance(arg, ast.Constant)]\n        return constructor(*params)\n    elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):\n        # Handle `not` operation\n        operand_expr = build_expression(node.operand, variable_hook, operator_mapping)\n        return operator_mapping[type(node.op)](operand_expr)\n    elif isinstance(node, ast.Name):\n        # Handle variables by calling the variable_hook\n        return variable_hook(node.id)\n    elif isinstance(node, ast.Constant):\n        # Handle constants by returning the value\n        return build_constant(node.value)\n    else:\n        raise ValueError(f\"Unsupported expression structure: {node.__class__.__name__}\")\n\n\ndef parse_boolean_expr(expr, variable_hook, operator_mapping):\n    \"\"\"Parses the expression into an AST and build a custom expression tree\"\"\"\n    if expr.strip() == \"\":\n        raise SyntaxError(\"Empty expression\")\n\n    # Optimization trying to avoid parsing the expression if not needed\n    if \"!\" not in expr and \" \" not in expr and \"In(\" not in expr:\n        return variable_hook(expr)\n    expr = replace_operators(expr)\n    tree = ast.parse(expr, mode=\"eval\")\n    return build_expression(tree.body, variable_hook, operator_mapping)\n\n\noperator_mapping = {\n    ast.Or: custom_or,\n    ast.And: custom_and,\n    ast.Not: custom_not,\n    ast.GtE: build_custom_operator(operator.ge),\n    ast.Gt: build_custom_operator(operator.gt),\n    ast.LtE: build_custom_operator(operator.le),\n    ast.Lt: build_custom_operator(operator.lt),\n    ast.Eq: build_custom_operator(operator.eq),\n    ast.NotEq: build_custom_operator(operator.ne),\n}\n"
  },
  {
    "path": "statemachine/state.py",
    "content": "from enum import Enum\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Generator\nfrom typing import List\nfrom typing import cast\nfrom weakref import ref\n\nfrom .callbacks import CallbackGroup\nfrom .callbacks import CallbackPriority\nfrom .callbacks import CallbackSpecList\nfrom .event import _expand_event_id\nfrom .exceptions import InvalidDefinition\nfrom .i18n import _\nfrom .invoke import normalize_invoke_callbacks\nfrom .transition import Transition\nfrom .transition_list import TransitionList\nfrom .utils import humanize_id\n\nif TYPE_CHECKING:\n    from .statemachine import StateChart\n\n\nclass _TransitionBuilder:\n    def __init__(self, state: \"State\"):\n        self._state = state\n\n    def itself(self, **kwargs):\n        return self.__call__(self._state, **kwargs)\n\n    def __call__(self, *states: \"State\", **kwargs):\n        raise NotImplementedError\n\n\nclass _ToState(_TransitionBuilder):\n    def __call__(self, *states: \"State | NestedStateFactory | None\", **kwargs):\n        transitions = TransitionList(\n            Transition(self._state, cast(\"State | None\", state), **kwargs) for state in states\n        )\n        self._state.transitions.add_transitions(transitions)\n        return transitions\n\n\nclass _FromState(_TransitionBuilder):\n    def any(self, **kwargs):\n        \"\"\"Create transitions from all non-final states (reversed).\"\"\"\n        return self.__call__(AnyState(), **kwargs)\n\n    def __call__(self, *states: \"State | NestedStateFactory\", **kwargs):\n        transitions = TransitionList()\n        for origin in states:\n            state = cast(State, origin)\n            transition = Transition(state, self._state, **kwargs)\n            state.transitions.add_transitions(transition)\n            transitions.add_transitions(transition)\n        return transitions\n\n\nclass NestedStateFactory(type):\n    def __new__(  # type: ignore [misc]\n        cls, classname, bases, attrs, name=\"\", **kwargs\n    ) -> \"State\":\n        if not bases:\n            new_cls = super().__new__(cls, classname, bases, attrs)  # type: ignore [return-value]\n            new_cls._factory_kwargs = kwargs  # type: ignore [attr-defined]\n            return new_cls  # type: ignore [return-value]\n\n        # Inherit factory kwargs from base classes (e.g., parallel=True from State.Parallel)\n        inherited_kwargs: dict = {}\n        for base in bases:\n            inherited_kwargs.update(getattr(base, \"_factory_kwargs\", {}))\n        inherited_kwargs.update(kwargs)\n\n        states = []\n        history = []\n        callbacks = {}\n        for key, value in attrs.items():\n            if isinstance(value, HistoryState):\n                value._set_id(key)\n                history.append(value)\n            elif isinstance(value, State):\n                value._set_id(key)\n                states.append(value)\n            elif isinstance(value, TransitionList):\n                value.add_event(_expand_event_id(key))\n            elif callable(value):\n                callbacks[key] = value\n\n        return State(\n            name=name, states=states, history=history, _callbacks=callbacks, **inherited_kwargs\n        )\n\n    @classmethod\n    def to(cls, *args: \"State | NestedStateFactory\", **kwargs) -> \"_ToState\":  # pragma: no cover\n        \"\"\"Create transitions to the given target states.\n        .. note: This method is only a type hint for mypy.\n            The actual implementation belongs to the :ref:`State` class.\n        \"\"\"\n        return _ToState(State())\n\n    @classmethod\n    def from_(  # pragma: no cover\n        cls, *args: \"State | NestedStateFactory\", **kwargs\n    ) -> \"_FromState\":\n        \"\"\"Create transitions from the given target states (reversed).\n        .. note: This method is only a type hint for mypy.\n            The actual implementation belongs to the :ref:`State` class.\n        \"\"\"\n        return _FromState(State())\n\n\nclass State:\n    \"\"\"\n    A State in a :ref:`StateMachine` describes a particular behavior of the machine.\n    When we say that a machine is “in” a state, it means that the machine behaves\n    in the way that state describes.\n\n    Args:\n        name: A human-readable representation of the state. Default is derived\n            from the name of the variable assigned to the state machine class,\n            by replacing ``_`` and ``.`` with spaces and capitalizing the first word.\n\n        value: A specific value to the storage and retrieval of states.\n            If specified, you can use It to map a more friendly representation to a low-level\n            value.\n        initial: Set ``True`` if the ``State`` is the initial one. There must be one and only\n            one initial state in a statemachine. Defaults to ``False``.\n            If not specified, the default initial state is the first child state in document order.\n        final: Set ``True`` if represents a final state. A machine can have\n            optionally many final states. Final states have no :ref:`transition` starting from It.\n            Defaults to ``False``.\n        enter: One or more callbacks assigned to be executed when the state is entered.\n            See :ref:`actions`.\n        exit: One or more callbacks assigned to be executed when the state is exited.\n            See :ref:`actions`.\n\n    State is a core component on how this library implements an expressive API to declare\n    StateMachines.\n\n    >>> from statemachine import State\n\n    Given a few states...\n\n    >>> draft = State(name=\"Draft\", initial=True)\n\n    >>> producing = State(\"Producing\")\n\n    >>> closed = State('Closed', final=True)\n\n    Transitions are declared using the :func:`State.to` or :func:`State.from_` (reversed) methods.\n\n    >>> draft.to(producing)\n    TransitionList([Transition('Draft', 'Producing', event=[], internal=False, initial=False)])\n\n    The result is a :ref:`TransitionList`.\n    Don't worry about this internal class.\n    But the good thing is that it implements the ``OR`` operator to combine transitions,\n    so you can use the ``|`` syntax to compound a list of transitions and assign\n    to the same event.\n\n    You can declare all transitions for a state in one single line ...\n\n    >>> transitions = draft.to(draft) | producing.to(closed)\n\n    ... and you can append additional transitions for a state to previous definitions.\n\n    >>> transitions |= closed.to(draft)\n\n    >>> [(t.source.name, t.target.name) for t in transitions]\n    [('Draft', 'Draft'), ('Producing', 'Closed'), ('Closed', 'Draft')]\n\n    There are handy shortcuts that you can use to express this same set of transitions.\n\n    The first one, ``draft.to(draft)``, is also called a :ref:`self-transition`, and can be\n    expressed using an alternative syntax:\n\n    >>> draft.to.itself()\n    TransitionList([Transition('Draft', 'Draft', event=[], internal=False, initial=False)])\n\n    You can even pass a list of target states to declare at once all transitions starting\n    from the same state.\n\n    >>> transitions = draft.to(draft, producing, closed)\n\n    >>> [(t.source.name, t.target.name) for t in transitions]\n    [('Draft', 'Draft'), ('Draft', 'Producing'), ('Draft', 'Closed')]\n\n    Sometimes it's easier to use the :func:`State.from_` method:\n\n    >>> transitions = closed.from_(draft, producing, closed)\n\n    >>> [(t.source.name, t.target.name) for t in transitions]\n    [('Draft', 'Closed'), ('Producing', 'Closed'), ('Closed', 'Closed')]\n\n    \"\"\"\n\n    class Compound(metaclass=NestedStateFactory):\n        \"Uses the class namespace to build a :ref:`State` instance of a compound state\"\n\n    class Parallel(metaclass=NestedStateFactory, parallel=True):\n        \"Uses the class namespace to build a :ref:`State` instance of a parallel state\"\n\n    def __init__(\n        self,\n        name: str = \"\",\n        value: Any = None,\n        initial: bool = False,\n        final: bool = False,\n        parallel: bool = False,\n        states: \"List[State] | None\" = None,\n        history: \"List[HistoryState] | None\" = None,\n        enter: Any = None,\n        exit: Any = None,\n        invoke: Any = None,\n        donedata: Any = None,\n        _callbacks: Any = None,\n    ):\n        self.name = name\n        self.value = value\n        self._parallel = parallel\n        self.states = states or []\n        self.history = history or []\n        self.is_atomic = bool(not self.states)\n        self._initial = initial\n        self._final = final\n        self.is_active = False\n        self._id: str = \"\"\n        self._callbacks = _callbacks\n        self.parent: \"State | None\" = None\n        self.transitions = TransitionList()\n        self._specs = CallbackSpecList()\n        self.enter = self._specs.grouper(CallbackGroup.ENTER).add(\n            enter, priority=CallbackPriority.INLINE\n        )\n        self.exit = self._specs.grouper(CallbackGroup.EXIT).add(\n            exit, priority=CallbackPriority.INLINE\n        )\n        self.invoke = self._specs.grouper(CallbackGroup.INVOKE).add(\n            normalize_invoke_callbacks(invoke), priority=CallbackPriority.INLINE\n        )\n        if donedata is not None:\n            if not final:\n                raise InvalidDefinition(_(\"'donedata' can only be specified on final states.\"))\n            self.enter.add(donedata, priority=CallbackPriority.INLINE)\n        self.document_order = 0\n        self._hash = id(self)\n        self._init_states()\n\n    def _init_states(self):\n        for state in self.states:\n            state.parent = self\n            state._initial = state.initial or self.parallel\n            setattr(self, state.id, state)\n\n        for history in self.history:\n            history.parent = self\n            setattr(self, history.id, history)\n\n    def __eq__(self, other):\n        return (\n            isinstance(other, State)\n            and self.name == other.name\n            and self.id == other.id\n            or (self.value == other)\n        )\n\n    def __hash__(self):\n        return self._hash\n\n    def _setup(self):\n        self.enter.add(\"on_enter_state\", priority=CallbackPriority.GENERIC, is_convention=True)\n        self.enter.add(f\"on_enter_{self.id}\", priority=CallbackPriority.NAMING, is_convention=True)\n        self.exit.add(\"on_exit_state\", priority=CallbackPriority.GENERIC, is_convention=True)\n        self.exit.add(f\"on_exit_{self.id}\", priority=CallbackPriority.NAMING, is_convention=True)\n        self.invoke.add(\"on_invoke_state\", priority=CallbackPriority.GENERIC, is_convention=True)\n        self.invoke.add(\n            f\"on_invoke_{self.id}\", priority=CallbackPriority.NAMING, is_convention=True\n        )\n\n    def _on_event_defined(self, event: str, transition: Transition, states: List[\"State\"]):\n        \"\"\"Called by statemachine factory when an event is defined having a transition\n        starting from this state.\n        \"\"\"\n        pass\n\n    def __repr__(self):\n        return (\n            f\"{type(self).__name__}({self.name!r}, id={self.id!r}, value={self.value!r}, \"\n            f\"initial={self.initial!r}, final={self.final!r}, parallel={self.parallel!r})\"\n        )\n\n    def __str__(self):\n        return self.name\n\n    @property\n    def id(self) -> str:\n        return self._id\n\n    def _set_id(self, id: str) -> \"State\":\n        self._id = id\n        if self.value is None:\n            self.value = id\n        if not self.name:\n            self.name = humanize_id(self._id)\n        self._hash = hash((self.name, self._id))\n\n        return self\n\n    @property\n    def to(self) -> _ToState:\n        \"\"\"Create transitions to the given target states.\"\"\"\n        return _ToState(self)\n\n    @property\n    def from_(self) -> _FromState:\n        \"\"\"Create transitions from the given target states (reversed).\"\"\"\n        return _FromState(self)\n\n    @property\n    def initial(self):\n        return self._initial\n\n    @property\n    def final(self):\n        return self._final\n\n    @property\n    def parallel(self):\n        return self._parallel\n\n    @property\n    def is_compound(self):\n        return bool(self.states) and not self.parallel\n\n    @property\n    def is_history(self):\n        return isinstance(self, HistoryState)\n\n    def ancestors(self, parent: \"State | None\" = None) -> Generator[\"State\", None, None]:  # noqa: UP043\n        selected = self.parent\n        while selected:\n            if parent and selected == parent:\n                break\n            yield selected\n            selected = selected.parent\n\n    def is_descendant(self, state: \"State\") -> bool:\n        return state in self.ancestors()\n\n\nclass InstanceState(State):\n    \"\"\"Per-instance proxy for a State, delegating attribute access to the underlying State.\n\n    Uses ``__getattr__`` for automatic delegation of instance attributes (name, value,\n    transitions, etc.) and explicit property overrides for attributes that access private\n    fields or have custom logic (id, initial, final, parallel, is_active).\n    \"\"\"\n\n    def __init__(\n        self,\n        state: State,\n        machine: \"StateChart\",\n    ):\n        self._state = state\n        self._machine = ref(machine)\n        self._hash = hash(state)\n        self._init_states()\n\n    def __getattr__(self, name: str):\n        value = getattr(self._state, name)\n        self.__dict__[name] = value\n        return value\n\n    def __eq__(self, other):\n        return self._state == other\n\n    def __hash__(self):\n        return self._hash\n\n    def __repr__(self):\n        return repr(self._state)\n\n    @property\n    def id(self) -> str:\n        return self._state._id\n\n    @property\n    def initial(self):\n        return self._state._initial\n\n    @property\n    def final(self):\n        return self._state._final\n\n    @property\n    def parallel(self):\n        return self._state._parallel\n\n    @property\n    def is_active(self):\n        machine = self._machine()\n        assert machine is not None\n        return self.value in machine.configuration_values\n\n\nclass AnyState(State):\n    \"\"\"A special state that works as a \"ANY\" placeholder.\n\n    It is used as the \"From\" state of a transtion,\n    until the state machine class is evaluated.\n    \"\"\"\n\n    def _on_event_defined(self, event: str, transition: Transition, states: List[State]):\n        for state in states:\n            if state.final:\n                continue\n            new_transition = transition._copy_with_args(source=state, event=event)\n\n            state.transitions.add_transitions(new_transition)\n\n\nclass HistoryType(str, Enum):\n    \"\"\"Type of history recorded by a :class:`HistoryState`.\"\"\"\n\n    SHALLOW = \"shallow\"\n    \"\"\"Remembers only the direct children of the compound state.\n    If the remembered child is itself a compound, it re-enters from its initial state.\"\"\"\n\n    DEEP = \"deep\"\n    \"\"\"Remembers the exact leaf (atomic) state across the entire nested hierarchy.\n    Re-entering restores the full ancestor chain down to that leaf.\"\"\"\n\n    @property\n    def is_deep(self) -> bool:\n        return self == HistoryType.DEEP\n\n\nclass HistoryState(State):\n    def __init__(\n        self, name: str = \"\", value: Any = None, type: \"str | HistoryType\" = HistoryType.SHALLOW\n    ):\n        super().__init__(name=name, value=value)\n        self.type = HistoryType(type)\n        self.is_active = False\n"
  },
  {
    "path": "statemachine/statemachine.py",
    "content": "import warnings\nfrom inspect import isawaitable\nfrom typing import TYPE_CHECKING\nfrom typing import Any\nfrom typing import Dict\nfrom typing import Generic\nfrom typing import List\nfrom typing import MutableSet\nfrom typing import TypeVar\n\nfrom statemachine.orderedset import OrderedSet\n\nfrom .callbacks import SPECS_ALL\nfrom .callbacks import SPECS_SAFE\nfrom .callbacks import CallbackSpecList\nfrom .callbacks import CallbacksRegistry\nfrom .callbacks import SpecListGrouper\nfrom .callbacks import SpecReference\nfrom .configuration import Configuration\nfrom .dispatcher import Listener\nfrom .dispatcher import Listeners\nfrom .engines.async_ import AsyncEngine\nfrom .engines.sync import SyncEngine\nfrom .event import BoundEvent\nfrom .event_data import TriggerData\nfrom .exceptions import InvalidDefinition\nfrom .exceptions import InvalidStateValue\nfrom .exceptions import StateMachineError\nfrom .exceptions import TransitionNotAllowed\nfrom .factory import StateMachineMetaclass\nfrom .graph import iterate_states_and_transitions\nfrom .i18n import _\nfrom .model import Model\nfrom .signature import SignatureAdapter\nfrom .state import InstanceState\nfrom .utils import run_async_from_sync\n\nif TYPE_CHECKING:\n    from .event import Event\n    from .state import State\n    from .states import States\n\nTModel = TypeVar(\"TModel\")\n\n\nclass StateChart(Generic[TModel], metaclass=StateMachineMetaclass):\n    \"\"\"\n\n    Args:\n        model: An optional external object to store state. See :ref:`domain models`.\n\n        state_field (str): The model's field which stores the current state.\n            Default: ``state``.\n\n        start_value: An optional start state value if there's no current state assigned\n            on the :ref:`domain models`. Default: ``None``.\n\n        listeners: An optional list of objects that provies attributes to be used as callbacks.\n            See :ref:`listeners` for more details.\n\n    \"\"\"\n\n    TransitionNotAllowed = TransitionNotAllowed\n    \"\"\"Shortcut alias for easy exception handling.\n\n    Example::\n\n        try:\n            sm.send(\"an-inexistent-event\")\n        except sm.TransitionNotAllowed:\n            pass\n    \"\"\"\n\n    _loop_sleep_in_ms = 0.001\n\n    allow_event_without_transition: bool = True\n    \"\"\"If ``False`` when an event does not result in a transition, an exception\n    ``TransitionNotAllowed`` will be raised. If ``True`` the state machine allows triggering\n    events that may not lead to a state :ref:`transition`, including tolerance to unknown\n    :ref:`event` triggers. Default: ``True``.\"\"\"\n\n    enable_self_transition_entries: bool = True\n    \"\"\"If `False` (default), when a self-transition is selected,\n    the state entry/exit actions will not be executed. If `True`, the state entry actions\n    will be executed, which is conformant with the SCXML spec.\n    \"\"\"\n\n    atomic_configuration_update: bool = False\n    \"\"\"If `False` (default), the state machine will follow the SCXML\n    specification, that means in a microstep, it will first exit and execute exit callbacks\n    for all the states in the exit set in reversed document order, then execute the\n    transition content (on callbaks), then enter all the states in the enter set in\n    document order.\n\n    If `True`, the state machine will execute the exit callbacks, the on transition\n    callbacks, then atomically update the configuration of exited and entered states, then\n    execute the enter callbacks.\n    \"\"\"\n\n    catch_errors_as_events: bool = True\n    \"\"\"If ``True`` (default), runtime exceptions in callbacks (guards, actions, entry/exit)\n    produce an ``error.execution`` internal event instead of propagating, as mandated by the\n    SCXML specification. If ``False``, exceptions propagate normally.\"\"\"\n\n    start_configuration_values: List[Any] = []\n    \"\"\"Default state values to be entered when the state machine starts.\n\n    If empty (default), the root ``initial`` state will be used.\n    \"\"\"\n\n    # -- Attributes set by StateMachineMetaclass during class construction --\n\n    name: str\n    \"\"\"The class name of the state machine (e.g. ``\"TrafficLightMachine\"``).\"\"\"\n\n    id: str\n    \"\"\"Lowercase version of :attr:`name` (e.g. ``\"trafficlightmachine\"``).\"\"\"\n\n    states: \"States\"\n    \"\"\"Collection of top-level :ref:`State` objects declared on this class.\"\"\"\n\n    states_map: Dict[Any, \"State\"]\n    \"\"\"Mapping from each state's ``value`` to the corresponding :ref:`State` instance.\n    Includes states at all nesting levels (compound children, parallel regions, etc.).\"\"\"\n\n    initial_state: \"State | None\"\n    \"\"\"The single top-level initial :ref:`State`, or ``None`` for abstract classes.\"\"\"\n\n    final_states: \"List[State]\"\n    \"\"\"List of top-level :ref:`State` objects marked as ``final``.\"\"\"\n\n    _abstract: bool\n    _events: \"Dict[Event, None]\"\n    _protected_attrs: set\n    _specs: CallbackSpecList\n    _class_listeners: List[Any]\n    prepare: SpecListGrouper\n\n    def __init__(\n        self,\n        model: \"TModel | None\" = None,\n        state_field: str = \"state\",\n        start_value: Any = None,\n        listeners: \"List[object] | None\" = None,\n        **kwargs: Any,\n    ):\n        self.model: TModel = model if model is not None else Model()  # type: ignore[assignment]\n        self.history_values: Dict[\n            str, List[State]\n        ] = {}  # Mapping of compound states to last active state(s).\n        self.state_field = state_field\n        self.start_configuration_values = (\n            [start_value] if start_value is not None else list(self.start_configuration_values)\n        )\n        self._callbacks = CallbacksRegistry()\n        self._config = self._build_configuration()\n        self._listeners: Dict[int, Any] = {}\n        \"\"\"Listeners that provides attributes to be used as callbacks.\"\"\"\n\n        if self._abstract:\n            raise InvalidDefinition(_(\"There are no states or transitions.\"))\n\n        class_listener_instances = self._resolve_class_listeners(**kwargs)\n        all_listeners = class_listener_instances + (listeners or [])\n        self._register_callbacks(all_listeners)\n\n        # Activate the initial state, this only works if the outer scope is sync code.\n        # for async code, the user should manually call `await sm.activate_initial_state()`\n        # after state machine creation.\n        self._engine = self._get_engine()\n        self._engine.start(**kwargs)\n\n    def _get_engine(self):\n        if self._callbacks.has_async_callbacks:\n            return AsyncEngine(self)\n\n        return SyncEngine(self)\n\n    def _resolve_class_listeners(self, **kwargs: Any) -> List[object]:\n        resolved: List[object] = []\n        for entry in self._class_listeners:\n            if callable(entry):\n                instance = entry()\n                setup = getattr(instance, \"setup\", None)\n                if setup is not None:\n                    sig = SignatureAdapter.from_callable(setup)\n                    ba = sig.bind_expected(self, **kwargs)\n                    try:\n                        setup(*ba.args, **ba.kwargs)\n                    except TypeError as err:\n                        raise TypeError(\n                            f\"Error calling setup() on listener {type(instance).__name__}: {err}\"\n                        ) from err\n            else:\n                instance = entry\n            resolved.append(instance)\n        return resolved\n\n    def _build_configuration(self) -> Configuration:\n        \"\"\"Create InstanceState entries and return a new Configuration.\"\"\"\n        instance_states: Dict[str, Any] = {}\n        events = self.__class__._events\n        for state in self.states_map.values():\n            ist = InstanceState(state, self)\n            instance_states[state.id] = ist\n            if state.id not in events:\n                vars(self)[state.id] = ist\n        return Configuration(\n            instance_states=instance_states,\n            model=self.model,\n            state_field=self.state_field,\n            states_map=self.states_map,\n        )\n\n    def activate_initial_state(self) -> Any:\n        result = self._engine.activate_initial_state()\n        if not isawaitable(result):\n            return result\n        return run_async_from_sync(result)\n\n    def _processing_loop(self, caller_future: \"Any | None\" = None) -> Any:\n        result = self._engine.processing_loop(caller_future)\n        if not isawaitable(result):\n            return result\n        return run_async_from_sync(result)\n\n    def __setattr__(self, name, value):\n        # Fast path: internal/private attributes are never state IDs.\n        if not name.startswith(\"_\") and name in self.__class__.states_map:\n            raise StateMachineError(\n                _(\"State overriding is not allowed. Trying to add '{}' to {}\").format(value, name)\n            )\n        super().__setattr__(name, value)\n\n    def __repr__(self):\n        configuration_ids = [s.id for s in self.configuration]\n        return (\n            f\"{type(self).__name__}(model={self.model!r}, state_field={self.state_field!r}, \"\n            f\"configuration={configuration_ids!r})\"\n        )\n\n    def __format__(self, fmt: str) -> str:\n        from .contrib.diagram.formatter import formatter\n\n        return formatter.render(self, fmt)\n\n    def __getstate__(self):\n        state = {k: v for k, v in self.__dict__.items() if not isinstance(v, InstanceState)}\n        del state[\"_callbacks\"]\n        del state[\"_config\"]\n        del state[\"_engine\"]\n        return state\n\n    def __setstate__(self, state: Dict[str, Any]) -> None:\n        listeners = state.pop(\"_listeners\")\n        self.__dict__.update(state)  # type: ignore[attr-defined]\n        self._callbacks = CallbacksRegistry()\n        self._config = self._build_configuration()\n        self._listeners = {}\n\n        # _listeners already contained both class-level and runtime listeners\n        # when serialized, so just re-register them all.\n        self._register_callbacks([])\n        if listeners:\n            self.add_listener(*listeners.values())\n        self._engine = self._get_engine()\n        self._engine.start()\n\n    def _get_initial_configuration(self):\n        initial_state_values = (\n            self.start_configuration_values\n            if self.start_configuration_values\n            else [self.initial_state.value]  # type: ignore[union-attr]\n        )\n        try:\n            return [self.states_map[value] for value in initial_state_values]\n        except KeyError as err:\n            raise InvalidStateValue(initial_state_values) from err\n\n    def bind_events_to(self, *targets):\n        \"\"\"Bind the state machine events to the target objects.\"\"\"\n\n        for event in self.events:\n            trigger = getattr(self, event)\n            for target in targets:\n                if hasattr(target, event):\n                    warnings.warn(\n                        f\"Attribute '{event}' already exists on {target!r}. Skipping binding.\",\n                        UserWarning,\n                        stacklevel=2,\n                    )\n                    continue\n                setattr(target, event, trigger)\n\n    def _add_listener(self, listeners: \"Listeners\", allowed_references: SpecReference = SPECS_ALL):\n        registry = self._callbacks\n        listeners.resolve(self._specs, registry=registry, allowed_references=allowed_references)\n        for visited in iterate_states_and_transitions(self.states):\n            listeners.resolve(\n                visited._specs,\n                registry=registry,\n                allowed_references=allowed_references,\n            )\n\n        return self\n\n    def _register_callbacks(self, listeners: List[object]):\n        self._listeners.update({id(listener): listener for listener in listeners})\n        self._add_listener(\n            Listeners.from_listeners(\n                (\n                    Listener.from_obj(self, skip_attrs=self._protected_attrs),\n                    Listener.from_obj(self.model, skip_attrs={self.state_field}),\n                    *(Listener.from_obj(listener) for listener in listeners),\n                )\n            )\n        )\n\n        check_callbacks = self._callbacks.check\n        for visited in iterate_states_and_transitions(self.states):\n            try:\n                check_callbacks(visited._specs)\n            except Exception as err:\n                raise InvalidDefinition(\n                    f\"Error on {visited!s} when resolving callbacks: {err}\"\n                ) from err\n\n        self._callbacks.async_or_sync()\n\n    @property\n    def active_listeners(self) -> List[object]:\n        \"\"\"List of all active listeners attached to this instance.\n\n        Includes class-level listeners (resolved from the ``listeners`` class attribute),\n        constructor ``listeners=`` parameter, and any added via :meth:`add_listener`.\n        \"\"\"\n        return list(self._listeners.values())\n\n    def add_listener(self, *listeners):\n        \"\"\"Add a listener.\n\n        Listener are a way to generically add behavior to a :ref:`StateMachine` without changing\n        its internal implementation.\n\n        .. seealso::\n\n            :ref:`listeners`.\n        \"\"\"\n        self._listeners.update({id(listener): listener for listener in listeners})\n        return self._add_listener(\n            Listeners.from_listeners(Listener.from_obj(listener) for listener in listeners),\n            allowed_references=SPECS_SAFE,\n        )\n\n    def _repr_html_(self):\n        return f'<div class=\"statemachine\">{self._repr_svg_()}</div>'\n\n    def _repr_svg_(self):\n        return self._graph().create_svg().decode()  # type: ignore[attr-defined]\n\n    def _graph(self):\n        from .contrib.diagram import DotGraphMachine\n\n        return DotGraphMachine(self).get_graph()\n\n    @property\n    def configuration_values(self) -> OrderedSet[Any]:\n        \"\"\"The state configuration values is the set of currently active states's values\n        (or ids if no custom value is defined).\"\"\"\n        return self._config.values\n\n    @property\n    def configuration(self) -> OrderedSet[\"State\"]:\n        \"\"\"The set of currently active states.\"\"\"\n        return self._config.states\n\n    @configuration.setter\n    def configuration(self, new_configuration: OrderedSet[\"State\"]):\n        self._config.states = new_configuration\n\n    @property\n    def current_state_value(self):\n        \"\"\"Get/Set the current :ref:`state` value.\n\n        This is a low level API, that can be used to assign any valid state value\n        completely bypassing all the hooks and validations.\n        \"\"\"\n        return self._config.value\n\n    @current_state_value.setter\n    def current_state_value(self, value):\n        self._config.value = value\n\n    @property\n    def current_state(self) -> \"State | MutableSet[State]\":\n        \"\"\"Get/Set the current :ref:`state`.\n\n        This is a low level API, that can be to assign any valid state\n        completely bypassing all the hooks and validations.\n        \"\"\"\n        warnings.warn(\n            \"\"\"Property `current_state` is deprecated in favor of `configuration`.\"\"\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        return self._config.current_state\n\n    @current_state.setter\n    def current_state(self, value):  # pragma: no cover\n        self.current_state_value = value.value\n\n    @property\n    def events(self) -> \"List[Event]\":\n        return [getattr(self, event) for event in self.__class__._events]\n\n    @property\n    def allowed_events(self) -> \"List[Event]\":\n        \"\"\"List of the current allowed events.\"\"\"\n        return [\n            getattr(self, event)\n            for state in self.configuration\n            for event in state.transitions.unique_events\n        ]\n\n    def enabled_events(self, *args, **kwargs) -> Any:\n        \"\"\"List of the current enabled events, considering guard conditions.\n\n        An event is **enabled** if at least one of its transitions from the current\n        state has all ``cond``/``unless`` guards satisfied.\n\n        Args:\n            *args: Positional arguments forwarded to condition callbacks.\n            **kwargs: Keyword arguments forwarded to condition callbacks.\n\n        Returns:\n            A list of enabled :ref:`Event` instances.\n        \"\"\"\n        result = self._engine.enabled_events(*args, **kwargs)\n        if not isawaitable(result):\n            return result\n        return run_async_from_sync(result)\n\n    def _put_nonblocking(self, trigger_data: TriggerData, internal: bool = False):\n        \"\"\"Put the trigger on the queue without blocking the caller.\"\"\"\n        self._engine.put(trigger_data, internal=internal)\n\n    def send(\n        self,\n        event: str,\n        *args,\n        delay: float = 0,\n        send_id: \"str | None\" = None,\n        internal: bool = False,\n        **kwargs,\n    ) -> Any:\n        \"\"\"Send an :ref:`Event` to the state machine.\n\n        :param event: The trigger for the state machine, specified as an event id string.\n        :param args: Additional positional arguments to pass to the event.\n        :param delay: A time delay in milliseconds to process the event. Default is 0.\n        :param send_id: An identifier for the event, used with ``cancel_event()`` to cancel\n            delayed events.\n        :param kwargs: Additional keyword arguments to pass to the event.\n\n        .. seealso::\n\n            See: :ref:`triggering events`.\n        \"\"\"\n        know_event = getattr(self, event, None)\n        event_name = know_event.name if know_event else event\n        delay = (\n            delay if delay else know_event and know_event.delay or 0\n        )  # first the param, then the event, or 0\n        event_instance = BoundEvent(\n            id=event, name=event_name, delay=delay, internal=internal, _sm=self\n        )\n        result = event_instance(*args, send_id=send_id, **kwargs)\n        if not isawaitable(result):\n            return result\n        return run_async_from_sync(result)\n\n    def raise_(\n        self, event: str, *args, delay: float = 0, send_id: \"str | None\" = None, **kwargs\n    ) -> Any:\n        \"\"\"Send an :ref:`Event` to the state machine in the internal event queue.\n\n        Events on the internal queue are processed immediately within the current\n        macrostep, before any pending external events. This is equivalent to calling\n        ``send(..., internal=True)``.\n\n        .. seealso::\n\n            See: :ref:`triggering-events`.\n        \"\"\"\n        return self.send(event, *args, delay=delay, send_id=send_id, internal=True, **kwargs)\n\n    def cancel_event(self, send_id: str):\n        \"\"\"Cancel all the delayed events with the given ``send_id``.\"\"\"\n        self._engine.cancel_event(send_id)\n\n    @property\n    def is_terminated(self):\n        \"\"\"Whether the state machine has reached a final state.\n\n        Returns ``True`` when a top-level final state has been entered and the\n        engine is no longer running.  This is the recommended way to check for\n        completion -- it works for flat, compound, and parallel topologies.\n        \"\"\"\n        return not self._engine.running\n\n\nclass StateMachine(StateChart):\n    allow_event_without_transition: bool = False\n    enable_self_transition_entries: bool = False\n    atomic_configuration_update: bool = True\n    catch_errors_as_events: bool = False\n"
  },
  {
    "path": "statemachine/states.py",
    "content": "from enum import Enum\nfrom typing import Dict  # deprecated since 3.9: https://peps.python.org/pep-0585/\nfrom typing import Type\n\nfrom .state import State\nfrom .utils import ensure_iterable\n\nEnumType = Type[Enum]\n\n\nclass States:\n    \"\"\"\n    A class representing a collection of :ref:`State` objects.\n\n    Helps creating :ref:`StateChart`'s :ref:`state` definitions from other\n    sources, like an ``Enum`` class, using :meth:`States.from_enum`.\n\n    >>> states_def = [('open', {'initial': True}), ('closed', {'final': True})]\n\n    >>> from statemachine import StateChart\n    >>> class SM(StateChart):\n    ...\n    ...     states = States({\n    ...         name: State(**params) for name, params in states_def\n    ...     })\n    ...\n    ...     close = states.open.to(states.closed)\n\n    And states can be used as usual.\n\n    >>> sm = SM()\n    >>> sm.send(\"close\")\n    >>> sm.closed.is_active\n    True\n\n    \"\"\"\n\n    def __init__(self, states: \"Dict[str, State] | None\" = None) -> None:\n        \"\"\"\n        Initializes a new instance of the States class.\n\n        Args:\n            states: A dictionary mapping keys as ``State.id`` and values :ref:`state` instances.\n\n        Returns:\n            None.\n        \"\"\"\n        self._states: Dict[str, State] = states if states is not None else {}\n\n    def __repr__(self):\n        return f\"{list(self)}\"\n\n    def __eq__(self, other):\n        return list(self) == list(other)\n\n    def __getattr__(self, name: str) -> \"State\":\n        if name in self._states:\n            return self._states[name]\n        raise AttributeError(f\"{name} not found in {self.__class__.__name__}\")\n\n    def __len__(self):\n        return len(self._states)\n\n    def __getitem__(self, index):\n        return list(self)[index]\n\n    def __iter__(self):\n        return iter(self._states.values())\n\n    def append(self, state):\n        self._states[state.id] = state\n\n    def items(self):\n        \"\"\"\n        Returns a view object of the states, with pairs of ``(State.id, State)``.\n\n        Args:\n            None.\n\n        Returns:\n            A view object of the items in the the instance.\n        \"\"\"\n        return self._states.items()\n\n    @classmethod\n    def from_enum(cls, enum_type: EnumType, initial, final=None, use_enum_instance: bool = True):\n        \"\"\"\n        Creates a new instance of the ``States`` class from an enumeration.\n\n        Consider an ``Enum`` type that declares our expected states:\n\n        >>> class Status(Enum):\n        ...     pending = 1\n        ...     completed = 2\n\n        A :ref:`StateChart` that uses this enum can be declared as follows:\n\n        >>> from statemachine import StateChart\n        >>> class ApprovalMachine(StateChart):\n        ...\n        ...     _ = States.from_enum(Status, initial=Status.pending, final=Status.completed)\n        ...\n        ...     finish = _.pending.to(_.completed)\n        ...\n        ...     def on_enter_completed(self):\n        ...         print(\"Completed!\")\n\n        .. tip::\n            When you assign the result of ``States.from_enum`` to a class-level variable in your\n            :ref:`StateChart`, you're all set. You can use any name for this variable. In this\n            example, we used ``_`` to show that the name doesn't matter. The metaclass will inspect\n            the variable of type :ref:`States (class)` and automatically assign the inner\n            :ref:`State` instances to the state machine.\n\n\n        Everything else is similar, the ``Enum`` is only used to declare the :ref:`State`\n        instances.\n\n        >>> sm = ApprovalMachine()\n\n        >>> sm.pending.is_active\n        True\n\n        >>> sm.send(\"finish\")\n        Completed!\n\n        >>> sm.completed.is_active\n        True\n\n        >>> sm.current_state_value\n        <Status.completed: 2>\n\n        If you need to use the raw enum value instead of the enum instance, you can set\n        ``use_enum_instance=False``:\n\n        >>> states = States.from_enum(Status, initial=Status.pending, use_enum_instance=False)\n        >>> states.completed.value\n        2\n\n        .. versionchanged:: 3.0.0\n\n            The default changed from ``False`` to ``True``.\n\n        Args:\n            enum_type: An enumeration containing the states of the machine.\n            initial: The initial state of the machine.\n            final: A set of final states of the machine.\n            use_enum_instance: If ``True``, the value of the state will be the enum item instance,\n                otherwise the enum item value. Defaults to ``True``.\n\n        Returns:\n            A new instance of the :ref:`States (class)`.\n        \"\"\"\n        final_set = set(ensure_iterable(final))\n        return cls(\n            {\n                e.name: State(\n                    value=(e if use_enum_instance else e.value),\n                    initial=e is initial,\n                    final=e in final_set,\n                )\n                for e in enum_type\n            }\n        )\n"
  },
  {
    "path": "statemachine/transition.py",
    "content": "from copy import deepcopy\nfrom typing import TYPE_CHECKING\nfrom typing import List\n\nfrom .callbacks import CallbackGroup\nfrom .callbacks import CallbackPriority\nfrom .callbacks import CallbackSpecList\nfrom .events import Events\nfrom .exceptions import InvalidDefinition\nfrom .i18n import _\n\nif TYPE_CHECKING:\n    from .statemachine import State\n\n\nclass Transition:\n    \"\"\"A transition holds reference to the source and target state.\n\n    Args:\n        source (State): The origin state of the transition.\n        target: The target state(s) of the transition. Can be a single ``State``, a list of\n            states (for multi-target transitions, e.g. SCXML parallel region entry), or ``None``\n            (targetless transition).\n        event (Optional[Union[str, List[str]]]): List of designators of events that trigger this\n            transition. Can be either a list of strings, or a space-separated string list of event\n            descriptors.\n        internal (bool): Is the transition internal or external? Internal transitions\n            don't execute the state entry/exit actions. Default ``False``.\n        validators (Optional[Union[str, Callable, List[Callable]]]): The validation callbacks to\n            be invoked before the transition is executed.\n        cond (Optional[Union[str, Callable, List[Callable]]]): The condition callbacks to be\n            invoked before the transition is executed that should evaluate to `True`.\n        unless (Optional[Union[str, Callable, List[Callable]]]): The condition callbacks to be\n            invoked if the `cond` is False before the transition is executed.\n        on (Optional[Union[str, Callable, List[Callable]]]): The callbacks to be invoked\n            when the transition is executed.\n        before (Optional[Union[str, Callable, List[Callable]]]): The callbacks to be invoked\n            before the transition is executed.\n        after (Optional[Union[str, Callable, List[Callable]]]): The callbacks to be invoked\n            after the transition is executed.\n    \"\"\"\n\n    def __init__(\n        self,\n        source: \"State\",\n        target: \"State | List[State] | None\" = None,\n        event=None,\n        internal=False,\n        initial=False,\n        validators=None,\n        cond=None,\n        unless=None,\n        on=None,\n        before=None,\n        after=None,\n    ):\n        self.source = source\n        if isinstance(target, list):\n            self._targets: \"List[State]\" = target\n        elif target is not None:\n            self._targets = [target]\n        else:\n            self._targets = []\n        self.internal = internal\n        self.initial = initial\n        first_target = self._targets[0] if self._targets else None\n        self.is_self = first_target is source\n        \"\"\"Is the target state the same as the source state?\"\"\"\n\n        if internal and not (\n            self.is_self or (first_target and first_target.is_descendant(source))\n        ):\n            raise InvalidDefinition(\n                _(\n                    \"Not a valid internal transition from source {source!r}, \"\n                    \"target {target!r} should be self or a descendant.\"\n                ).format(source=source, target=first_target)\n            )\n\n        if initial and any([cond, unless, event]):\n            raise InvalidDefinition(\"Initial transitions should not have conditions or events.\")\n\n        self._events = Events().add(event)\n        self._specs = CallbackSpecList()\n        self.validators = self._specs.grouper(CallbackGroup.VALIDATOR).add(\n            validators, priority=CallbackPriority.INLINE\n        )\n        self.before = self._specs.grouper(CallbackGroup.BEFORE).add(\n            before, priority=CallbackPriority.INLINE\n        )\n        self.on = self._specs.grouper(CallbackGroup.ON).add(on, priority=CallbackPriority.INLINE)\n        self.after = self._specs.grouper(CallbackGroup.AFTER).add(\n            after, priority=CallbackPriority.INLINE\n        )\n        self.cond = (\n            self._specs.grouper(CallbackGroup.COND)\n            .add(cond, priority=CallbackPriority.INLINE, expected_value=True)\n            .add(unless, priority=CallbackPriority.INLINE, expected_value=False)\n        )\n\n    @property\n    def target(self) -> \"State | None\":\n        \"\"\"Primary target state (first target for multi-target transitions).\"\"\"\n        return self._targets[0] if self._targets else None\n\n    @property\n    def targets(self) -> \"List[State]\":\n        \"\"\"All target states. For single-target transitions, returns a one-element list.\"\"\"\n        return self._targets\n\n    def __repr__(self):\n        return (\n            f\"{type(self).__name__}({self.source.name!r}, {self.target and self.target.name!r}, \"\n            f\"event={self._events!r}, internal={self.internal!r}, initial={self.initial!r})\"\n        )\n\n    def __str__(self):\n        return f\"transition {self.event!s} from {self.source!s} to {self.target!s}\"\n\n    def _setup(self):\n        before = self.before.add\n        on = self.on.add\n        after = self.after.add\n\n        before(\"before_transition\", priority=CallbackPriority.GENERIC, is_convention=True)\n        on(\"on_transition\", priority=CallbackPriority.GENERIC, is_convention=True)\n\n        for event in self._events:\n            same_event_cond = event.is_same_event\n            before(\n                f\"before_{event}\",\n                priority=CallbackPriority.NAMING,\n                is_convention=True,\n                cond=same_event_cond,\n            )\n            on(\n                f\"on_{event}\",\n                priority=CallbackPriority.NAMING,\n                is_convention=True,\n                cond=same_event_cond,\n            )\n            after(\n                f\"after_{event}\",\n                priority=CallbackPriority.NAMING,\n                is_convention=True,\n                cond=same_event_cond,\n            )\n\n        after(\n            \"after_transition\",\n            priority=CallbackPriority.AFTER,\n            is_convention=True,\n        )\n\n    def match(self, event: str):\n        return self._events.match(event)\n\n    @property\n    def event(self):\n        return str(self._events)\n\n    @property\n    def events(self):\n        return self._events\n\n    def add_event(self, value):\n        self._events.add(value)\n\n    def _copy_with_args(self, **kwargs):\n        source = kwargs.pop(\"source\", self.source)\n        target = kwargs.pop(\"target\", list(self._targets) if self._targets else None)\n        event = kwargs.pop(\"event\", self.event)\n        internal = kwargs.pop(\"internal\", self.internal)\n        new_transition = Transition(\n            source=source, target=target, event=event, internal=internal, **kwargs\n        )\n        for spec in self._specs:\n            new_spec = deepcopy(spec)\n            new_transition._specs.add(new_spec, new_spec.group)\n\n        return new_transition\n\n    @property\n    def is_eventless(self):\n        return self._events.is_empty\n"
  },
  {
    "path": "statemachine/transition_list.py",
    "content": "from typing import TYPE_CHECKING\nfrom typing import Iterable\nfrom typing import List\n\nfrom .callbacks import CallbackGroup\nfrom .transition import Transition\nfrom .transition_mixin import AddCallbacksMixin\nfrom .utils import ensure_iterable\n\nif TYPE_CHECKING:\n    from .events import Event\n    from .state import State\n\n\nclass TransitionList(AddCallbacksMixin):\n    \"\"\"A list-like container of :ref:`transitions` with callback functions.\"\"\"\n\n    def __init__(self, transitions: \"Iterable[Transition] | None\" = None):\n        \"\"\"\n        Args:\n            transitions: An iterable of `Transition` objects.\n                Defaults to `None`.\n\n        \"\"\"\n        self.transitions: List[Transition] = list(transitions) if transitions else []\n\n    def __repr__(self):\n        \"\"\"Return a string representation of the :ref:`TransitionList`.\"\"\"\n        return f\"{type(self).__name__}({self.transitions!r})\"\n\n    def __or__(self, other: \"TransitionList | Iterable\"):\n        \"\"\"Return a new :ref:`TransitionList` that combines the transitions of this\n        :ref:`TransitionList` with another :ref:`TransitionList` or iterable.\n\n        Args:\n            other: Another :ref:`TransitionList` or iterable of :ref:`Transition` objects.\n\n        Returns:\n            TransitionList: A new :ref:`TransitionList` object that combines the\n                transitions of this :ref:`TransitionList` with `other`.\n\n        \"\"\"\n        return TransitionList(self.transitions).add_transitions(other)\n\n    def _on_event_defined(self, event: str, states: List[\"State\"]):\n        self.add_event(event)\n\n        for transition in self.transitions:\n            transition.source._on_event_defined(event=event, transition=transition, states=states)\n\n    def add_transitions(self, transition: \"Transition | TransitionList | Iterable\"):\n        \"\"\"Adds one or more transitions to the :ref:`TransitionList` instance.\n\n        Args:\n            transition: A sequence of transitions or a :ref:`TransitionList` instance.\n\n        Returns:\n            The updated :ref:`TransitionList` instance.\n        \"\"\"\n        if isinstance(transition, TransitionList):\n            transition = transition.transitions\n        transitions = ensure_iterable(transition)\n\n        for transition in transitions:\n            assert isinstance(transition, Transition)  # makes mypy happy\n            self.transitions.append(transition)\n\n        return self\n\n    def __getitem__(self, index: int) -> \"Transition\":\n        \"\"\"Returns the :ref:`transition` at the specified ``index``.\n\n        Args:\n            index: The index of the transition.\n\n        Returns:\n            The :ref:`transition` at the specified index.\n        \"\"\"\n        return self.transitions[index]\n\n    def __len__(self):\n        \"\"\"Returns the number of transitions in the :ref:`TransitionList` instance.\n\n        Returns:\n            The number of transitions.\n        \"\"\"\n        return len(self.transitions)\n\n    def __iter__(self):\n        return iter(self.transitions)\n\n    def _add_callback(self, callback, grouper: CallbackGroup, is_event=False, **kwargs):\n        for transition in self.transitions:\n            list_obj = transition._specs.grouper(grouper)\n            list_obj._add_unbounded_callback(\n                callback,\n                is_event=is_event,\n                transitions=self,\n                **kwargs,\n            )\n        return callback\n\n    def add_event(self, event: str):\n        \"\"\"\n        Adds an event to all transitions in the :ref:`TransitionList` instance.\n\n        Args:\n            event: The name of the event to be added.\n        \"\"\"\n        for transition in self.transitions:\n            transition.add_event(event)\n\n    @property\n    def unique_events(self) -> List[\"Event\"]:\n        \"\"\"\n        Returns a list of unique event names across all transitions in the :ref:`TransitionList`\n        instance.\n\n        Returns:\n            A list of unique event names.\n        \"\"\"\n        tmp_ordered_unique_events_as_keys_on_dict = {}\n        for transition in self.transitions:\n            for event in transition.events:\n                tmp_ordered_unique_events_as_keys_on_dict[event] = True\n\n        return list(tmp_ordered_unique_events_as_keys_on_dict.keys())\n\n    @property\n    def has_eventless_transition(self):\n        return any(transition.is_eventless for transition in self.transitions)\n"
  },
  {
    "path": "statemachine/transition_mixin.py",
    "content": "from typing import Any\nfrom typing import Callable\nfrom typing import TypeVar\n\nfrom .callbacks import CallbackGroup\nfrom .i18n import _\n\nT = TypeVar(\"T\", bound=Callable)\n\n\nclass AddCallbacksMixin:\n    def _add_callback(self, callback: T, grouper: CallbackGroup, is_event=False, **kwargs) -> T:\n        raise NotImplementedError\n\n    def __call__(self, *args, **kwargs) -> Any:\n        if len(args) == 1 and callable(args[0]) and not kwargs:\n            return self._add_callback(args[0], CallbackGroup.ON, is_event=True)\n        raise TypeError(\n            _(\"{} only supports the decorator syntax to register callbacks.\").format(\n                type(self).__name__\n            )\n        )\n\n    def before(self, f: Callable):\n        \"\"\"Adds a ``before`` :ref:`transition actions` callback to every :ref:`transition` in the\n        :ref:`TransitionList` instance.\n\n        Args:\n            f: The ``before`` :ref:`transition actions` callback function to be added.\n\n        Returns:\n            The `f` callable.\n        \"\"\"\n        return self._add_callback(f, CallbackGroup.BEFORE)\n\n    def after(self, f: Callable):\n        \"\"\"Adds a ``after`` :ref:`transition actions` callback to every :ref:`transition` in the\n        :ref:`TransitionList` instance.\n\n        Args:\n            f: The ``after`` :ref:`transition actions` callback function to be added.\n\n        Returns:\n            The `f` callable.\n        \"\"\"\n        return self._add_callback(f, CallbackGroup.AFTER)\n\n    def on(self, f: Callable):\n        \"\"\"Adds a ``on`` :ref:`transition actions` callback to every :ref:`transition` in the\n        :ref:`TransitionList` instance.\n\n        Args:\n            f: The ``on`` :ref:`transition actions` callback function to be added.\n\n        Returns:\n            The `f` callable.\n        \"\"\"\n        return self._add_callback(f, CallbackGroup.ON)\n\n    def cond(self, f: Callable):\n        \"\"\"Adds a ``cond`` :ref:`guards` callback to every :ref:`transition` in the\n        :ref:`TransitionList` instance.\n\n        Args:\n            f: The ``cond`` :ref:`guards` callback function to be added.\n\n        Returns:\n            The `f` callable.\n        \"\"\"\n        return self._add_callback(f, CallbackGroup.COND, expected_value=True)\n\n    def unless(self, f: Callable):\n        \"\"\"Adds a ``unless`` :ref:`guards` callback with expected value ``False`` to every\n        :ref:`transition` in the :ref:`TransitionList` instance.\n\n        Args:\n            f: The ``unless`` :ref:`guards` callback function to be added.\n\n        Returns:\n            The `f` callable.\n        \"\"\"\n        return self._add_callback(f, CallbackGroup.COND, expected_value=False)\n\n    def validators(self, f: Callable):\n        \"\"\"Adds a :ref:`validators` callback to the :ref:`TransitionList` instance.\n\n        Args:\n            f: The ``validators`` callback function to be added.\n        Returns:\n            The callback function.\n\n        \"\"\"\n        return self._add_callback(f, CallbackGroup.VALIDATOR)\n"
  },
  {
    "path": "statemachine/utils.py",
    "content": "import asyncio\nimport re\nimport threading\nfrom typing import Any\n\n_SEPARATOR_RE = re.compile(r\"[_.]\")\n\n_cached_loop = threading.local()\n\"\"\"Loop that will be used when the SM is running in a synchronous context. One loop per thread.\"\"\"\n\n\ndef qualname(cls):\n    \"\"\"\n    Returns a fully qualified name of the class, to avoid name collisions.\n    \"\"\"\n    return \".\".join([cls.__module__, cls.__name__])\n\n\ndef ensure_iterable(obj):\n    \"\"\"\n    Returns an iterator if obj is not an instance of string or if it\n    encounters type error, otherwise it returns a list.\n    \"\"\"\n    if isinstance(obj, str):\n        return [obj]\n    try:\n        return iter(obj)\n    except TypeError:\n        return [obj]\n\n\ndef humanize_id(id: str) -> str:\n    \"\"\"Convert a machine identifier to a human-readable name.\n\n    Splits on ``_`` and ``.`` separators and capitalizes the first word.\n\n    >>> humanize_id(\"go\")\n    'Go'\n    >>> humanize_id(\"done_state_parent\")\n    'Done state parent'\n    >>> humanize_id(\"error.execution\")\n    'Error execution'\n    \"\"\"\n    return _SEPARATOR_RE.sub(\" \", id).strip().capitalize()\n\n\ndef run_async_from_sync(coroutine: \"Any\") -> \"Any\":\n    \"\"\"\n    Compatibility layer to run an async coroutine from a synchronous context.\n    \"\"\"\n    global _cached_loop\n    try:\n        asyncio.get_running_loop()\n        return coroutine\n    except RuntimeError:\n        if not hasattr(_cached_loop, \"loop\"):\n            _cached_loop.loop = asyncio.new_event_loop()\n        return _cached_loop.loop.run_until_complete(coroutine)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/conftest.py",
    "content": "import asyncio\nimport threading\nimport time\nfrom datetime import datetime\n\nimport pytest\n\n\ndef pytest_addoption(parser):\n    parser.addoption(\n        \"--gen-diagram\",\n        action=\"store_true\",\n        default=False,\n        help=\"Generate a diagram of the SCXML machine\",\n    )\n\n\n@pytest.fixture()\ndef current_time():\n    return datetime.now()\n\n\n@pytest.fixture()\ndef campaign_machine():\n    from tests.machines.workflow.campaign_machine import CampaignMachine\n\n    return CampaignMachine\n\n\n@pytest.fixture()\ndef campaign_machine_with_validator():\n    from tests.machines.workflow.campaign_machine_with_validator import (\n        CampaignMachineWithValidator,\n    )\n\n    return CampaignMachineWithValidator\n\n\n@pytest.fixture()\ndef campaign_machine_with_final_state():\n    from tests.machines.workflow.campaign_machine import CampaignMachine\n\n    return CampaignMachine\n\n\n@pytest.fixture()\ndef campaign_machine_with_values():\n    from tests.machines.workflow.campaign_machine_with_values import CampaignMachineWithValues\n\n    return CampaignMachineWithValues\n\n\n@pytest.fixture()\ndef traffic_light_machine():\n    from tests.examples.traffic_light_machine import TrafficLightMachine\n\n    return TrafficLightMachine\n\n\n@pytest.fixture()\ndef OrderControl():\n    from tests.examples.order_control_machine import OrderControl\n\n    return OrderControl\n\n\n@pytest.fixture()\ndef AllActionsMachine():\n    from tests.examples.all_actions_machine import AllActionsMachine\n\n    return AllActionsMachine\n\n\n@pytest.fixture()\ndef classic_traffic_light_machine(engine):\n    from statemachine import State\n    from statemachine import StateChart\n\n    class TrafficLightMachine(StateChart):\n        green = State(initial=True)\n        yellow = State()\n        red = State()\n\n        slowdown = green.to(yellow)\n        stop = yellow.to(red)\n        go = red.to(green)\n\n        def _get_engine(self):\n            return engine(self)\n\n    return TrafficLightMachine\n\n\n@pytest.fixture()\ndef classic_traffic_light_machine_allow_event(classic_traffic_light_machine):\n    \"\"\"Already allow_event_without_transition=True (StateChart default).\"\"\"\n    return classic_traffic_light_machine\n\n\n@pytest.fixture()\ndef reverse_traffic_light_machine():\n    from tests.machines.workflow.reverse_traffic_light import ReverseTrafficLightMachine\n\n    return ReverseTrafficLightMachine\n\n\n@pytest.fixture()\ndef approval_machine(current_time):  # noqa: C901\n    from statemachine import State\n    from statemachine import StateChart\n\n    class ApprovalMachine(StateChart):\n        \"A workflow machine\"\n\n        requested = State(initial=True)\n        accepted = State()\n        rejected = State()\n\n        completed = State(final=True)\n\n        validate = requested.to(accepted, cond=\"is_ok\") | requested.to(rejected)\n\n        @validate\n        def do_validate(self, *args, **kwargs):\n            if self.model.is_ok():\n                self.model.accepted_at = current_time\n                return self.model\n            else:\n                self.model.rejected_at = current_time\n                return self.model\n\n        @accepted.to(completed)\n        def complete(self):\n            self.model.completed_at = current_time\n\n        @requested.to(requested)\n        def update(self, **kwargs):\n            for k, v in kwargs.items():\n                setattr(self.model, k, v)\n            return self.model\n\n        @rejected.to(requested)\n        def retry(self):\n            self.model.rejected_at = None\n            return self.model\n\n    return ApprovalMachine\n\n\n@pytest.fixture(params=[\"sync\", \"async\"])\ndef engine(request):\n    from statemachine.engines.async_ import AsyncEngine\n    from statemachine.engines.sync import SyncEngine\n\n    if request.param == \"sync\":\n        return SyncEngine\n    else:\n        return AsyncEngine\n\n\nclass _AsyncListener:\n    \"\"\"No-op async listener that triggers AsyncEngine selection.\"\"\"\n\n    async def on_enter_state(\n        self, **kwargs\n    ): ...  # No-op: presence of async callback triggers AsyncEngine selection\n\n\nclass SMRunner:\n    \"\"\"Helper for running state machine tests on both sync and async engines.\n\n    Usage in tests::\n\n        async def test_something(self, sm_runner):\n            sm = await sm_runner.start(MyStateChart)\n            await sm_runner.send(sm, \"some_event\")\n            assert \"expected_state\" in sm.configuration_values\n    \"\"\"\n\n    def __init__(self, is_async: bool):\n        self.is_async = is_async\n\n    async def start(self, cls, **kwargs):\n        \"\"\"Create and activate a state machine instance.\"\"\"\n        from inspect import isawaitable\n\n        if self.is_async:\n            listeners = list(kwargs.pop(\"listeners\", []))\n            listeners.append(_AsyncListener())\n            sm = cls(listeners=listeners, **kwargs)\n            result = sm.activate_initial_state()\n            if isawaitable(result):\n                await result\n        else:\n            sm = cls(**kwargs)\n        return sm\n\n    async def send(self, sm, event, **kwargs):\n        \"\"\"Send an event to the state machine.\"\"\"\n        from inspect import isawaitable\n\n        result = sm.send(event, **kwargs)\n        if isawaitable(result):\n            return await result\n        return result\n\n    async def processing_loop(self, sm):\n        \"\"\"Run the processing loop (for delayed event tests).\"\"\"\n        from inspect import isawaitable\n\n        result = sm._processing_loop()\n        if isawaitable(result):\n            return await result\n        return result\n\n    async def sleep(self, seconds: float):\n        \"\"\"Sleep that works for both sync and async engines.\"\"\"\n        if self.is_async:\n            await asyncio.sleep(seconds)\n        else:\n            time.sleep(seconds)\n\n\n@pytest.fixture(params=[\"sync\", \"async\"])\ndef sm_runner(request):\n    \"\"\"Fixture that runs tests on both sync and async engines.\"\"\"\n    return SMRunner(is_async=request.param == \"async\")\n\n\n@pytest.fixture(autouse=True)\ndef _check_leaked_threads():\n    \"\"\"Detect threads leaked by test cases (e.g. invoke daemon threads).\n\n    Snapshots active threads before the test, yields, then checks for any new\n    threads still alive after teardown.  Leaked threads are joined with a\n    timeout and reported as a test failure.\n    \"\"\"\n    before = set(threading.enumerate())\n    yield\n\n    new_threads = set(threading.enumerate()) - before\n    if not new_threads:\n        return\n\n    # Filter out asyncio event loop threads (managed by pytest-asyncio, not by us)\n    # and DummyThreads (created by Python for foreign threads — cannot be joined).\n    new_threads = {\n        t\n        for t in new_threads\n        if not t.name.startswith(\"asyncio_\") and not isinstance(t, threading._DummyThread)\n    }\n    if not new_threads:\n        return\n\n    # Give ephemeral threads (e.g. executor workers) a chance to finish.\n    for t in new_threads:\n        t.join(timeout=2.0)\n\n    leaked = [t for t in new_threads if t.is_alive()]\n    if not leaked:\n        return\n\n    details: list[str] = []\n    for t in leaked:\n        details.append(f\"  - {t.name!r} (daemon={t.daemon}, ident={t.ident})\")\n\n    pytest.fail(\n        f\"Test leaked {len(leaked)} thread(s) still alive after join:\\n\" + \"\\n\".join(details),\n        pytrace=False,\n    )\n"
  },
  {
    "path": "tests/django_project/app.py",
    "content": "from django.http import HttpResponse\nfrom django.urls import re_path\n\n\ndef home(request):\n    return HttpResponse(\"WE LOVE DJANGO\")\n\n\nurlpatterns = [\n    re_path(r\"^$\", home, name=\"homepage\"),\n]\n"
  },
  {
    "path": "tests/django_project/core/__init__,.py",
    "content": ""
  },
  {
    "path": "tests/django_project/core/settings.py",
    "content": "from pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nDEBUG = True\n\nROOT_URLCONF = \"app\"\n\nINSTALLED_APPS = [\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.staticfiles\",\n    \"workflow\",\n]\n\nDATABASES = {\n    \"default\": {\n        \"ENGINE\": \"django.db.backends.sqlite3\",\n        \"NAME\": BASE_DIR / \"db.sqlite3\",\n        \"TEST\": {\n            \"NAME\": \"testdb.sqlite3\",\n        },\n    }\n}\n\nWSGI_APPLICATION = \"core.wsgi.application\"\n"
  },
  {
    "path": "tests/django_project/core/wsgi.py",
    "content": "\"\"\"\nWSGI config for project project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"core.settings\")\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "tests/django_project/manage.py",
    "content": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\n\nimport os\nimport sys\n\n\ndef main():\n    \"\"\"Run administrative tasks.\"\"\"\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"core.settings\")\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tests/django_project/workflow/__init__.py",
    "content": ""
  },
  {
    "path": "tests/django_project/workflow/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass WorfklowConfig(AppConfig):\n    default_auto_field = \"django.db.models.BigAutoField\"\n    name = \"workflow\"\n"
  },
  {
    "path": "tests/django_project/workflow/models.py",
    "content": "from django.contrib.auth import get_user_model\nfrom django.db import models\nfrom statemachine.mixins import MachineMixin\n\nUser = get_user_model()\n\n\nclass WorkflowSteps(models.TextChoices):\n    DRAFT = \"draft\"\n    PUBLISHED = \"published\"\n\n\nclass Workflow(models.Model, MachineMixin):\n    state_machine_name = \"workflow.statemachines.WorfklowStateMachine\"\n    state_machine_attr = \"wf\"\n    bind_events_as_methods = True\n\n    state = models.CharField(\n        max_length=30, choices=WorkflowSteps.choices, default=WorkflowSteps.DRAFT\n    )\n    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)\n    is_active = models.BooleanField(default=False)\n"
  },
  {
    "path": "tests/django_project/workflow/statemachines.py",
    "content": "from statemachine.states import States\n\nfrom statemachine import StateChart\n\nfrom .models import WorkflowSteps\n\n\nclass WorfklowStateMachine(StateChart):\n    allow_event_without_transition = False\n\n    _ = States.from_enum(WorkflowSteps, initial=WorkflowSteps.DRAFT, final=WorkflowSteps.PUBLISHED)\n\n    publish = _.DRAFT.to(_.PUBLISHED, cond=\"is_active\")\n    notify_user = _.DRAFT.to.itself(internal=True, cond=\"has_user\")\n\n    def has_user(self):\n        return bool(self.model.user)\n"
  },
  {
    "path": "tests/django_project/workflow/tests.py",
    "content": "import pytest\nfrom statemachine.exceptions import TransitionNotAllowed\n\nfrom workflow.models import WorkflowSteps\nfrom workflow.statemachines import WorfklowStateMachine\n\npytestmark = [\n    pytest.mark.django_db,\n]\n\n\n@pytest.fixture()\ndef Workflow():\n    from workflow.models import Workflow\n\n    return Workflow\n\n\n@pytest.fixture()\ndef User():\n    from django.contrib.auth import get_user_model\n\n    return get_user_model()\n\n\n@pytest.fixture()\ndef one(Workflow):\n    return Workflow.objects.create()\n\n\nclass TestWorkflow:\n    def test_one(self, one):\n        with pytest.raises(TransitionNotAllowed):\n            one.wf.send(\"publish\")\n\n    def test_two(self, one):\n        # Managing this instance works if I call it like this instead.\n        # So this test works\n        wf = WorfklowStateMachine(one)\n        with pytest.raises(TransitionNotAllowed):\n            wf.send(\"publish\")\n\n    def test_async_with_db_operation(self, one, User, Workflow):\n        \"\"\"Regression test for https://github.com/fgmacedo/python-statemachine/issues/446\"\"\"\n\n        user = User.objects.create_user(\"user\")\n        one.user = user\n        one.save()\n\n        wf = WorfklowStateMachine(one)\n        wf.send(\"notify_user\")\n\n        # And clear model cache, casing user to be loaded later on\n        one = Workflow.objects.get(pk=one.pk)\n\n        wf = WorfklowStateMachine(one)\n        wf.send(\"notify_user\")\n\n    def test_should_publish(self, one):\n        one.is_active = True\n        one.publish()\n        one.save()\n\n        assert one.state == \"published\"\n        assert one.wf.current_state_value == \"published\"\n        assert one.wf.current_state_value == WorkflowSteps.PUBLISHED\n"
  },
  {
    "path": "tests/examples/README.rst",
    "content": "Examples\n--------\n\nBelow is a gallery of ``StateMachine`` examples.\n\n.. only:: comment\n\n    sphinx-gallery does not support .md files yet.\n    See https://github.com/sphinx-gallery/sphinx-gallery/issues/710\n"
  },
  {
    "path": "tests/examples/__init__.py",
    "content": ""
  },
  {
    "path": "tests/examples/ai_shell_machine.py",
    "content": "\"\"\"\nAI Shell -- coding assistant\n=============================\n\nA feature-rich coding assistant powered by python-statemachine.\n\nA standalone interactive CLI that uses the OpenAI SDK for LLM calls with\ntool_use. Demonstrates **parallel states**, **compound states**,\n**HistoryState**, **eventless transitions**, **In() guards**,\n**done.state**, **error.execution**, **invoke**, and **raise_()** — all\nworking together in a practical application.\n\n.. warning::\n\n    This example grants an LLM the ability to read files, list directories,\n    and execute shell commands — which can be very useful for exploring a\n    codebase, running tests, or automating tasks. However, the actual behavior\n    depends on the prompts you send and the model you use, and unintended\n    actions (e.g., deleting files or exposing credentials) are possible.\n\n    **Use at your own risk.** This code is provided for educational and\n    demonstration purposes only. The authors and contributors of\n    python-statemachine accept no liability for any damage or data loss.\n    Consider running it in an isolated environment (e.g., a container or\n    virtual machine) and avoid using elevated privileges.\n\nUsage::\n\n    # Standalone (installs deps from PyPI)\n    OPENAI_API_KEY=sk-... uv run examples/ai_shell.py\n\n    # From the repo (uses local statemachine)\n    OPENAI_API_KEY=sk-... uv run --with openai python examples/ai_shell.py\n\n    # Debug mode — shows engine macro/micro step log on stderr\n    OPENAI_API_KEY=sk-... uv run --with openai python examples/ai_shell.py -v\n\n\"\"\"\n# /// script\n# requires-python = \">=3.9\"\n# dependencies = [\n#     \"openai\",\n#     \"python-statemachine\",\n# ]\n# ///\n\nimport itertools\nimport json\nimport logging\nimport os\nimport random\nimport subprocess\nimport sys\nimport threading\n\nfrom statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\nif \"-v\" in sys.argv or \"--verbose\" in sys.argv:\n    logging.basicConfig(level=logging.DEBUG, format=\"%(name)s  %(message)s\", stream=sys.stderr)\n\n# ---------------------------------------------------------------------------\n# Tool definitions (OpenAI function calling format)\n# ---------------------------------------------------------------------------\n\nTOOLS = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"read_file\",\n            \"description\": (\n                \"Read the contents of a file at the given path. \"\n                \"Returns the file contents (truncated to 10 000 characters).\"\n            ),\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"path\": {\"type\": \"string\", \"description\": \"Absolute or relative file path.\"},\n                },\n                \"required\": [\"path\"],\n            },\n        },\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"list_files\",\n            \"description\": \"List files and directories at the given path.\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"path\": {\n                        \"type\": \"string\",\n                        \"description\": \"Directory path. Defaults to '.' (current directory).\",\n                    },\n                },\n            },\n        },\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"run_command\",\n            \"description\": (\n                \"Run a shell command and return its stdout and stderr. \"\n                \"Commands are executed with a 30-second timeout.\"\n            ),\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"command\": {\n                        \"type\": \"string\",\n                        \"description\": \"The shell command to execute.\",\n                    },\n                },\n                \"required\": [\"command\"],\n            },\n        },\n    },\n]\n\nSYSTEM_PROMPT = (\n    \"You are a helpful coding assistant. You can read files, list directory contents, \"\n    \"and run shell commands to help the user with their tasks. Be concise and practical. \"\n    \"You also have tools to introspect the state machine that powers this shell — use them \"\n    \"when the user asks about the current state, allowed transitions, or other metadata.\"\n)\n\nMAX_FILE_CHARS = 10_000\nCOMMAND_TIMEOUT = 30\nMAX_RETRIES = 3\n\n# ---------------------------------------------------------------------------\n# Spinner animation\n# ---------------------------------------------------------------------------\n\nSPINNER_CHARS = \"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\"\n\nSPINNER_MESSAGES = [\n    \"thinking...\",\n    \"contemplating...\",\n    \"cooking something up...\",\n    \"making something special...\",\n    \"crunching the data...\",\n    \"pondering...\",\n    \"culminating...\",\n    \"brewing ideas...\",\n    \"connecting the dots...\",\n    \"almost there...\",\n]\n\n\nclass Spinner:\n    \"\"\"Animated terminal spinner shown while the LLM is working.\"\"\"\n\n    def __init__(self):\n        self._stop = threading.Event()\n        self._thread: \"threading.Thread | None\" = None\n\n    def __enter__(self):\n        self._stop.clear()\n        self._thread = threading.Thread(target=self._run, daemon=True)\n        self._thread.start()\n        return self\n\n    def __exit__(self, *args):\n        self._stop.set()\n        if self._thread is not None:\n            self._thread.join(timeout=2)\n\n    def _run(self):\n        messages = SPINNER_MESSAGES[:]\n        random.shuffle(messages)\n        msg_cycle = itertools.cycle(messages)\n        char_cycle = itertools.cycle(SPINNER_CHARS)\n        msg = next(msg_cycle)\n        tick = 0\n        while not self._stop.wait(timeout=0.08):\n            char = next(char_cycle)\n            if tick > 0 and tick % 30 == 0:\n                msg = next(msg_cycle)\n            line = f\"  {char} {msg}\"\n            print(f\"\\r{line:<50}\", end=\"\", flush=True)\n            tick += 1\n        print(f\"\\r{'':50}\\r\", end=\"\", flush=True)\n\n\n# ---------------------------------------------------------------------------\n# Tool execution\n# ---------------------------------------------------------------------------\n\n\ndef _tool_read_file(input_data: dict) -> str:\n    path = input_data[\"path\"]\n    try:\n        with open(path) as f:\n            content = f.read(MAX_FILE_CHARS + 1)\n        if len(content) > MAX_FILE_CHARS:\n            content = content[:MAX_FILE_CHARS] + \"\\n... (truncated)\"\n        return content\n    except OSError as e:\n        return f\"Error reading file: {e}\"\n\n\ndef _tool_list_files(input_data: dict) -> str:\n    path = input_data.get(\"path\", \".\")\n    try:\n        entries = sorted(os.listdir(path))\n        return \"\\n\".join(entries)\n    except OSError as e:\n        return f\"Error listing directory: {e}\"\n\n\ndef _tool_run_command(input_data: dict) -> str:\n    command = input_data[\"command\"]\n    try:\n        result = subprocess.run(\n            command,\n            shell=True,\n            capture_output=True,\n            text=True,\n            timeout=COMMAND_TIMEOUT,\n        )\n        output = \"\"\n        if result.stdout:\n            output += result.stdout\n        if result.stderr:\n            output += (\"\" if not output else \"\\n\") + f\"stderr: {result.stderr}\"\n        if result.returncode != 0:\n            output += f\"\\n(exit code {result.returncode})\"\n        return output or \"(no output)\"\n    except subprocess.TimeoutExpired:\n        return f\"Error: command timed out after {COMMAND_TIMEOUT}s\"\n    except OSError as e:\n        return f\"Error running command: {e}\"\n\n\nTOOL_HANDLERS = {\n    \"read_file\": _tool_read_file,\n    \"list_files\": _tool_list_files,\n    \"run_command\": _tool_run_command,\n}\n\n\n# ---------------------------------------------------------------------------\n# State machine introspection tools\n# ---------------------------------------------------------------------------\n\n\ndef _tool_sm_configuration(sm, input_data: dict) -> str:\n    states = sorted(sm.configuration_values)\n    return json.dumps({\"active_states\": states})\n\n\ndef _tool_sm_enabled_events(sm, input_data: dict) -> str:\n    events = sorted({e.name for e in sm.enabled_events()})\n    return json.dumps({\"enabled_events\": events})\n\n\ndef _tool_sm_macrostep_count(sm, input_data: dict) -> str:\n    return json.dumps({\"macrostep_count\": sm._engine._macrostep_count})\n\n\ndef _tool_sm_states(sm, input_data: dict) -> str:\n    all_states = sorted(sm.states_map.keys())\n    return json.dumps({\"all_states\": all_states})\n\n\nSM_TOOL_HANDLERS = {\n    \"sm_configuration\": _tool_sm_configuration,\n    \"sm_enabled_events\": _tool_sm_enabled_events,\n    \"sm_macrostep_count\": _tool_sm_macrostep_count,\n    \"sm_states\": _tool_sm_states,\n}\n\nSM_TOOLS = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"sm_configuration\",\n            \"description\": (\n                \"Get the current active states (configuration) of the state machine. \"\n                \"Returns which states are currently active.\"\n            ),\n            \"parameters\": {\"type\": \"object\", \"properties\": {}},\n        },\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"sm_enabled_events\",\n            \"description\": (\n                \"List events (transitions) that can be triggered from the current \"\n                \"state machine configuration, considering guard conditions.\"\n            ),\n            \"parameters\": {\"type\": \"object\", \"properties\": {}},\n        },\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"sm_macrostep_count\",\n            \"description\": (\n                \"Get the current macrostep counter of the state machine engine. \"\n                \"A macrostep is the full processing cycle for one external event.\"\n            ),\n            \"parameters\": {\"type\": \"object\", \"properties\": {}},\n        },\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"sm_states\",\n            \"description\": (\n                \"List all states defined in the state machine, including nested states \"\n                \"inside compound and parallel states.\"\n            ),\n            \"parameters\": {\"type\": \"object\", \"properties\": {}},\n        },\n    },\n]\n\n\ndef execute_tool(name: str, input_data: dict, sm=None) -> str:\n    sm_handler = SM_TOOL_HANDLERS.get(name)\n    if sm_handler is not None:\n        return sm_handler(sm, input_data)\n    handler = TOOL_HANDLERS.get(name)\n    if handler is None:\n        return f\"Unknown tool: {name}\"\n    return handler(input_data)\n\n\n# ---------------------------------------------------------------------------\n# State machine\n# ---------------------------------------------------------------------------\n\nGOODBYE_WORDS = {\"bye\", \"exit\", \"quit\"}\n\n\nclass AIShell(StateChart):\n    \"\"\"An agentic coding assistant as a StateChart.\n\n    Demonstrates parallel states, compound states, HistoryState, eventless\n    transitions, In() guards, done.state, error.execution, invoke, and\n    raise_() — all in a practical application.\n\n    States::\n\n        session (Parallel)\n        ├── conversation (Compound)\n        │   ├── idle (initial)\n        │   ├── processing (Compound)\n        │   │   ├── thinking (initial, invoke) ← API call + spinner\n        │   │   ├── using_tool (invoke) ← tool execution\n        │   │   ├── done (final)\n        │   │   └── h = HistoryState(deep) ← for error retry\n        │   ├── responding\n        │   ├── recovering ← error.execution handler\n        │   └── conversation_ended (final)\n        └── context_tracker (Compound)\n            ├── fresh (initial)\n            ├── active (≥4 messages)\n            ├── deep (≥20 messages, shows warning)\n            └── tracker_done (final)\n\n    \"\"\"\n\n    catch_errors_as_events = True\n\n    # --- Top-level parallel state: two independent regions ---\n\n    class session(State.Parallel):\n        class conversation(State.Compound):\n            idle = State(\"Idle\", initial=True)\n\n            class processing(State.Compound):\n                thinking = State(\"Thinking\", initial=True)\n                using_tool = State(\"Using Tool\")\n                done = State(\"Done\", final=True)\n                h = HistoryState(type=\"deep\")\n\n                # Invoke results route automatically\n                done_invoke_thinking = thinking.to(\n                    using_tool, cond=\"has_tool_calls\"\n                ) | thinking.to(done)\n                done_invoke_using_tool = using_tool.to(thinking)\n\n            responding = State(\"Responding\")\n            recovering = State(\"Recovering\")\n            conversation_ended = State(\"Ended\", final=True)\n\n            # Named events\n            user_message = idle.to(processing, cond=\"is_not_goodbye\") | idle.to(\n                conversation_ended, cond=\"is_goodbye\"\n            )\n            done_state_processing = processing.to(responding)\n            error_execution = processing.to(recovering)\n\n            # Eventless transitions\n            responding.to(idle)\n            recovering.to(processing.h, cond=\"can_retry\")\n            recovering.to(idle, cond=\"cannot_retry\")\n\n        class context_tracker(State.Compound):\n            fresh = State(\"Fresh\", initial=True)\n            active = State(\"Active\")\n            deep = State(\"Deep\")\n            tracker_done = State(final=True)\n\n            # Eventless: track conversation depth\n            fresh.to(active, cond=\"is_active_context\")\n            active.to(deep, cond=\"is_deep_context\")\n\n            # Eventless + In() guard: follow conversation end\n            fresh.to(tracker_done, cond=\"In('conversation_ended')\")\n            active.to(tracker_done, cond=\"In('conversation_ended')\")\n            deep.to(tracker_done, cond=\"In('conversation_ended')\")\n\n    # --- Initialization ---\n\n    def __init__(self):\n        from openai import OpenAI  # type: ignore[import-not-found]\n\n        self.client = OpenAI()\n        self.messages: list = [{\"role\": \"system\", \"content\": SYSTEM_PROMPT}]\n        self._last_text: str = \"\"\n        self._retries: int = 0\n        self._ready = threading.Event()\n        super().__init__()\n\n    # --- Guards ---\n\n    def is_goodbye(self, text=\"\", **kwargs) -> bool:\n        return text.strip().lower() in GOODBYE_WORDS\n\n    def is_not_goodbye(self, text=\"\", **kwargs) -> bool:\n        return not self.is_goodbye(text=text)\n\n    def can_retry(self, **kwargs) -> bool:\n        return self._retries < MAX_RETRIES\n\n    def cannot_retry(self, **kwargs) -> bool:\n        return not self.can_retry()\n\n    def is_active_context(self, **kwargs) -> bool:\n        return len(self.messages) >= 5\n\n    def is_deep_context(self, **kwargs) -> bool:\n        return len(self.messages) >= 20\n\n    # --- Callbacks ---\n\n    def on_user_message(self, text, **kwargs):\n        \"\"\"Append the user's message to conversation history.\"\"\"\n        self.messages.append({\"role\": \"user\", \"content\": text})\n\n    def has_tool_calls(self, data=None, **kwargs) -> bool:\n        \"\"\"Guard: check if the API response contains tool calls.\"\"\"\n        return bool(getattr(data, \"tool_calls\", None))\n\n    def on_invoke_thinking(self, **kwargs):\n        \"\"\"Call the OpenAI API with a spinner animation. Returns the message.\"\"\"\n        with Spinner():\n            response = self.client.chat.completions.create(\n                model=\"gpt-4o-mini\",\n                messages=self.messages,\n                tools=TOOLS + SM_TOOLS,\n            )\n\n        message = response.choices[0].message\n        self.messages.append(message)\n\n        if not message.tool_calls:\n            self._last_text = message.content or \"\"\n\n        return message\n\n    def on_invoke_using_tool(self, data, **kwargs):\n        \"\"\"Execute tool calls from the API response.\"\"\"\n        for call in data.tool_calls:\n            args = json.loads(call.function.arguments)\n            print(f\"  [tool] {call.function.name}({json.dumps(args)})\")\n            result = execute_tool(call.function.name, args, sm=self)\n            self.messages.append(\n                {\n                    \"role\": \"tool\",\n                    \"tool_call_id\": call.id,\n                    \"content\": result,\n                }\n            )\n\n    def on_enter_responding(self, **kwargs):\n        \"\"\"Print the assistant's text response.\"\"\"\n        if self._last_text:\n            print(f\"\\n{self._last_text}\")\n            self._last_text = \"\"\n\n    def on_enter_idle(self, **kwargs):\n        \"\"\"Reset retry counter and signal readiness when returning to idle.\"\"\"\n        self._retries = 0\n        self._ready.set()\n\n    def on_enter_recovering(self, **kwargs):\n        \"\"\"Handle API errors with retry logic (via error.execution).\"\"\"\n        self._retries += 1\n        if self._retries < MAX_RETRIES:\n            print(f\"\\n  [error] API call failed, retrying ({self._retries}/{MAX_RETRIES})...\")\n        else:\n            print(f\"\\n  [error] API call failed after {MAX_RETRIES} attempts. Giving up.\")\n\n    def on_enter_deep(self, **kwargs):\n        \"\"\"Warn when conversation context is getting long.\"\"\"\n        print(\"  [context] Conversation is getting long — responses may degrade.\")\n\n    def on_enter_conversation_ended(self, **kwargs):\n        print(\"\\nGoodbye!\")\n\n\n# ---------------------------------------------------------------------------\n# Main loop\n# ---------------------------------------------------------------------------\n\n\ndef _check_openai():\n    \"\"\"Return True if the openai package is available.\"\"\"\n    try:\n        import openai  # noqa: F401\n\n        return True\n    except ImportError:\n        return False\n\n\ndef main():\n    if not _check_openai():\n        print(\"This example requires the 'openai' package.\")\n        print(\"Install it with: pip install openai\")\n        return\n\n    print(\"AI Shell\")\n    print(\"A coding assistant powered by python-statemachine + OpenAI.\")\n    print(\"Type 'bye', 'exit', or 'quit' to end. Ctrl+C to interrupt.\")\n    if \"-v\" in sys.argv or \"--verbose\" in sys.argv:\n        print(\"Debug mode enabled — engine log is written to stderr.\\n\")\n    else:\n        print(\"Tip: run with -v to see engine macro/micro step debug log.\\n\")\n\n    try:\n        sm = AIShell()\n    except Exception as e:\n        sys.exit(f\"Error initializing: {e}\")\n\n    while not sm.is_terminated:\n        sm._ready.wait()\n        sm._ready.clear()\n        try:\n            text = input(\"> \")\n        except (EOFError, KeyboardInterrupt):\n            print()\n            break\n        if text.strip():\n            sm.send(\"user_message\", text=text)\n\n\nif __name__ == \"__main__\" and \"sphinx\" not in sys.modules:  # pragma: no cover\n    main()\n"
  },
  {
    "path": "tests/examples/air_conditioner_machine.py",
    "content": "\"\"\"\nAir Conditioner machine\n=======================\n\nA StateChart that exercises reading from a stream of events.\n\n\"\"\"\n\nimport random\n\nfrom statemachine.utils import run_async_from_sync\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\ndef sensor_temperature_reader(seed: int, lower: int = 15, higher: int = 35):\n    \"Infinitely generates random temperature readings.\"\n    random.seed(seed)\n    while True:\n        yield random.randint(lower, higher)\n\n\nclass AirConditioner(StateChart):\n    off = State(initial=True)\n    cooling = State()\n    standby = State()\n\n    sensor_updated = (\n        off.to(cooling, cond=\"is_hot\")\n        | cooling.to(standby, cond=\"is_good\")\n        | standby.to(cooling, cond=\"is_hot\")\n        | standby.to(off, cond=\"is_cool\")\n        | off.to.itself(internal=True)\n        | cooling.to.itself(internal=True)\n        | standby.to.itself(internal=True)\n    )\n\n    async def is_hot(self, temperature: int):\n        return temperature > 25\n\n    async def is_good(self, temperature: int):\n        return temperature < 20\n\n    async def is_cool(self, temperature: int):\n        return temperature < 18\n\n    async def after_transition(self, event: str, source: State, target: State, event_data):\n        print(f\"Running {event} from {source!s} to {target!s}: {event_data.trigger_data.kwargs!r}\")\n\n\n# %%\n# Testing\n\n\nasync def main():\n    sensor = sensor_temperature_reader(123456)\n    print(\"Will create AirConditioner machine\")\n    sm = AirConditioner()\n\n    generator = ((\"sensor_updated\", next(sensor)) for _ in range(20))\n    for event, temperature in generator:\n        await sm.send(event, temperature=temperature)\n\n\nif __name__ == \"__main__\":\n    # using `run_async_from_sync` to better integration with an already running loop.\n    # on real life you should use `asyncio.run(main())`\n    run_async_from_sync(main())\n"
  },
  {
    "path": "tests/examples/all_actions_machine.py",
    "content": "\"\"\"\nAll actions machine\n===================\n\nA StateChart that exercises all possible :ref:`Actions` and :ref:`Guards`.\n\n\"\"\"\n\nfrom unittest import mock\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass AllActionsMachine(StateChart):\n    initial = State(initial=True)\n    final = State(final=True)\n\n    go = initial.to(\n        final,\n        validators=[\"validation_1\", \"validation_2\"],\n        cond=[\"condition_1\", \"condition_2\"],\n        unless=[\"unless_1\", \"unless_2\"],\n        on=[\"on_inline_1\", \"on_inline_2\"],\n        before=[\"before_go_inline_1\", \"before_go_inline_2\"],\n        after=[\"after_go_inline_1\", \"after_go_inline_2\"],\n    )\n\n    def __init__(self, *args, **kwargs):\n        self.spy = mock.Mock(side_effect=lambda x: x)\n        super().__init__(*args, **kwargs)\n\n    # validators and guards\n\n    def validation_1(self):\n        # this method may raise an exception\n        return self.spy(\"validation_1\")\n\n    def validation_2(self):\n        # this method may raise an exception\n        return self.spy(\"validation_2\")\n\n    def condition_1(self):\n        self.spy(\"condition_1\")\n        return True\n\n    def condition_2(self):\n        self.spy(\"condition_2\")\n        return True\n\n    def unless_1(self):\n        self.spy(\"unless_1\")\n        return False\n\n    def unless_2(self):\n        self.spy(\"unless_2\")\n        return False\n\n    # generics state\n\n    def on_enter_state(self):\n        return self.spy(\"on_enter_state\")\n\n    def on_exit_state(self):\n        return self.spy(\"on_exit_state\")\n\n    # generics transition\n\n    def before_transition(self):\n        return self.spy(\"before_transition\")\n\n    def on_transition(self):\n        return self.spy(\"on_transition\")\n\n    def after_transition(self):\n        return self.spy(\"after_transition\")\n\n    # before / after specific\n\n    @go.before\n    def before_go_decor(self):\n        return self.spy(\"before_go_decor\")\n\n    def before_go_inline_1(self):\n        return self.spy(\"before_go_inline_1\")\n\n    def before_go_inline_2(self):\n        return self.spy(\"before_go_inline_2\")\n\n    def before_go(self):\n        return self.spy(\"before_go\")\n\n    @go.on\n    def go_on_decor(self):\n        return self.spy(\"go_on_decor\")\n\n    def on_inline_1(self):\n        return self.spy(\"on_inline_1\")\n\n    def on_inline_2(self):\n        return self.spy(\"on_inline_2\")\n\n    def on_go(self):\n        return self.spy(\"on_go\")\n\n    @go.after\n    def after_go_decor(self):\n        return self.spy(\"after_go_decor\")\n\n    def after_go_inline_1(self):\n        return self.spy(\"after_go_inline_1\")\n\n    def after_go_inline_2(self):\n        return self.spy(\"after_go_inline_2\")\n\n    def after_go(self):\n        return self.spy(\"after_go\")\n\n    # enter / exit specific\n\n    @initial.enter\n    def enter_initial_decor(self):\n        return self.spy(\"enter_initial_decor\")\n\n    def on_enter_initial(self):\n        return self.spy(\"on_enter_initial\")\n\n    @initial.exit\n    def exit_initial_decor(self):\n        return self.spy(\"exit_initial_decor\")\n\n    def on_exit_initial(self):\n        return self.spy(\"on_exit_initial\")\n\n    def on_enter_final(self):\n        return self.spy(\"on_enter_final\")\n\n    def on_exit_final(self):\n        \"hopefully this will not be called\"\n        return self.spy(\"on_exit_final\")\n\n\n# %%\n# Testing\n# -------\n\nmachine = AllActionsMachine()\nspy = machine.spy\n\n\n# %%\n# Only before/on actions have their result collected.\n\nresult = machine.go()\nexpected = [\n    \"before_transition\",\n    \"before_go_inline_1\",\n    \"before_go_inline_2\",\n    \"before_go_decor\",\n    \"before_go\",\n    \"on_transition\",\n    \"on_inline_1\",\n    \"on_inline_2\",\n    \"go_on_decor\",\n    \"on_go\",\n]\nassert result == expected\n\n# %%\n# Checking the method resolution order\n\nassert spy.call_args_list == [\n    mock.call(\"on_enter_state\"),\n    mock.call(\"enter_initial_decor\"),\n    mock.call(\"on_enter_initial\"),\n    mock.call(\"validation_1\"),\n    mock.call(\"validation_2\"),\n    mock.call(\"condition_1\"),\n    mock.call(\"condition_2\"),\n    mock.call(\"unless_1\"),\n    mock.call(\"unless_2\"),\n    mock.call(\"before_transition\"),\n    mock.call(\"before_go_inline_1\"),\n    mock.call(\"before_go_inline_2\"),\n    mock.call(\"before_go_decor\"),\n    mock.call(\"before_go\"),\n    mock.call(\"on_exit_state\"),\n    mock.call(\"exit_initial_decor\"),\n    mock.call(\"on_exit_initial\"),\n    mock.call(\"on_transition\"),\n    mock.call(\"on_inline_1\"),\n    mock.call(\"on_inline_2\"),\n    mock.call(\"go_on_decor\"),\n    mock.call(\"on_go\"),\n    mock.call(\"on_enter_state\"),\n    mock.call(\"on_enter_final\"),\n    mock.call(\"after_go_inline_1\"),\n    mock.call(\"after_go_inline_2\"),\n    mock.call(\"after_go_decor\"),\n    mock.call(\"after_go\"),\n    mock.call(\"after_transition\"),\n]\n"
  },
  {
    "path": "tests/examples/async_guess_the_number_machine.py",
    "content": "\"\"\"\nAsync guess the number machine\n==============================\n\nAn async example of StateChart for the well known game.\n\nIn order to pay the game, run this script and type a number between 1 and 5.\nThe command line should include an extra param to run the script in interactive mode:\n\nOn the root folder of the project, run:\n\n    ``python tests/examples/async_guess_the_number_machine.py -i``\n\nIt's worth to mention that the same state machine can be used in syncronous code, as shown in the\ndocstring of the class. You can play on sync contextif you also pass the `-s` flag:\n\n    ``python tests/examples/async_guess_the_number_machine.py -i -s``\n\n\"\"\"\n\nimport asyncio\nimport random\nimport sys\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass GuessTheNumberMachine(StateChart):\n    \"\"\"\n    Guess the number machine.\n\n    This docstring exercises the SAME `GuessTheNumberMachine`` in synchronous code.\n\n    >>> random.seed(103)\n    >>> sm = GuessTheNumberMachine(print, seed=103)\n    >>> sm.activate_initial_state()  # doctest: +SKIP\n    I'm thinking of a number between 1 and 5. Can you guess what it is? >>>\n\n    >>> while not sm.is_terminated:  # doctest: +SKIP\n    ...     sm.send(\"guess\", random.randint(1, 5))\n    Your guess is 2...\n    Too low. Try again. >>>\n    Your guess is 1...\n    Too low. Try again. >>>\n    Your guess is 5...\n    Too high. Try again. >>>\n    Your guess is 1...\n    Too low. Try again. >>>\n    Your guess is 4...\n    Congratulations, you guessed the number in 5 guesses!\n\n    \"\"\"\n\n    start = State(initial=True)\n    low = State()\n    high = State()\n    won = State(final=True)\n    lose = State(final=True)\n\n    guess = (\n        lose.from_(low, high, cond=\"max_guesses_reached\")\n        | won.from_(low, high, start, cond=\"guess_is_equal\")\n        | low.from_(low, high, start, cond=\"guess_is_lower\")\n        | high.from_(low, high, start, cond=\"guess_is_higher\")\n    )\n\n    def __init__(self, writer, max_attempts=5, lower=1, higher=5, seed=42):\n        self.writer = writer\n        self.max_attempts = max_attempts\n        self.lower = lower\n        self.higher = higher\n        self.guesses = 0\n\n        # lets play a not so random game, or our tests will be crazy\n        random.seed(seed)\n        self.number = random.randint(self.lower, self.higher)\n        super().__init__()\n\n    async def max_guesses_reached(self):\n        return self.guesses >= self.max_attempts\n\n    async def before_guess(self, number):\n        self.guesses += 1\n        self.writer(f\"Your guess is {number}...\")\n\n    async def guess_is_lower(self, number):\n        return number < self.number\n\n    async def guess_is_higher(self, number):\n        return number > self.number\n\n    async def guess_is_equal(self, number):\n        return self.number == number\n\n    async def on_enter_start(self):\n        self.writer(\n            f\"I'm thinking of a number between {self.lower} and {self.higher}. \"\n            f\"Can you guess what it is? >>> \"\n        )\n\n    async def on_enter_low(self):\n        self.writer(\"Too low. Try again. >>> \")\n\n    async def on_enter_high(self):\n        self.writer(\"Too high. Try again. >>> \")\n\n    async def on_enter_won(self):\n        self.writer(f\"Congratulations, you guessed the number in {self.guesses} guesses!\")\n\n    async def on_enter_lose(self):\n        self.writer(f\"Oh, no! You've spent all your {self.guesses} attempts!\")\n\n\n# %%\n# Async stdin/stdout\n# ------------------\n\n# This function will be used to connect the stdin and stdout to the asyncio event loop.\n\n\nasync def connect_stdin_stdout():\n    loop = asyncio.get_event_loop()\n    reader = asyncio.StreamReader()\n    protocol = asyncio.StreamReaderProtocol(reader)\n    await loop.connect_read_pipe(lambda: protocol, sys.stdin)\n    w_transport, w_protocol = await loop.connect_write_pipe(\n        asyncio.streams.FlowControlMixin, sys.stdout\n    )\n    writer = asyncio.StreamWriter(w_transport, w_protocol, reader, loop)\n    return reader, writer\n\n\n# %%\n# Executing the game\n# ------------------\n#\n# This script only run by passing the `-i` flag, avoiding blocking while running automated tests.\n#\n# To play the game, run this script and type a number between 1 and 5.\n#\n# Note that when running a SM in async code, the initial state must be activated manually.\n# This is done by calling ``await sm.activate_initial_state()``.\n\n\nasync def main_async():\n    reader, writer = await connect_stdin_stdout()\n    sm = GuessTheNumberMachine(\n        lambda s: writer.write(b\"\\n\" + s.encode(\"utf-8\")), seed=random.randint(1, 1000)\n    )\n    await sm.activate_initial_state()\n    while not sm.is_terminated:\n        res = await reader.read(100)\n        if not res:\n            break\n        await sm.send(\"guess\", int(res))\n        await writer.drain()\n    writer.close()\n\n\ndef main_sync():\n    sm = GuessTheNumberMachine(print, seed=random.randint(1, 1000))\n    sm.activate_initial_state()\n    while not sm.is_terminated:\n        res = sys.stdin.readline()\n        if not res:\n            break\n        sm.send(\"guess\", int(res))\n\n\nif __name__ == \"__main__\" and \"-i\" in sys.argv:\n    if \"-s\" in sys.argv:\n        main_sync()\n    else:\n        asyncio.run(main_async())\n"
  },
  {
    "path": "tests/examples/async_without_loop_machine.py",
    "content": "\"\"\"\nAsync without external loop\n===========================\n\nDemonstrates that the state machine can have async callbacks even if the calling context\nis synchronous.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass AsyncStateMachine(StateChart):\n    initial = State(\"Initial\", initial=True)\n    processing = State()\n    final = State(\"Final\", final=True)\n\n    start = initial.to(processing)\n    finish = processing.to(final)\n\n    async def on_start(self):\n        return \"starting\"\n\n    async def on_finish(self):\n        return \"finishing\"\n\n\n# %%\n# Executing\n# ---------\n\n\ndef sync_main():\n    sm = AsyncStateMachine()\n    result = sm.start()\n    print(f\"Start result is {result}\")\n    result = sm.send(\"finish\")\n    print(f\"Finish result is {result}\")\n    print(list(sm.configuration))\n    assert sm.final in sm.configuration\n\n\nif __name__ == \"__main__\":\n    sync_main()\n"
  },
  {
    "path": "tests/examples/enum_campaign_machine.py",
    "content": "\"\"\"\nEnum campaign machine\n=====================\n\nA :ref:`StateChart` that demonstrates declaring :ref:`States from Enum types` as source for\n``States`` definition.\n\n\"\"\"\n\nfrom enum import Enum\n\nfrom statemachine.states import States\n\nfrom statemachine import StateChart\n\n\nclass CampaignStatus(Enum):\n    DRAFT = 1\n    PRODUCING = 2\n    CLOSED = 3\n\n\nclass CampaignMachine(StateChart):\n    \"A workflow machine\"\n\n    states = States.from_enum(\n        CampaignStatus,\n        initial=CampaignStatus.DRAFT,\n        final=CampaignStatus.CLOSED,\n    )\n\n    add_job = states.DRAFT.to(states.DRAFT) | states.PRODUCING.to(states.PRODUCING)\n    produce = states.DRAFT.to(states.PRODUCING)\n    deliver = states.PRODUCING.to(states.CLOSED)\n\n\n# %%\n# Asserting campaign machine declaration\n\nassert CampaignMachine.states.DRAFT.initial\nassert not CampaignMachine.states.DRAFT.final\n\nassert not CampaignMachine.states.PRODUCING.initial\nassert not CampaignMachine.states.PRODUCING.final\n\nassert not CampaignMachine.states.CLOSED.initial\nassert CampaignMachine.states.CLOSED.final\n\n\n# %%\n# Testing our campaign machine\n\nsm = CampaignMachine()\nres = sm.send(\"produce\")\n\nassert CampaignStatus.DRAFT not in sm.configuration_values\nassert CampaignStatus.PRODUCING in sm.configuration_values\nassert CampaignStatus.CLOSED not in sm.configuration_values\nassert CampaignStatus.PRODUCING in sm.configuration_values\n"
  },
  {
    "path": "tests/examples/guess_the_number_machine.py",
    "content": "\"\"\"\nGuess the number machine\n========================\n\nA StateChart for the well known game.\n\nWell leave the machine imagine a number and also play the game. Why not?\n\n\"\"\"\n\nimport random\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass GuessTheNumberMachine(StateChart):\n    allow_event_without_transition = False\n    start = State(initial=True)\n    low = State()\n    high = State()\n    won = State(final=True)\n    lose = State(final=True)\n\n    guess = (\n        lose.from_(low, high, cond=\"max_guesses_reached\")\n        | won.from_(low, high, start, cond=\"guess_is_equal\")\n        | low.from_(low, high, start, cond=\"guess_is_lower\")\n        | high.from_(low, high, start, cond=\"guess_is_higher\")\n    )\n\n    def __init__(self, max_attempts=5, lower=1, higher=5, seed=42):\n        self.max_attempts = max_attempts\n        self.lower = lower\n        self.higher = higher\n        self.guesses = 0\n\n        # lets play a not so random game, or our tests will be crazy\n        random.seed(seed)\n        self.number = random.randint(self.lower, self.higher)\n        super().__init__()\n\n    def max_guesses_reached(self):\n        return self.guesses >= self.max_attempts\n\n    def before_guess(self, number):\n        self.guesses += 1\n        print(f\"Your guess is {number}...\")\n\n    def guess_is_lower(self, number):\n        return number < self.number\n\n    def guess_is_higher(self, number):\n        return number > self.number\n\n    def guess_is_equal(self, number):\n        return self.number == number\n\n    def on_enter_start(self):\n        print(f\"(psss.. don't tell anyone the number is {self.number})\")\n        print(\n            f\"I'm thinking of a number between {self.lower} and {self.higher}. \"\n            f\"Can you guess what it is?\"\n        )\n\n    def on_enter_low(self):\n        print(\"Too low. Try again.\")\n\n    def on_enter_high(self):\n        print(\"Too high. Try again.\")\n\n    def on_enter_won(self):\n        print(f\"Congratulations, you guessed the number in {self.guesses} guesses!\")\n\n    def on_enter_lose(self):\n        print(f\"Oh, no! You've spent all your {self.guesses} attempts!\")\n\n\n# %%\n# Playing\n# -------\n#\n\nsm = GuessTheNumberMachine(seed=103)\n\n# %%\n\nsm.guess(random.randint(1, 5))\n\n# %%\n\nsm\n\n# %%\n\nsm.guess(random.randint(1, 5))\n\n# %%\n\nsm.guess(random.randint(1, 5))\n\n\nsm\n\n# %%\n\n# %%\n\nsm.guess(random.randint(1, 5))\n\n# %%\n\nsm.guess(random.randint(1, 5))\n\n# %%\n\nsm\n\n# %%\n\ntry:\n    sm.guess(random.randint(1, 5))\nexcept Exception as e:\n    print(e)\n"
  },
  {
    "path": "tests/examples/lor_machine.py",
    "content": "\"\"\"\nLord of the Rings Quest - Boolean algebra\n=========================================\n\nExample that demonstrates the use of Boolean algebra in conditions.\n\n\"\"\"\n\nfrom statemachine.exceptions import TransitionNotAllowed\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass LordOfTheRingsQuestStateMachine(StateChart):\n    allow_event_without_transition = False\n    # Define the states\n    shire = State(\"In the Shire\", initial=True)\n    bree = State(\"In Bree\")\n    rivendell = State(\"At Rivendell\")\n    moria = State(\"In Moria\")\n    lothlorien = State(\"In Lothlorien\")\n    mordor = State(\"In Mordor\")\n    mount_doom = State(\"At Mount Doom\", final=True)\n\n    # Define transitions with Boolean conditions\n    start_journey = shire.to(bree, cond=\"frodo_has_ring and !sauron_alive and frodo_stamina > 90\")\n    meet_elves = bree.to(rivendell, cond=\"gandalf_present and frodo_has_ring\")\n    enter_moria = rivendell.to(moria, cond=\"orc_army_nearby or frodo_has_ring\")\n    reach_lothlorien = moria.to(lothlorien, cond=\"!orc_army_nearby\")\n    journey_to_mordor = lothlorien.to(mordor, cond=\"frodo_has_ring and sam_is_loyal\")\n    destroy_ring = mordor.to(mount_doom, cond=\"frodo_has_ring and frodo_resists_ring\")\n\n    # Conditions (attributes representing the state of conditions)\n    frodo_stamina: int = 100\n    frodo_has_ring: bool = True\n    sauron_alive: bool = True  # Initially, Sauron is alive\n    gandalf_present: bool = False  # Gandalf is not present at the start\n    orc_army_nearby: bool = False\n    sam_is_loyal: bool = True\n    frodo_resists_ring: bool = False  # Initially, Frodo is not resisting the ring\n\n\n# %%\n# Playing\n\nquest = LordOfTheRingsQuestStateMachine()\n\n# Track state changes\nprint(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should start at \"shire\"\n\n# Step 1: Start the journey\nquest.sauron_alive = False  # Assume Sauron is no longer alive\ntry:\n    quest.start_journey()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"bree\"\nexcept TransitionNotAllowed:\n    print(\"Unable to start journey: conditions not met.\")\n\n# Step 2: Meet the elves in Rivendell\nquest.gandalf_present = True  # Gandalf is now present\ntry:\n    quest.meet_elves()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"rivendell\"\nexcept TransitionNotAllowed:\n    print(\"Unable to meet elves: conditions not met.\")\n\n# Step 3: Enter Moria\nquest.orc_army_nearby = True  # Orc army is nearby\ntry:\n    quest.enter_moria()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"moria\"\nexcept TransitionNotAllowed:\n    print(\"Unable to enter Moria: conditions not met.\")\n\n# Step 4: Reach Lothlorien\nquest.orc_army_nearby = False  # Orcs are no longer nearby\ntry:\n    quest.reach_lothlorien()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"lothlorien\"\nexcept TransitionNotAllowed:\n    print(\"Unable to reach Lothlorien: conditions not met.\")\n\n# Step 5: Journey to Mordor\ntry:\n    quest.journey_to_mordor()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"mordor\"\nexcept TransitionNotAllowed:\n    print(\"Unable to journey to Mordor: conditions not met.\")\n\n# Step 6: Fight with Smeagol\ntry:\n    quest.destroy_ring()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"mount_doom\"\nexcept TransitionNotAllowed:\n    print(\"Unable to destroy the ring: conditions not met.\")\n\n\n# Step 7: Destroy the ring at Mount Doom\nquest.frodo_resists_ring = True  # Frodo is now resisting the ring\ntry:\n    quest.destroy_ring()\n    print(f\"Current State: {[s.id for s in quest.configuration]}\")  # Should be \"mount_doom\"\nexcept TransitionNotAllowed:\n    print(\"Unable to destroy the ring: conditions not met.\")\n"
  },
  {
    "path": "tests/examples/order_control_machine.py",
    "content": "\"\"\"\nOrder control machine\n---------------------\n\nA StateChart that demonstrates :ref:`Guards` being used to control the state flow.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass OrderControl(StateChart):\n    allow_event_without_transition = False\n    enable_self_transition_entries = False\n    waiting_for_payment = State(initial=True)\n    processing = State()\n    shipping = State()\n    completed = State(final=True)\n\n    add_to_order = waiting_for_payment.to(waiting_for_payment)\n    receive_payment = waiting_for_payment.to(\n        processing, cond=\"payments_enough\"\n    ) | waiting_for_payment.to(waiting_for_payment, unless=\"payments_enough\")\n    process_order = processing.to(shipping, cond=\"payment_received\")\n    ship_order = shipping.to(completed)\n\n    def __init__(self):\n        self.order_total = 0\n        self.payments = []\n        self.payment_received = False\n        super().__init__()\n\n    def payments_enough(self, amount):\n        return sum(self.payments) + amount >= self.order_total\n\n    def before_add_to_order(self, amount):\n        self.order_total += amount\n        return self.order_total\n\n    def before_receive_payment(self, amount):\n        self.payments.append(amount)\n        return self.payments\n\n    def after_receive_payment(self):\n        self.payment_received = True\n\n    def on_enter_waiting_for_payment(self):\n        self.payment_received = False\n"
  },
  {
    "path": "tests/examples/order_control_rich_model_machine.py",
    "content": "\"\"\"\nOrder control machine (rich model)\n==================================\n\nA StateChart that demonstrates :ref:`Actions` being used on a rich model.\n\n\"\"\"\n\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass Order:\n    def __init__(self):\n        self.order_total = 0\n        self.payments = []\n        self.payment_received = False\n\n    def payments_enough(self, amount):\n        return sum(self.payments) + amount >= self.order_total\n\n    def before_add_to_order(self, amount):\n        self.order_total += amount\n        return self.order_total\n\n    def on_receive_payment(self, amount):\n        self.payments.append(amount)\n        return self.payments\n\n    def after_receive_payment(self):\n        self.payment_received = True\n\n    def wait_for_payment(self):\n        self.payment_received = False\n\n\nclass OrderControl(StateChart):\n    allow_event_without_transition = False\n    enable_self_transition_entries = False\n\n    waiting_for_payment = State(initial=True, enter=\"wait_for_payment\")\n    processing = State()\n    shipping = State()\n    completed = State(final=True)\n\n    add_to_order = waiting_for_payment.to(waiting_for_payment)\n    receive_payment = waiting_for_payment.to(\n        processing, cond=\"payments_enough\"\n    ) | waiting_for_payment.to(waiting_for_payment, unless=\"payments_enough\")\n    process_order = processing.to(shipping, cond=\"payment_received\")\n    ship_order = shipping.to(completed)\n\n\n# %%\n# Testing OrderControl\n# --------------------\n#\n# Let's first try to create a statemachine instance, using the default dummy model that doesn't\n# have the needed methods to complete the state machine. Since the required methods will not be\n# found either in the state machine or in the model, an exception ``AttrNotFound`` will be raised.\n\ntry:\n    control = OrderControl()\nexcept InvalidDefinition as e:\n    assert (  # noqa: PT017\n        str(e)\n        == (\n            \"Error on Waiting for payment when resolving callbacks: \"\n            \"Did not found name 'wait_for_payment' from model or statemachine\"\n        )\n    )\n\n\n# %%\n# Now initializing with a proper ``order`` instance.\n\norder = Order()\ncontrol = OrderControl(order)\n\n# %%\n# Send events to add to order\n\nassert control.send(\"add_to_order\", 3) == 3\nassert control.send(\"add_to_order\", 7) == 10\n\n# %%\n# Receive a payment of $4...\n\ncontrol.send(\"receive_payment\", 4)\n\n# %%\n# Since there's still $6 left to fulfill the payment, we cannot process the order.\ntry:\n    control.send(\"process_order\")\nexcept StateChart.TransitionNotAllowed as err:\n    print(err)\n\n# %%\n\ncontrol\n\n# %%\n# Now paying the left amount, we can proceed.\n\ncontrol.send(\"receive_payment\", 6)\n\n# %%\n\ncontrol\n\n# %%\n\ncontrol.send(\"process_order\")\n\n# %%\n\ncontrol\n\n# %%\n\ncontrol.send(\"ship_order\")\n\n# %%\n# Just checking the final expected state\n\norder.order_total\n\n# %%\n\norder.payments\n\n# %%\n\ncontrol.completed.is_active\n\n# %%\n\ncontrol\n\n\n# %%\nassert order.order_total == 10\nassert order.payments == [4, 6]\nassert control.completed.is_active\n"
  },
  {
    "path": "tests/examples/persistent_model_machine.py",
    "content": "\"\"\"\nPersistent domain model\n=======================\n\nAn example originated from a question: \"How to save state to disk?\". There are many ways to\nimplement this, but you can get an insight of one possibility. This example implements a custom\ndomain model that persists it's state using a generic strategy that can be extended to any storage\nformat.\n\nOriginal `issue <https://github.com/fgmacedo/python-statemachine/issues/358>`_.\n\n\nResource management state machine\n---------------------------------\n\nGiven a simple on/off machine for resource management.\n\n\"\"\"\n\nimport tempfile\nfrom abc import ABC\nfrom abc import abstractmethod\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ResourceManagement(StateChart):\n    power_off = State(initial=True)\n    power_on = State()\n\n    turn_on = power_off.to(power_on)\n    shutdown = power_on.to(power_off)\n\n\n# %%\n# Abstract model with persistency protocol\n# ----------------------------------------\n#\n# Abstract Base Class for persistent models.\n# Subclasses should implement concrete strategies for:\n#\n# - `_read_state`: Read the state from the concrete persistent layer.\n# - `_write_state`: Write the state from the concrete persistent layer.\n\n\nclass AbstractPersistentModel(ABC):\n    \"\"\"Abstract Base Class for persistent models.\n\n    Subclasses should implement concrete strategies for:\n\n    - `_read_state`: Read the state from the concrete persistent layer.\n    - `_write_state`: Write the state from the concrete persistent layer.\n    \"\"\"\n\n    def __init__(self):\n        self._state = None\n\n    def __repr__(self):\n        return f\"{type(self).__name__}(state={self.state})\"\n\n    @property\n    def state(self):\n        if self._state is None:\n            self._state = self._read_state()\n        return self._state\n\n    @state.setter\n    def state(self, value):\n        self._state = value\n        self._write_state(value)\n\n    @abstractmethod\n    def _read_state(self): ...\n\n    @abstractmethod\n    def _write_state(self, value): ...\n\n\n# %%\n# FilePersistentModel: Concrete model strategy\n# --------------------------------------------\n#\n# A concrete implementation of the generic storage protocol above, that reads and writes to a file\n# in plain text.\n\n\nclass FilePersistentModel(AbstractPersistentModel):\n    \"\"\"A concrete implementation of a storage strategy for a Model\n    that reads and writes to a file.\n    \"\"\"\n\n    def __init__(self, file):\n        super().__init__()\n        self.file = file\n\n    def _read_state(self):\n        self.file.seek(0)\n        state = self.file.read().strip()\n        return state if state != \"\" else None\n\n    def _write_state(self, value):\n        self.file.seek(0)\n        self.file.truncate(0)\n        self.file.write(value or \"\")\n\n\n# %%\n# Given a temporary file to store our state.\n\nstate_file = tempfile.TemporaryFile(mode=\"r+\")\n\n# %%\n# Let's create instances and test the persistence.\n\nmodel = FilePersistentModel(file=state_file)\nsm = ResourceManagement(model=model)\n\nprint(f\"Initial state: {[s.id for s in sm.configuration]}\")\n\nsm.send(\"turn_on\")\n\nprint(f\"State after transition: {[s.id for s in sm.configuration]}\")\n\n# %%\n# Remove the instances from memory.\n\ndel sm\ndel model\n\n# %%\n# Restore the previous state from disk.\n\nmodel = FilePersistentModel(file=state_file)\nsm = ResourceManagement(model=model)\n\nprint(f\"State restored from file system: {[s.id for s in sm.configuration]}\")\n\n# %%\n# Closing the file (the temporary file will be removed).\n\nstate_file.close()\n"
  },
  {
    "path": "tests/examples/recursive_event_machine.py",
    "content": "\"\"\"\nLooping state machine\n=====================\n\nThis example demonstrates that you can call an event as a side-effect of another event.\nThe event will be put on an internal queue and processed in the same loop after the previous event\nin the queue is processed.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass MyStateMachine(StateChart):\n    catch_errors_as_events = False\n    startup = State(initial=True)\n    test = State()\n\n    counter = 0\n    do_startup = startup.to(test, after=\"do_test\")\n    do_test = test.to.itself(after=\"do_test\")\n\n    def on_enter_state(self, target, event):\n        self.counter += 1\n        print(f\"{self.counter:>3}: Entering {target} from {event}\")\n\n        if self.counter >= 5:\n            raise StopIteration\n\n\n# %%\n# Let's create an instance and test the machine.\n\nsm = MyStateMachine()\n\ntry:\n    sm.do_startup()\nexcept StopIteration:\n    pass\n"
  },
  {
    "path": "tests/examples/reusing_transitions_machine.py",
    "content": "\"\"\"\n\n-------------------\nReusing transitions\n-------------------\n\nThis example helps to turn visual the different compositions of how to declare\nand bind :ref:`transitions` to :ref:`event`.\n\n.. note::\n\n    Even sharing the same transition instance, only the transition actions associated with the\n    event will be called.\n\n\nTrafficLightMachine\n   The same transitions are bound to more than one event.\n\nTrafficLightIsolatedTransitions\n    We define new transitions, thus, isolating the connection\n    between states.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass TrafficLightMachine(StateChart):\n    \"A traffic light machine\"\n\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n\n    slowdown = green.to(yellow)\n    stop = yellow.to(red)\n    go = red.to(green)\n\n    cycle = slowdown | stop | go\n\n    def before_slowdown(self):\n        print(\"Slowdown\")\n\n    def before_cycle(self, event: str, source: State, target: State, message: str = \"\"):\n        message = \". \" + message if message else \"\"\n        return f\"Running {event} from {source.id} to {target.id}{message}\"\n\n    def on_enter_red(self):\n        print(\"Don't move.\")\n\n    def on_exit_red(self):\n        print(\"Go ahead!\")\n\n\n# %%\n# Run a transition\n\nsm = TrafficLightMachine()\nsm.send(\"cycle\")\n\n\n# %%\n\n\nclass TrafficLightIsolatedTransitions(StateChart):\n    \"A traffic light machine\"\n\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n\n    slowdown = green.to(yellow)\n    stop = yellow.to(red)\n    go = red.to(green)\n\n    cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n    def before_slowdown(self):\n        print(\"Slowdown\")\n\n    def before_cycle(self, event: str, source: State, target: State, message: str = \"\"):\n        message = \". \" + message if message else \"\"\n        return f\"Running {event} from {source.id} to {target.id}{message}\"\n\n    def on_enter_red(self):\n        print(\"Don't move.\")\n\n    def on_exit_red(self):\n        print(\"Go ahead!\")\n\n\n# %%\n# Run a transition\n\nsm2 = TrafficLightIsolatedTransitions()\nsm2.send(\"cycle\")\n"
  },
  {
    "path": "tests/examples/sqlite_persistent_model_machine.py",
    "content": "\"\"\"\nSQLite-backed approval workflow\n================================\n\nReal-world state machines often need to survive process restarts. This example\nshows how to **persist a StateChart configuration to a relational database**,\nusing the same property getter/setter pattern that ORMs like Django and\nSQLAlchemy use under the hood.\n\nWe build a **document approval workflow** where each document must pass both a\nlegal and a technical review (parallel tracks) before it can be approved. If\n**any** reviewer rejects, the document is rejected immediately — the entire\nparallel state is exited at once.\n\nThe example also compares two configuration update strategies controlled by\n:attr:`~statemachine.statemachine.StateChart.atomic_configuration_update`:\n\n- **Incremental** (``False``, ``StateChart`` default, SCXML-spec compliant):\n  the configuration is updated state-by-state as the engine enters and exits\n  states during a microstep.\n- **Atomic** (``True``, ``StateMachine`` default): the full configuration is\n  computed first and written in a single operation — fewer database writes\n  per transition.\n\n\"\"\"\n\nimport sqlite3\n\nfrom statemachine.orderedset import OrderedSet\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n# %%\n# Database abstraction\n# --------------------\n#\n# ``WorkflowDB`` manages two tables:\n#\n# - **documents** — each row is a domain entity with ``id``, ``title``,\n#   ``author``, and a ``state`` column that holds the serialized state chart\n#   configuration.\n# - **state_history** — an append-only log of every state mutation, useful for\n#   auditing, debugging, or building a timeline view.\n#\n# The state is serialized as a comma-separated string. ``NULL`` means\n# \"no state yet\" (the state chart will enter its initial state on creation).\n\n\nclass WorkflowDB:\n    \"\"\"SQLite persistence layer for document workflows.\"\"\"\n\n    def __init__(self):\n        self.conn = sqlite3.connect(\":memory:\")\n        self.conn.execute(\n            \"CREATE TABLE documents (\"\n            \"  id INTEGER PRIMARY KEY AUTOINCREMENT,\"\n            \"  title TEXT NOT NULL,\"\n            \"  author TEXT NOT NULL,\"\n            \"  state TEXT\"\n            \")\"\n        )\n        self.conn.execute(\n            \"CREATE TABLE state_history (\"\n            \"  id INTEGER PRIMARY KEY AUTOINCREMENT,\"\n            \"  document_id INTEGER NOT NULL REFERENCES documents(id),\"\n            \"  old_state TEXT,\"\n            \"  new_state TEXT\"\n            \")\"\n        )\n        self.conn.commit()\n\n    def insert_document(self, title, author):\n        \"\"\"Insert a new document and return its id.\"\"\"\n        cur = self.conn.execute(\n            \"INSERT INTO documents (title, author) VALUES (?, ?)\", (title, author)\n        )\n        self.conn.commit()\n        return cur.lastrowid\n\n    def find_document(self, doc_id):\n        \"\"\"Return ``(title, author)`` for the given document.\"\"\"\n        return self.conn.execute(\n            \"SELECT title, author FROM documents WHERE id = ?\", (doc_id,)\n        ).fetchone()\n\n    def get_state(self, doc_id):\n        \"\"\"Read state from the DB and deserialize.\"\"\"\n        raw = self.conn.execute(\"SELECT state FROM documents WHERE id = ?\", (doc_id,)).fetchone()[\n            0\n        ]\n        if raw is None:\n            return None\n        parts = raw.split(\",\")\n        return parts[0] if len(parts) == 1 else OrderedSet(parts)\n\n    def set_state(self, doc_id, value):\n        \"\"\"Serialize state, persist it, and record the mutation in history.\"\"\"\n        old_raw = self.conn.execute(\n            \"SELECT state FROM documents WHERE id = ?\", (doc_id,)\n        ).fetchone()[0]\n\n        if value is None:\n            new_raw = None\n        elif isinstance(value, OrderedSet):\n            new_raw = \",\".join(str(v) for v in value)\n        else:\n            new_raw = str(value)\n\n        self.conn.execute(\"UPDATE documents SET state = ? WHERE id = ?\", (new_raw, doc_id))\n        self.conn.execute(\n            \"INSERT INTO state_history (document_id, old_state, new_state) VALUES (?, ?, ?)\",\n            (doc_id, old_raw, new_raw),\n        )\n        self.conn.commit()\n\n    def all_documents(self):\n        \"\"\"Return all rows from the documents table.\"\"\"\n        return self.conn.execute(\n            \"SELECT id, title, author, state FROM documents ORDER BY id\"\n        ).fetchall()\n\n    def history_for(self, doc_id):\n        \"\"\"Return the state mutation history for a specific document.\"\"\"\n        return self.conn.execute(\n            \"SELECT id, old_state, new_state FROM state_history WHERE document_id = ? ORDER BY id\",\n            (doc_id,),\n        ).fetchall()\n\n    def mutation_count(self):\n        \"\"\"Return the total number of state mutations recorded.\"\"\"\n        return self.conn.execute(\"SELECT COUNT(*) FROM state_history\").fetchone()[0]\n\n    def close(self):\n        self.conn.close()\n\n\n# %%\n# Domain model\n# ------------\n#\n# ``Document`` is a domain entity. Its ``state`` property reads from and writes\n# to the database **on every access** — each getter call returns a **freshly\n# deserialized** object. This is exactly how Django model fields and\n# SQLAlchemy column properties work: the ORM never hands you the same Python\n# object twice.\n#\n# Each ``Document`` owns a workflow instance, following the same pattern as\n# :class:`~statemachine.mixins.MachineMixin`: the model holds a reference to\n# its state machine. The workflow class is injected at creation time, keeping\n# the model decoupled from any specific chart definition.\n\n\nclass Document:\n    \"\"\"A document that needs approval.\"\"\"\n\n    def __init__(self, store, doc_id, title, author):\n        self.store = store\n        self.id = doc_id\n        self.title = title\n        self.author = author\n        self.workflow: \"ApprovalWorkflow | None\" = None\n\n    @classmethod\n    def create(cls, store, workflow_cls, title, author):\n        \"\"\"Insert a new document into the DB and start its workflow.\"\"\"\n        doc_id = store.insert_document(title, author)\n        doc = cls(store, doc_id, title, author)\n        doc.workflow = workflow_cls(model=doc)\n        return doc\n\n    @classmethod\n    def load(cls, store, workflow_cls, doc_id):\n        \"\"\"Restore a document and its workflow from the DB.\"\"\"\n        title, author = store.find_document(doc_id)\n        doc = cls(store, doc_id, title, author)\n        doc.workflow = workflow_cls(model=doc)\n        return doc\n\n    @property\n    def state(self):\n        return self.store.get_state(self.id)\n\n    @state.setter\n    def state(self, value):\n        self.store.set_state(self.id, value)\n\n    def __repr__(self):\n        config = list(self.workflow.configuration_values) if self.workflow else \"?\"\n        return f\"Document(#{self.id} {self.title!r} by {self.author}, state={config})\"\n\n\n# %%\n# Approval workflow\n# -----------------\n#\n# A document starts as a **draft**. When submitted, it enters a **parallel**\n# review state: legal and technical tracks run independently.\n#\n# - **Both approve** → ``done.state.review`` fires → **approved**\n# - **Any reviewer rejects** → exits the entire parallel state → **rejected**\n\n\nclass ApprovalWorkflow(StateChart):\n    \"\"\"Document approval with parallel legal and technical review tracks.\"\"\"\n\n    draft = State(\"Draft\", initial=True)\n\n    class review(State.Parallel):\n        class legal_track(State.Compound):\n            legal_pending = State(\"Legal Pending\", initial=True)\n            legal_approved = State(\"Legal Approved\", final=True)\n\n            approve_legal = legal_pending.to(legal_approved)\n\n        class tech_track(State.Compound):\n            tech_pending = State(\"Tech Pending\", initial=True)\n            tech_approved = State(\"Tech Approved\", final=True)\n\n            approve_tech = tech_pending.to(tech_approved)\n\n    submit = draft.to(review)\n\n    approved = State(\"Approved\", final=True)\n    rejected = State(\"Rejected\", final=True)\n\n    done_state_review = review.to(approved)\n    reject_legal = review.to(rejected)\n    reject_tech = review.to(rejected)\n\n\n# %%\n# Here is the workflow diagram — note the two parallel regions inside\n# ``review`` and the ``reject_legal`` / ``reject_tech`` transitions that exit\n# the entire parallel state at once.\n\nsm = ApprovalWorkflow()\n\n# %%\n\nsm\n\n\n# %%\n# Display helper\n# ~~~~~~~~~~~~~~\n\n\ndef print_table(headers, rows):\n    \"\"\"Print a simple formatted table.\"\"\"\n    widths = [len(h) for h in headers]\n    for row in rows:\n        for i, val in enumerate(row):\n            widths[i] = max(widths[i], len(str(val) if val is not None else \"NULL\"))\n    fmt = \"  \".join(f\"{{:<{w}}}\" for w in widths)\n    print(fmt.format(*headers))\n    print(\"  \".join(\"-\" * w for w in widths))\n    for row in rows:\n        print(fmt.format(*(str(v) if v is not None else \"NULL\" for v in row)))\n\n\n# %%\n# Incremental configuration updates\n# ----------------------------------\n#\n# ``StateChart`` defaults to ``atomic_configuration_update=False``, following\n# the SCXML specification: the configuration is modified state-by-state as the\n# engine enters and exits states during each microstep (``configuration.add()``\n# and ``configuration.discard()`` in the W3C algorithm).\n#\n# Each ``add()`` or ``discard()`` call triggers the model's ``state`` property\n# setter, which writes to the database. This means you'll see **one DB write\n# per state** entered or exited — fine for correctness, but chatty for\n# persistence layers.\n\ndb_inc = WorkflowDB()\n\nalice = Document.create(db_inc, ApprovalWorkflow, \"RFC-001: API Redesign\", \"Alice\")\nbob = Document.create(db_inc, ApprovalWorkflow, \"RFC-002: DB Migration\", \"Bob\")\n\nprint(f\"Created: {alice}\")\nprint(f\"Created: {bob}\")\n\nassert alice.state == \"draft\"\nassert bob.state == \"draft\"\n\n# %%\n# Alice's document goes through full approval.\n\nalice.workflow.send(\"submit\")\nprint(f\"After submit:   {alice}\")\n\nalice.workflow.send(\"approve_legal\")\nprint(f\"Legal approved: {alice}\")\nassert \"legal_approved\" in alice.workflow.configuration_values\nassert \"tech_pending\" in alice.workflow.configuration_values\n\nalice.workflow.send(\"approve_tech\")\nprint(f\"Fully approved: {alice}\")\n\n# %%\n# When both tracks reach their final state, ``done.state.review`` fires\n# automatically and the workflow transitions to **approved**.\n\nassert alice.workflow.approved.is_active\nassert alice.state == \"approved\"\n\n# %%\n# Bob's document is **rejected** by the legal team. The ``reject_legal``\n# event transitions out of the ``review`` parallel state, exiting all child\n# states at once — even though technical review hasn't started yet.\n\nbob.workflow.send(\"submit\")\nbob.workflow.send(\"reject_legal\")\nprint(f\"Rejected:       {bob}\")\nassert bob.workflow.rejected.is_active\nassert bob.state == \"rejected\"\n\n# %%\n# Documents table (incremental mode)\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nprint()\nprint_table([\"id\", \"title\", \"author\", \"state\"], db_inc.all_documents())\n\n# %%\n# State mutation history — Alice's document\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n#\n# Every ``add()`` / ``discard()`` call during state entry or exit is a\n# separate DB write. The history reveals the step-by-step construction and\n# teardown of the parallel configuration:\n#\n# ``draft`` → ``NULL`` → ``review`` → add ``legal_track`` → add\n# ``legal_pending`` → add ``tech_track`` → add ``tech_pending`` → ...\n\nprint()\nprint_table([\"#\", \"old_state\", \"new_state\"], db_inc.history_for(alice.id))\n\ninc_mutations = db_inc.mutation_count()\nprint(f\"\\nTotal mutations (incremental, 2 docs): {inc_mutations}\")\n\n\n# %%\n# Atomic configuration updates\n# -----------------------------\n#\n# Setting ``atomic_configuration_update=True`` changes the strategy: the\n# engine computes the full new configuration first, then writes it in a\n# **single** ``setattr`` call. This means one DB write per microstep instead\n# of one per state — a significant reduction for parallel charts.\n#\n# We can enable this with a one-line subclass:\n\n\nclass ApprovalWorkflowAtomic(ApprovalWorkflow):\n    \"\"\"Same workflow with atomic configuration updates.\"\"\"\n\n    atomic_configuration_update = True\n\n\n# %%\n# Run the same scenario with atomic updates.\n\ndb_atom = WorkflowDB()\n\nalice2 = Document.create(db_atom, ApprovalWorkflowAtomic, \"RFC-001: API Redesign\", \"Alice\")\nbob2 = Document.create(db_atom, ApprovalWorkflowAtomic, \"RFC-002: DB Migration\", \"Bob\")\n\nalice2.workflow.send(\"submit\")\nalice2.workflow.send(\"approve_legal\")\nalice2.workflow.send(\"approve_tech\")\nassert alice2.state == \"approved\"\n\nbob2.workflow.send(\"submit\")\nbob2.workflow.send(\"reject_legal\")\nassert bob2.state == \"rejected\"\n\nprint(f\"Alice: {alice2}\")\nprint(f\"Bob:   {bob2}\")\n\n# %%\n# State mutation history — Alice's document (atomic mode)\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n#\n# Each microstep now produces **one** DB write with the full configuration.\n# No intermediate states are visible.\n\nprint()\nprint_table([\"#\", \"old_state\", \"new_state\"], db_atom.history_for(alice2.id))\n\natom_mutations = db_atom.mutation_count()\nprint(f\"\\nTotal mutations (atomic, 2 docs): {atom_mutations}\")\n\n# %%\n# Comparison\n# ~~~~~~~~~~\n#\n# Both modes produce identical final states, but atomic mode generates\n# significantly fewer database writes — especially with parallel states where\n# many children are entered and exited simultaneously.\n\nprint(f\"\\nIncremental: {inc_mutations} mutations\")\nprint(f\"Atomic:      {atom_mutations} mutations\")\nassert atom_mutations < inc_mutations\n\n\n# %%\n# State restoration from the database\n# ------------------------------------\n#\n# The real test of persistence: delete the Python objects and recreate them\n# from the database. The state chart should resume exactly where it left off,\n# preserving even parallel configurations.\n\nalice_id = alice.id\nalice_config = list(alice.workflow.configuration_values)\ndel alice\n\nalice_restored = Document.load(db_inc, ApprovalWorkflow, alice_id)\nprint(f\"Restored: {alice_restored}\")\nassert list(alice_restored.workflow.configuration_values) == alice_config\n\n# %%\n# Bob's rejection is also preserved — no state is lost.\n\nbob_id = bob.id\ndel bob\n\nbob_restored = Document.load(db_inc, ApprovalWorkflow, bob_id)\nprint(f\"Restored: {bob_restored}\")\nassert bob_restored.state == \"rejected\"\n\n# %%\n# Final documents table\n# ~~~~~~~~~~~~~~~~~~~~~~\n\nprint()\nprint_table([\"id\", \"title\", \"author\", \"state\"], db_inc.all_documents())\n\n\n# %%\n# Cleanup.\n\ndb_inc.close()\ndb_atom.close()\n"
  },
  {
    "path": "tests/examples/statechart_cleanup_machine.py",
    "content": "\"\"\"\nCleanup / finalize pattern\n===========================\n\nThis example demonstrates how to guarantee cleanup code runs after a transition\n**regardless of success or failure** — similar to a ``try/finally`` block.\n\nWith ``StateChart`` (where ``catch_errors_as_events=True`` by default), errors in\ncallbacks are caught at the **block level** — meaning the microstep continues\nand ``after_<event>()`` callbacks still run. This makes ``after_<event>()`` a\nnatural **finalize** hook.\n\nFor error-specific handling (logging, recovery), define an ``error.execution``\ntransition and use :func:`raise_() <statemachine.StateMachine.raise_>` to\nauto-recover within the same macrostep.\n\n\"\"\"\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ResourceManager(StateChart):\n    \"\"\"A machine that acquires a resource, processes, and always releases it.\n\n    ``after_start`` acts as the **finalize** callback — it runs after the\n    ``start`` transition regardless of whether the job succeeds or fails.\n\n    On failure, ``error.execution`` additionally transitions to ``recovering``\n    for error-specific handling before auto-recovering back to ``idle``.\n    \"\"\"\n\n    idle = State(\"Idle\", initial=True)\n    working = State(\"Working\")\n    recovering = State(\"Recovering\")\n\n    start = idle.to(working)\n    done = working.to(idle)\n    recover = recovering.to(idle)\n\n    error_execution = Event(\n        working.to(recovering),\n        id=\"error.execution\",\n    )\n\n    def __init__(self, job=None):\n        self.job = job or (lambda: None)\n        super().__init__()\n\n    def on_enter_working(self):\n        print(\"  [working] resource acquired\")\n        self.job()\n        self.raise_(\"done\")\n\n    # --- Finalize (runs on both success and failure) ---\n\n    def after_start(self):\n        print(\"  [after_start] resource released\")\n\n    # --- Error-specific handling ---\n\n    def on_enter_recovering(self, error=None, **kwargs):\n        print(f\"  [recovering] error caught: {error}\")\n        self.raise_(\"recover\")\n\n    def on_enter_idle(self):\n        print(\"  [idle] ready\")\n\n\n# %%\n# Success path\n# -------------\n#\n# When the job completes without errors, the machine transitions\n# ``idle → working → idle``. The ``after_start`` callback releases the resource.\n\n\ndef good_job():\n    print(\"  [working] processing... done!\")\n\n\nsm = ResourceManager(job=good_job)\nprint(f\"State: {sorted(sm.configuration_values)}\")\n\nsm.send(\"start\")\nprint(f\"State: {sorted(sm.configuration_values)}\")\n\nassert \"idle\" in sm.configuration_values\n\n# %%\n# Failure path\n# -------------\n#\n# When the job raises, the error is caught at the block level and\n# ``after_start`` **still runs** — releasing the resource. Then\n# ``error.execution`` fires, transitioning to ``recovering`` for\n# error-specific handling before auto-recovering to ``idle``.\n\n\ndef bad_job():\n    print(\"  [working] processing... oops!\")\n    raise RuntimeError(\"something went wrong\")\n\n\nsm2 = ResourceManager(job=bad_job)\n\nsm2.send(\"start\")\nprint(f\"State: {sorted(sm2.configuration_values)}\")\n\nassert \"idle\" in sm2.configuration_values\n"
  },
  {
    "path": "tests/examples/statechart_compound_machine.py",
    "content": "\"\"\"\nCompound states -- Quest through Middle-earth\n==============================================\n\nThis example demonstrates compound (hierarchical) states using ``StateChart``.\nA compound state contains inner child states, allowing you to model nested behavior.\n\nWhen a compound state is entered, both the parent and its initial child become active.\nTransitions within a compound change the active child while the parent stays active.\nExiting a compound removes all descendants.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestMachine(StateChart):\n    \"\"\"A quest through Middle-earth with compound states.\n\n    The journey has two compound regions: the ``shire`` (with locations to visit)\n    and ``rivendell`` (with council activities). A ``wilderness`` state connects them.\n    \"\"\"\n\n    class shire(State.Compound):\n        bag_end = State(\"Bag End\", initial=True)\n        green_dragon = State(\"The Green Dragon\")\n\n        visit_pub = bag_end.to(green_dragon)\n\n    class rivendell(State.Compound):\n        council = State(\"Council of Elrond\", initial=True)\n        forging = State(\"Reforging Narsil\", final=True)\n\n        begin_forging = council.to(forging)\n\n    wilderness = State(\"Wilderness\")\n    destination = State(\"Quest continues\", final=True)\n\n    depart_shire = shire.to(wilderness)\n    arrive_rivendell = wilderness.to(rivendell)\n    done_state_rivendell = rivendell.to(destination)\n\n\n# %%\n# Starting the quest\n# ------------------\n#\n# When the machine starts, the ``shire`` compound and its initial child ``bag_end``\n# are both active.\n\nsm = QuestMachine()\nprint(f\"Active states: {sorted(sm.configuration_values)}\")\nassert {\"shire\", \"bag_end\"} == set(sm.configuration_values)\n\n# %%\n# Transitioning within a compound\n# --------------------------------\n#\n# Moving within a compound changes the active child. The parent stays active.\n\nsm.send(\"visit_pub\")\nprint(f\"After visiting pub: {sorted(sm.configuration_values)}\")\nassert \"shire\" in sm.configuration_values\nassert \"green_dragon\" in sm.configuration_values\nassert \"bag_end\" not in sm.configuration_values\n\n# %%\n# Exiting a compound\n# -------------------\n#\n# Leaving a compound removes the parent and all children.\n\nsm.send(\"depart_shire\")\nprint(f\"In the wilderness: {sorted(sm.configuration_values)}\")\nassert {\"wilderness\"} == set(sm.configuration_values)\n\n# %%\n# Entering another compound\n# --------------------------\n#\n# Entering ``rivendell`` activates its initial child ``council``.\n\nsm.send(\"arrive_rivendell\")\nprint(f\"At Rivendell: {sorted(sm.configuration_values)}\")\nassert {\"rivendell\", \"council\"} == set(sm.configuration_values)\n\n# %%\n# done.state event\n# ------------------\n#\n# When the final child of a compound is reached, a ``done.state.{parent}`` event\n# fires automatically, triggering the transition to ``destination``.\n\nsm.send(\"begin_forging\")\nprint(f\"Quest continues: {sorted(sm.configuration_values)}\")\nassert {\"destination\"} == set(sm.configuration_values)\n"
  },
  {
    "path": "tests/examples/statechart_delayed_machine.py",
    "content": "\"\"\"\nSupervised task -- Beacons of Gondor\n=====================================\n\nThis example demonstrates a **self-driven** ``StateChart`` combining\n**compound states**, **parallel states**, **internal events**, **delayed\nevents**, **eventless transitions**, and **event cancellation**.\n\n- **Compound states** model the beacon chain: each beacon is a sub-state\n  of a compound, and the ``light_next`` event advances through them.\n- **Parallel states** run the beacon lighting and the siege clock\n  concurrently inside a single ``StateChart``.\n- ``raise_(\"event\")`` queues an event on the **internal** queue, processed\n  immediately within the current macrostep.\n- ``send(\"event\", delay=N)`` schedules a delayed event on the **external**\n  queue, processed only after ``N`` milliseconds.\n- **Eventless transitions** fire automatically when their ``In()`` guard\n  becomes true, without requiring an explicit event.\n- ``cancel_event(send_id)`` removes a pending event before it fires.\n\nThe scenario: Minas Tirith is besieged and the Beacons of Gondor must be\nlit to summon Rohan's aid.  Two things happen in parallel:\n\n1. **Beacons** -- Each beacon's ``on_enter`` lights the next via\n   ``raise_()``, chaining through all seven relay points in a single\n   macrostep (microseconds in wall-clock time).\n2. **Siege** -- A delayed ``fall`` event ticks down.  If the beacons\n   aren't all lit before the timer expires, the city is overrun.\n\nWhen the last beacon fires and the signal reaches Rohan, an eventless\ntransition detects ``In('rohan_reached')`` and transitions the whole\nparallel state to the happy ending -- cancelling the siege timer.  If the\nsiege timer fires first, ``In('fallen')`` triggers the sad ending instead.\n\n.. tip::\n\n   Run with ``-v`` to see the engine's macro/micro step debug log::\n\n       uv run python tests/examples/statechart_delayed_machine.py -v\n\n\"\"\"\n\nimport logging\nimport sys\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\nif \"-v\" in sys.argv or \"--verbose\" in sys.argv:\n    logging.basicConfig(level=logging.DEBUG, format=\"%(name)s  %(message)s\", stream=sys.stdout)\n\n\nclass BeaconsMachine(StateChart):\n    \"\"\"Light the Beacons of Gondor before the siege overwhelms Minas Tirith.\n\n    A parallel state runs two concurrent regions:\n\n    * **beacons** -- a compound state whose sub-states are the seven beacon\n      relay points from Minas Tirith to Rohan.  Each beacon's entry action\n      fires ``raise_(\"light_next\")`` to chain to the next one.\n    * **siege** -- a compound state with a delayed ``fall`` event that\n      represents the city being overrun.\n\n    Two eventless transitions on the parallel state detect the outcome:\n\n    * ``In('rohan_reached')`` -- all beacons lit, Rohan is summoned.\n    * ``In('fallen')`` -- siege timer expired, the city falls.\n    \"\"\"\n\n    idle = State(\"Idle\", initial=True)\n\n    class quest(State.Parallel):\n        class beacons(State.Compound):\n            minas_tirith = State(\"Minas Tirith\", initial=True)\n            amon_din = State(\"Amon Din\")\n            eilenach = State(\"Eilenach\")\n            nardol = State(\"Nardol\")\n            erelas = State(\"Erelas\")\n            min_rimmon = State(\"Min-Rimmon\")\n            calenhad = State(\"Calenhad\")\n            rohan_reached = State(\"Signal reaches Rohan\", final=True)\n\n            light_next = (\n                minas_tirith.to(amon_din)\n                | amon_din.to(eilenach)\n                | eilenach.to(nardol)\n                | nardol.to(erelas)\n                | erelas.to(min_rimmon)\n                | min_rimmon.to(calenhad)\n                | calenhad.to(rohan_reached)\n            )\n\n        class siege(State.Compound):\n            holding = State(\"The city holds\", initial=True)\n            fallen = State(\"City overrun\", final=True)\n\n            fall = holding.to(fallen)\n\n    rohan_rides = State(\"Rohan rides to aid!\", final=True)\n    city_falls = State(\"Minas Tirith has fallen!\", final=True)\n\n    # External event to kick off the quest\n    start = idle.to(quest)\n\n    # Eventless transitions -- checked automatically each macrostep\n    quest.to(rohan_rides, cond=\"In('rohan_reached')\")\n    quest.to(city_falls, cond=\"In('fallen')\")\n\n    siege_timeout_ms: int = 5000\n\n    def on_enter_minas_tirith(self):\n        \"\"\"Gandalf lights the first beacon.  The chain begins.\"\"\"\n        print(\"  Minas Tirith -- The beacon is lit!\")\n        self.raise_(\"light_next\")\n\n    def after_light_next(self, target):\n        \"\"\"Each beacon keeper spots the fire and lights their own.\"\"\"\n        if target.final:\n            print(f\"  {target.name}!\")\n        else:\n            print(f\"  {target.name} -- The beacon is lit!\")\n            self.raise_(\"light_next\")\n\n    def on_enter_holding(self):\n        \"\"\"The siege clock starts ticking.\"\"\"\n        self.send(\"fall\", delay=self.siege_timeout_ms, send_id=\"siege_timer\")\n\n    def on_enter_rohan_rides(self):\n        self.cancel_event(\"siege_timer\")\n        print(\"  The beacons are answered! Rohan rides to aid!\")\n\n    def on_enter_city_falls(self):\n        print(\"  The beacons were never lit. Minas Tirith has fallen.\")\n\n\n# %%\n# Scenario 1: All beacons lit before the siege\n# -----------------------------------------------\n#\n# A single ``send(\"start\")`` triggers the entire workflow:\n#\n# 1. Entering the ``quest`` parallel state activates both regions.\n# 2. In the **beacons** region, ``on_enter_minas_tirith`` fires\n#    ``raise_(\"light_next\")``, and ``after_light_next`` chains through\n#    all seven beacons via internal events -- completing in microseconds.\n# 3. In the **siege** region, ``on_enter_holding`` schedules a delayed\n#    ``fall`` event (5 seconds).\n# 4. The eventless guard ``In('rohan_reached')`` becomes true and the\n#    machine exits the parallel state into ``rohan_rides``.\n# 5. ``on_enter_rohan_rides`` cancels the pending siege timer.\n\nprint(\"=== Scenario 1: Beacons lit in time ===\")\nsm = BeaconsMachine()\nsm.send(\"start\")\nprint(f\"  Result: {sorted(sm.configuration_values)}\")\nassert \"rohan_rides\" in sm.configuration_values\n\n\n# %%\n# Scenario 2: The beacons are never lit\n# ----------------------------------------\n#\n# Denethor, in his despair, refuses to light the beacon.  The chain never\n# starts.  Because the beacon region stays stuck at ``minas_tirith``, the\n# processing loop has nothing to do except busy-wait (sleeping 1 ms per\n# cycle) for the delayed ``fall`` event.\n#\n# The siege timeout is set to just 10 ms for this demonstration -- any\n# value > 0 would work since the machine is completely idle while waiting.\n# When the delayed ``fall`` event fires, ``holding`` transitions to\n# ``fallen``, and the eventless guard ``In('fallen')`` routes the machine\n# to ``city_falls``.\n\n\nclass FailedBeaconsMachine(BeaconsMachine):\n    \"\"\"Denethor refuses to light the beacons.  The city is lost.\"\"\"\n\n    siege_timeout_ms: int = 10\n\n    def on_enter_minas_tirith(self):\n        print(\"  Denethor: 'Why do the fools fly? Better to die sooner than late.'\")\n\n\nprint()\nprint(\"=== Scenario 2: The beacons are never lit ===\")\nsm2 = FailedBeaconsMachine()\nsm2.send(\"start\")\nprint(f\"  Result: {sorted(sm2.configuration_values)}\")\nassert \"city_falls\" in sm2.configuration_values\n"
  },
  {
    "path": "tests/examples/statechart_error_handling_machine.py",
    "content": "\"\"\"\nError handling -- Quest Recovery\n=================================\n\nThis example demonstrates **error.execution** handling using ``StateChart``.\n\nWhen ``catch_errors_as_events=True`` (the ``StateChart`` default), runtime errors in\ncallbacks are caught and dispatched as ``error.execution`` events instead of\npropagating as exceptions. This lets you define error-recovery transitions.\n\n- The ``error_`` naming convention auto-registers both ``error_X`` and ``error.X``\n  event names.\n- Alternatively, use ``Event(transitions, id=\"error.execution\")`` for explicit\n  registration.\n- Error data (the original exception, event, etc.) is available in handler kwargs.\n\n\"\"\"\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestRecoveryMachine(StateChart):\n    \"\"\"A quest where actions can fail and the error handler routes to recovery.\n\n    When ``on_enter_danger_zone`` raises, the ``error.execution`` event fires\n    and transitions to the ``recovering`` state instead of crashing.\n    \"\"\"\n\n    safe = State(\"Safe\", initial=True)\n    danger_zone = State(\"Danger Zone\")\n    recovering = State(\"Recovering\")\n    completed = State(\"Quest Complete\", final=True)\n\n    venture = safe.to(danger_zone)\n    survive = danger_zone.to(completed)\n    recover = recovering.to(safe)\n\n    # Register error.execution handler using Event with explicit id\n    error_execution = Event(\n        safe.to(recovering) | danger_zone.to(recovering),\n        id=\"error.execution\",\n    )\n\n    def on_enter_danger_zone(self):\n        # This simulates an unexpected error during a quest action\n        raise RuntimeError(\"Ambush! Orcs attack!\")\n\n    def on_enter_recovering(self, error=None, **kwargs):\n        if error:\n            print(f\"Error caught: {error}\")\n        print(\"Retreating to recover...\")\n\n\n# %%\n# Error triggers recovery instead of crashing\n# ----------------------------------------------\n#\n# When entering ``danger_zone`` raises a ``RuntimeError``, the error is caught\n# and dispatched as ``error.execution``. The machine transitions to ``recovering``.\n\nsm = QuestRecoveryMachine()\nprint(f\"Start: {sorted(sm.configuration_values)}\")\nassert \"safe\" in sm.configuration_values\n\nsm.send(\"venture\")\nprint(f\"After venture: {sorted(sm.configuration_values)}\")\nassert \"recovering\" in sm.configuration_values\n\n# %%\n# Recover and try again\n# -----------------------\n\nsm.send(\"recover\")\nprint(f\"After recovery: {sorted(sm.configuration_values)}\")\nassert \"safe\" in sm.configuration_values\n\n\n# %%\n# Comparison with catch_errors_as_events=False (error propagation)\n# --------------------------------------------------------------\n#\n# With ``catch_errors_as_events=False``, the same error\n# would propagate as an exception instead of being caught.\n\n\nclass QuestNoCatch(StateChart):\n    catch_errors_as_events = False\n\n    safe = State(\"Safe\", initial=True)\n    danger_zone = State(\"Danger Zone\", final=True)\n\n    venture = safe.to(danger_zone)\n\n    def on_enter_danger_zone(self):\n        raise RuntimeError(\"Ambush! Orcs attack!\")\n\n\nsm2 = QuestNoCatch()\ntry:\n    sm2.send(\"venture\")\nexcept RuntimeError as e:\n    print(f\"Exception propagated: {e}\")\n"
  },
  {
    "path": "tests/examples/statechart_eventless_machine.py",
    "content": "\"\"\"\nEventless (automatic) transitions -- The One Ring's Corruption\n==============================================================\n\nThis example demonstrates **eventless transitions** using ``StateChart``.\nAn eventless transition has no triggering event -- it fires automatically\nwhen its guard condition becomes true during the macrostep processing loop.\n\nEventless transitions are evaluated after every macrostep. If the condition\nis met, the transition fires without any explicit event. Multiple eventless\ntransitions can cascade in a single macrostep.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass RingCorruptionMachine(StateChart):\n    \"\"\"The One Ring gradually corrupts its bearer.\n\n    As ``ring_power`` increases, automatic transitions fire when thresholds\n    are crossed. No explicit events drive the state changes -- only the\n    guard conditions.\n\n    A ``tick`` internal self-transition is used to re-trigger the processing\n    loop after changing ``ring_power`` from the outside.\n    \"\"\"\n\n    # States represent corruption stages\n    resisting = State(\"Resisting\", initial=True)\n    tempted = State(\"Tempted\")\n    corrupted = State(\"Corrupted\")\n    lost = State(\"Lost to the Ring\", final=True)\n\n    # Eventless transitions: fire automatically when conditions are met\n    resisting.to(tempted, cond=\"is_tempted\")\n    tempted.to(corrupted, cond=\"is_corrupted\")\n    corrupted.to(lost, cond=\"is_lost\")\n\n    # A no-op event to re-trigger the processing loop\n    tick = (\n        resisting.to.itself(internal=True)\n        | tempted.to.itself(internal=True)\n        | corrupted.to.itself(internal=True)\n    )\n\n    ring_power: int = 0\n\n    def is_tempted(self):\n        return self.ring_power >= 3\n\n    def is_corrupted(self):\n        return self.ring_power >= 6\n\n    def is_lost(self):\n        return self.ring_power >= 9\n\n\n# %%\n# The bearer starts by resisting\n# -------------------------------\n\nsm = RingCorruptionMachine()\nprint(f\"Stage: {sorted(sm.configuration_values)}\")\nassert \"resisting\" in sm.configuration_values\n\n# %%\n# Increase ring power below threshold -- nothing changes\n# -------------------------------------------------------\n#\n# Setting ``ring_power`` alone doesn't trigger processing. We send a ``tick``\n# event to re-enter the processing loop where eventless transitions are checked.\n\nsm.ring_power = 2\nsm.send(\"tick\")\nprint(f\"Power 2 -> Stage: {sorted(sm.configuration_values)}\")\nassert \"resisting\" in sm.configuration_values\n\n# %%\n# Cross the first threshold -- automatic transition to \"tempted\"\n# ---------------------------------------------------------------\n\nsm.ring_power = 4\nsm.send(\"tick\")\nprint(f\"Power 4 -> Stage: {sorted(sm.configuration_values)}\")\nassert \"tempted\" in sm.configuration_values\n\n# %%\n# Cross multiple thresholds at once -- cascade in one macrostep\n# --------------------------------------------------------------\n#\n# When ``ring_power`` jumps past several thresholds, all matching eventless\n# transitions fire in sequence within a single macrostep.\n\nsm.ring_power = 10\nsm.send(\"tick\")\nprint(f\"Power 10 -> Stage: {sorted(sm.configuration_values)}\")\nassert \"lost\" in sm.configuration_values\n"
  },
  {
    "path": "tests/examples/statechart_history_machine.py",
    "content": "\"\"\"\nHistory states -- Gollum's dual personality\n============================================\n\nThis example demonstrates history pseudo-states using ``StateChart``.\nA history state records the active child of a compound state when it is\nexited. Re-entering via the history state restores the previously active\nchild instead of starting from the initial child.\n\nBoth shallow history (``HistoryState()``) and deep history\n(``HistoryState(type=\"deep\")``) are shown.\n\n\"\"\"\n\nfrom statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass PersonalityMachine(StateChart):\n    \"\"\"Gollum's dual personality with shallow history.\n\n    The ``personality`` compound has two children: ``smeagol`` and ``gollum``.\n    When Gollum leaves the ``personality`` state and returns via the history\n    pseudo-state, the previously active personality is restored.\n    \"\"\"\n\n    class personality(State.Compound):\n        smeagol = State(\"Smeagol\", initial=True)\n        gollum = State(\"Gollum\")\n        h = HistoryState()\n\n        dark_side = smeagol.to(gollum)\n        light_side = gollum.to(smeagol)\n\n    outside = State(\"Outside\")\n    leave = personality.to(outside)\n    return_via_history = outside.to(personality.h)\n\n\n# %%\n# Shallow history remembers the last child\n# ------------------------------------------\n\nsm = PersonalityMachine()\nprint(f\"Initial: {sorted(sm.configuration_values)}\")\nassert \"smeagol\" in sm.configuration_values\n\n# Switch to Gollum, then leave\nsm.send(\"dark_side\")\nprint(f\"Gollum active: {sorted(sm.configuration_values)}\")\nassert \"gollum\" in sm.configuration_values\n\nsm.send(\"leave\")\nprint(f\"Left: {sorted(sm.configuration_values)}\")\nassert {\"outside\"} == set(sm.configuration_values)\n\n# Return via history -> Gollum is restored\nsm.send(\"return_via_history\")\nprint(f\"History restored: {sorted(sm.configuration_values)}\")\nassert \"gollum\" in sm.configuration_values\nassert \"personality\" in sm.configuration_values\n\n# %%\n# Multiple exit/reentry cycles\n# ------------------------------\n#\n# History updates each time the compound is exited.\n\nsm.send(\"light_side\")\nprint(f\"Switched to Smeagol: {sorted(sm.configuration_values)}\")\nassert \"smeagol\" in sm.configuration_values\n\nsm.send(\"leave\")\nsm.send(\"return_via_history\")\nprint(f\"Smeagol restored: {sorted(sm.configuration_values)}\")\nassert \"smeagol\" in sm.configuration_values\n\n\n# %%\n# Deep history with nested compounds\n# ------------------------------------\n#\n# Deep history remembers the exact leaf state in nested compounds.\n\n\nclass DeepPersonalityMachine(StateChart):\n    \"\"\"A machine with nested compounds and deep history.\"\"\"\n\n    class realm(State.Compound):\n        class inner(State.Compound):\n            entrance = State(\"Entrance\", initial=True)\n            chamber = State(\"Chamber\")\n\n            explore = entrance.to(chamber)\n\n        assert isinstance(inner, State)\n        h = HistoryState(type=\"deep\")  # type: ignore[has-type]\n        bridge = State(\"Bridge\", final=True)\n        flee = inner.to(bridge)\n\n    outside = State(\"Outside\")\n    escape = realm.to(outside)\n    return_deep = outside.to(realm.h)  # type: ignore[has-type]\n\n\nsm2 = DeepPersonalityMachine()\nprint(f\"\\nDeep history initial: {sorted(sm2.configuration_values)}\")\nassert \"entrance\" in sm2.configuration_values\n\n# Move to the inner leaf state\nsm2.send(\"explore\")\nprint(f\"Explored chamber: {sorted(sm2.configuration_values)}\")\nassert \"chamber\" in sm2.configuration_values\n\n# Exit and return via deep history\nsm2.send(\"escape\")\nprint(f\"Escaped: {sorted(sm2.configuration_values)}\")\nassert {\"outside\"} == set(sm2.configuration_values)\n\nsm2.send(\"return_deep\")\nprint(f\"Deep history restored: {sorted(sm2.configuration_values)}\")\nassert \"chamber\" in sm2.configuration_values\nassert \"inner\" in sm2.configuration_values\nassert \"realm\" in sm2.configuration_values\n"
  },
  {
    "path": "tests/examples/statechart_in_condition_machine.py",
    "content": "\"\"\"\nIn() guard condition -- Fellowship Coordination\n=================================================\n\nThis example demonstrates the **In()** guard condition using ``StateChart``\nwith parallel states.\n\n``In('state_id')`` checks whether a given state is currently active. This is\nespecially useful in parallel regions where one region's transitions depend\non the state of another region.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass FellowshipMachine(StateChart):\n    \"\"\"Fellowship coordination with parallel regions.\n\n    Two parallel regions track Frodo and Sam independently. The key\n    transition -- ``sam_to_mordor`` -- uses ``In('mordor_f')`` to ensure Sam\n    only follows Frodo to Mordor after Frodo has already arrived there.\n    \"\"\"\n\n    class quest(State.Parallel):\n        class frodo_path(State.Compound):\n            shire_f = State(\"Frodo in Shire\", initial=True)\n            rivendell_f = State(\"Frodo at Rivendell\")\n            mordor_f = State(\"Frodo in Mordor\", final=True)\n\n            frodo_to_rivendell = shire_f.to(rivendell_f)\n            frodo_to_mordor = rivendell_f.to(mordor_f)\n\n        class sam_path(State.Compound):\n            shire_s = State(\"Sam in Shire\", initial=True)\n            rivendell_s = State(\"Sam at Rivendell\")\n            mordor_s = State(\"Sam in Mordor\")\n            mount_doom_s = State(\"Sam at Mount Doom\", final=True)\n\n            sam_to_rivendell = shire_s.to(rivendell_s)\n\n            # Sam can only go to Mordor when Frodo is already there\n            sam_to_mordor = rivendell_s.to(mordor_s, cond=\"In('mordor_f')\")\n            sam_to_mount_doom = mordor_s.to(mount_doom_s)\n\n    victory = State(\"Victory\", final=True)\n    done_state_quest = quest.to(victory)\n\n\n# %%\n# Initial state -- both in the Shire\n# ------------------------------------\n\nsm = FellowshipMachine()\nvals = set(sm.configuration_values)\nprint(f\"Start: {sorted(vals)}\")\nassert \"shire_f\" in vals\nassert \"shire_s\" in vals\n\n# %%\n# Move both to Rivendell independently\n# ---------------------------------------\n\nsm.send(\"frodo_to_rivendell\")\nsm.send(\"sam_to_rivendell\")\nvals = set(sm.configuration_values)\nprint(f\"Both at Rivendell: {sorted(vals)}\")\nassert \"rivendell_f\" in vals\nassert \"rivendell_s\" in vals\n\n# %%\n# Sam can't go to Mordor yet -- In('mordor_f') is false\n# -------------------------------------------------------\n#\n# Frodo hasn't reached Mordor, so ``In('mordor_f')`` evaluates to false\n# and Sam's transition is blocked.\n\nsm.send(\"sam_to_mordor\")\nvals = set(sm.configuration_values)\nprint(f\"Sam blocked: {sorted(vals)}\")\nassert \"rivendell_s\" in vals  # Sam still at Rivendell\n\n# %%\n# Frodo reaches Mordor -- now Sam can follow\n# ---------------------------------------------\n#\n# After Frodo transitions to ``mordor_f``, the ``In('mordor_f')`` condition\n# becomes true. Now sending ``sam_to_mordor`` will succeed.\n\nsm.send(\"frodo_to_mordor\")\nvals = set(sm.configuration_values)\nprint(f\"Frodo in Mordor: {sorted(vals)}\")\nassert \"mordor_f\" in vals\nassert \"rivendell_s\" in vals  # Sam still waiting\n\n# %%\n# Sam follows Frodo -- In() guard passes\n# ----------------------------------------\n\nsm.send(\"sam_to_mordor\")\nvals = set(sm.configuration_values)\nprint(f\"Sam follows: {sorted(vals)}\")\nassert \"mordor_s\" in vals\n\n# %%\n# Both regions complete -- done.state fires\n# -------------------------------------------\n#\n# When both parallel regions reach their final states, ``done.state.quest``\n# fires automatically and transitions to ``victory``.\n\nsm.send(\"sam_to_mount_doom\")\nprint(f\"Victory: {sorted(sm.configuration_values)}\")\nassert \"victory\" in sm.configuration_values\n"
  },
  {
    "path": "tests/examples/statechart_parallel_machine.py",
    "content": "\"\"\"\nParallel states -- War of the Ring\n===================================\n\nThis example demonstrates parallel states using ``StateChart``.\nA parallel state activates all child regions simultaneously. Each region\noperates independently -- events in one region don't affect others.\n\nThe ``done.state`` event fires only when **all** regions reach a final state.\n\n\"\"\"\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass WarMachine(StateChart):\n    \"\"\"The War of the Ring with parallel fronts.\n\n    Three independent fronts run simultaneously inside the ``war`` parallel state:\n    Frodo's quest to destroy the Ring, Aragorn's path to kingship, and\n    Gandalf's defense of the realms.\n    \"\"\"\n\n    class war(State.Parallel):\n        class frodos_quest(State.Compound):\n            shire = State(\"The Shire\", initial=True)\n            mordor = State(\"Mordor\")\n            mount_doom = State(\"Mount Doom\", final=True)\n\n            journey = shire.to(mordor)\n            destroy_ring = mordor.to(mount_doom)\n\n        class aragorns_path(State.Compound):\n            ranger = State(\"Ranger\", initial=True)\n            king = State(\"King of Gondor\", final=True)\n\n            coronation = ranger.to(king)\n\n        class gandalfs_defense(State.Compound):\n            rohan = State(\"Rohan\", initial=True)\n            gondor = State(\"Gondor\", final=True)\n\n            ride_to_gondor = rohan.to(gondor)\n\n    peace = State(\"Peace in Middle-earth\", final=True)\n    done_state_war = war.to(peace)\n\n\n# %%\n# All regions activate at once\n# -----------------------------\n#\n# Entering the ``war`` parallel state activates the initial child of every region.\n\nsm = WarMachine()\nconfig = set(sm.configuration_values)\nprint(f\"Active states: {sorted(config)}\")\nexpected = {\"war\", \"frodos_quest\", \"shire\", \"aragorns_path\", \"ranger\", \"gandalfs_defense\", \"rohan\"}\nassert expected.issubset(config)\n\n# %%\n# Independent transitions\n# ------------------------\n#\n# An event in one region does not affect others.\n\nsm.send(\"journey\")\nprint(f\"Frodo journeys: {sorted(sm.configuration_values)}\")\nassert \"mordor\" in sm.configuration_values\nassert \"ranger\" in sm.configuration_values  # Aragorn unchanged\nassert \"rohan\" in sm.configuration_values  # Gandalf unchanged\n\n# %%\n# Partial completion\n# -------------------\n#\n# One region reaching final doesn't end the parallel state.\n\nsm.send(\"coronation\")\nprint(f\"Aragorn crowned: {sorted(sm.configuration_values)}\")\nassert \"king\" in sm.configuration_values\nassert \"war\" in sm.configuration_values  # parallel still active\n\n# %%\n# All regions reach final\n# ------------------------\n#\n# When all regions reach final, ``done.state.war`` fires and transitions to ``peace``.\n\nsm.send(\"ride_to_gondor\")\nprint(f\"Gandalf in Gondor: {sorted(sm.configuration_values)}\")\nassert \"war\" in sm.configuration_values  # Frodo not done yet\n\nsm.send(\"destroy_ring\")\nprint(f\"Peace: {sorted(sm.configuration_values)}\")\nassert {\"peace\"} == set(sm.configuration_values)\n"
  },
  {
    "path": "tests/examples/traffic_light_machine.py",
    "content": "\"\"\"\n\n---------------------\nTraffic light machine\n---------------------\n\nThis example demonstrates how to create a traffic light machine using the ``StateChart`` class.\n\nThe state machine will run in a dedicated thread and will cycle through the states.\n\n\"\"\"\n\nimport time\nfrom threading import Event as ThreadingEvent\nfrom threading import Thread\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass TrafficLightMachine(StateChart):\n    \"A traffic light machine\"\n\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n\n    cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n    def before_cycle(self, event: str, source: State, target: State):\n        print(f\"Running {event} from {source.id} to {target.id}\")\n\n\n# %%\n# Run in a dedicated thread\n\n\nclass Supervisor:\n    def __init__(self, sm: StateChart, sm_event: str):\n        self.sm = sm\n        self.sm_event = sm_event\n        self.stop_event = ThreadingEvent()\n\n    def run(self):\n        while not self.stop_event.is_set():\n            self.sm.send(self.sm_event)\n            self.stop_event.wait(0.1)\n\n    def stop(self):\n        self.stop_event.set()\n\n\ndef main():\n    supervisor = Supervisor(TrafficLightMachine(), \"cycle\")\n    t = Thread(target=supervisor.run)\n    t.start()\n\n    time.sleep(0.5)\n    supervisor.stop()\n    t.join()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tests/examples/user_machine.py",
    "content": "\"\"\"\nUser workflow machine\n=====================\n\nThis machine binds the events to the User model, the StateChart is wrapped internally\nin the `User` class.\n\nDemonstrates that multiple state machines can be used in the same model.\n\nAnd that logic can be reused with listeners.\n\n\"\"\"\n\nfrom dataclasses import dataclass\nfrom enum import Enum\n\nfrom statemachine.states import States\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass UserStatus(str, Enum):\n    signup_incomplete = \"SIGNUP_INCOMPLETE\"\n    signup_complete = \"SIGNUP_COMPLETE\"\n    signup_rejected = \"SIGNUP_REJECTED\"\n    operational_enabled = \"OPERATIONAL_ENABLED\"\n    operational_disabled = \"OPERATIONAL_DISABLED\"\n    operational_rescinded = \"OPERATIONAL_RESCINDED\"\n\n\nclass UserExperience(str, Enum):\n    basic = \"BASIC\"\n    premium = \"PREMIUM\"\n\n\n@dataclass\nclass User:\n    name: str\n    email: str\n    status: UserStatus = UserStatus.signup_incomplete\n    experience: UserExperience = UserExperience.basic\n\n    verified: bool = False\n\n    def __post_init__(self):\n        self._status_sm = UserStatusMachine(\n            self, state_field=\"status\", listeners=[MachineChangeListenter()]\n        )\n        self._status_sm.bind_events_to(self)\n\n        self._experience_sm = UserExperienceMachine(\n            self, state_field=\"experience\", listeners=[MachineChangeListenter()]\n        )\n        self._experience_sm.bind_events_to(self)\n\n\nclass MachineChangeListenter:\n    def before_transition(self, event: str, state: State):\n        print(f\"Before {event} in {state}\")\n\n    def on_enter_state(self, state: State, event: str):\n        print(f\"Entering {state} from {event}\")\n\n\nclass UserStatusMachine(StateChart):\n    catch_errors_as_events = False\n    _states = States.from_enum(\n        UserStatus,\n        initial=UserStatus.signup_incomplete,\n        final=[\n            UserStatus.operational_rescinded,\n            UserStatus.signup_rejected,\n        ],\n    )\n\n    signup = _states.signup_incomplete.to(_states.signup_complete)\n    reject = _states.signup_rejected.from_(\n        _states.signup_incomplete,\n        _states.signup_complete,\n    )\n    enable = _states.signup_complete.to(_states.operational_enabled)\n    disable = _states.operational_enabled.to(_states.operational_disabled)\n    rescind = _states.operational_rescinded.from_(\n        _states.operational_enabled,\n        _states.operational_disabled,\n    )\n\n    def on_signup(self, token: str):\n        if token == \"\":\n            raise ValueError(\"Token is required\")\n        self.model.verified = True  # type: ignore[union-attr]\n\n\nclass UserExperienceMachine(StateChart):\n    _states = States.from_enum(\n        UserExperience,\n        initial=UserExperience.basic,\n    )\n\n    upgrade = _states.basic.to(_states.premium)\n    downgrade = _states.premium.to(_states.basic)\n\n\n# %%\n# Executing\n\n\ndef main():  # type: ignore[attr-defined]\n    # By binding the events to the User model, the events can be fired directly from the model\n    user = User(name=\"Frodo\", email=\"frodo@lor.com\")\n\n    try:\n        # Trying to signup with an empty token should raise an exception\n        user.signup(\"\")\n    except Exception as e:\n        print(e)\n\n    assert user.verified is False\n\n    user.signup(\"1234\")\n\n    assert user.status == UserStatus.signup_complete\n    assert user.verified is True\n\n    print(user.experience)\n    user.upgrade()\n    print(user.experience)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tests/examples/weighted_idle_machine.py",
    "content": "\"\"\"\n\n------------------------------\nWeighted idle animation machine\n------------------------------\n\nThis example demonstrates how to use ``weighted_transitions`` to create probabilistic\nidle animations for a game character. Each time the ``idle`` event fires, the character\nrandomly picks an animation based on relative weights.\n\n\"\"\"\n\nfrom statemachine.contrib.weighted import weighted_transitions\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass WeightedIdleMachine(StateChart):\n    \"\"\"A game character with weighted idle animations.\n\n    When idle, the character randomly picks an animation based on weights:\n    - 70% chance: shift weight from foot to foot\n    - 20% chance: adjust hair\n    - 10% chance: bang shield\n    \"\"\"\n\n    standing = State(initial=True)\n    shift_weight = State()\n    adjust_hair = State()\n    bang_shield = State()\n\n    idle = weighted_transitions(\n        standing,\n        (shift_weight, 70),\n        (adjust_hair, 20),\n        (bang_shield, 10),\n        seed=42,\n    )\n\n    finish = shift_weight.to(standing) | adjust_hair.to(standing) | bang_shield.to(standing)\n"
  },
  {
    "path": "tests/helpers.py",
    "content": "import importlib\nfrom pathlib import Path\n\n\ndef import_module_by_path(src_file: Path):\n    module_name = str(src_file).replace(\"/\", \".\")\n    try:\n        return importlib.import_module(module_name)\n    except ModuleNotFoundError:\n        return\n"
  },
  {
    "path": "tests/machines/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/compound/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/compound/middle_earth_journey.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MiddleEarthJourney(StateChart):\n    class rivendell(State.Compound):\n        council = State(initial=True)\n        preparing = State()\n\n        get_ready = council.to(preparing)\n\n    class moria(State.Compound):\n        gates = State(initial=True)\n        bridge = State(final=True)\n\n        cross = gates.to(bridge)\n\n    class lothlorien(State.Compound):\n        mirror = State(initial=True)\n        departure = State(final=True)\n\n        leave = mirror.to(departure)\n\n    march_to_moria = rivendell.to(moria)\n    march_to_lorien = moria.to(lothlorien)\n"
  },
  {
    "path": "tests/machines/compound/middle_earth_journey_two_compounds.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MiddleEarthJourneyTwoCompounds(StateChart):\n    class rivendell(State.Compound):\n        council = State(initial=True)\n        preparing = State()\n\n        get_ready = council.to(preparing)\n\n    class moria(State.Compound):\n        gates = State(initial=True)\n        bridge = State(final=True)\n\n        cross = gates.to(bridge)\n\n    march_to_moria = rivendell.to(moria)\n"
  },
  {
    "path": "tests/machines/compound/middle_earth_journey_with_finals.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MiddleEarthJourneyWithFinals(StateChart):\n    class rivendell(State.Compound):\n        council = State(initial=True)\n        preparing = State(final=True)\n\n        get_ready = council.to(preparing)\n\n    class moria(State.Compound):\n        gates = State(initial=True)\n        bridge = State(final=True)\n\n        cross = gates.to(bridge)\n\n    class lothlorien(State.Compound):\n        mirror = State(initial=True)\n        departure = State(final=True)\n\n        leave = mirror.to(departure)\n\n    march_to_moria = rivendell.to(moria)\n    march_to_lorien = moria.to(lothlorien)\n"
  },
  {
    "path": "tests/machines/compound/moria_expedition.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MoriaExpedition(StateChart):\n    class moria(State.Compound):\n        class upper_halls(State.Compound):\n            entrance = State(initial=True)\n            bridge = State(final=True)\n\n            cross = entrance.to(bridge)\n\n        assert isinstance(upper_halls, State)\n        depths = State(final=True)\n        descend = upper_halls.to(depths)\n"
  },
  {
    "path": "tests/machines/compound/moria_expedition_with_escape.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MoriaExpeditionWithEscape(StateChart):\n    class moria(State.Compound):\n        class upper_halls(State.Compound):\n            entrance = State(initial=True)\n            bridge = State()\n\n            cross = entrance.to(bridge)\n\n        assert isinstance(upper_halls, State)\n        depths = State(final=True)\n        descend = upper_halls.to(depths)\n\n    daylight = State(final=True)\n    escape = moria.to(daylight)\n"
  },
  {
    "path": "tests/machines/compound/quest_for_erebor.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForErebor(StateChart):\n    class lonely_mountain(State.Compound):\n        approach = State(initial=True)\n        inside = State(final=True)\n\n        enter_mountain = approach.to(inside)\n\n    victory = State(final=True)\n    done_state_lonely_mountain = lonely_mountain.to(victory)\n"
  },
  {
    "path": "tests/machines/compound/shire_to_rivendell.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ShireToRivendell(StateChart):\n    class shire(State.Compound):\n        bag_end = State(initial=True)\n        green_dragon = State()\n\n        visit_pub = bag_end.to(green_dragon)\n\n    road = State(final=True)\n    depart = shire.to(road)\n"
  },
  {
    "path": "tests/machines/donedata/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/donedata/destroy_the_ring.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass DestroyTheRing(StateChart):\n    class quest(State.Compound):\n        traveling = State(initial=True)\n        completed = State(final=True, donedata=\"get_quest_result\")\n\n        finish = traveling.to(completed)\n\n        def get_quest_result(self):\n            return {\"ring_destroyed\": True, \"hero\": \"frodo\"}\n\n    epilogue = State(final=True)\n    done_state_quest = Event(quest.to(epilogue, on=\"capture_result\"))  # type: ignore[arg-type]\n\n    def capture_result(self, ring_destroyed=None, hero=None, **kwargs):\n        self.received = {\"ring_destroyed\": ring_destroyed, \"hero\": hero}\n"
  },
  {
    "path": "tests/machines/donedata/destroy_the_ring_simple.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass DestroyTheRingSimple(StateChart):\n    class quest(State.Compound):\n        traveling = State(initial=True)\n        completed = State(final=True, donedata=\"get_result\")\n\n        finish = traveling.to(completed)\n\n        def get_result(self):\n            return {\"outcome\": \"victory\"}\n\n    celebration = State(final=True)\n    done_state_quest = Event(quest.to(celebration))  # type: ignore[arg-type]\n"
  },
  {
    "path": "tests/machines/donedata/nested_quest_donedata.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass NestedQuestDoneData(StateChart):\n    class outer(State.Compound):\n        class inner(State.Compound):\n            start = State(initial=True)\n            end = State(final=True, donedata=\"inner_result\")\n\n            go = start.to(end)\n\n            def inner_result(self):\n                return {\"level\": \"inner\"}\n\n        assert isinstance(inner, State)\n        after_inner = State(final=True)\n        done_state_inner = Event(inner.to(after_inner))  # type: ignore[arg-type]\n\n    final = State(final=True)\n    done_state_outer = Event(outer.to(final))  # type: ignore[arg-type]\n"
  },
  {
    "path": "tests/machines/donedata/quest_for_erebor_done_convention.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForEreborDoneConvention(StateChart):\n    class quest(State.Compound):\n        traveling = State(initial=True)\n        arrived = State(final=True)\n\n        finish = traveling.to(arrived)\n\n    celebration = State(final=True)\n    done_state_quest = quest.to(celebration)\n"
  },
  {
    "path": "tests/machines/donedata/quest_for_erebor_explicit_id.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForEreborExplicitId(StateChart):\n    class quest(State.Compound):\n        traveling = State(initial=True)\n        arrived = State(final=True)\n\n        finish = traveling.to(arrived)\n\n    celebration = State(final=True)\n    done_state_quest = Event(quest.to(celebration), id=\"done.state.quest\")  # type: ignore[arg-type]\n"
  },
  {
    "path": "tests/machines/donedata/quest_for_erebor_multi_word.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForEreborMultiWord(StateChart):\n    class lonely_mountain(State.Compound):\n        approach = State(initial=True)\n        inside = State(final=True)\n\n        enter_mountain = approach.to(inside)\n\n    victory = State(final=True)\n    done_state_lonely_mountain = lonely_mountain.to(victory)\n"
  },
  {
    "path": "tests/machines/donedata/quest_for_erebor_with_event.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass QuestForEreborWithEvent(StateChart):\n    class quest(State.Compound):\n        traveling = State(initial=True)\n        arrived = State(final=True)\n\n        finish = traveling.to(arrived)\n\n    celebration = State(final=True)\n    done_state_quest = Event(quest.to(celebration))  # type: ignore[arg-type]\n"
  },
  {
    "path": "tests/machines/error/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/error/error_convention_event.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorConventionEventSC(StateChart):\n    \"\"\"Using Event without explicit id with error_ prefix auto-registers dot notation.\"\"\"\n\n    s1 = State(\"s1\", initial=True)\n    error_state = State(\"error_state\", final=True)\n\n    go = s1.to(s1, on=\"bad_action\")\n    error_execution = Event(s1.to(error_state))\n\n    def bad_action(self):\n        raise RuntimeError(\"action failed\")\n"
  },
  {
    "path": "tests/machines/error/error_convention_transition_list.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorConventionTransitionListSC(StateChart):\n    \"\"\"Using bare TransitionList with error_ prefix auto-registers dot notation.\"\"\"\n\n    s1 = State(\"s1\", initial=True)\n    error_state = State(\"error_state\", final=True)\n\n    go = s1.to(s1, on=\"bad_action\")\n    error_execution = s1.to(error_state)\n\n    def bad_action(self):\n        raise RuntimeError(\"action failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_action_sc.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInActionSC(StateChart):\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    error_state = State(\"error_state\", final=True)\n\n    go = s1.to(s2, on=\"bad_action\")\n    error_execution = Event(s1.to(error_state) | s2.to(error_state), id=\"error.execution\")\n\n    def bad_action(self):\n        raise RuntimeError(\"action failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_action_sm_with_flag.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInActionSMWithFlag(StateChart):\n    \"\"\"StateChart subclass (catch_errors_as_events = True by default).\"\"\"\n\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    error_state = State(\"error_state\", final=True)\n\n    go = s1.to(s2, on=\"bad_action\")\n    error_execution = Event(s1.to(error_state) | s2.to(error_state), id=\"error.execution\")\n\n    def bad_action(self):\n        raise RuntimeError(\"action failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_after_sc.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInAfterSC(StateChart):\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    error_state = State(\"error_state\", final=True)\n\n    go = s1.to(s2, after=\"bad_after\")\n    error_execution = Event(s2.to(error_state), id=\"error.execution\")\n\n    def bad_after(self):\n        raise RuntimeError(\"after failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_error_handler_sc.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInErrorHandlerSC(StateChart):\n    \"\"\"Error in error.execution handler should not cause infinite loop.\"\"\"\n\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    s3 = State(\"s3\", final=True)\n\n    go = s1.to(s2, on=\"bad_action\")\n    finish = s2.to(s3)\n    error_execution = Event(\n        s1.to(s1, on=\"bad_error_handler\") | s2.to(s2, on=\"bad_error_handler\"),\n        id=\"error.execution\",\n    )\n\n    def bad_action(self):\n        raise RuntimeError(\"action failed\")\n\n    def bad_error_handler(self):\n        raise RuntimeError(\"error handler also failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_guard_sc.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInGuardSC(StateChart):\n    initial = State(\"initial\", initial=True)\n    error_state = State(\"error_state\", final=True)\n\n    go = initial.to(initial, cond=\"bad_guard\") | initial.to(initial)\n    error_execution = Event(initial.to(error_state), id=\"error.execution\")\n\n    def bad_guard(self):\n        raise RuntimeError(\"guard failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_guard_sm.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInGuardSM(StateChart):\n    \"\"\"StateChart subclass with catch_errors_as_events=False: exceptions should propagate.\"\"\"\n\n    catch_errors_as_events = False\n\n    initial = State(\"initial\", initial=True)\n\n    go = initial.to(initial, cond=\"bad_guard\") | initial.to(initial)\n\n    def bad_guard(self):\n        raise RuntimeError(\"guard failed\")\n"
  },
  {
    "path": "tests/machines/error/error_in_on_enter_sc.py",
    "content": "from statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ErrorInOnEnterSC(StateChart):\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    error_state = State(\"error_state\", final=True)\n\n    go = s1.to(s2)\n    error_execution = Event(s1.to(error_state) | s2.to(error_state), id=\"error.execution\")\n\n    def on_enter_s2(self):\n        raise RuntimeError(\"on_enter failed\")\n"
  },
  {
    "path": "tests/machines/eventless/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/eventless/auto_advance.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass AutoAdvance(StateChart):\n    class journey(State.Compound):\n        step1 = State(initial=True)\n        step2 = State()\n        step3 = State(final=True)\n\n        step1.to(step2)\n        step2.to(step3)\n\n    done = State(final=True)\n    done_state_journey = journey.to(done)\n"
  },
  {
    "path": "tests/machines/eventless/beacon_chain.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass BeaconChain(StateChart):\n    class beacons(State.Compound):\n        first = State(initial=True)\n        last = State(final=True)\n\n        first.to(last)\n\n    signal_received = State(final=True)\n    done_state_beacons = beacons.to(signal_received)\n"
  },
  {
    "path": "tests/machines/eventless/beacon_chain_lighting.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass BeaconChainLighting(StateChart):\n    class chain(State.Compound):\n        amon_din = State(initial=True)\n        eilenach = State()\n        nardol = State()\n        halifirien = State(final=True)\n\n        # Eventless chain: each fires immediately\n        amon_din.to(eilenach)\n        eilenach.to(nardol)\n        nardol.to(halifirien)\n\n    all_lit = State(final=True)\n    done_state_chain = chain.to(all_lit)\n"
  },
  {
    "path": "tests/machines/eventless/coordinated_advance.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CoordinatedAdvance(StateChart):\n    class forces(State.Parallel):\n        class vanguard(State.Compound):\n            waiting = State(initial=True)\n            advanced = State(final=True)\n\n            move_forward = waiting.to(advanced)\n\n        class rearguard(State.Compound):\n            holding = State(initial=True)\n            moved_up = State(final=True)\n\n            # Eventless: advance only when vanguard has advanced\n            holding.to(moved_up, cond=\"In('advanced')\")\n"
  },
  {
    "path": "tests/machines/eventless/ring_corruption.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass RingCorruption(StateChart):\n    resisting = State(initial=True)\n    corrupted = State(final=True)\n\n    # eventless: no event name\n    resisting.to(corrupted, cond=\"is_corrupted\")\n\n    ring_power = 0\n\n    def is_corrupted(self):\n        return self.ring_power > 5\n\n    def increase_power(self):\n        self.ring_power += 3\n"
  },
  {
    "path": "tests/machines/eventless/ring_corruption_with_bear_ring.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass RingCorruptionWithBearRing(StateChart):\n    resisting = State(initial=True)\n    corrupted = State(final=True)\n\n    resisting.to(corrupted, cond=\"is_corrupted\")\n    bear_ring = resisting.to.itself(internal=True, on=\"increase_power\")\n\n    ring_power = 0\n\n    def is_corrupted(self):\n        return self.ring_power > 5\n\n    def increase_power(self):\n        self.ring_power += 2\n"
  },
  {
    "path": "tests/machines/eventless/ring_corruption_with_tick.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass RingCorruptionWithTick(StateChart):\n    resisting = State(initial=True)\n    corrupted = State(final=True)\n\n    resisting.to(corrupted, cond=\"is_corrupted\")\n    tick = resisting.to.itself(internal=True)\n\n    ring_power = 0\n\n    def is_corrupted(self):\n        return self.ring_power > 5\n"
  },
  {
    "path": "tests/machines/history/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/history/deep_memory_of_moria.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass DeepMemoryOfMoria(StateChart):\n    class moria(State.Compound):\n        class halls(State.Compound):\n            entrance = State(initial=True)\n            chamber = State()\n\n            explore = entrance.to(chamber)\n\n        assert isinstance(halls, State)\n        h = HistoryState(type=\"deep\")\n        bridge = State(final=True)\n        flee = halls.to(bridge)\n\n    outside = State()\n    escape = moria.to(outside)\n    return_deep = outside.to(moria.h)  # type: ignore[has-type]\n"
  },
  {
    "path": "tests/machines/history/gollum_personality.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass GollumPersonality(StateChart):\n    class personality(State.Compound):\n        smeagol = State(initial=True)\n        gollum = State()\n        h = HistoryState()\n\n        dark_side = smeagol.to(gollum)\n        light_side = gollum.to(smeagol)\n\n    outside = State()\n    leave = personality.to(outside)\n    return_via_history = outside.to(personality.h)\n"
  },
  {
    "path": "tests/machines/history/gollum_personality_default_gollum.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass GollumPersonalityDefaultGollum(StateChart):\n    class personality(State.Compound):\n        smeagol = State(initial=True)\n        gollum = State()\n        h = HistoryState()\n\n        dark_side = smeagol.to(gollum)\n        _ = h.to(gollum)  # default: gollum (not the initial smeagol)\n\n    outside = State(initial=True)\n    enter_via_history = outside.to(personality.h)\n    leave = personality.to(outside)\n"
  },
  {
    "path": "tests/machines/history/gollum_personality_with_default.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass GollumPersonalityWithDefault(StateChart):\n    class personality(State.Compound):\n        smeagol = State(initial=True)\n        gollum = State()\n        h = HistoryState()\n\n        dark_side = smeagol.to(gollum)\n        _ = h.to(smeagol)  # default: smeagol\n\n    outside = State(initial=True)\n    enter_via_history = outside.to(personality.h)\n    leave = personality.to(outside)\n"
  },
  {
    "path": "tests/machines/history/shallow_moria.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ShallowMoria(StateChart):\n    class moria(State.Compound):\n        class halls(State.Compound):\n            entrance = State(initial=True)\n            chamber = State()\n\n            explore = entrance.to(chamber)\n\n        assert isinstance(halls, State)\n        h = HistoryState()\n        bridge = State(final=True)\n        flee = halls.to(bridge)\n\n    outside = State()\n    escape = moria.to(outside)\n    return_shallow = outside.to(moria.h)  # type: ignore[has-type]\n"
  },
  {
    "path": "tests/machines/in_condition/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/in_condition/combined_guard.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CombinedGuard(StateChart):\n    class positions(State.Parallel):\n        class scout(State.Compound):\n            out = State(initial=True)\n            back = State(final=True)\n\n            return_scout = out.to(back)\n\n        class warrior(State.Compound):\n            idle = State(initial=True)\n            attacking = State(final=True)\n\n            # Only attacks when scout is back\n            charge = idle.to(attacking, cond=\"In('back')\")\n"
  },
  {
    "path": "tests/machines/in_condition/descendant_check.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass DescendantCheck(StateChart):\n    class realm(State.Compound):\n        village = State(initial=True)\n        castle = State()\n\n        ascend = village.to(castle)\n\n    conquered = State(final=True)\n    # Guarded by being inside the castle\n    conquer = realm.to(conquered, cond=\"In('castle')\")\n    explore = realm.to.itself(internal=True)  # type: ignore[attr-defined]\n"
  },
  {
    "path": "tests/machines/in_condition/eventless_in.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass EventlessIn(StateChart):\n    class coordination(State.Parallel):\n        class leader(State.Compound):\n            planning = State(initial=True)\n            ready = State(final=True)\n\n            get_ready = planning.to(ready)\n\n        class follower(State.Compound):\n            waiting = State(initial=True)\n            moving = State(final=True)\n\n            # Eventless: move when leader is ready\n            waiting.to(moving, cond=\"In('ready')\")\n"
  },
  {
    "path": "tests/machines/in_condition/fellowship.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass Fellowship(StateChart):\n    class positions(State.Parallel):\n        class frodo(State.Compound):\n            shire_f = State(initial=True)\n            mordor_f = State(final=True)\n\n            journey = shire_f.to(mordor_f)\n\n        class sam(State.Compound):\n            shire_s = State(initial=True)\n            mordor_s = State(final=True)\n\n            # Sam follows Frodo: eventless, guarded by In('mordor_f')\n            shire_s.to(mordor_s, cond=\"In('mordor_f')\")\n"
  },
  {
    "path": "tests/machines/in_condition/fellowship_coordination.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass FellowshipCoordination(StateChart):\n    class mission(State.Parallel):\n        class scouts(State.Compound):\n            scouting = State(initial=True)\n            reported = State(final=True)\n\n            report = scouting.to(reported)\n\n        class army(State.Compound):\n            waiting = State(initial=True)\n            marching = State(final=True)\n\n            # Army marches only after scouts report\n            waiting.to(marching, cond=\"In('reported')\")\n"
  },
  {
    "path": "tests/machines/in_condition/gate_of_moria.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass GateOfMoria(StateChart):\n    outside = State(initial=True)\n    at_gate = State()\n    inside = State(final=True)\n\n    approach = outside.to(at_gate)\n    # Can only enter if we are at the gate\n    enter_gate = outside.to(inside, cond=\"In('at_gate')\")\n    speak_friend = at_gate.to(inside)\n"
  },
  {
    "path": "tests/machines/parallel/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/parallel/session.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass Session(StateChart):\n    class session(State.Parallel):\n        class ui(State.Compound):\n            active = State(initial=True)\n            closed = State(final=True)\n\n            close_ui = active.to(closed)\n\n        class backend(State.Compound):\n            running = State(initial=True)\n            stopped = State(final=True)\n\n            stop_backend = running.to(stopped)\n"
  },
  {
    "path": "tests/machines/parallel/session_with_done_state.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass SessionWithDoneState(StateChart):\n    class session(State.Parallel):\n        class ui(State.Compound):\n            active = State(initial=True)\n            closed = State(final=True)\n\n            close_ui = active.to(closed)\n\n        class backend(State.Compound):\n            running = State(initial=True)\n            stopped = State(final=True)\n\n            stop_backend = running.to(stopped)\n\n    finished = State(final=True)\n    done_state_session = session.to(finished)\n"
  },
  {
    "path": "tests/machines/parallel/two_towers.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass TwoTowers(StateChart):\n    class battle(State.Parallel):\n        class helms_deep(State.Compound):\n            fighting = State(initial=True)\n            victory = State(final=True)\n\n            win = fighting.to(victory)\n\n        class isengard(State.Compound):\n            besieging = State(initial=True)\n            flooded = State(final=True)\n\n            flood = besieging.to(flooded)\n\n    aftermath = State(final=True)\n    done_state_battle = battle.to(aftermath)\n"
  },
  {
    "path": "tests/machines/parallel/war_of_the_ring.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass WarOfTheRing(StateChart):\n    class war(State.Parallel):\n        class frodos_quest(State.Compound):\n            shire = State(initial=True)\n            mordor = State()\n            mount_doom = State(final=True)\n\n            journey = shire.to(mordor)\n            destroy_ring = mordor.to(mount_doom)\n\n        class aragorns_path(State.Compound):\n            ranger = State(initial=True)\n            king = State(final=True)\n\n            coronation = ranger.to(king)\n\n        class gandalfs_defense(State.Compound):\n            rohan = State(initial=True)\n            gondor = State(final=True)\n\n            ride_to_gondor = rohan.to(gondor)\n"
  },
  {
    "path": "tests/machines/parallel/war_with_exit.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass WarWithExit(StateChart):\n    class war(State.Parallel):\n        class front_a(State.Compound):\n            fighting = State(initial=True)\n            won = State(final=True)\n\n            win_a = fighting.to(won)\n\n        class front_b(State.Compound):\n            holding = State(initial=True)\n            held = State(final=True)\n\n            hold_b = holding.to(held)\n\n    peace = State(final=True)\n    truce = war.to(peace)\n"
  },
  {
    "path": "tests/machines/showcase_actions.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ActionsSC(StateChart):\n    off = State(initial=True)\n    on = State()\n    done = State(final=True)\n\n    power_on = off.to(on)\n    shutdown = on.to(done)\n\n    def on_exit_off(self): ...\n    def on_enter_on(self): ...\n    def on_exit_on(self): ...\n    def on_enter_done(self): ...\n"
  },
  {
    "path": "tests/machines/showcase_compound.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CompoundSC(StateChart):\n    class active(State.Compound, name=\"Active\"):\n        idle = State(initial=True)\n        working = State()\n        begin = idle.to(working)\n\n    off = State(initial=True)\n    done = State(final=True)\n\n    turn_on = off.to(active)\n    turn_off = active.to(done)\n"
  },
  {
    "path": "tests/machines/showcase_deep_history.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass DeepHistorySC(StateChart):\n    class outer(State.Compound, name=\"Outer\"):\n        class inner(State.Compound, name=\"Inner\"):\n            a = State(initial=True)\n            b = State()\n            go = a.to(b)\n\n        start = State(initial=True)\n        enter_inner = start.to(inner)\n        h = HistoryState(type=\"deep\")\n\n    away = State(initial=True)\n\n    dive = away.to(outer)\n    leave = outer.to(away)\n    restore = away.to(outer.h)\n"
  },
  {
    "path": "tests/machines/showcase_guards.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass GuardSC(StateChart):\n    pending = State(initial=True)\n    approved = State(final=True)\n    rejected = State(final=True)\n\n    def is_valid(self):\n        return True\n\n    def is_invalid(self):\n        return False\n\n    review = pending.to(approved, cond=\"is_valid\") | pending.to(rejected, cond=\"is_invalid\")\n"
  },
  {
    "path": "tests/machines/showcase_history.py",
    "content": "from statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass HistorySC(StateChart):\n    class process(State.Compound, name=\"Process\"):\n        step1 = State(initial=True)\n        step2 = State()\n        advance = step1.to(step2)\n        h = HistoryState()\n\n    paused = State(initial=True)\n\n    pause = process.to(paused)\n    resume = paused.to(process.h)\n    begin = paused.to(process)\n"
  },
  {
    "path": "tests/machines/showcase_internal.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass InternalSC(StateChart):\n    monitoring = State(initial=True)\n    done = State(final=True)\n\n    def log_status(self): ...\n\n    check = monitoring.to.itself(internal=True, on=\"log_status\")\n    stop = monitoring.to(done)\n"
  },
  {
    "path": "tests/machines/showcase_parallel.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ParallelSC(StateChart):\n    class both(State.Parallel, name=\"Both\"):\n        class left(State.Compound, name=\"Left\"):\n            l1 = State(initial=True)\n            l2 = State(final=True)\n            go_l = l1.to(l2)\n\n        class right(State.Compound, name=\"Right\"):\n            r1 = State(initial=True)\n            r2 = State(final=True)\n            go_r = r1.to(r2)\n\n    start = State(initial=True)\n    end = State(final=True)\n\n    enter = start.to(both)\n    done_state_both = both.to(end)\n"
  },
  {
    "path": "tests/machines/showcase_parallel_compound.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ParallelCompoundSC(StateChart):\n    \"\"\"Parallel regions with a cross-boundary transition into an inner compound.\n\n    The ``rebuild`` transition targets ``pipeline.build`` — a compound state\n    inside a parallel region.  This is the exact pattern that triggers\n    `mermaid-js/mermaid#4052 <https://github.com/mermaid-js/mermaid/issues/4052>`_;\n    the Mermaid renderer works around it by redirecting the arrow to the\n    compound's initial child.\n\n    {statechart:rst}\n    \"\"\"\n\n    class pipeline(State.Parallel, name=\"Pipeline\"):\n        class build(State.Compound, name=\"Build\"):\n            compile = State(initial=True)\n            link = State(final=True)\n            do_build = compile.to(link)\n\n        class test(State.Compound, name=\"Test\"):\n            unit = State(initial=True)\n            e2e = State(final=True)\n            do_test = unit.to(e2e)\n\n    idle = State(initial=True)\n    review = State()\n\n    start = idle.to(pipeline)\n    done_state_pipeline = pipeline.to(review)\n    rebuild = review.to(pipeline.build)\n    accept = review.to(idle)\n"
  },
  {
    "path": "tests/machines/showcase_self_transition.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass SelfTransitionSC(StateChart):\n    counting = State(initial=True)\n    done = State(final=True)\n\n    increment = counting.to.itself()\n    stop = counting.to(done)\n"
  },
  {
    "path": "tests/machines/showcase_simple.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass SimpleSC(StateChart):\n    \"\"\"A simple three-state machine.\n\n    {statechart:rst}\n    \"\"\"\n\n    idle = State(initial=True)\n    running = State()\n    done = State(final=True)\n\n    start = idle.to(running)\n    finish = running.to(done)\n"
  },
  {
    "path": "tests/machines/transition_from_any.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass OrderWorkflow(StateChart):\n    pending = State(initial=True)\n    processing = State()\n    done = State()\n    completed = State(final=True)\n    cancelled = State(final=True)\n\n    process = pending.to(processing)\n    complete = processing.to(done)\n    finish = done.to(completed)\n    cancel = cancelled.from_.any()\n\n\nclass OrderWorkflowCompound(StateChart):\n    class active(State.Compound):\n        pending = State(initial=True)\n        processing = State()\n        done = State(final=True)\n\n        process = pending.to(processing)\n        complete = processing.to(done)\n\n    completed = State(final=True)\n    cancelled = State(final=True)\n    done_state_active = active.to(completed)\n    cancel = active.to(cancelled)\n"
  },
  {
    "path": "tests/machines/tutorial_coffee_order.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CoffeeOrder(StateChart):\n    pending = State(initial=True)\n    preparing = State()\n    ready = State()\n    picked_up = State(final=True)\n\n    start = pending.to(preparing)\n    finish = preparing.to(ready)\n    pick_up = ready.to(picked_up)\n"
  },
  {
    "path": "tests/machines/validators/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/validators/multi_validator.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass MultiValidator(StateChart):\n    \"\"\"Machine with multiple validators — first failure stops the chain.\"\"\"\n\n    idle = State(initial=True)\n    active = State(final=True)\n\n    start = idle.to(active, validators=[\"check_a\", \"check_b\"])\n\n    def check_a(self, **kwargs):\n        if not kwargs.get(\"a_ok\"):\n            raise ValueError(\"A failed\")\n\n    def check_b(self, **kwargs):\n        if not kwargs.get(\"b_ok\"):\n            raise ValueError(\"B failed\")\n"
  },
  {
    "path": "tests/machines/validators/order_validation.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass OrderValidation(StateChart):\n    \"\"\"StateChart with catch_errors_as_events=True (the default).\"\"\"\n\n    pending = State(initial=True)\n    confirmed = State()\n    cancelled = State(final=True)\n\n    confirm = pending.to(confirmed, validators=\"check_stock\")\n    cancel = confirmed.to(cancelled)\n\n    def check_stock(self, quantity=0, **kwargs):\n        if quantity <= 0:\n            raise ValueError(\"Quantity must be positive\")\n"
  },
  {
    "path": "tests/machines/validators/order_validation_no_error_events.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass OrderValidationNoErrorEvents(StateChart):\n    \"\"\"Same machine but with catch_errors_as_events=False.\"\"\"\n\n    catch_errors_as_events = False\n\n    pending = State(initial=True)\n    confirmed = State()\n    cancelled = State(final=True)\n\n    confirm = pending.to(confirmed, validators=\"check_stock\")\n    cancel = confirmed.to(cancelled)\n\n    def check_stock(self, quantity=0, **kwargs):\n        if quantity <= 0:\n            raise ValueError(\"Quantity must be positive\")\n"
  },
  {
    "path": "tests/machines/validators/validator_fallthrough.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ValidatorFallthrough(StateChart):\n    \"\"\"Machine with multiple transitions for the same event.\n\n    When the first transition's validator rejects, the exception propagates\n    immediately — the engine does NOT fall through to the next transition.\n    \"\"\"\n\n    idle = State(initial=True)\n    path_a = State(final=True)\n    path_b = State(final=True)\n\n    go = idle.to(path_a, validators=\"must_be_premium\") | idle.to(path_b)\n\n    def must_be_premium(self, **kwargs):\n        if not kwargs.get(\"premium\"):\n            raise PermissionError(\"Premium required\")\n"
  },
  {
    "path": "tests/machines/validators/validator_with_cond.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ValidatorWithCond(StateChart):\n    \"\"\"Machine that combines validators and conditions on the same transition.\"\"\"\n\n    idle = State(initial=True)\n    active = State(final=True)\n\n    start = idle.to(active, validators=\"check_auth\", cond=\"has_permission\")\n\n    has_permission = False\n\n    def check_auth(self, token=None, **kwargs):\n        if token != \"valid\":\n            raise PermissionError(\"Invalid token\")\n"
  },
  {
    "path": "tests/machines/validators/validator_with_error_transition.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ValidatorWithErrorTransition(StateChart):\n    \"\"\"Machine with both a validator and an error.execution transition.\n\n    The error.execution transition should NOT be triggered by validator\n    rejection — only by actual execution errors in actions.\n    \"\"\"\n\n    idle = State(initial=True)\n    active = State()\n    error_state = State(final=True)\n\n    start = idle.to(active, validators=\"check_input\")\n    do_work = active.to.itself(on=\"risky_action\")\n    error_execution = active.to(error_state)\n\n    def check_input(self, value=None, **kwargs):\n        if value is None:\n            raise ValueError(\"Input required\")\n\n    def risky_action(self, **kwargs):\n        raise RuntimeError(\"Boom\")\n"
  },
  {
    "path": "tests/machines/workflow/__init__.py",
    "content": ""
  },
  {
    "path": "tests/machines/workflow/campaign_machine.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CampaignMachine(StateChart):\n    \"A workflow machine\"\n\n    draft = State(initial=True)\n    producing = State(\"Being produced\")\n    closed = State(final=True)\n\n    add_job = draft.to(draft) | producing.to(producing)\n    produce = draft.to(producing)\n    deliver = producing.to(closed)\n"
  },
  {
    "path": "tests/machines/workflow/campaign_machine_with_validator.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CampaignMachineWithValidator(StateChart):\n    \"A workflow machine\"\n\n    draft = State(initial=True)\n    producing = State(\"Being produced\")\n    closed = State(final=True)\n\n    add_job = draft.to(draft) | producing.to(producing)\n    produce = draft.to(producing, validators=\"can_produce\")\n    deliver = producing.to(closed)\n\n    def can_produce(*args, **kwargs):\n        if \"goods\" not in kwargs:\n            raise LookupError(\"Goods not found.\")\n"
  },
  {
    "path": "tests/machines/workflow/campaign_machine_with_values.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass CampaignMachineWithValues(StateChart):\n    \"A workflow machine\"\n\n    draft = State(initial=True, value=1)\n    producing = State(\"Being produced\", value=2)\n    closed = State(value=3, final=True)\n\n    add_job = draft.to(draft) | producing.to(producing)\n    produce = draft.to(producing)\n    deliver = producing.to(closed)\n"
  },
  {
    "path": "tests/machines/workflow/reverse_traffic_light.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\nclass ReverseTrafficLightMachine(StateChart):\n    \"A traffic light machine\"\n\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n\n    stop = red.from_(yellow, green, red)\n    cycle = green.from_(red) | yellow.from_(green) | red.from_(yellow) | red.from_.itself()\n"
  },
  {
    "path": "tests/models.py",
    "content": "class MyModel:\n    \"A class that can be used to hold arbitrary key/value pairs as attributes.\"\n\n    def __init__(self, **kwargs):\n        for k, v in kwargs.items():\n            setattr(self, k, v)\n        super().__init__()\n\n    def __repr__(self):\n        return f\"{type(self).__name__}({self.__dict__!r})\"\n"
  },
  {
    "path": "tests/scrape_images.py",
    "content": "import os\nimport re\n\nfrom statemachine.contrib.diagram import DotGraphMachine\nfrom statemachine.factory import StateMachineMetaclass\n\nfrom .helpers import import_module_by_path\n\n\nclass MachineScraper:\n    \"\"\"Scrapes images of the statemachines defined into the examples for the gallery\"\"\"\n\n    re_replace_png_extension = re.compile(r\"\\.png$\")\n\n    def __init__(self, project_root):\n        self.project_root = project_root\n        sanitized_path = re.escape(os.path.abspath(self.project_root))\n        self.re_machine_module_name = re.compile(f\"{sanitized_path}/(.*)\\\\.py$\")\n        self.seen = set()\n\n    def __repr__(self):\n        return \"MachineScraper\"\n\n    def _get_module(self, src_file):\n        module_name = self.re_machine_module_name.findall(src_file)\n        if len(module_name) != 1:\n            return\n\n        return import_module_by_path(module_name[0])\n\n    def generate_image(self, sm_class, original_path):\n        image_path = self.re_replace_png_extension.sub(\".svg\", original_path)\n\n        svg = DotGraphMachine(sm_class).get_graph().create_svg().decode()\n        with open(image_path, \"w\") as f:\n            f.write(svg)\n        return image_path\n\n    def __call__(self, block, block_vars, gallery_conf):\n        \"Find all PNG files in the directory of this example.\"\n        from sphinx_gallery.scrapers import figure_rst\n\n        module = self._get_module(block_vars[\"src_file\"])\n        if module is None:\n            return \"\"\n\n        image_names = []\n        image_path_iterator = block_vars[\"image_path_iterator\"]\n        for key, value in module.__dict__.items():\n            unique_key = f\"{module.__name__}.{key}\"\n\n            if (\n                key.startswith(\"__\")\n                or unique_key in self.seen\n                or not isinstance(value, StateMachineMetaclass)\n                or value._abstract\n            ):\n                continue\n\n            self.seen.add(unique_key)\n            image_names.append(self.generate_image(value, image_path_iterator.next()))\n\n        # Use the `figure_rst` helper function to generate rST for image files\n        return figure_rst(image_names, gallery_conf[\"src_dir\"])\n"
  },
  {
    "path": "tests/scxml/__init__.py",
    "content": ""
  },
  {
    "path": "tests/scxml/conftest.py",
    "content": "from pathlib import Path\n\nimport pytest\n\nCURRENT_DIR = Path(__file__).parent\nTESTCASES_DIR = CURRENT_DIR\n\n# xfail sets — tests that fail identically on both engines\nXFAIL_BOTH = {\n    # mandatory — invoke-related (still failing)\n    \"test187\",  # delayed <send> cancelled when sending session terminates before delay\n    \"test229\",  # autoforward: parent forwards events to child automatically\n    \"test236\",  # done.invoke.id arrives after all other child-generated events\n    \"test240\",  # datamodel values passed to invoked child via namelist and <param>\n    \"test554\",  # invocation cancelled when evaluation of invoke arguments errors\n    # optional — ecmascript/JSON datamodel\n    \"test201\",  # JSON data in <data> parsed in ecmascript datamodel\n    \"test446\",  # JSON data loaded via src attribute parsed as array\n    # optional — Basic HTTP Event I/O Processor\n    \"test509\",  # basic HTTP event I/O processor: send with target\n    \"test510\",  # basic HTTP event I/O processor: send without target\n    \"test518\",  # basic HTTP event I/O processor: event field in POST\n    \"test519\",  # basic HTTP event I/O processor: namelist data in POST body\n    \"test520\",  # basic HTTP event I/O processor: <param> data in POST body\n    \"test522\",  # basic HTTP event I/O processor: <content> in POST body\n    \"test531\",  # basic HTTP event I/O processor: POST response populates _event.data\n    \"test532\",  # basic HTTP event I/O processor: error.communication on bad target\n    \"test534\",  # basic HTTP event I/O processor: #_scxml_sessionid target\n    # optional — data/content handling\n    \"test557\",  # XML data in <send> content becomes DOM-like object (python datamodel)\n    \"test558\",  # text data in <send> preserves string type (python datamodel)\n    \"test561\",  # XML content in events creates DOM object\n    \"test567\",  # HTTP message parameters populate _event.data\n    \"test577\",  # <send> without target causes error.communication\n}\nXFAIL_SYNC_ONLY: set[str] = set()\nXFAIL_ASYNC_ONLY: set[str] = set()\n\nXFAIL_SYNC = XFAIL_BOTH | XFAIL_SYNC_ONLY\nXFAIL_ASYNC = XFAIL_BOTH | XFAIL_ASYNC_ONLY\n\n\n@pytest.fixture(scope=\"session\")\ndef should_generate_debug_diagram(request):\n    return request.config.getoption(\"--gen-diagram\")\n\n\ndef compute_testcase_marks(testcase_path: Path, is_async: bool) -> list[pytest.MarkDecorator]:\n    marks: list[pytest.MarkDecorator] = [pytest.mark.scxml]\n    test_id = testcase_path.stem\n    xfail_set = XFAIL_ASYNC if is_async else XFAIL_SYNC\n    if test_id in xfail_set:\n        marks.append(pytest.mark.xfail)\n    return marks\n\n\ndef pytest_generate_tests(metafunc):\n    if \"testcase_path\" not in metafunc.fixturenames:\n        return\n\n    is_async = \"async\" in metafunc.function.__name__\n\n    metafunc.parametrize(\n        \"testcase_path\",\n        [\n            pytest.param(\n                testcase_path,\n                id=str(testcase_path.relative_to(TESTCASES_DIR)),\n                marks=compute_testcase_marks(testcase_path, is_async),\n            )\n            for testcase_path in TESTCASES_DIR.glob(\"**/*.scxml\")\n            if \"sub\" not in testcase_path.name\n        ],\n    )\n"
  },
  {
    "path": "tests/scxml/test_microwave.py",
    "content": "import pytest\nfrom statemachine.io.scxml.processor import SCXMLProcessor\nfrom statemachine.state import State\n\nfrom statemachine import StateChart\n\n\"\"\"\nThe <initial> specifies a transition that specifies the default child initial states.\n\nThe problem is that the transition must occur, and the state itself is not marked\nas `initial` in the model.\n\n\"\"\"\n\nMICROWAVE_SCXML = \"\"\"\n<scxml initial=\"unplugged\">\n  <state id=\"unplugged\">\n    <transition event=\"plug-in\" target=\"plugged-in\"/>\n  </state>\n\n  <state id=\"plugged-in\">\n    <initial>\n      <transition target=\"idle\"/>\n    </initial>\n    <state id=\"idle\">\n      <transition event=\"start\" target=\"cooking\"/>\n    </state>\n    <state id=\"cooking\">\n      <transition event=\"stop\" target=\"idle\"/>\n    </state>\n    <transition event=\"unplug\" target=\"unplugged\"/>\n  </state>\n</scxml>\n\"\"\"\n\n\n@pytest.mark.scxml()\ndef test_microwave_scxml():\n    processor = SCXMLProcessor()\n    processor.parse_scxml(\"microwave\", MICROWAVE_SCXML)\n    sm = processor.start()\n\n    assert \"unplugged\" in sm.current_state_value\n    sm.send(\"plug-in\")\n\n    assert \"idle\" in sm.current_state_value\n    assert \"plugged-in\" in sm.current_state_value\n\n    sm.send(\"start\")\n\n    assert \"cooking\" in sm.current_state_value\n    assert \"idle\" not in sm.current_state_value\n    assert \"plugged-in\" in sm.current_state_value\n\n    sm.send(\"unplug\")\n\n    assert \"unplugged\" in sm.current_state_value\n    assert \"idle\" not in sm.current_state_value\n    assert \"plugged-in\" not in sm.current_state_value\n    assert \"cooking\" not in sm.current_state_value\n\n\nclass TestMicrowave:\n    @pytest.fixture()\n    def microwave_cls(self):\n        class MicroWave(StateChart):\n            door_closed: bool = True\n\n            class oven(State.Parallel, name=\"Microwave oven\"):\n                class engine(State.Compound):\n                    off = State(initial=True)\n\n                    class on(State.Compound):\n                        idle = State(initial=True)\n                        cooking = State()\n\n                        idle.to(cooking, cond=\"In('closed')\")\n                        cooking.to(idle, cond=\"In('open')\")\n                        time = cooking.to.itself(internal=True, on=\"increment_timer\")\n\n                        def increment_timer(self):\n                            self.timer += 1\n\n                    assert isinstance(on, State)  # so mypy stop complaining\n                    on.to(off, event=\"turn-off\")\n                    off.to(on, event=\"turn-on\")\n                    on.to(off, cond=\"timer >= cook_time\")  # eventless transition\n\n                class door(State.Compound):\n                    closed = State(initial=True)\n                    open = State()\n\n                    closed.to(open, event=\"door.open\")\n                    open.to(closed, event=\"door.close\")\n\n                    def on_enter_open(self):\n                        self.door_closed = False\n\n                    def on_enter_closed(self):\n                        self.door_closed = True\n\n            def __init__(self):\n                self.cook_time = 5\n                self.timer = 0\n                super().__init__()\n\n        return MicroWave\n\n    def test_microwave(self, microwave_cls):\n        sm = microwave_cls()\n\n        assert {\"door\", \"closed\", \"oven\", \"engine\", \"off\"} == {*sm.current_state_value}\n        assert sm.door_closed is True\n\n        sm.send(\"turn-on\")\n        assert {\"door\", \"closed\", \"oven\", \"engine\", \"on\", \"cooking\"} == {*sm.current_state_value}\n\n        sm.send(\"door.open\")\n        assert {\"door\", \"open\", \"oven\", \"engine\", \"on\", \"idle\"} == {*sm.current_state_value}\n        assert sm.door_closed is False\n\n        sm.send(\"door.close\")\n        assert {\"door\", \"closed\", \"oven\", \"engine\", \"on\", \"cooking\"} == {*sm.current_state_value}\n        assert sm.door_closed is True\n\n        for _ in range(5):\n            sm.send(\"time\")\n\n        assert {\"door\", \"closed\", \"oven\", \"engine\", \"off\"} == {*sm.current_state_value}\n        assert sm.door_closed is True\n"
  },
  {
    "path": "tests/scxml/test_scxml_cases.py",
    "content": "import time\nfrom pathlib import Path\n\nimport pytest\nfrom statemachine.io.scxml.processor import SCXMLProcessor\n\nfrom statemachine import StateChart\n\n\"\"\"\nTest cases as defined by W3C SCXML Test Suite\n\n- https://www.w3.org/Voice/2013/scxml-irp/\n- https://alexzhornyak.github.io/SCXML-tutorial/Tests/ecma/W3C/Mandatory/Auto/report__USCXML_2_0_0___msvc2015_32bit__Win7_1.html\n- https://github.com/alexzhornyak/PyBlendSCXML/tree/master/w3c_tests\n- https://github.com/jbeard4/SCION/wiki/Pseudocode-for-SCION-step-algorithm\n\n\"\"\"  # noqa: E501\n\n\nclass AsyncListener:\n    \"\"\"No-op async listener to trigger AsyncEngine selection.\"\"\"\n\n    async def on_enter_state(\n        self, **kwargs\n    ): ...  # No-op: presence of async callback triggers AsyncEngine selection\n\n\ndef _run_scxml_testcase(\n    testcase_path: Path,\n    should_generate_debug_diagram,\n    *,\n    async_mode: bool = False,\n) -> StateChart:\n    \"\"\"Shared logic for sync and async SCXML test variants.\n\n    Parses the SCXML file, starts the state machine, and asserts the final\n    configuration contains ``pass``.  Returns the SM instance.\n    \"\"\"\n    from statemachine.contrib.diagram import DotGraphMachine\n\n    listeners: list = []\n    if async_mode:\n        listeners.append(AsyncListener())\n    processor = SCXMLProcessor()\n    processor.parse_scxml_file(testcase_path)\n\n    sm = processor.start(listeners=listeners)\n    if should_generate_debug_diagram:\n        DotGraphMachine(sm).get_graph().write_png(\n            testcase_path.parent / f\"{testcase_path.stem}.png\"\n        )\n    assert isinstance(sm, StateChart)\n    return sm\n\n\ndef _assert_passed(sm: StateChart):\n    assert isinstance(sm, StateChart)\n    assert \"pass\" in {s.id for s in sm.configuration}\n\n\ndef _wait_for_completion(sm: StateChart, timeout_s: float = 5.0):\n    \"\"\"Poll the processing loop until the SM reaches a final state or times out.\"\"\"\n    deadline = time.monotonic() + timeout_s\n    while not sm.is_terminated and time.monotonic() < deadline:\n        time.sleep(0.02)\n        # Trigger processing loop to handle events from invoke threads\n        sm._engine.processing_loop()\n\n\ndef test_scxml_usecase_sync(testcase_path: Path, should_generate_debug_diagram, caplog):\n    sm = _run_scxml_testcase(\n        testcase_path,\n        should_generate_debug_diagram,\n        async_mode=False,\n    )\n    _wait_for_completion(sm)\n    _assert_passed(sm)\n\n\nasync def _async_wait_for_completion(sm: StateChart, timeout_s: float = 5.0):\n    \"\"\"Poll the processing loop until the SM reaches a final state or times out.\"\"\"\n    import asyncio\n\n    deadline = time.monotonic() + timeout_s\n    while not sm.is_terminated and time.monotonic() < deadline:\n        await asyncio.sleep(0.02)\n        await sm._engine.processing_loop()\n\n\n@pytest.mark.asyncio()\nasync def test_scxml_usecase_async(testcase_path: Path, should_generate_debug_diagram, caplog):\n    sm = _run_scxml_testcase(\n        testcase_path,\n        should_generate_debug_diagram,\n        async_mode=True,\n    )\n    # In async context, the engine only queued __initial__ during __init__.\n    # Activate now within the running event loop.\n    await sm.activate_initial_state()\n    await _async_wait_for_completion(sm)\n    _assert_passed(sm)\n"
  },
  {
    "path": "tests/scxml/w3c/LICENSE",
    "content": "BSD 3-Clause License\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of [Your Name or Your Organization] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test144.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that events are inserted into the queue in the order in which they are raised.  If\nfoo occurs before bar, success, otherwise failure -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n      <raise event=\"bar\" />\n    </onentry>\n    <transition event=\"foo\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n\n  </state>\n\n  <state id=\"s1\">\n    <transition event=\"bar\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test145.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test eventless transition -->\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest145\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--self\n\tdefined test, to test spontaneous transitions after event driven transitions-->\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<raise event=\"foo\" />\n\t\t\t<raise event=\"bar\" />\n\t\t</onentry>\n\t\t<transition event=\"foo\" target=\"s1\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<state id=\"s1\">\n\t\t<transition event=\"bar\" target=\"s2\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<state id=\"s2\">\n\t\t<transition target=\"pass\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test147.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that the first clause that evaluates to true - and only that clause - is executed.\nOnly one event should be raised, and it should be bar -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n    initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n    <datamodel>\n        <data id=\"Var1\" expr=\"0\" />\n    </datamodel>\n    <state id=\"s0\">\n        <onentry>\n            <if cond=\"false\">\n                <raise event=\"foo\" />\n                <assign location=\"Var1\" expr=\"Var1 + 1\" />\n                <elseif cond=\"true\" />\n                <raise event=\"bar\" />\n                <assign location=\"Var1\" expr=\"Var1 + 1\" />\n                <else />\n                <raise event=\"baz\" />\n                <assign location=\"Var1\" expr=\"Var1 + 1\" />\n            </if>\n            <raise event=\"bat\" />\n        </onentry>\n        <transition event=\"bar\" cond=\"Var1==1\" target=\"pass\" />\n        <transition event=\"*\" target=\"fail\" />\n    </state>\n    <final id=\"pass\">\n        <onentry>\n            <log label=\"Outcome\" expr=\"'pass'\" />\n        </onentry>\n    </final>\n    <final id=\"fail\">\n        <onentry>\n            <log label=\"Outcome\" expr=\"'fail'\" />\n        </onentry>\n    </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test148.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that the else clause executes if <if> and <elseif> evaluate to false.\nBaz should be the only event generated by the <if>.  bat is raised to catch the case where the <else> clause\nfails and baz is not generated, i.e. it makes sure that the test doesn't hang.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\"/>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <if cond=\"false\">\n        <raise event=\"foo\"/>\n        <assign location=\"Var1\" expr=\"Var1 + 1\"/>\n        <elseif cond=\"false\"/>\n        <raise event=\"bar\"/>\n        <assign location=\"Var1\" expr=\"Var1 + 1\"/>\n        <else/>\n        <raise event=\"baz\"/>\n        <assign location=\"Var1\" expr=\"Var1 + 1\"/>\n      </if>\n      <raise event=\"bat\"/>\n    </onentry>\n    <transition event=\"baz\" cond=\"Var1==1\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test149.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that neither if clause executes, so that bat is the only event raised. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"c89\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <if cond=\"false\">\n        <raise event=\"foo\" />\n        <assign location=\"Var1\" expr=\"Var1 + 1\" />\n        <elseif cond=\"false\" />\n        <raise event=\"bar\" />\n        <assign location=\"Var1\" expr=\"Var1 + 1\" />\n      </if>\n      <raise event=\"bat\" />\n    </onentry>\n    <transition event=\"bat\" cond=\"Var1 == 0\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test150.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that foreach causes a new variable to be declared if 'item' doesn't already exist.  Also\ntest that it will use an existing var if it does exist. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"c89\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" />\n    <data id=\"Var2\" />\n    <data id=\"Var3\">\n      [1,2,3]\n    </data>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <!-- first use declared variables -->\n      <foreach item=\"Var1\" index=\"Var2\" array=\"Var3\" />\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"error\" target=\"fail\" />\n    <transition event=\"*\" target=\"s1\" />\n  </state>\n  <state id=\"s1\">\n    <onentry>\n      <!-- now use undeclared variables -->\n      <foreach item=\"Var4\" index=\"Var5\" array=\"Var3\" />\n      <raise event=\"bar\" />\n    </onentry>\n    <transition event=\"error\" target=\"fail\" />\n    <transition event=\"*\" target=\"s2\" />\n  </state>\n  <state id=\"s2\">\n    <!-- check that var4 is bound -->\n    <transition cond=\"Var4\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test151.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest151\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n\tthat foreach causes a new variable to be declared if 'item' doesn't already exist.  Also\ntest that it will use an existing var if it does exist.-->\n\t<datamodel>\n\t\t<data id=\"Var1\" />\n\t\t<data id=\"Var2\" />\n\t\t<data id=\"Var3\">\n\t\t\t[1,2,3]\n\t\t</data>\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<onentry><!--first\n\t\t\tuse declared variables-->\n\t\t\t<foreach array=\"Var3\" index=\"Var2\" item=\"Var1\" />\n\t\t\t<raise event=\"foo\" />\n\t\t</onentry>\n\t\t<transition event=\"error\" target=\"fail\" />\n\t\t<transition event=\"*\" target=\"s1\" />\n\t</state>\n\t<state id=\"s1\">\n\t\t<onentry><!--now\n\t\t\tuse undeclared variables-->\n\t\t\t<foreach array=\"Var3\" index=\"Var5\" item=\"Var4\" />\n\t\t\t<raise event=\"bar\" />\n\t\t</onentry>\n\t\t<transition event=\"error\" target=\"fail\" />\n\t\t<transition event=\"*\" target=\"s2\" />\n\t</state>\n\t<state id=\"s2\"><!--check\n\t\tthat var5 is bound-->\n\t\t<transition cond=\"Var5\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test152.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest152\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n\tthat an illegal array or item value causes error.execution and results in executable content\nnot being executed.-->\n\t<datamodel>\n\t\t<data expr=\"0\" id=\"Var1\" />\n\t\t<data id=\"Var2\" />\n\t\t<data id=\"Var3\" />\n\t\t<data expr=\"7\" id=\"Var4\" />\n\t\t<data id=\"Var5\">\n\t\t\t[1,2,3]\n\t\t</data>\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<onentry><!--invalid\n\t\t\tarray, legal item-->\n\t\t\t<foreach array=\"Var4\" index=\"Var3\" item=\"Var2\">\n\t\t\t\t<assign expr=\"Var1 + 1\" location=\"Var1\" />\n\t\t\t</foreach>\n\t\t\t<raise event=\"foo\" />\n\t\t</onentry>\n\t\t<transition event=\"error.execution\" target=\"s1\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<state id=\"s1\">\n\t\t<onentry><!--illegal\n\t\t\titem, legal array-->\n\t\t\t<foreach array=\"Var5\" index=\"Var3\" item=\"'continue'\">\n\t\t\t\t<assign expr=\"Var1 + 1\" location=\"Var1\" />\n\t\t\t</foreach>\n\t\t\t<raise event=\"bar\" />\n\t\t</onentry>\n\t\t<transition event=\"error.execution\" target=\"s2\" />\n\t\t<transition event=\"bar\" target=\"fail\" />\n\t</state>\n\t<state id=\"s2\"><!--check\n\t\tthat var1 has its original value (so executable content never got executed-->\n\t\t<transition cond=\"Var1==0\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test153.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that foreach goes over the array in the right order.  since the array contains 1 2 3, we\ncompare the current\nvalue with the previous value, which is stored in var1. The current value should always be larger.\nIf\nit ever isn't, set Var4 to 0, indicating failure -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n\tinitial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n\t<datamodel>\n\t\t<data id=\"Var1\" expr=\"0\" />\n\t\t<!-- contains the previous value -->\n\t\t<data id=\"Var2\" />\n\t\t<!-- the item which will contain the current value -->\n\t\t<data id=\"Var3\">\n\t\t\t[1,2,3]\n\t\t</data>\n\t\t<data id=\"Var4\" expr=\"1\" />\n\t\t<!-- 1 if success, 0 if failure -->\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<foreach item=\"Var2\" array=\"Var3\">\n\t\t\t\t<if cond=\"Var1&lt;Var2\">\n\t\t\t\t\t<assign location=\"Var1\" expr=\"Var2\" />\n\t\t\t\t\t<else />\n\t\t\t\t\t<!-- values are out of order, record failure -->\n\t\t\t\t\t<assign location=\"Var4\" expr=\"0\" />\n\t\t\t\t</if>\n\t\t\t</foreach>\n\t\t</onentry>\n\t\t<!-- check that var1 has its original value  -->\n\t\t<transition cond=\"Var4==0\" target=\"fail\" />\n\t\t<transition target=\"pass\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'pass'\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'fail'\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test155.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that foreach executes the executable content once for each item in the list '(1,2,3)'. The executable\ncontent sums the items into var1 so it should be 6 at the end -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\"/>\n    <data id=\"Var2\"/>\n    <data id=\"Var3\">\n    [1,2,3]\n    </data>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <foreach item=\"Var2\" array=\"Var3\">\n        <assign location=\"Var1\" expr=\"Var1 + Var2\"/>\n      </foreach>\n    </onentry>\n    <transition cond=\"Var1==6\" target=\"pass\"/>\n    <transition target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test156.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that an error causes the foreach to stop execution.  The second piece of executable content\nshould cause an error, so var1 should be incremented only once -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n    <data id=\"Var2\" />\n    <data id=\"Var3\">\n      [1,2,3]\n    </data>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <foreach item=\"Var2\" array=\"Var3\">\n        <assign location=\"Var1\" expr=\"Var1 + 1\" />\n        <!-- assign an illegal value to a non-existent var -->\n        <assign location=\"Var5\" expr=\"return\" />\n      </foreach>\n    </onentry>\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test158.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that executable content executes in document order.  if event1 occurs then event2, succeed,\notherwise fail -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"event1\" />\n      <raise event=\"event2\" />\n    </onentry>\n    <transition event=\"event1\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition event=\"event2\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test159.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that any error raised by an element of executable content causes all subsequent elements to be skipped.\nThe send tag will raise an error so var1 should not be incremented.  If it is fail, otherwise succeed -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\"/>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"thisWillFail\" target=\"baz\"/>\n      <assign location=\"Var1\" expr=\"Var1 + 1\"/>\n    </onentry>\n    <transition cond=\"Var1==1\" target=\"fail\"/>\n    <transition target=\"pass\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test172.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that eventexpr uses the current value of var1, not its initial value  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"'event1'\"/>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"'event2'\"/>\n      <send eventexpr=\"Var1\"/>\n    </onentry>\n    <transition event=\"event2\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test173.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that targetexpr uses the current value of var1, not its initial value\n(If it uses the initial value, it will generate an error.  If it uses the current value, event1 will be raised  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"27\"/>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"'#_internal'\"/>\n      <send targetexpr=\"Var1\" event=\"event1\"/>\n    </onentry>\n    <transition event=\"event1\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test174.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that typeexpr uses the current value of var1, not its initial value\n(If it uses the initial value, it will generate an error.  If it uses the current value, event1 will be raised  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"27\"/>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"'http://www.w3.org/TR/scxml/#SCXMLEventProcessor'\"/>\n      <send typeexpr=\"Var1\" event=\"event1\"/>\n    </onentry>\n    <transition event=\"event1\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test175.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"ecmascript\" initial=\"s0\" name=\"ScxmlTest175\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we\n\ttest that delayexpr uses the current value of var1, not its initial value\n(If it uses the initial value, event2 will be generated first, before event1.  If it uses the\n\tcurrent value,\nevent1 will be raised first.  Succeed if event1 occurs before event2, otherwise fail-->\n\t<datamodel>\n\t\t<data expr=\"'0s'\" id=\"Var1\" />\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<assign expr=\"'1s'\" location=\"Var1\" />\n\t\t\t<send delayexpr=\"Var1\" event=\"event2\" />\n\t\t\t<send delayexpr=\"'.5s'\" event=\"event1\" />\n\t\t</onentry>\n\t\t<transition event=\"event1\" target=\"s1\" />\n\t\t<transition event=\"event2\" target=\"fail\" />\n\t</state>\n\t<state id=\"s1\">\n\t\t<transition event=\"event2\" target=\"pass\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test176.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that <param> uses the current value of var1, not its initial value.  If the value of\naParam in event1 is 2 so that var2 gets set to 2, success, otherwise failure  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n    <data id=\"Var2\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"2\" />\n      <send event=\"event1\">\n        <param name=\"aParam\" expr=\"Var1\" />\n      </send>\n    </onentry>\n    <transition event=\"event1\" target=\"s1\">\n      <assign location=\"Var2\" expr=\"_event.data.aParam\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition cond=\"Var2==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test179.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that <content> can be used to populate body of a message -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"event1\">\n        <content>123</content>\n      </send>\n    </onentry>\n    <transition event=\"event1\" cond=\"_event.data == 123\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test183.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that <send> stores the value of the sendid in idlocation.  If it does,\nvar1 has a value and we pass.  Otherwise we fail  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"event1\" idlocation=\"Var1\" />\n    </onentry>\n    <transition cond=\"Var1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test185.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that <send> respects the delay specification.  If it does, event1 arrives before event2\n and we pass.  Otherwise we fail  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"event2\" delayexpr=\"'1s'\"/>\n      <send event=\"event1\"/>\n    </onentry>\n    <transition event=\"event1\" target=\"s1\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <state id=\"s1\">\n    <transition event=\"event2\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test186.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that <send> evals its args when it is evaluated, not when the delay interval expires\nand the\nmessage is actually sent.  If it does, aParam will have the value of 1 (even though var1 has been\nincremented\nin the interval.)  If var2 ends up == 1, we pass.  Otherwise we fail  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n    <data id=\"Var2\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"event1\" delayexpr=\"'1s'\">\n        <param name=\"aParam\" expr=\"Var1\" />\n      </send>\n      <assign location=\"Var1\" expr=\"2\" />\n    </onentry>\n    <transition event=\"event1\" target=\"s1\">\n      <assign location=\"Var2\" expr=\"_event.data.aParam\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition cond=\"Var2==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test187.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that delayed <send> is not sent if the sending session terminates.  In this case,\na subscript is invoked which sends the event childToParent delayed by 1 second, and then\nterminates.  The\nparent session, should not receive childToParent. If it does, we fail.  Otherwise the\n10 sec timer expires and we pass -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delayexpr=\"'1s'\" />\n    </onentry>\n    <invoke type=\"scxml\">\n      <content>\n        <!-- exit before the delayed send can execute -->\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"ecmascript\">\n          <state id=\"sub0\">\n            <onentry>\n              <send event=\"childToParent\" target=\"#_parent\" delayexpr=\"'.5s'\" />\n            </onentry>\n            <transition target=\"subFinal\" />\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"childToParent\" target=\"fail\" />\n    <transition event=\"timeout\" target=\"pass\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test189.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"ecmascript\" initial=\"s0\" name=\"ScxmlTest189\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we\n\ttest that #_internal as a target of <send> puts the event on the internal queue.  If it does,\nevent1 will be processed before event2, because event1 is added to the internal queue while event2\n\tis\nadded to the external queue (event though event2 is generated first)-->\n\t<state id=\"s0\"><!--once\n\t\twe've entered the state, we should check for internal events first-->\n\t\t<onentry><!--goes\n\t\t\tto the external queue-->\n\t\t\t<send event=\"event2\" /><!--to\n\t\t\tthe internal queue-->\n\t\t\t<send event=\"event1\" target=\"#_internal\" />\n\t\t</onentry>\n\t\t<transition event=\"event1\" target=\"pass\" />\n\t\t<transition event=\"event2\" target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test190.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"ecmascript\" initial=\"s0\" name=\"ScxmlTest190\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we\n\ttest that #_scxml_sessionid as a target of <send> puts the event on the external queue.  If it\n\tdoes,\nevent1 will be processed before event2, because event1 is added to the internal queue while event2\n\tis\nadded to the external queue (event though event2 is generated first).  we have to make sure that\n\tevent2\nis actually delivered.  The delayed <send> makes sure another event is generated (so the test\n\tdoesn't hang)-->\n\t<datamodel>\n\t\t<data expr=\"'#_scxml_'\" id=\"Var1\" />\n\t\t<data expr=\"_sessionid\" id=\"Var2\" />\n\t</datamodel>\n\t<state id=\"s0\"><!--once\n\t\twe've entered the state, we should check for internal events first-->\n\t\t<onentry>\n\t\t\t<assign expr=\"Var1 + Var2\" location=\"Var1\" /><!--goes\n\t\t\tto the external queue-->\n\t\t\t<send event=\"event2\" targetexpr=\"Var1\" /><!--to\n\t\t\tthe internal queue-->\n\t\t\t<raise event=\"event1\" /><!--this\n\t\t\tshould get added to the external queue after event2-->\n\t\t\t<send event=\"timeout\" />\n\t\t</onentry>\n\t\t<transition event=\"event1\" target=\"s1\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<state id=\"s1\"><!--now\n\t\tcheck that we get event2 and not a timeout-->\n\t\t<transition event=\"event2\" target=\"pass\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test191.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that #_parent works as  a target of  <send> .  a subscript is invoked and sends the event\nchildToParent to its parent session (ths session) using #_parent as the target. If we get this\nevent, we\npass, otherwise we fail.  The timer insures that some event is generated and that the test does not\nhang. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <invoke type=\"scxml\">\n      <content>\n        <!-- send an event to the parent session using #_parent as the target -->\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"ecmascript\">\n          <state id=\"sub0\">\n            <onentry>\n              <send event=\"childToParent\" target=\"#_parent\" />\n            </onentry>\n            <transition target=\"subFinal\" />\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"childToParent\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test192.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that #_invokeid works as  a target of  <send> .  A child  script is invoked and sends us\nchildToParent once its running.  Then we send it the event parentToChild using its invokeid as the\ntarget.\nIf it receives this event, it sends sends the event eventReceived to its parent session (ths\nsession).\nIf we get this event, we pass, otherwise the child script eventually times out sends invoke.done\nand we fail.\nWe also set a timeout in this process to make sure the test doesn't hang  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <invoke type=\"scxml\" id=\"invokedChild\">\n      <content>\n        <!-- let the parent session know we're running by sending childToParent, then wait for\n        parentToChild.\n       If we get it, send eventReceived.  If we don't we eventually time out -->\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"ecmascript\">\n          <state id=\"sub0\">\n            <onentry>\n              <send event=\"childToParent\" target=\"#_parent\" />\n              <send event=\"timeout\" delay=\"3s\" />\n            </onentry>\n            <transition event=\"parentToChild\" target=\"subFinal\">\n              <send target=\"#_parent\" event=\"eventReceived\" />\n            </transition>\n            <transition event=\"timeout\" target=\"subFinal\" />\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"timeout\" target=\"fail\" />\n    <transition event=\"done.invoke\" target=\"fail\" />\n    <state id=\"s01\">\n      <transition event=\"childToParent\" target=\"s02\">\n        <send target=\"#_invokedChild\" event=\"parentToChild\" />\n      </transition>\n    </state>\n    <state id=\"s02\">\n      <transition event=\"eventReceived\" target=\"pass\" />\n    </state>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test194.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that specifying an illegal target for <send> causes the event error.execution to be raised.  If it does,\nwe succeed.  Otherwise we eventually timeout and fail.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <!-- should cause an error -->\n      <send target=\"baz\" event=\"event2\"/>\n      <!-- this will get added to the external event queue after the error has been raised -->\n      <send event=\"timeout\"/>\n    </onentry>\n    <!-- once we've entered the state, we should check for internal events first -->\n    <transition event=\"error.execution\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test198.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that if type is not provided <send> uses the scxml event i/o processor.  The only way to\ntell\nwhat processor was used is to look at the origintype of the resulting event  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"event1\" />\n      <send event=\"timeout\" />\n    </onentry>\n    <transition event=\"event1\"\n      cond=\" _event.origintype == 'http://www.w3.org/TR/scxml/#SCXMLEventProcessor'\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test199.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that using an invalid send type results in error.execution -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send type=\"27\" event=\"event1\" />\n      <send event=\"timeout\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test200.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that the processor supports the scxml event i/o processor -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" event=\"event1\" />\n      <send event=\"timeout\" />\n    </onentry>\n    <transition event=\"event1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test205.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that the processor doesn't  change the message.  We can't test that it never does this,\nbut\nat least we can check that the event name and included data are the same as we sent.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"event1\">\n        <param name=\"aParam\" expr=\"1\" />\n      </send>\n      <send event=\"timeout\" />\n    </onentry>\n    <transition event=\"event1\" target=\"s1\">\n      <assign location=\"Var1\" expr=\"_event.data.aParam\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test207.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"ecmascript\" initial=\"s0\" name=\"ScxmlTest207\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--We\n\ttest that that we can't cancel an event in another session.\nWe invoke a child process. It notifies us when it has generated\na delayed event with sendid `foo`.  We try to cancel `foo`.\nThe child  process sends us event.\nEvent success if the event is not cancelled, event fail otherwise.\nThis doesn't test that there is absolutely no way to cancel an event\nraised in another session, but the spec doesn't define any way\nto refer to an event in another process-->\n\t<state id=\"s0\" initial=\"s01\">\n\t\t<onentry>\n\t\t\t<send delayexpr=\"'2s'\" event=\"timeout\" />\n\t\t</onentry>\n\t\t<invoke type=\"scxml\">\n\t\t\t<content>\n\t\t\t\t<scxml datamodel=\"ecmascript\" initial=\"sub0\" name=\"ScxmlSub\" version=\"1.0\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2005/07/scxml\">\n\t\t\t\t\t<state id=\"sub0\">\n\t\t\t\t\t\t<onentry>\n\t\t\t\t\t\t\t<send delayexpr=\"'1s'\" event=\"event1\" id=\"foo\" />\n\t\t\t\t\t\t\t<send delayexpr=\"'1.5s'\" event=\"event2\" />\n\t\t\t\t\t\t\t<send event=\"childToParent\" target=\"#_parent\" />\n\t\t\t\t\t\t</onentry>\n\t\t\t\t\t\t<transition event=\"event1\" target=\"subFinal\">\n\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"pass\" />\n\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t<transition event=\"*\" target=\"subFinal\">\n\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"fail\" />\n\t\t\t\t\t\t</transition>\n\t\t\t\t\t</state>\n\t\t\t\t\t<final id=\"subFinal\" />\n\t\t\t\t</scxml><!--when\n\t\t\t\tinvoked, we raise a delayed event1 with sendid 'foo' and notify our parent.  Then we\n\t\t\t\twait.\n      If event1 occurs, the parent hasn't succeeded in canceling it and we return pass.  If event2 occurs\n      it means event1 was canceled (because event2 is delayed longer than event1) and we return\n\t\t\t\t'fail'.-->\n\t\t\t</content>\n\t\t</invoke>\n\t\t<transition event=\"timeout\" target=\"fail\" />\n\t\t<state id=\"s01\">\n\t\t\t<transition event=\"childToParent\" target=\"s02\">\n\t\t\t\t<cancel sendid=\"foo\" />\n\t\t\t</transition>\n\t\t</state>\n\t\t<state id=\"s02\">\n\t\t\t<transition event=\"pass\" target=\"pass\" />\n\t\t\t<transition event=\"fail\" target=\"fail\" />\n\t\t</state>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test208.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that cancel works.  We cancel delayed event1.  If cancel works, we get event2 first and\npass.  If\nwe get event1 or an error first, cancel didn't work and we fail.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send id=\"foo\" event=\"event1\" delayexpr=\"'1s'\" />\n      <send event=\"event2\" delayexpr=\"'1.5s'\" />\n      <cancel sendid=\"foo\" />\n    </onentry>\n    <transition event=\"event2\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test210.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that sendidexpr works with cancel.  If it takes the most recent value of var1, it should cancel\ndelayed event1.  Thus we get event2 first and pass.  If we get event1 or an error first, cancel didn't work and we fail.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"'bar'\"/>\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send id=\"foo\" event=\"event1\" delayexpr=\"'1s'\"/>\n      <send event=\"event2\" delayexpr=\"'1.5s'\"/>\n      <assign location=\"Var1\" expr=\"'foo'\"/>\n      <cancel sendidexpr=\"Var1\"/>\n    </onentry>\n    <transition event=\"event2\" target=\"pass\"/>\n    <transition event=\"*\" target=\"fail\"/>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\"/>\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\"/>\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test215.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that typexpr is evaluated at runtime.  If the original\nvalue of var1 is used, the invocation\nwill fail (test215sub1.scxml is not of type 'foo', even if the platform supports foo as a type).  If\nthe runtime value is used, the invocation will succeed -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n\n  <datamodel>\n    <data id=\"Var1\" expr=\"'foo'\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <assign location=\"Var1\" expr=\"'http://www.w3.org/TR/scxml/'\" />\n    </onentry>\n    <invoke typeexpr=\"Var1\">\n      <content>\n        <!-- when invoked, terminate returning done.invoke. This proves that the invocation\n        succeeded.   -->\n        <scxml initial=\"subFinal\" datamodel=\"python\" version=\"1.0\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"done.invoke\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test216.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that srcexpr is evaluated at runtime.  If the original\nvalue of var1 is used, the invocation\nwill fail (assuming that there is no script named 'foo').  If\nthe runtime value is used, the invocation will succeed -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\">\n\n  <datamodel>\n    <data id=\"Var1\" expr=\"'foo'\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <assign location=\"Var1\" expr=\"'file:test216sub1.scxml'\" />\n    </onentry>\n    <invoke srcexpr=\"Var1\" type=\"http://www.w3.org/TR/scxml\" />\n    <transition event=\"done.invoke\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test216sub1.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- when invoked, terminate returning done.invoke. This proves that\nthe invocation succeeded.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n    initial=\"final\" datamodel=\"python\">\n\n    <final id=\"final\" />\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test220.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that the scxml type is supported.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <!-- when invoked, terminate returning done.invoke. This proves that the invocation\n        succeeded.   -->\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"done.invoke\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test223.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that idlocation is supported.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\" idlocation=\"Var1\">\n      <content>\n        <!-- when invoked, terminate returning done.invoke. This proves that the invocation\n        succeeded.   -->\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"*\" target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test224.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that the automatically generated id has the form\nstateid.platformid.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" />\n    <data id=\"Var2\" expr=\"'s0.'\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\" idlocation=\"Var1\">\n      <content>\n        <!-- when invoked, terminate returning done.invoke. This proves that the invocation\n        succeeded.   -->\n        <scxml version=\"1.0\" initial=\"subFinal\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"*\" target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"&#xA;Var1.startswith(Var2)\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test225.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that the automatically generated id is unique, we call\ninvoke twice and compare the ids.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n   <datamodel>\n      <data id=\"Var1\" />\n      <data id=\"Var2\" />\n   </datamodel>\n\n   <state id=\"s0\">\n      <onentry>\n         <send event=\"timeout\" delay=\"1s\" />\n      </onentry>\n\n      <invoke type=\"http://www.w3.org/TR/scxml/\" idlocation=\"Var1\">\n         <content>\n            <scxml initial=\"subFinal1\" version=\"1.0\" datamodel=\"python\">\n               <final id=\"subFinal1\" />\n            </scxml>\n         </content>\n      </invoke>\n      <invoke type=\"http://www.w3.org/TR/scxml/\" idlocation=\"Var2\">\n         <content>\n            <scxml initial=\"subFinal2\" version=\"1.0\" datamodel=\"python\">\n               <final id=\"subFinal2\" />\n            </scxml>\n         </content>\n      </invoke>\n\n      <transition event=\"*\" target=\"s1\" />\n   </state>\n\n   <state id=\"s1\">\n      <transition cond=\"Var1==Var2\" target=\"fail\" />\n      <transition target=\"pass\" />\n   </state>\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test226.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- this is basically just a test that invoke works correctly and\nthat you can pass data\nto the invoked process.  If the invoked session finds aParam==1, it exits, signalling\nsuccess.  otherwise it will hang and the timeout in this doc signifies failure.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n    </onentry>\n\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <param name=\"Var1\" expr=\"1\" />\n      <content>\n        <!-- when invoked, if var1 has a value notify parent. Then terminate.   -->\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\">\n          <datamodel>\n            <data id=\"Var1\" />\n          </datamodel>\n\n          <state id=\"sub0\">\n            <transition cond=\"Var1\" target=\"subFinal\">\n              <send target=\"#_parent\" event=\"varBound\" />\n            </transition>\n            <transition target=\"subFinal\" />\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"varBound\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test226sub1.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- when invoked, if var1 has a value notify parent. Then terminate.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\">\n\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <transition cond=\"Var1\" target=\"final\">\n      <send target=\"#_parent\" event=\"varBound\" />\n    </transition>\n    <transition target=\"final\" />\n  </state>\n\n  <final id=\"final\" />\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test228.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the invokeid is included in events returned from the\ninvoked process.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\" id=\"foo\">\n      <content>\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"done.invoke\" target=\"s1\">\n      <assign location=\"Var1\" expr=\"_event.invokeid\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1=='foo'\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test229.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that autofoward works. If the child  process receives back a\ncopy of the\nchildToParent event that it sends to this doc, it sends eventReceived, signalling success. (Note\nthat this doc is not required to process that event explicitly.  It should be forwarded in any\ncase.) Otherwise\nit eventually times out and the done.invoke signals failure   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\" autoforward=\"true\">\n      <content>\n        <!-- when invoked, send childToParent to parent.\n      If it is forwarded back to us, send\n      eventReceived to signal success and terminate.\n      Otherwise wait for timer to expire and terminate.  -->\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\">\n          <state id=\"sub0\">\n            <onentry>\n              <send target=\"#_parent\" event=\"childToParent\" />\n              <send event=\"timeout\" delay=\"3s\" />\n            </onentry>\n            <transition event=\"childToParent\" target=\"subFinal\">\n              <send target=\"#_parent\" event=\"eventReceived\" />\n            </transition>\n            <transition event=\"*\" target=\"subFinal\" />\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"childToParent\" />\n    <transition event=\"eventReceived\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test232.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that a parent process can receive multiple events from a\nchild process   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\">\n            <onentry>\n              <send target=\"#_parent\" event=\"childToParent1\" />\n              <send target=\"#_parent\" event=\"childToParent2\" />\n            </onentry>\n          </final>\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"timeout\" target=\"fail\" />\n\n\n    <state id=\"s01\">\n      <transition event=\"childToParent1\" target=\"s02\" />\n    </state>\n\n    <state id=\"s02\">\n      <transition event=\"childToParent2\" target=\"s03\" />\n    </state>\n\n    <state id=\"s03\">\n      <transition event=\"done.invoke\" target=\"pass\" />\n    </state>\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test233.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that finalize markup runs before the event is processed.\nThe invoked process will\nreturn 2 in _event.data.aParam, so that new value should be in force when we select\nthe transtitions.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\">\n            <onentry>\n              <send target=\"#_parent\" event=\"childToParent\">\n                <param name=\"aParam\" expr=\"2\" />\n              </send>\n            </onentry>\n          </final>\n        </scxml>\n      </content>\n      <finalize>\n        <assign location=\"Var1\" expr=\"_event.data.get('aParam')\" />\n      </finalize>\n    </invoke>\n\n    <transition event=\"childToParent\" cond=\"\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test234.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that only finalize markup in the invoking state runs.  the\nfirst invoked process will\nreturn 2 in _event.data.aParam, while second invoked process sleeps without returning any events.\nOnly the first finalize should execute.  So when we get to s1 var1 should have value 2 but\nvar2 should still be set to 1  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"p0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n    <data id=\"Var2\" expr=\"1\" />\n  </datamodel>\n  <parallel id=\"p0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"p01\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\">\n        <content>\n          <scxml version=\"1.0\" initial=\"subFinal1\" datamodel=\"python\">\n            <final id=\"subFinal1\">\n              <onentry>\n                <send target=\"#_parent\" event=\"childToParent\">\n                  <param name=\"aParam\" expr=\"2\" />\n                </send>\n              </onentry>\n            </final>\n          </scxml>\n        </content>\n        <finalize>\n          <assign location=\"Var1\" expr=\"_event.data.get('aParam')\" />\n        </finalize>\n      </invoke>\n\n      <transition event=\"childToParent\" cond=\"Var1==2\" target=\"s1\" />\n      <transition event=\"childToParent\" target=\"fail\" />\n    </state>\n\n    <state id=\"p02\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\">\n        <content>\n          <scxml version=\"1.0\" initial=\"sub0\" datamodel=\"python\">\n            <state id=\"sub0\">\n              <onentry>\n                <send event=\"timeout\" delay=\"2s\" />\n              </onentry>\n              <transition event=\"timeout\" target=\"subFinal2\" />\n            </state>\n            <final id=\"subFinal2\" />\n          </scxml>\n        </content>\n        <finalize>\n          <assign location=\"Var2\" expr=\"_event.data.get('aParam')\" />\n        </finalize>\n      </invoke>\n    </state>\n\n  </parallel>\n\n\n  <state id=\"s1\">\n    <transition cond=\"Var2==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test235.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that done.invoke.id event has the right id.  the invoked\nchild terminates immediately\nand should generate done.invoke.foo   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\" id=\"foo\">\n      <content>\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"done.invoke.foo\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test236.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that done.invoke.id event is the last event we receive.  the\ninvoked process sends childToParent\nin the exit handler of its final state.  We should get it before the done.invoke, and we should get\nno\nevents after the done.invoke.  Hence timeout indicates success   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\">\n            <onexit>\n              <send target=\"#_parent\" event=\"childToParent\" />\n            </onexit>\n          </final>\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"childToParent\" target=\"s1\" />\n    <transition event=\"done.invoke\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <!-- here we should get done.invoke -->\n    <transition event=\"done.invoke\" target=\"s2\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n    <transition event=\"timeout\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test237.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that cancelling works. invoked child sleeps for two\nseconds, then terminates.  We\nsleep for 1 sec in s0, then move to s1.  This should cause the invocation to get cancelled.\nIf we receive done.invoke, the invocation wasn't cancelled, and we fail. If we receive no events by\nthe time timeout2 fires, success   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout1\" delay=\"1s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <!-- when invoked, sleep for 2 secs then terminate.  Parent will try to cancel this session -->\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\">\n          <state id=\"sub0\">\n            <onentry>\n              <send event=\"timeout\" delay=\"2s\" />\n            </onentry>\n            <transition event=\"timeout\" target=\"subFinal\" />\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"timeout1\" target=\"s1\" />\n\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <send event=\"timeout2\" delay=\"2s\" />\n    </onentry>\n    <!-- here we should NOT get done.invoke -->\n    <transition event=\"done.invoke\" target=\"fail\" />\n    <transition event=\"*\" target=\"pass\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test239.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that markup can be specified both by 'src' and by <content>  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"s01\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\" src=\"file:test239sub1.scxml\" />\n      <transition event=\"done.invoke\" target=\"s02\" />\n    </state>\n\n    <state id=\"s02\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\">\n        <!-- identical to test239sub1.scxml.  -->\n        <content>\n          <scxml version=\"1.0\" initial=\"final\" datamodel=\"python\">\n            <final id=\"final\" />\n          </scxml>\n        </content>\n      </invoke>\n\n      <transition event=\"done.invoke\" target=\"pass\" />\n    </state>\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test239sub1.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- when invoked, just terminate.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n    initial=\"final\" version=\"1.0\" datamodel=\"python\">\n\n    <final id=\"final\" />\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test240.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that datamodel values can\n\tbe specified both by 'namelist' and by <param>. invoked child will return\n\tsuccess if its Var1 is set to 1, failure otherwise. This test will fail schema\n\tvalidation because of the multiple occurences of Var1, but should run correctly. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n\tinitial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\t<datamodel>\n\t\t<data id=\"Var1\" expr=\"1\" />\n\t</datamodel>\n\n\t<state id=\"s0\" initial=\"s01\">\n\t\t<onentry>\n\t\t\t<send event=\"timeout\" delay=\"2s\" />\n\t\t</onentry>\n\t\t<transition event=\"timeout\" target=\"fail\" />\n\n\t\t<state id=\"s01\">\n\t\t\t<invoke type=\"http://www.w3.org/TR/scxml/\" namelist=\"Var1\">\n\t\t\t\t<content>\n\t\t\t\t\t<scxml initial=\"sub01\" version=\"1.0\" datamodel=\"python\">\n\t\t\t\t\t\t<datamodel>\n\t\t\t\t\t\t\t<data id=\"Var1\" expr=\"0\" />\n\t\t\t\t\t\t</datamodel>\n\t\t\t\t\t\t<state id=\"sub01\">\n\t\t\t\t\t\t\t<transition cond=\"Var1==1\" target=\"subFinal1\">\n\t\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"success\" />\n\t\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t\t<transition target=\"subFinal1\">\n\t\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"failure\" />\n\t\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t</state>\n\t\t\t\t\t\t<final id=\"subFinal1\" />\n\t\t\t\t\t</scxml>\n\t\t\t\t</content>\n\t\t\t</invoke>\n\t\t\t<transition event=\"success\" target=\"s02\" />\n\t\t\t<transition event=\"failure\" target=\"fail\" />\n\t\t</state>\n\n\t\t<state id=\"s02\">\n\t\t\t<invoke type=\"http://www.w3.org/TR/scxml/\">\n\t\t\t\t<param name=\"Var1\" expr=\"1\" />\n\t\t\t\t<content>\n\t\t\t\t\t<scxml initial=\"sub02\" version=\"1.0\" datamodel=\"python\">\n\t\t\t\t\t\t<datamodel>\n\t\t\t\t\t\t\t<data id=\"Var1\" expr=\"0\" />\n\t\t\t\t\t\t</datamodel>\n\n\t\t\t\t\t\t<state id=\"sub02\">\n\t\t\t\t\t\t\t<transition cond=\"Var1==1\" target=\"subFinal2\">\n\t\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"success\" />\n\t\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t\t<transition target=\"subFinal1\">\n\t\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"failure\" />\n\t\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t</state>\n\t\t\t\t\t\t<final id=\"subFinal2\" />\n\t\t\t\t\t</scxml>\n\t\t\t\t</content>\n\t\t\t</invoke>\n\t\t\t<transition event=\"success\" target=\"pass\" />\n\t\t\t<transition event=\"failure\" target=\"fail\" />\n\t\t</state>\n\n\t</state>\n\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'pass'\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'fail'\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test241.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- The child process will return success ifits Var1 is set to 1,\nfailure otherwise. For this test\nwe try passing in Var1 by param and by namelist and check that we either get two successes\nor two failures.  This test will fail schema validation due to multiple  declarations of\nVar1, but should  run correctly.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"s01\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\" namelist=\"Var1\">\n        <content>\n          <scxml initial=\"sub01\" version=\"1.0\" datamodel=\"python\">\n            <datamodel>\n              <data id=\"Var1\" expr=\"0\" />\n            </datamodel>\n\n            <state id=\"sub01\">\n              <transition cond=\"Var1==1\" target=\"subFinal1\">\n                <send target=\"#_parent\" event=\"success\" />\n              </transition>\n              <transition target=\"subFinal1\">\n                <send target=\"#_parent\" event=\"failure\" />\n              </transition>\n            </state>\n\n            <final id=\"subFinal1\" />\n          </scxml>\n        </content>\n      </invoke>\n      <transition event=\"success\" target=\"s02\" />\n      <transition event=\"failure\" target=\"s03\" />\n    </state>\n\n    <state id=\"s02\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\">\n        <param name=\"Var1\" expr=\"1\" />\n        <content>\n          <scxml initial=\"sub02\" version=\"1.0\" datamodel=\"python\">\n            <datamodel>\n              <data id=\"Var1\" expr=\"0\" />\n            </datamodel>\n\n            <state id=\"sub02\">\n              <transition cond=\"Var1==1\" target=\"subFinal2\">\n                <send target=\"#_parent\" event=\"success\" />\n              </transition>\n              <transition target=\"subFinal2\">\n                <send target=\"#_parent\" event=\"failure\" />\n              </transition>\n            </state>\n\n            <final id=\"subFinal2\" />\n          </scxml>\n        </content>\n      </invoke>\n      <!-- we got success in s01, so we need to do so here -->\n      <transition event=\"success\" target=\"pass\" />\n      <transition event=\"failure\" target=\"fail\" />\n    </state>\n\n    <state id=\"s03\">\n      <invoke type=\"http://www.w3.org/TR/scxml/\">\n        <param name=\"Var1\" expr=\"1\" />\n        <content>\n          <scxml initial=\"sub03\" version=\"1.0\" datamodel=\"python\">\n            <datamodel>\n              <data id=\"Var1\" expr=\"0\" />\n            </datamodel>\n\n            <state id=\"sub03\">\n              <transition cond=\"Var1==1\" target=\"subFinal3\">\n                <send target=\"#_parent\" event=\"success\" />\n              </transition>\n              <transition target=\"subFinal3\">\n                <send target=\"#_parent\" event=\"failure\" />\n              </transition>\n            </state>\n\n            <final id=\"subFinal3\" />\n          </scxml>\n        </content>\n      </invoke>\n      <!-- we got failure in s01, so we need to do so here -->\n      <transition event=\"failure\" target=\"pass\" />\n      <transition event=\"success\" target=\"fail\" />\n    </state>\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test242.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that markup specified  by 'src' and by <content> is treated\nthe same way.  That means that\neither we get done.invoke in both cases or in neither case (in which case we timeout) -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout1\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\" src=\"file:test242sub1.scxml\" />\n    <transition event=\"done.invoke\" target=\"s02\" />\n    <transition event=\"timeout1\" target=\"s03\" />\n  </state>\n\n  <state id=\"s02\">\n    <onentry>\n      <send event=\"timeout2\" delay=\"1s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <!-- identical to test242sub1.scxml.  -->\n      <content>\n        <scxml initial=\"final\" datamodel=\"python\">\n          <final id=\"final\" />\n        </scxml>\n      </content>\n    </invoke>\n    <!-- we got done.invoke last time, so we need it this time too -->\n    <transition event=\"done.invoke\" target=\"pass\" />\n    <transition event=\"timeout2\" target=\"fail\" />\n  </state>\n\n  <state id=\"s03\">\n    <onentry>\n      <send event=\"timeout3\" delay=\"1s\" />\n    </onentry>\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <!-- identical to test242sub1.scxml.  -->\n      <content>\n        <scxml initial=\"final\" datamodel=\"python\">\n          <final id=\"final\" />\n        </scxml>\n      </content>\n    </invoke>\n    <!-- we got timeout last time, so we need it this time too -->\n    <transition event=\"timeout3\" target=\"pass\" />\n    <transition event=\"done.invoke\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test242sub1.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- when invoked, just terminate.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n    initial=\"final\" datamodel=\"python\">\n\n    <final id=\"final\" />\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test243.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that datamodel values can be specified by param.\ntest240sub1 will return success ifits Var1 is set to 1, failure otherwise.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <param name=\"Var1\" expr=\"1\" />\n      <content>\n        <scxml version=\"1.0\" initial=\"sub0\" datamodel=\"python\">\n          <datamodel>\n            <data id=\"Var1\" expr=\"0\" />\n          </datamodel>\n\n          <state id=\"sub0\">\n            <transition cond=\"Var1==1\" target=\"subFinal\">\n              <send target=\"#_parent\" event=\"success\" />\n            </transition>\n            <transition target=\"subFinal\">\n              <send target=\"#_parent\" event=\"failure\" />\n            </transition>\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"success\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test244.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that datamodel values can be specified by  namelist.\ninvoked child will return success ifits Var1 is set to 1, failure otherwise.\nThis test will fail schema validation due to multiple occurrences of Var1,\nbut should run correctly. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\" namelist=\"Var1\">\n      <content>\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\">\n          <datamodel>\n            <data id=\"Var1\" expr=\"0\" />\n          </datamodel>\n\n          <state id=\"sub0\">\n            <transition cond=\"Var1==1\" target=\"subFinal\">\n              <send target=\"#_parent\" event=\"success\" />\n            </transition>\n            <transition target=\"subFinal\">\n              <send target=\"#_parent\" event=\"failure\" />\n            </transition>\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"success\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test245.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that non-existent datamodel values are not set.  Var2 is not\ndefined in\ninvoked child's datamodel.  It will will return success if its Var2 remains unbound, failure\notherwise.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var2\" expr=\"3\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\" namelist=\"Var2\">\n      <content>\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\">\n          <state id=\"sub0\">\n            <transition cond=\"Var2\" target=\"subFinal\">\n              <send target=\"#_parent\" event=\"failure\" />\n            </transition>\n            <transition target=\"subFinal\">\n              <send target=\"#_parent\" event=\"success\" />\n            </transition>\n          </state>\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n\n    <transition event=\"success\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test247.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that we get done.invoke.  timeout indicates failure  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <scxml version=\"1.0\" initial=\"subFinal\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"done.invoke\" target=\"pass\" />\n    <transition event=\"timeout\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test252.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that we don't process any\n\tevents received from the invoked process once it is cancelled. child process\n\ttries to send us childToParent in an onexit handler. If we get it, we fail.\n\ttimeout indicates success. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n\tinitial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n\t<state id=\"s0\" initial=\"s01\">\n\t\t<onentry>\n\t\t\t<send event=\"timeout\" delay=\"2s\" />\n\t\t</onentry>\n\n\t\t<transition event=\"timeout\" target=\"pass\" />\n\t\t<transition event=\"childToParent\" target=\"fail\" />\n\t\t<transition event=\"done.invoke\" target=\"fail\" />\n\n\t\t<state id=\"s01\">\n\t\t\t<onentry>\n\t\t\t\t<send event=\"foo\" />\n\t\t\t</onentry>\n\n\t\t\t<invoke type=\"http://www.w3.org/TR/scxml/\">\n\t\t\t\t<content>\n\t\t\t\t\t<scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\">\n\t\t\t\t\t\t<state id=\"sub0\">\n\t\t\t\t\t\t\t<onentry>\n\t\t\t\t\t\t\t\t<send event=\"timeout\" delay=\"2s\" />\n\t\t\t\t\t\t\t</onentry>\n\t\t\t\t\t\t\t<transition event=\"timeout\" target=\"subFinal\" />\n\t\t\t\t\t\t\t<onexit>\n\t\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"childToParent\" />\n\t\t\t\t\t\t\t</onexit>\n\t\t\t\t\t\t</state>\n\t\t\t\t\t\t<final id=\"subFinal\" />\n\t\t\t\t\t</scxml>\n\t\t\t\t</content>\n\t\t\t</invoke>\n\n\t\t\t<!-- this transition will cause the invocation to be cancelled -->\n\t\t\t<transition event=\"foo\" target=\"s02\" />\n\t\t</state>\n\n\t\t<state id=\"s02\" />\n\n\t</state>\n\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'pass'\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'fail'\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test253.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest253\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n\tthat the scxml event processor is used in both directions.  If child process uses the\nscxml event i/o processor to communicate with us, send it an event.  It will send back success if\nthis process uses the scxml processor to send the message to it, otherwise failure.  For this test\n\twe allow\n'scxml' as an alternative to the full url.-->\n\t<datamodel>\n\t\t<data id=\"Var1\" />\n\t</datamodel>\n\t<state id=\"s0\" initial=\"s01\">\n\t\t<onentry>\n\t\t\t<send delay=\"2s\" event=\"timeout\" />\n\t\t</onentry>\n\t\t<invoke id=\"foo\" type=\"scxml\">\n\t\t\t<content>\n\t\t\t\t<scxml datamodel=\"python\" initial=\"sub0\" name=\"ScxmlShape1\" version=\"1.0\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2005/07/scxml\">\n\t\t\t\t\t<datamodel>\n\t\t\t\t\t\t<data id=\"Var2\" />\n\t\t\t\t\t</datamodel>\n\t\t\t\t\t<state id=\"sub0\">\n\t\t\t\t\t\t<onentry>\n\t\t\t\t\t\t\t<send event=\"childRunning\" target=\"#_parent\" />\n\t\t\t\t\t\t</onentry>\n\t\t\t\t\t\t<transition event=\"parentToChild\" target=\"sub1\">\n\t\t\t\t\t\t\t<assign location=\"Var2\" expr=\"_event.origintype\" />\n\t\t\t\t\t\t</transition>\n\t\t\t\t\t</state>\n\t\t\t\t\t<state id=\"sub1\">\n\t\t\t\t\t\t<transition cond=\"Var2=='http://www.w3.org/TR/scxml/#SCXMLEventProcessor'\"\n\t\t\t\t\t\t\ttarget=\"subFinal\">\n\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"success\" />\n\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t<transition cond=\"Var2=='scxml'\" target=\"subFinal\">\n\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"success\" />\n\t\t\t\t\t\t</transition>\n\t\t\t\t\t\t<transition target=\"subFinal\">\n\t\t\t\t\t\t\t<send target=\"#_parent\" event=\"failure\" />\n\t\t\t\t\t\t</transition>\n\t\t\t\t\t</state>\n\t\t\t\t\t<final id=\"subFinal\" />\n\t\t\t\t</scxml><!--inform\n\t\t\t\tparent we're running then wait for it to send us an event.  If it uses the scxml\n\t\t\t\tevent i/o\n             processor to do so, return success, otherwise return failure.-->\n\t\t\t</content>\n\t\t</invoke>\n\t\t<transition event=\"timeout\" target=\"fail\" />\n\t\t<state id=\"s01\">\n\t\t\t<transition event=\"childRunning\" target=\"s02\">\n\t\t\t\t<assign location=\"Var1\" expr=\"_event.origintype\" />\n\t\t\t</transition>\n\t\t</state>\n\t\t<state id=\"s02\">\n\t\t\t<transition cond=\"Var1=='http://www.w3.org/TR/scxml/#SCXMLEventProcessor'\" target=\"s03\">\n\t\t\t\t<send target=\"#_foo\" event=\"parentToChild\" />\n\t\t\t</transition>\n\t\t\t<transition cond=\"Var1=='scxml'\" target=\"s03\">\n\t\t\t\t<send target=\"#_foo\" event=\"parentToChild\" />\n\t\t\t</transition>\n\t\t\t<transition target=\"fail\" />\n\t\t</state>\n\t\t<state id=\"s03\">\n\t\t\t<transition event=\"success\" target=\"pass\" />\n\t\t\t<transition event=\"fail\" target=\"fail\" />\n\t\t</state>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test276.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that values passed in from parent process override default\nvalues specified in the child, test276sub1.scxml.\nThe child returns event1 if var1 has value 1, event0 if it has default value 0.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\">\n\n  <state id=\"s0\">\n    <invoke type=\"scxml\" src=\"file:test276sub1.scxml\">\n      <param name=\"Var1\" expr=\"1\" />\n    </invoke>\n    <transition event=\"event1\" target=\"pass\" />\n    <transition event=\"event0\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test276sub1.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- define var1 with default value 0.  Parent will invoke this\nprocess setting var1 = 1.  Return event1 if var1 == 1, event0 otherwise -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" datamodel=\"python\">\n   <datamodel>\n      <data id=\"Var1\" expr=\"0\" />\n   </datamodel>\n\n   <state id=\"s0\">\n\n\n      <transition cond=\"Var1==1\" target=\"final\">\n         <send target=\"#_parent\" event=\"event1\" />\n      </transition>\n\n      <transition target=\"final\">\n         <send target=\"#_parent\" event=\"event0\" />\n      </transition>\n\n   </state>\n\n   <final id=\"final\" />\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test277.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that platform raises error.execution if we assign an\nillegal value to a var but that var is defined\nso that we can assign to it later in state s1. Timeout keeps test from hanging in s0 -->\n<scxml xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1 = 1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"s1\" />\n    <transition event=\"timeout\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"1\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"fail\" />\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n\n  </state>\n\n  <final xmlns=\"http://www.w3.org/2005/07/scxml\" id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final xmlns=\"http://www.w3.org/2005/07/scxml\" id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test279.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- identical to test278, but this time testing that in case of\nearly binding variables are assigned values at init time -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <transition cond=\"Var1==1\" target=\"pass\" />\n\n    <transition target=\"fail\" />\n    <transition target=\"s1\" /><!-- only to have a single graph component -->\n\n  </state>\n\n  <state id=\"s1\">\n    <datamodel>\n      <data id=\"Var1\" expr=\"1\" />\n    </datamodel>\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test280.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><?xml-stylesheet type=\"text/xsl\" href=\"conf.xsl\"?>\n<!-- test late binding.  var2 won't get bound until s1 is entered, so it shouldn't  have a value in s0\nand\naccessing it should cause an error. It should get bound before the onentry code in s1 so it should\nbe\npossible access it there and assign its value to var1 -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" binding=\"late\">\n\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <transition cond=\"Var2==1\" target=\"fail\" />\n    <transition target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <datamodel>\n      <data id=\"Var2\" expr=\"Var1\" />\n    </datamodel>\n    <onentry>\n    </onentry>\n    <transition cond=\"Var1==Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test286.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that assigment to a non-declared var causes an error.  the\ntransition on foo catches the case\nwhere no error is raised -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\" initial=\"s0\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"1\" />\n      <raise event=\"foo\" />\n    </onentry>\n\n    <transition event=\"error.execution\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test287.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- a simple test that a legal value may be assigned to a valid data\nmodel location -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\" initial=\"s0\">\n\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"1\" />\n    </onentry>\n\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test294.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- simple test that a param inside donedata ends up in the data\nfield of the done event -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\" initial=\"s0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n\n  <state id=\"s0\" initial=\"s01\">\n\n    <transition event=\"done.state.s0\" cond=\"_event.data.get('Var1')==1\" target=\"pass\">\n    </transition>\n\n    <transition event=\"done.state.s0\" target=\"fail\">\n    </transition>\n\n    <state id=\"s01\">\n      <transition target=\"s02\" />\n    </state>\n    <final id=\"s02\">\n      <donedata>\n        <param name=\"Var1\" expr=\"1\" />\n      </donedata>\n    </final>\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test298.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- refence a non-existent data model location in param in donedata\nand see that the right error is raised -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   version=\"1.0\" datamodel=\"python\" initial=\"s0\">\n   <datamodel>\n      <data id=\"Var1\" expr=\"0\" />\n   </datamodel>\n\n   <state id=\"s0\" initial=\"s01\">\n      <transition event=\"error.execution\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n\n      <state id=\"s01\">\n         <transition target=\"s02\" />\n      </state>\n      <final id=\"s02\">\n         <donedata>\n            <param name=\"Var3\" location=\"Var2\" />\n         </donedata>\n      </final>\n   </state>\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test302.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that a script is evaluated at load time.  If it is, Var1 has\na value in the initial state s0.\nThis test is valid only for datamodels that support scripting -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n  <script>Var1 = 1</script>\n\n  <state id=\"s0\">\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test303.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- to test that scripts are run as part of executable content, we\ncheck that it changes the value of a var at the\nright point. This test is valid only for datamodels that support scripting -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"2\" />\n      <script>Var1 = 1</script>\n    </onentry>\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test304.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that a variable declared by a script can be accessed like\nany other part of the data model -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n  <script>Var1 = 1</script>\n\n  <state id=\"s0\">\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test309.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that an expression that cannot be interpreted as a boolean\nis treated as false -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n\n  <state id=\"s0\">\n    <transition cond=\"1 = 1\" target=\"fail\" />\n    <transition target=\"pass\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test310.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- simple test of the in() predicate -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"p\">\n\n\n  <parallel id=\"p\">\n\n    <state id=\"s0\">\n      <transition cond=\"In('s1')\" target=\"pass\" />\n      <transition target=\"fail\" />\n    </state>\n\n    <state id=\"s1\" />\n  </parallel>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test311.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that assignment to a non-existent location yields an error -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"1\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"pass\" />\n    <transition event=\".*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test312.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that assignment with an illegal expr raises an error -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"1 = 1\" />\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"pass\" />\n    <transition event=\".*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test318.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _event stays bound during the onexit and entry into\nthe next state -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"bar\" />\n      <!-- _event should still be bound to 'foo' at this point -->\n      <assign location=\"Var1\" expr=\"_event.name\" />\n    </onentry>\n    <transition cond=\"Var1=='foo'\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test319.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _event is not bound before any event has been raised -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\" name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <if cond=\"_event\">\n        <raise event=\"bound\" />\n        <else />\n        <raise event=\"unbound\" />\n      </if>\n    </onentry>\n    <transition event=\"unbound\" target=\"pass\" />\n    <transition event=\"bound\" target=\"fail\" />\n\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test321.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _sessionid is bound on startup -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" datamodel=\"python\" version=\"1.0\" name=\"machineName\">\n   <datamodel>\n      <data id=\"Var1\" expr=\"_sessionid\" />\n   </datamodel>\n\n   <state id=\"s0\">\n      <transition cond=\"Var1\" target=\"pass\" />\n      <transition cond=\"True\" target=\"fail\" />\n   </state>\n\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test322.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that _sessionid remains bound to the same value throught\nthe session.  this means that it can't\nbe assigned to  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\" name=\"machineName\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"_sessionid\" />\n    <data id=\"Var2\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <transition target=\"s1\" />\n\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <assign location=\"_sessionid\" expr=\"'otherName'\" />\n      <raise event=\"foo\" />\n    </onentry>\n\n    <transition event=\"error.execution\" target=\"s2\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n    <onentry>\n      <assign location=\"Var2\" expr=\"_sessionid\" />\n    </onentry>\n    <transition cond=\"Var1==Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test323.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _name is bound on startup -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n   <datamodel>\n      <data id=\"Var1\" expr=\"_name\" />\n   </datamodel>\n\n   <state id=\"s0\">\n      <transition cond=\"Var1\" target=\"pass\" />\n      <transition cond=\"True\" target=\"fail\" />\n   </state>\n\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test324.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _name stays bound till the session ends.  This means\nthat it cannot be assigned to -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n\n\n  <state id=\"s0\">\n    <transition cond=\"_name  == 'machineName'\" target=\"s1\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <assign location=\"_name\" expr=\"'otherName'\" />\n    </onentry>\n    <transition cond=\"_name  == 'machineName'\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test325.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _ioprocessors is bound at startup.  I'm not sure how to\ntest for a set value -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" datamodel=\"python\" version=\"1.0\" name=\"machineName\">\n   <datamodel>\n      <data id=\"Var1\" expr=\"_ioprocessors\" />\n   </datamodel>\n\n\n   <state id=\"s0\">\n      <transition cond=\"Var1\" target=\"pass\" />\n      <transition target=\"fail\" />\n   </state>\n\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test326.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that _ioprocessors stays bound till the session ends.  This\nmeans that it cannot be assigned to -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\" name=\"machineName\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"_ioprocessors\" />\n    <data id=\"Var2\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <transition cond=\"Var1\" target=\"s1\" />\n    <transition cond=\"True\" target=\"fail\" />\n  </state>\n\n\n  <state id=\"s1\">\n    <onentry>\n      <assign location=\"_ioprocessors\" expr=\"'otherName'\" />\n      <raise event=\"foo\" />\n    </onentry>\n\n    <transition event=\"error.execution\" target=\"s2\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n    <onentry>\n      <assign location=\"Var2\" expr=\"_ioprocessors\" />\n    </onentry>\n    <transition cond=\"Var1==Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test329.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that none of the system variables can be modified -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n  <datamodel>\n    <data id=\"Var1\" />\n    <data id=\"Var2\" />\n    <data id=\"Var3\" />\n    <data id=\"Var4\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <!-- get _event bound so we can use it in s1-->\n      <raise event=\"foo\" />\n      <assign location=\"Var1\" expr=\"_sessionid\" />\n      <assign location=\"_sessionid\" expr=\"27\" />\n    </onentry>\n\n    <transition event=\"foo\" cond=\"Var1==_sessionid\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <assign location=\"Var2\" expr=\"_event\" />\n      <assign location=\"_event\" expr=\"27\" />\n    </onentry>\n    <transition cond=\"Var2==_event\" target=\"s2\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n    <onentry>\n      <assign location=\"Var3\" expr=\"_name\" />\n      <assign location=\"_name\" expr=\"27\" />\n    </onentry>\n    <transition cond=\"Var3==_name\" target=\"s3\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <state id=\"s3\">\n    <onentry>\n      <assign location=\"Var4\" expr=\"_ioprocessors\" />\n      <assign location=\"_ioprocessors\" expr=\"27\" />\n    </onentry>\n    <transition cond=\"Var4==_ioprocessors\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test330.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- check that the required fields are present in both internal and\nexternal events -->\n<scxml xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" datamodel=\"python\"\n  name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\"\n      cond=\"hasattr(_event, &#34;name&#34;) and hasattr(_event, &#34;type&#34;) and hasattr(_event, &#34;sendid&#34;) and hasattr(_event, &#34;origin&#34;) and hasattr(_event, &#34;origintype&#34;) and hasattr(_event, &#34;invokeid&#34;) and hasattr(_event, &#34;data&#34;)\"\n      target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <send event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\"\n      cond=\"hasattr(_event, &#34;name&#34;) and hasattr(_event, &#34;type&#34;) and hasattr(_event, &#34;sendid&#34;) and hasattr(_event, &#34;origin&#34;) and hasattr(_event, &#34;origintype&#34;) and hasattr(_event, &#34;invokeid&#34;) and hasattr(_event, &#34;data&#34;)\"\n      target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final xmlns=\"http://www.w3.org/2005/07/scxml\" id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final xmlns=\"http://www.w3.org/2005/07/scxml\" id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test331.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\" initial=\"s0\" name=\"machineName\">\n\n  <!-- test that _event.type is set correctly for internal, platform, and external events -->\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <!-- internal event -->\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" target=\"s1\">\n      <assign location=\"Var1\" expr=\"_event.type\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1=='internal'\" target=\"s2\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n    <onentry>\n      <!-- this will generate an error, which is a platform event -->\n      <assign location=\"Var2\" expr=\"1\" />\n    </onentry>\n    <transition event=\"error\" target=\"s3\">\n      <assign location=\"Var1\" expr=\"_event.type\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s3\">\n    <transition cond=\"Var1=='platform'\" target=\"s4\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s4\">\n    <onentry>\n      <!-- external event -->\n      <send event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" target=\"s5\">\n      <assign location=\"Var1\" expr=\"_event.type\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s5\">\n    <transition cond=\"Var1=='external'\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test332.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that sendid is present in error events triggered by send\nerrors -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\" datamodel=\"python\" name=\"machineName\">\n  <datamodel>\n    <data id=\"Var1\" />\n    <data id=\"Var2\" />\n  </datamodel>\n\n\n  <state id=\"s0\">\n    <onentry>\n      <!-- this will raise an error and also store the sendid in var1 -->\n      <send target=\"baz\" event=\"foo\" idlocation=\"Var1\" />\n    </onentry>\n    <transition event=\"error\" target=\"s1\">\n      <!-- get the sendid out of the error event -->\n      <assign location=\"Var2\" expr=\"_event.sendid\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <!-- make sure that the sendid in the error event matches the one generated when send executed -->\n    <transition cond=\"Var1==Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test333.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- make sure sendid is blank in a non-error event -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\" datamodel=\"python\" name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" cond=\"not _event.sendid\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test335.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that origin field is blank for internal events -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\" datamodel=\"python\" name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" cond=\"not _event.origin\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test336.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the origin field of an external event contains a URL\nthat lets you send back to the originator.  In\nthis case it's the same session, so if we get bar we succeed -->\n<scxml xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" initial=\"s0\" datamodel=\"python\"\n  name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" target=\"s1\">\n      <send event=\"bar\" targetexpr=\"_event.origin\" typeexpr=\"_event.origintype\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <send event=\"baz\" />\n    </onentry>\n    <transition event=\"bar\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final xmlns=\"http://www.w3.org/2005/07/scxml\" id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final xmlns=\"http://www.w3.org/2005/07/scxml\" id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test337.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that origintype is blank on internal events -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\" name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" cond=\"not _event.origintype\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test338.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that invokeid is set correctly in events received from an\ninvoked process.  timeout event catches the\ncase where the invoke doesn't work correctly -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" />\n    <data id=\"Var2\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <invoke idlocation=\"Var1\" type=\"http://www.w3.org/TR/scxml/\">\n      <content>\n        <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n          <final id=\"sub0\">\n            <onentry>\n              <send target=\"#_parent\" event=\"event1\" />\n            </onentry>\n          </final>\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"event1\" target=\"s1\">\n      <assign location=\"Var2\" expr=\"_event.invokeid\" />\n    </transition>\n    <transition event=\"event0\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1==Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test339.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that invokeid is blank in an event that wasn't returned\nfrom an invoked process -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"foo\" cond=\"not _event.invokeid\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test342.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that eventexpr works and sets the name field of the\nresulting event -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"'foo'\" />\n    <data id=\"Var2\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send eventexpr=\"Var1\" />\n    </onentry>\n    <transition event=\"foo\" target=\"s1\">\n      <assign location=\"Var2\" expr=\"_event.name\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1==Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test343.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--  test that illegal <param> produces error.execution and empty event.data -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\" initial=\"s0\">\n  <state id=\"s0\" initial=\"s01\">\n    <!-- we should get the error before the done event -->\n    <transition event=\"error.execution\" target=\"s1\" />\n    <transition event=\"done.state.s0\" target=\"fail\" />\n    <transition event=\"done.state.s0\" target=\"fail\">\n    </transition>\n    <state id=\"s01\">\n      <transition target=\"s02\" />\n    </state>\n    <final id=\"s02\">\n      <donedata>\n        <param location=\"foo.bar.baz \" name=\"someParam\" />\n      </donedata>\n    </final>\n  </state>\n  <!-- if we get here, we received the error event. Now check that the done\n event has empty event.data -->\n  <state id=\"s1\">\n    <transition event=\"done.state.s0\" cond=\"_event.data == None\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test344.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that a non-boolean cond expression evaluates to false and\ncauses error.execution to be raised -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\">\n\n  <state id=\"s0\">\n    <transition cond=\"1 = 1\" target=\"fail\" />\n    <transition target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test346.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that any attempt to change the value of a system variable\ncauses error.execution to be raised.\nEvent1..4 are there to catch the case where the error event is not raised. In cases where it is, we\nhave\nto dispose of eventn in the next state, hence the targetless transitions (which simply throw away\nthe event.) -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n\n\n   <state id=\"s0\">\n      <onentry>\n         <assign location=\"_sessionid\" expr=\"'otherName'\" />\n         <raise event=\"event1\" />\n      </onentry>\n\n      <transition event=\"error.execution\" target=\"s1\" />\n      <transition event=\"*\" target=\"fail\" />\n   </state>\n\n   <state id=\"s1\">\n      <onentry>\n         <assign location=\"_event\" expr=\"'otherName'\" />\n         <raise event=\"event2\" />\n      </onentry>\n      <!-- throw out event1 if it's still around -->\n      <transition event=\"event1\" />\n      <transition event=\"error.execution\" target=\"s2\" />\n      <!-- event1 would trigger this transition if we didn't drop it.  We want this transition to\n      have\n   a very general trigger to catch cases where the wrong error event was raised -->\n      <transition event=\"*\" target=\"fail\" />\n   </state>\n\n   <state id=\"s2\">\n      <onentry>\n         <assign location=\"_ioprocessors\" expr=\"'otherName'\" />\n         <raise event=\"event3\" />\n      </onentry>\n      <transition event=\"event2\" />\n      <transition event=\"error.execution\" target=\"s3\" />\n      <transition event=\"*\" target=\"fail\" />\n   </state>\n\n   <state id=\"s3\">\n      <onentry>\n         <assign location=\"_name\" expr=\"'otherName'\" />\n         <raise event=\"event4\" />\n      </onentry>\n      <transition event=\"event3\" />\n      <transition event=\"error.execution\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n   </state>\n\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test347.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the scxml event I/O processor works by sending events\nback and forth between an invoked child\nand its parent process -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n\n\n   <state id=\"s0\" initial=\"s01\">\n      <invoke id=\"child\" type=\"scxml\">\n         <content>\n            <scxml initial=\"sub0\" version=\"1.0\" datamodel=\"python\" name=\"machineName\">\n               <state id=\"sub0\">\n                  <onentry>\n                     <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" target=\"#_parent\"\n                        event=\"childToParent\" />\n                  </onentry>\n                  <transition event=\"parentToChild\" target=\"subFinal\" />\n               </state>\n               <final id=\"subFinal\" />\n            </scxml>\n         </content>\n      </invoke>\n      <onentry>\n         <send delay=\"20s\" event=\"timeout\" />\n      </onentry>\n      <transition event=\"timeout\" target=\"fail\" />\n\n      <state id=\"s01\">\n         <transition event=\"childToParent\" target=\"s02\" />\n      </state>\n\n      <state id=\"s02\">\n         <onentry>\n            <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" target=\"#_child\"\n               event=\"parentToChild\" />\n         </onentry>\n         <transition event=\"done.invoke\" target=\"pass\" />\n         <transition event=\"error\" target=\"fail\" />\n      </state>\n   </state>\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test348.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n\n   <!-- test that event param of send sets the name of the event -->\n\n   <state id=\"s0\">\n\n      <onentry>\n         <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" event=\"s0Event\" />\n      </onentry>\n      <transition event=\"s0Event\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n\n   </state>\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test349.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that value in origin field can be used to send an event\nback to the sender -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n\n    <onentry>\n      <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" event=\"s0Event\" />\n    </onentry>\n    <transition event=\"s0Event\" target=\"s2\">\n      <assign location=\"Var1\" expr=\"_event.origin\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n\n  </state>\n\n  <state id=\"s2\">\n    <onentry>\n      <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" targetexpr=\"Var1\"\n        event=\"s0Event2\" />\n    </onentry>\n    <transition event=\"s0Event2\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test350.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that target value is used to decide what session to deliver\nthe event to.  A session should be\nable to send an event to itself using its own session ID as the target -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n   initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n   <datamodel>\n      <data id=\"Var1\" expr=\"'#_scxml_' + _sessionid\" />\n   </datamodel>\n\n   <state id=\"s0\">\n\n      <onentry>\n         <send delay=\"2s\" event=\"timeout\" />\n         <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" targetexpr=\"Var1\"\n            event=\"s0Event\" />\n      </onentry>\n      <transition event=\"s0Event\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n\n   </state>\n\n\n   <final id=\"pass\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'pass'\" />\n      </onentry>\n   </final>\n   <final id=\"fail\">\n      <onentry>\n         <log label=\"Outcome\" expr=\"'fail'\" />\n      </onentry>\n   </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test351.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that sendid is set in event if present in send, blank\notherwise -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" />\n    <data id=\"Var2\" />\n  </datamodel>\n\n\n  <state id=\"s0\">\n\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n      <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" id=\"send1\" event=\"s0Event\" />\n    </onentry>\n    <transition event=\"s0Event\" target=\"s1\">\n      <assign location=\"Var1\" expr=\"_event.sendid\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\">\n    </transition>\n\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1=='send1'\" target=\"s2\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n      <send event=\"s0Event2\" />\n    </onentry>\n    <transition event=\"s0Event2\" target=\"s3\">\n      <assign location=\"Var2\" expr=\"_event.sendid\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s3\">\n    <transition cond=\"not Var2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test352.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test the origintype is \"scxml\" -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" />\n  </datamodel>\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n      <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" event=\"s0Event\" />\n    </onentry>\n    <transition event=\"s0Event\" target=\"s1\">\n      <assign location=\"Var1\" expr=\"_event.origintype\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\">\n    </transition>\n\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1=='scxml'\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test354.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that event.data can be populated using both namelist, param\nand <content>\nand that correct values are used -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n    <data id=\"Var2\" />\n    <data id=\"Var3\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n      <send event=\"event1\" type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" namelist=\"Var1\">\n        <param name=\"param1\" expr=\"2\" />\n      </send>\n    </onentry>\n    <transition event=\"event1\" target=\"s1\">\n      <assign location=\"Var2\" expr=\"_event.data.get('Var1')\" />\n      <assign location=\"Var3\" expr=\"_event.data.get('param1')\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\">\n    </transition>\n\n  </state>\n\n  <state id=\"s1\">\n\n    <transition cond=\"Var2==1\" target=\"s2\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s2\">\n    <transition cond=\"Var3==2\" target=\"s3\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s3\">\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n      <send event=\"event2\">\n        <content>foo</content>\n      </send>\n    </onentry>\n    <transition event=\"event2\" cond=\"_event.data == 'foo'\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test355.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that default initial state is first in document order.  If\nwe enter s0 first we succeed, if s1, failure. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\">\n\n\n  <state id=\"s0\">\n    <transition target=\"pass\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test364.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that default initial states are entered when a compound state is entered.  First we test\nthe 'initial' attribute, then the initial element, then default to the first child in document\norder.\nIf we get to s01111 we succeed, if any other state, failure.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" initial=\"s1\" version=\"1.0\">\n  <state id=\"s1\" initial=\"s11p112 s11p122\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n    <state id=\"s11\" initial=\"s111\">\n      <state id=\"s111\" />\n      <parallel id=\"s11p1\">\n        <state id=\"s11p11\" initial=\"s11p111\">\n          <state id=\"s11p111\" />\n          <state id=\"s11p112\">\n            <onentry>\n              <raise event=\"In-s11p112\" />\n            </onentry>\n          </state>\n        </state>\n        <state id=\"s11p12\" initial=\"s11p121\">\n          <state id=\"s11p121\" />\n          <state id=\"s11p122\">\n            <transition event=\"In-s11p112\" target=\"s2\" />\n          </state>\n        </state>\n      </parallel>\n    </state>\n  </state>\n  <state id=\"s2\">\n    <initial>\n      <transition target=\"s21p112 s21p122\" />\n    </initial>\n    <transition event=\"timeout\" target=\"fail\" />\n    <state id=\"s21\" initial=\"s211\">\n      <state id=\"s211\" />\n      <parallel id=\"s21p1\">\n        <state id=\"s21p11\" initial=\"s21p111\">\n          <state id=\"s21p111\" />\n          <state id=\"s21p112\">\n            <onentry>\n              <raise event=\"In-s21p112\" />\n            </onentry>\n          </state>\n        </state>\n        <state id=\"s21p12\" initial=\"s21p121\">\n          <state id=\"s21p121\" />\n          <state id=\"s21p122\">\n            <transition event=\"In-s21p112\" target=\"s3\" />\n          </state>\n        </state>\n      </parallel>\n    </state>\n  </state>\n  <state id=\"s3\">\n    <transition target=\"fail\" />\n    <state id=\"s31\">\n      <state id=\"s311\">\n        <state id=\"s3111\">\n          <transition target=\"pass\" />\n        </state>\n        <state id=\"s3112\" />\n        <state id=\"s312\" />\n        <state id=\"s32\" />\n      </state>\n    </state>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test372.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that entering a final state generates done.state.parentid\nafter executing the onentry elements.\nVar1 should be set to 2 by the time the event is raised -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\">\n\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\" initial=\"s0final\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"done.state.s0\" cond=\"Var1==2\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n\n    <final id=\"s0final\">\n      <onentry>\n        <assign location=\"Var1\" expr=\"2\" />\n      </onentry>\n    </final>\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test375.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that onentry handlers are executed in document order.\nevent1 should be raised before event2 -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"event1\" />\n    </onentry>\n    <onentry>\n      <raise event=\"event2\" />\n    </onentry>\n\n    <transition event=\"event1\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n\n  </state>\n\n  <state id=\"s1\">\n    <transition event=\"event2\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test376.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that each onentry handler is a separate block.  The <send>\nof event1 will cause an error but\n the increment to var1 should happen anyways -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send target=\"baz\" event=\"event1\" />\n    </onentry>\n    <onentry>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onentry>\n\n    <transition cond=\"Var1==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test377.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that onexit handlers are executed in document order.  event1\nshould be raised before event2 -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\">\n\n\n  <state id=\"s0\">\n    <onexit>\n      <raise event=\"event1\" />\n    </onexit>\n    <onexit>\n      <raise event=\"event2\" />\n    </onexit>\n\n    <transition target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n\n    <transition event=\"event1\" target=\"s2\" />\n    <transition event=\"*\" target=\"fail\" />\n\n  </state>\n\n  <state id=\"s2\">\n    <transition event=\"event2\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test378.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that each onexithandler is a separate block.  The <send> of\nevent1 will cause an error but\n the increment to var1 should happen anyways -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onexit>\n      <send target=\"baz\" event=\"event1\" />\n    </onexit>\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n\n    <transition target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test387.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the default history state works correctly.  From s1 we\ntake a transition to s0's default\ndeep history state.  We should end up in s022, generating \"enteringS022\".  Otherwise failure.-->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s1\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" intial=\"s01\">\n    <transition event=\"enteringS022\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n\n    <history type=\"shallow\" id=\"s0HistShallow\">\n      <transition target=\"s02\" />\n    </history>\n    <history type=\"deep\" id=\"s0HistDeep\">\n      <transition target=\"s022\" />\n    </history>\n    <state id=\"s01\" initial=\"s011\">\n      <state id=\"s011\">\n        <onentry>\n          <raise event=\"enteringS011\" />\n        </onentry>\n      </state>\n      <state id=\"s012\">\n        <onentry>\n          <raise event=\"enteringS012\" />\n        </onentry>\n      </state>\n    </state>\n    <state id=\"s02\" initial=\"s021\">\n      <state id=\"s021\">\n        <onentry>\n          <raise event=\"enteringS021\" />\n        </onentry>\n      </state>\n      <state id=\"s022\">\n        <onentry>\n          <raise event=\"enteringS022\" />\n        </onentry>\n      </state>\n    </state>\n\n  </state>\n\n\n  <state id=\"s1\">\n    <transition target=\"s0HistDeep\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test388.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that history states works correctly.  The counter Var1 counts how many times\nwe have entered s0.  The initial state is s012.  We then transition to s1, which transitions\nto s0's deep history state.  entering.s012 should be raised, otherwise failure.  Then we transition\nto s02, which transitions to s0's shallow history state.  That should have value s01, and its\ninitial\nstate is s011, so we should get entering.s011, otherwise failure.-->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s012\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onentry>\n    <!-- the first time through, go to s1, setting  a timer just in case something hangs -->\n    <transition event=\"entering.s012\" cond=\"Var1==1\" target=\"s1\">\n      <send event=\"timeout\" delay=\"2s\" />\n    </transition>\n    <!-- the second time, we should get entering.s012.  If so, go to s2, otherwise fail -->\n    <transition event=\"entering.s012\" cond=\"Var1==2\" target=\"s2\" />\n    <transition event=\"entering\" cond=\"Var1==2\" target=\"fail\" />\n    <!-- the third time we should get entering-s011. If so, pass, otherwise fail -->\n    <transition event=\"entering.s011\" cond=\"Var1==3\" target=\"pass\" />\n    <transition event=\"entering\" cond=\"Var1==3\" target=\"fail\" />\n    <!-- if we timeout, the state machine is hung somewhere, so fail -->\n    <transition event=\"timeout\" target=\"fail\" />\n    <history type=\"shallow\" id=\"s0HistShallow\">\n      <transition target=\"s02\" />\n    </history>\n    <history type=\"deep\" id=\"s0HistDeep\">\n      <transition target=\"s022\" />\n    </history>\n    <state id=\"s01\" initial=\"s011\">\n      <state id=\"s011\">\n        <onentry>\n          <raise event=\"entering.s011\" />\n        </onentry>\n      </state>\n      <state id=\"s012\">\n        <onentry>\n          <raise event=\"entering.s012\" />\n        </onentry>\n      </state>\n    </state>\n    <state id=\"s02\" initial=\"s021\">\n      <state id=\"s021\">\n        <onentry>\n          <raise event=\"entering.s021\" />\n        </onentry>\n      </state>\n      <state id=\"s022\">\n        <onentry>\n          <raise event=\"entering.s022\" />\n        </onentry>\n      </state>\n    </state>\n  </state>\n  <state id=\"s1\">\n    <transition target=\"s0HistDeep\" />\n  </state>\n  <state id=\"s2\">\n    <transition target=\"s0HistShallow\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test396.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the value in _event.name matches the event name used\nto match against transitions  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"python\" version=\"1.0\">\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"foo\" />\n    </onentry>\n\n\n    <transition event=\"foo\" cond=\"_event.name == 'foo'\" target=\"pass\" />\n    <transition event=\"foo\" target=\"fail\" />\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test399.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the event name matching works correctly, including\nprefix matching and the fact\nthat the event attribute of transition may contain multiple event designators.    -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n\n    <!-- this will catch the failure case -->\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"s01\">\n      <onentry>\n        <raise event=\"foo\" />\n      </onentry>\n      <!-- test that an event can match against a transition with multiple descriptors -->\n      <transition event=\"foo bar\" target=\"s02\" />\n    </state>\n\n    <state id=\"s02\">\n      <onentry>\n        <raise event=\"bar\" />\n      </onentry>\n      <!-- test that an event can match the second descriptor as well -->\n      <transition event=\"foo bar\" target=\"s03\" />\n    </state>\n\n    <state id=\"s03\">\n      <onentry>\n        <raise event=\"foo.zoo\" />\n      </onentry>\n      <!-- test that a prefix descriptor matches -->\n      <transition event=\"foo bar\" target=\"s04\" />\n    </state>\n\n    <state id=\"s04\">\n      <onentry>\n        <raise event=\"foos\" />\n      </onentry>\n      <!-- test that only token prefixes match -->\n      <transition event=\"foo\" target=\"fail\" />\n      <transition event=\"foos\" target=\"s05\" />\n    </state>\n\n    <state id=\"s05\">\n      <onentry>\n        <raise event=\"foo.zoo\" />\n      </onentry>\n      <!-- test that .* works at the end of a descriptor -->\n      <transition event=\"foo.*\" target=\"s06\" />\n    </state>\n\n    <state id=\"s06\">\n      <onentry>\n        <raise event=\"foo\" />\n      </onentry>\n      <!-- test that \"*\" works by itself -->\n      <transition event=\"*\" target=\"pass\" />\n    </state>\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test401.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that errors go in the internal event queue.  We send ourselves an external event foo, then\nperform\nand operation that raises an error. Then check that the error event is processed first, even though\nit was raised second  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"foo\" />\n      <!-- assigning to a non-existent location should raise an error -->\n      <assign location=\"foo.bar.baz \" expr=\"2\" />\n    </onentry>\n    <transition event=\"foo\" target=\"fail\" />\n    <transition event=\"error\" target=\"pass\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test402.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- the assertion that errors are 'like any other event' is pretty\nbroad, but we can check that they\nare pulled off the internal queue in order, and that prefix matching works on them.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <!-- catch the failure case -->\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"s01\">\n      <onentry>\n        <!-- the first internal event.  The error will be the second, and event2 will be the third -->\n        <raise event=\"event1\" />\n        <!-- assigning to a non-existent location should raise an error -->\n        <assign location=\"Var1\" expr=\"2\" />\n      </onentry>\n\n      <transition event=\"event1\" target=\"s02\">\n        <raise event=\"event2\" />\n      </transition>\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s02\">\n      <transition event=\"error\" target=\"s03\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s03\">\n      <transition event=\"event2\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test403a.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest403a\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we\n\ttest one part of 'optimal enablement' meaning that of all transitions that are enabled, we chose\n\tthe ones\nin child states over parent states, and use document order to break ties. We have\na parent state s0 with two children, s01 and s02. In s01, we test that a) if\na transition in the child matches, we don't consider matches in the parent and b)\nthat if two transitions match in any state, we take the first in document order.\nIn s02 we test that we take a transition in the parent if there is no\nmatching transition in the child.-->\n\t<state id=\"s0\" initial=\"s01\">\n\t\t<onentry><!--catch\n\t\t\tthe failure case-->\n\t\t\t<send delay=\"1s\" event=\"timeout\" />\n\t\t</onentry>\n\t\t<transition event=\"timeout\" target=\"fail\" />\n\t\t<transition event=\"event1\" target=\"fail\" />\n\t\t<transition event=\"event2\" target=\"pass\" />\n\t\t<state id=\"s01\">\n\t\t\t<onentry><!--this\n\t\t\t\tshould be caught by the first transition in this state, taking us to S02-->\n\t\t\t\t<raise event=\"event1\" />\n\t\t\t</onentry>\n\t\t\t<transition event=\"event1\" target=\"s02\" />\n\t\t\t<transition event=\"*\" target=\"fail\" />\n\t\t</state>\n\t\t<state id=\"s02\">\n\t\t\t<onentry><!--since\n\t\t\t\tthe local transition has a cond that evaluates to false this should be caught by a\n    transition in the parent state, taking us to pass-->\n\t\t\t\t<raise event=\"event2\" />\n\t\t\t</onentry>\n\t\t\t<transition event=\"event1\" target=\"fail\" />\n\t\t\t<transition cond=\"false\" event=\"event2\" target=\"fail\" />\n\t\t</state>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test403b.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest403b\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we\n\ttest that 'optimally enabled set' really is a set, specifically that if a transition is\n\toptimally enabled in\ntwo different states, it is taken only once.-->\n\t<datamodel>\n\t\t<data expr=\"0\" id=\"Var1\" />\n\t</datamodel>\n\t<state id=\"s0\" initial=\"p0\"><!--this\n\t\ttransition should never be taken because a transition in a lower state should\n  always be selected-->\n\t\t<transition event=\"event1\">\n\t\t\t<assign location=\"Var1\" expr=\"Var1 + 1\" />\n\t\t</transition>\n\t\t<parallel id=\"p0\">\n\t\t\t<onentry>\n\t\t\t\t<raise event=\"event1\" />\n\t\t\t\t<raise event=\"event2\" />\n\t\t\t</onentry><!--this\n\t\t\ttransition will be selected by both states p0s1 and p0s2, but should be executed only\n\t\t\tonce-->\n\t\t\t<transition event=\"event1\">\n\t\t\t\t<assign location=\"Var1\" expr=\"Var1 + 1\" />\n\t\t\t</transition>\n\t\t\t<state id=\"p0s1\">\n\t\t\t\t<transition cond=\"Var1==1\" event=\"event2\" target=\"pass\" />\n\t\t\t\t<transition event=\"event2\" target=\"fail\" />\n\t\t\t</state>\n\t\t\t<state id=\"p0s2\" />\n\t\t</parallel>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test403c.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest403c\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we\n\ttest 'optimally enabled set', specifically that preemption works correctly-->\n\t<datamodel>\n\t\t<data expr=\"0\" id=\"Var1\" />\n\t</datamodel>\n\t<state id=\"s0\" initial=\"p0\">\n\t\t<onentry>\n\t\t\t<raise event=\"event1\" />\n\t\t\t<send delay=\"1s\" event=\"timeout\" />\n\t\t</onentry>\n\t\t<transition event=\"event2\" target=\"fail\" />\n\t\t<transition event=\"timeout\" target=\"fail\" />\n\t\t<parallel id=\"p0\">\n\t\t\t<state id=\"p0s1\">\n\t\t\t\t<transition event=\"event1\" />\n\t\t\t\t<transition event=\"event2\" />\n\t\t\t</state>\n\t\t\t<state id=\"p0s2\">\n\t\t\t\t<transition event=\"event1\" target=\"p0s1\">\n\t\t\t\t\t<raise event=\"event2\" />\n\t\t\t\t</transition>\n\t\t\t</state>\n\t\t\t<state id=\"p0s3\"><!--this\n\t\t\t\ttransition should be blocked by the one in p0s2--><!--this\n\t\t\t\ttransition will preempt the one that p0s2 inherits\n   from an ancestor-->\n\t\t\t\t<transition event=\"event1\" target=\"fail\" />\n\t\t\t\t<transition event=\"event2\" target=\"s1\" />\n\t\t\t</state>\n\t\t\t<state id=\"p0s4\"><!--this\n\t\t\t\ttransition never gets preempted, should fire twice-->\n\t\t\t\t<transition event=\"*\">\n\t\t\t\t\t<assign location=\"Var1\" expr=\"Var1 + 1\" />\n\t\t\t\t</transition>\n\t\t\t</state>\n\t\t</parallel>\n\t</state>\n\t<state id=\"s1\">\n\t\t<transition cond=\"Var1==2\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test404.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--  test that states are exited in exit order (children before\nparents with reverse doc order used to break ties\n before the executable content in the transitions.  event1, event2, event3, event4 should be raised\nin that\n order when s01p is exited  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01p\">\n\n\n    <parallel id=\"s01p\">\n      <onexit>\n        <!-- this should be the 3rd event raised -->\n        <raise event=\"event3\" />\n      </onexit>\n      <transition target=\"s02\">\n        <!-- this should be the fourth event raised -->\n        <raise event=\"event4\" />\n      </transition>\n\n      <state id=\"s01p1\">\n        <onexit>\n          <!-- this should be the second event raised -->\n          <raise event=\"event2\" />\n        </onexit>\n      </state>\n\n      <state id=\"s01p2\">\n        <!-- this should be the first event raised -->\n        <onexit>\n          <raise event=\"event1\" />\n        </onexit>\n      </state>\n    </parallel>\n\n    <state id=\"s02\">\n      <transition event=\"event1\" target=\"s03\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s03\">\n      <transition event=\"event2\" target=\"s04\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s04\">\n      <transition event=\"event3\" target=\"s05\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s05\">\n      <transition event=\"event4\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test405.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--  test that the executable content in the transitions is executed\nin document order after\nthe states are exited. event1, event2, event3, event4 should be raised in that order when the\nstate machine is entered  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n  <state id=\"s0\" initial=\"s01p\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <parallel id=\"s01p\">\n      <transition event=\"event1\" target=\"s02\" />\n\n\n      <state id=\"s01p1\" initial=\"s01p11\">\n        <state id=\"s01p11\">\n          <onexit>\n            <!-- this should be the second event raised -->\n            <raise event=\"event2\" />\n          </onexit>\n          <transition target=\"s01p12\">\n            <!-- this should be the third event raised -->\n            <raise event=\"event3\" />\n          </transition>\n        </state>\n        <state id=\"s01p12\" />\n      </state>  <!-- end s01p1 -->\n\n      <state id=\"s01p2\" initial=\"s01p21\">\n        <state id=\"s01p21\">\n          <onexit>\n            <!-- this should be the first event raised -->\n            <raise event=\"event1\" />\n          </onexit>\n          <transition target=\"s01p22\">\n            <!-- this should be the fourth event raised -->\n            <raise event=\"event4\" />\n          </transition>\n        </state>\n        <state id=\"s01p22\" />\n\n      </state>  <!-- end s01p2 -->\n    </parallel>\n\n\n    <state id=\"s02\">\n      <transition event=\"event2\" target=\"s03\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s03\">\n      <transition event=\"event3\" target=\"s04\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n\n    <state id=\"s04\">\n      <transition event=\"event4\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n  </state>  <!-- end s01 -->\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test406.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--  Test that states are entered in entry order (parents before\nchildren with document order used to break ties)\nafter the executable content in the transition is executed. event1, event2, event3, event4 should\nbe raised in that\norder when the transition in s01 is taken  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\" datamodel=\"python\">\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"s01\">\n      <transition target=\"s0p2\">\n        <!-- this should be the first event raised -->\n        <raise event=\"event1\" />\n      </transition>\n    </state>\n\n    <parallel id=\"s0p2\">\n\n      <transition event=\"event1\" target=\"s03\" />\n\n      <state id=\"s01p21\">\n        <onentry>\n          <!-- third event -->\n          <raise event=\"event3\" />\n        </onentry>\n      </state>\n\n      <state id=\"s01p22\">\n        <onentry>\n          <!-- the fourth event -->\n          <raise event=\"event4\" />\n        </onentry>\n      </state>\n\n      <onentry>\n        <!-- this should be the second event raised -->\n        <raise event=\"event2\" />\n      </onentry>\n    </parallel>\n\n\n    <state id=\"s03\">\n      <transition event=\"event2\" target=\"s04\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s04\">\n      <transition event=\"event3\" target=\"s05\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n\n    <state id=\"s05\">\n      <transition event=\"event4\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n  </state>  <!-- end s0 -->\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test407.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- a simple test that onexit handlers work. var1 should be\nincremented when we leave s0 -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n    <transition target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test409.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that states are removed from the active states list as\nthey are exited.  When s01's onexit handler\nfires, s011 should not be on the active state list, so in(S011) should be false, and event1 should\nnot\nbe raised.  Therefore the timeout should fire to indicate success   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n\n    <transition event=\"timeout\" target=\"pass\" />\n    <transition event=\"event1\" target=\"fail\" />\n\n    <state id=\"s01\" initial=\"s011\">\n      <onexit>\n        <if cond=\"In('s011')\">\n          <raise event=\"event1\" />\n        </if>\n      </onexit>\n\n      <state id=\"s011\">\n        <transition target=\"s02\" />\n      </state>\n    </state> <!-- end s01 -->\n\n    <state id=\"s02\" />\n\n  </state> <!-- end s0 -->\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test411.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that states are added to the active states list as they\nare entered and before onentry handlers\nare executed.  When s0's onentry handler fires we should not be in s01.  But when s01's onentry\nhandler\nfires, we should be in s01.  Therefore event1 should not fire, but event2 should.  Either event1 or\ntimeout also indicates failure  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n      <if cond=\"In('s01')\">\n        <raise event=\"event1\" />\n      </if>\n    </onentry>\n\n    <transition event=\"timeout\" target=\"fail\" />\n    <transition event=\"event1\" target=\"fail\" />\n    <transition event=\"event2\" target=\"pass\" />\n\n    <state id=\"s01\">\n      <onentry>\n        <if cond=\"In('s01')\">\n          <raise event=\"event2\" />\n        </if>\n      </onentry>\n    </state>\n\n  </state> <!-- end s0 -->\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test412.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that executable content in the <initial> transition\nexecutes after the onentry handler on the state\nand before the onentry handler of the child states.\nEvent1, event2, and event3 should occur in that\norder. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\" initial=\"s01\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n\n    <transition event=\"timeout\" target=\"fail\" />\n    <transition event=\"event1\" target=\"fail\" />\n    <transition event=\"event2\" target=\"pass\" />\n\n    <state id=\"s01\">\n      <onentry>\n        <raise event=\"event1\" />\n      </onentry>\n      <initial>\n        <transition target=\"s011\">\n          <raise event=\"event2\" />\n        </transition>\n      </initial>\n\n      <state id=\"s011\">\n        <onentry>\n          <raise event=\"event3\" />\n        </onentry>\n        <transition target=\"s02\" />\n      </state>\n    </state>\n\n    <state id=\"s02\">\n      <transition event=\"event1\" target=\"s03\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s03\">\n      <transition event=\"event2\" target=\"s04\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n    <state id=\"s04\">\n      <transition event=\"event3\" target=\"pass\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n\n  </state> <!-- end s0 -->\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test413.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the state machine is put into the configuration\nspecified by the initial element, without regard\nto any other defaults.  we should start off in s2p111 and s2p122.  the atomic\nstates we should not enter all have immediate transitions to failure in them -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s2p112 s2p122\" version=\"1.0\" datamodel=\"python\">\n\n  <state id=\"s1\">\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s2\" initial=\"s2p1\">\n\n    <parallel id=\"s2p1\">\n      <!-- this transition will be triggered only if we end up in an illegal configuration where\n      we're in\n  either s2p112 or s2p122, but not both of them -->\n      <transition target=\"fail\" />\n\n      <state id=\"s2p11\" initial=\"s2p111\">\n        <state id=\"s2p111\">\n          <transition target=\"fail\" />\n        </state>\n\n        <state id=\"s2p112\">\n          <transition cond=\"In('s2p122')\" target=\"pass\" />\n        </state>\n\n      </state> <!-- end\n      s2p11 -->\n\n      <state id=\"s2p12\" initial=\"s2p121\">\n        <state id=\"s2p121\">\n          <transition target=\"fail\" />\n        </state>\n\n        <state id=\"s2p122\">\n          <transition cond=\"In('s2p112')\" target=\"pass\" />\n        </state>\n      </state>\n\n    </parallel>\n\n  </state>  <!--\n  end s2 -->\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test416.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the done.state.id gets generated when we enter the\nfinal state of a compound state -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s1\" datamodel=\"python\">\n\n  <state id=\"s1\" initial=\"s11\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <state id=\"s11\" initial=\"s111\">\n      <transition event=\"done.state.s11\" target=\"pass\" />\n      <state id=\"s111\">\n        <transition target=\"s11final\" />\n      </state>\n      <final id=\"s11final\" />\n    </state>\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test417.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that we get the done.state.id event when all of a parallel\nelements children enter final states -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s1\" datamodel=\"python\">\n\n  <state id=\"s1\" initial=\"s1p1\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n\n    <parallel id=\"s1p1\">\n      <transition event=\"done.state.s1p1\" target=\"pass\" />\n\n      <state id=\"s1p11\" initial=\"s1p111\">\n        <state id=\"s1p111\">\n          <transition target=\"s1p11final\" />\n        </state>\n        <final id=\"s1p11final\" />\n      </state>\n\n      <state id=\"s1p12\" initial=\"s1p121\">\n        <state id=\"s1p121\">\n          <transition target=\"s1p12final\" />\n        </state>\n        <final id=\"s1p12final\" />\n      </state>\n    </parallel>\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test419.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that eventless transitions take precedence -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s1\" datamodel=\"python\">\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"internalEvent\" />\n      <send event=\"externalEvent\" />\n    </onentry>\n\n    <transition event=\"*\" target=\"fail\" />\n    <transition target=\"pass\" />\n\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test421.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that internal events take priority over external ones -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s1\" datamodel=\"python\">\n\n  <state id=\"s1\" initial=\"s11\">\n    <onentry>\n      <send event=\"externalEvent\" />\n      <raise event=\"internalEvent1\" />\n      <raise event=\"internalEvent2\" />\n    </onentry>\n\n    <transition event=\"externalEvent\" target=\"fail\" />\n\n    <state id=\"s11\">\n      <transition event=\"internalEvent1\" target=\"s12\" />\n    </state>\n\n    <state id=\"s12\">\n      <transition event=\"internalEvent2\" target=\"pass\" />\n    </state>\n\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test422.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s1\" name=\"ScxmlTest422\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--Test\n\tthat at the end of a macrostep, the processor executes all invokes in states\nthat have been entered and not exited during the step.  (The invokes are supposed to be executed\nin document order, but we can test that since each invocation is separate and they may take\ndifferent amounts to time to start up.)  In this case, there are three invoke statements,\nin states s1, s11 and s12.  Each invoked process returns an event named after its parent state.\nThe invokes in s1 and s12 should execute, but not the one\nin s11. So we should receive invokeS1, invokeS12, but not invokeS12.  Furthermore, when the timeout\n\tfires, var1 should equal 2.-->\n\t<datamodel>\n\t\t<data expr=\"0\" id=\"Var1\" />\n\t</datamodel>\n\t<state id=\"s1\" initial=\"s11\">\n\t\t<onentry>\n\t\t\t<send delayexpr=\"'2s'\" event=\"timeout\" />\n\t\t</onentry>\n\t\t<invoke>\n\t\t\t<content>\n\t\t\t\t<scxml datamodel=\"python\" initial=\"sub0\" name=\"ScxmlShape1\" version=\"1.0\"\n\t\t\t\t\txmlns=\"http://www.w3.org/2005/07/scxml\">\n\t\t\t\t\t<state id=\"sub0\">\n\t\t\t\t\t\t<onentry>\n\t\t\t\t\t\t\t<send event=\"invokeS1\" target=\"#_parent\" />\n\t\t\t\t\t\t</onentry>\n\t\t\t\t\t\t<transition target=\"subFinal0\" />\n\t\t\t\t\t</state>\n\t\t\t\t\t<final id=\"subFinal0\" />\n\t\t\t\t</scxml><!--when\n\t\t\t\tinvoked, send 'foo' to parent, then terminate.-->\n\t\t\t</content>\n\t\t</invoke>\n\t\t<transition event=\"invokeS1 invokeS12\">\n\t\t\t<assign location=\"Var1\" expr=\"Var1 + 1\" />\n\t\t</transition>\n\t\t<transition event=\"invokeS11\" target=\"fail\" />\n\t\t<transition cond=\"Var1==2\" event=\"timeout\" target=\"pass\" />\n\t\t<transition event=\"timeout\" target=\"fail\" />\n\t\t<state id=\"s11\">\n\t\t\t<invoke>\n\t\t\t\t<content>\n\t\t\t\t\t<scxml datamodel=\"python\" initial=\"sub1\" name=\"ScxmlShape1\" version=\"1.0\"\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2005/07/scxml\">\n\t\t\t\t\t\t<state id=\"sub1\">\n\t\t\t\t\t\t\t<onentry>\n\t\t\t\t\t\t\t\t<send event=\"invokeS11\" target=\"#_parent\" />\n\t\t\t\t\t\t\t</onentry>\n\t\t\t\t\t\t\t<transition target=\"subFinal1\" />\n\t\t\t\t\t\t</state>\n\t\t\t\t\t\t<final id=\"subFinal1\" />\n\t\t\t\t\t</scxml><!--when\n\t\t\t\t\tinvoked, send 'foo' to parent, then terminate.-->\n\t\t\t\t</content>\n\t\t\t</invoke>\n\t\t\t<transition target=\"s12\" />\n\t\t</state>\n\t\t<state id=\"s12\">\n\t\t\t<invoke>\n\t\t\t\t<content>\n\t\t\t\t\t<scxml datamodel=\"python\" initial=\"sub2\" name=\"ScxmlShape1\" version=\"1.0\"\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2005/07/scxml\">\n\t\t\t\t\t\t<state id=\"sub2\">\n\t\t\t\t\t\t\t<onentry>\n\t\t\t\t\t\t\t\t<send event=\"invokeS12\" target=\"#_parent\" />\n\t\t\t\t\t\t\t</onentry>\n\t\t\t\t\t\t\t<transition target=\"subFinal2\" />\n\t\t\t\t\t\t</state>\n\t\t\t\t\t\t<final id=\"subFinal2\" />\n\t\t\t\t\t</scxml><!--when\n\t\t\t\t\tinvoked, send 'foo' to parent, then terminate.-->\n\t\t\t\t</content>\n\t\t\t</invoke>\n\t\t</state>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test423.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that we keep pulling external events off the queue till we\nfind one that matches a transition. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"externalEvent1\" />\n      <send event=\"externalEvent2\" delay=\"1s\" />\n      <raise event=\"internalEvent\" />\n    </onentry>\n    <!-- in this state we should process only internalEvent -->\n    <transition event=\"internalEvent\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <state id=\"s1\">\n    <!-- in this state we ignore externalEvent1 and wait for externalEvent2 -->\n    <transition event=\"externalEvent2\" target=\"pass\" />\n    <transition event=\"internalEvent\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test487.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test illegal assignment.  error.execution should be raised.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1 = 1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"event\" />\n    </onentry>\n\n    <transition event=\"error.execution\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test488.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest488\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n\tthat illegal expr in <param> produces error.execution and empty event.data--><!--if\n\twe get here, we received the error event. Now check that the done\n event has empty event.data-->\n\t<state id=\"s0\" initial=\"s01\"><!--we\n\t\tshould get the error before the done event-->\n\t\t<transition event=\"error.execution\" target=\"s1\" />\n\t\t<transition event=\"done.state.s0\" target=\"fail\" />\n\t\t<state id=\"s01\">\n\t\t\t<transition target=\"s02\" />\n\t\t</state>\n\t\t<final id=\"s02\">\n\t\t\t<donedata>\n\t\t\t\t<param expr=\"return\" name=\"someParam\" />\n\t\t\t</donedata>\n\t\t</final>\n\t</state>\n\t<state id=\"s1\">\n\t\t<transition cond=\"_event.data is None\" event=\"done.state.s0\" target=\"pass\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test495.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the scxml event i/o processor puts events in the\ncorrect queues.-->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <!-- default target is external queue -->\n      <send event=\"event1\" type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" />\n      <send event=\"event2\" target=\"#_internal\"\n        type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" />\n    </onentry>\n    <!-- we should get the internal event first -->\n    <transition event=\"event1\" target=\"fail\" />\n    <transition event=\"event2\" target=\"s1\" />\n  </state>\n\n  <state id=\"s1\">\n    <transition event=\"event1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test496.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" event=\"event\"\n        target=\"#_scxml_foo\" />\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"error.communication\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test500.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that location field is found\n\tinside entry for SCXML Event I/O processor -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n\tinitial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\t<datamodel>\n\t\t<data id=\"Var1\"\n\t\t\texpr=\"_ioprocessors['http://www.w3.org/TR/scxml/#SCXMLEventProcessor'].get('location')\" />\n\t</datamodel>\n\n\t<state id=\"s0\">\n\t\t<transition cond=\"Var1\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\n\t</state>\n\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'pass'\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'fail'\" />\n\t\t</onentry>\n\t</final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test501.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that the location entry for the SCXML Event I/O processor\ncan be used as the target for an event -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\"\n      expr=\"_ioprocessors[&#34;http://www.w3.org/TR/scxml/#SCXMLEventProcessor&#34;].get('location')\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <send targetexpr=\"Var1\" event=\"foo\" />\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n    <transition event=\"foo\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test503.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that a targetless transition does not exit and reenter its\nsource state -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s1\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />  <!-- how often we have exited s2 -->\n    <data id=\"Var2\" expr=\"0\" />  <!-- how often the targetless transition in s2 has been executed -->\n  </datamodel>\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"foo\" />\n      <raise event=\"bar\" />\n    </onentry>\n    <transition target=\"s2\" />\n  </state>\n\n  <state id=\"s2\">\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n    <transition event=\"foo\">\n      <assign location=\"Var2\" expr=\"Var2 + 1\" />\n    </transition>\n    <!-- make sure the transition on foo was actually taken -->\n    <transition event=\"bar\" cond=\"Var2==1\" target=\"s3\" />\n    <transition event=\"bar\" target=\"fail\" />\n  </state>\n\n  <state id=\"s3\">\n    <!-- make sure that s2 was exited only once -->\n    <transition cond=\"Var1==1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test504.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that an external transition\n\texits all states up the the LCCA -->\n<scxml xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n\tinitial=\"s1\" datamodel=\"python\" name=\"machineName\">\n\t<datamodel>\n\t\t<data id=\"Var1\" expr=\"0\" />  <!-- how often we have exited p -->\n\t\t<data id=\"Var2\" expr=\"0\" />  <!-- how often we have exited ps1 -->\n\t\t<data id=\"Var3\" expr=\"0\" />  <!-- how often we have exited ps2 -->\n\t\t<data id=\"Var4\" expr=\"0\" />  <!-- how often the transition for foo has been taken -->\n\t</datamodel>\n\n\t<state id=\"s1\">\n\t\t<onentry>\n\t\t\t<raise event=\"foo\" />\n\t\t\t<raise event=\"bar\" />\n\t\t</onentry>\n\t\t<transition target=\"p\" />\n\t</state>\n\n\t<parallel id=\"p\">\n\t\t<onexit>\n\t\t\t<log label=\"exit p\" />\n\t\t\t<assign location=\"Var1\"\n\t\t\t\texpr=\"Var1 + 1\" />\n\t\t</onexit>\n\t\t<transition event=\"foo\" target=\"ps1\">\n\t\t\t<assign location=\"Var4\"\n\t\t\t\texpr=\"Var4 + 1\" />\n\t\t</transition>\n\n\t\t<!-- make sure the transition on foo was actually taken -->\n\t\t<transition event=\"bar\" cond=\"Var4==1\" target=\"s2\" />\n\t\t<transition event=\"bar\" target=\"fail\" />\n\n\t\t<state id=\"ps1\">\n\t\t\t<onexit>\n\t\t\t\t<log label=\"exit ps1\" />\n\t\t\t\t<assign location=\"Var2\"\n\t\t\t\t\texpr=\"Var2 + 1\" />\n\t\t\t</onexit>\n\t\t</state>\n\t\t<state id=\"ps2\">\n\t\t\t<onexit>\n\t\t\t\t<log label=\"exit ps2\" />\n\t\t\t\t<assign location=\"Var3\"\n\t\t\t\t\texpr=\"Var3 + 1\" />\n\t\t\t</onexit>\n\t\t</state>\n\t</parallel>\n\n\t<state id=\"s2\">\n\t\t<!-- make sure that p was exited twice -->\n\t\t<transition cond=\"Var1==2\" target=\"s3\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\n\t<state id=\"s3\">\n\t\t<!-- make sure that ps1 was exited twice -->\n\t\t<transition cond=\"Var2==2\" target=\"s4\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\n\t<state id=\"s4\">\n\t\t<!-- make sure that ps2 was exited twice -->\n\t\t<transition cond=\"Var3==2\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log label=\"pass\" expr=\"[Var1, Var2, Var3, Var4]\" />\n\t\t\t<log label=\"Outcome\" expr=\"'pass'\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log label=\"Outcome\" expr=\"'fail'\" />\n\t\t</onentry>\n\t</final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test505.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that an internal transition does not exit its source state -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s1\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />  <!-- how often we have exited s1 -->\n    <data id=\"Var2\" expr=\"0\" />  <!-- how often we have exited s11 -->\n    <data id=\"Var3\" expr=\"0\" />  <!-- how often the transition for foo has been taken -->\n  </datamodel>\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"foo\" />\n      <raise event=\"bar\" />\n    </onentry>\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n    <transition event=\"foo\" type=\"internal\" target=\"s11\">\n      <assign location=\"Var3\" expr=\"Var3 + 1\" />\n    </transition>\n\n    <!-- make sure the transition on foo was actually taken -->\n    <transition event=\"bar\" cond=\"Var3==1\" target=\"s2\" />\n    <transition event=\"bar\" target=\"fail\" />\n\n    <state id=\"s11\">\n      <onexit>\n        <assign location=\"Var2\" expr=\"Var2 + 1\" />\n      </onexit>\n    </state>\n  </state>\n\n  <state id=\"s2\">\n    <!-- make sure that s1 was exited once -->\n    <transition cond=\"Var1==1\" target=\"s3\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <state id=\"s3\">\n    <!-- make sure that s11 was exited twice -->\n    <transition cond=\"Var2==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test506.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that an internal transition whose targets are not proper\ndescendants of its source state\nbehaves like an external transition -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s1\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />  <!-- how often we have exited s2 -->\n    <data id=\"Var2\" expr=\"0\" />  <!-- how often we have exited s21 -->\n    <data id=\"Var3\" expr=\"0\" />  <!-- how often the transition for foo has been taken -->\n  </datamodel>\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"foo\" />\n      <raise event=\"bar\" />\n    </onentry>\n    <transition target=\"s2\" />\n  </state>\n\n  <state id=\"s2\" initial=\"s21\">\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n    <transition event=\"foo\" type=\"internal\" target=\"s2\">\n      <assign location=\"Var3\" expr=\"Var3 + 1\" />\n    </transition>\n\n    <!-- make sure the transition on foo was actually taken -->\n    <transition event=\"bar\" cond=\"Var3==1\" target=\"s3\" />\n    <transition event=\"bar\" target=\"fail\" />\n\n    <state id=\"s21\">\n      <onexit>\n        <assign location=\"Var2\" expr=\"Var2 + 1\" />\n      </onexit>\n    </state>\n\n  </state>\n\n  <state id=\"s3\">\n    <!-- make sure that s2 was exited twice -->\n    <transition cond=\"Var1==2\" target=\"s4\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s4\">\n    <!-- make sure that s21 was exited twice -->\n    <transition cond=\"Var2==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test521.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that the processor raises error.communication if it\ncannot dispatch the event.\n(To create an undispatchable event, we choose a non-existent session as target).  If it raises\nthe error event, we succeed.  Otherwise we eventually timeout and fail.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <!-- should cause an error -->\n      <send target=\"#_scxml_foo\" event=\"event2\" />\n      <!-- this will get added to the external event queue after the error has been raised -->\n      <send event=\"timeout\" />\n    </onentry>\n\n    <!-- once we've entered the state, we should check for internal events first -->\n    <transition event=\"error.communication\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test525.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" name=\"ScxmlTest525\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n\txmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n\tthat <foreach> does a shallow copy, so that modifying the array does not change\nthe iteration behavior.-->\n\t<datamodel>\n\t\t<data id=\"Var1\">[1, 2, 3]</data>\n\t\t<data expr=\"0\" id=\"Var2\" /><!--counts\n\t\tthe number of iterations-->\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<foreach array=\"Var1\" item=\"Var3\">\n\t\t\t\t<assign expr=\"Var1 + ['4']\" location=\"Var1\" />\n\t\t\t\t<assign expr=\"Var2 + 1\" location=\"Var2\" />\n\t\t\t</foreach>\n\t\t</onentry>\n\t\t<transition cond=\"Var2==3\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test527.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- simple test that 'expr' works with <content> -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\" initial=\"s0\">\n  <state id=\"s0\" initial=\"s01\">\n    <transition event=\"done.state.s0\" cond=\"_event.data == 'foo'\" target=\"pass\">\n    </transition>\n    <transition event=\"done.state.s0\" target=\"fail\">\n    </transition>\n    <state id=\"s01\">\n      <transition target=\"s02\" />\n    </state>\n    <final id=\"s02\">\n      <donedata>\n        <content expr=\"'foo'\" />\n      </donedata>\n    </final>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test528.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--  test that illegal 'expr' produces error.execution and empty event.data -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"python\" initial=\"s0\">\n  <state id=\"s0\" initial=\"s01\">\n    <!-- we should get the error before the done event -->\n    <transition event=\"error.execution\" target=\"s1\" />\n    <transition event=\"done.state.s0\" target=\"fail\" />\n    <state id=\"s01\">\n      <transition target=\"s02\" />\n    </state>\n    <final id=\"s02\">\n      <donedata>\n        <content expr=\"return\" />\n      </donedata>\n    </final>\n  </state>\n  <!-- if we get here, we received the error event. Now check that the done\n event has empty event.data -->\n  <state id=\"s1\">\n    <transition event=\"done.state.s0\" cond=\"_event.data is None\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test529.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest529\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--simple\n\ttest that children workn with <content>-->\n\t<state id=\"s0\" initial=\"s01\">\n\t\t<transition cond=\"_event.data == 21\" event=\"done.state.s0\" target=\"pass\" />\n\t\t<transition event=\"done.state.s0\" target=\"fail\" />\n\t\t<state id=\"s01\">\n\t\t\t<transition target=\"s02\" />\n\t\t</state>\n\t\t<final id=\"s02\">\n\t\t\t<donedata>\n\t\t\t\t<content>21</content>\n\t\t\t</donedata>\n\t\t</final>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test530.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that <content> child is evaluated when <invoke> is. Var1 is\ninitialized\nwith an integer value, then set to an scxml script in the onentry to s0.  If <content>\nis evaluated at the right time, we should get invoke.done, otherwise an error  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"1\" />\n  </datamodel>\n\n  <state id=\"s0\">\n    <onentry>\n      <assign location=\"Var1\">\n        <scxml version=\"1.0\">\n          <final />\n        </scxml>\n      </assign>\n      <send event=\"timeout\" delay=\"2s\" />\n    </onentry>\n\n    <invoke type=\"http://www.w3.org/TR/scxml/\">\n      <content expr=\"Var1\" />\n    </invoke>\n\n    <transition event=\"done.invoke\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test533.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that an internal transition whose source state is not\ncompound does  exit its source state -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s1\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />  <!-- how often we have exited p -->\n    <data id=\"Var2\" expr=\"0\" />  <!-- how often we have exited ps1 -->\n    <data id=\"Var3\" expr=\"0\" />  <!-- how often we have exited ps2 -->\n    <data id=\"Var4\" expr=\"0\" />  <!-- how often the transition for foo has been taken -->\n  </datamodel>\n\n  <state id=\"s1\">\n    <onentry>\n      <raise event=\"foo\" />\n      <raise event=\"bar\" />\n    </onentry>\n    <transition target=\"p\" />\n  </state>\n\n  <parallel id=\"p\">\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n    <transition event=\"foo\" type=\"internal\" target=\"ps1\">\n      <assign location=\"Var4\" expr=\"Var4 + 1\" />\n    </transition>\n\n    <!-- make sure the transition on foo was actually taken -->\n    <transition event=\"bar\" cond=\"Var4==1\" target=\"s2\" />\n    <transition event=\"bar\" target=\"fail\" />\n\n    <state id=\"ps1\">\n      <onexit>\n        <assign location=\"Var2\" expr=\"Var2 + 1\" />\n      </onexit>\n    </state>\n    <state id=\"ps2\">\n      <onexit>\n        <assign location=\"Var3\" expr=\"Var3 + 1\" />\n      </onexit>\n    </state>\n  </parallel>\n\n  <state id=\"s2\">\n    <!-- make sure that p was exited twice -->\n    <transition cond=\"Var1==2\" target=\"s3\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s3\">\n    <!-- make sure that ps1 was exited twice -->\n    <transition cond=\"Var2==2\" target=\"s4\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <state id=\"s4\">\n    <!-- make sure that ps2 was exited twice -->\n    <transition cond=\"Var3==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test550.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that expr can be used to assign a value to a var.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"2\" />\n  </datamodel>\n\n  <state id=\"s0\">\n\n    <transition cond=\"Var1==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test551.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that inline content can be used to assign a value to a var.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\">\n      123\n    </data>\n  </datamodel>\n\n  <state id=\"s0\">\n\n    <transition cond=\"Var1\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test552.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest552\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n\tthat src content can be used\n\tto assign a value to a var. Edit test552.txt to have a value that's legal\n\tfor the datamodel in question-->\n\t<datamodel>\n\t\t<data id=\"Var1\" src=\"file:test552.txt\" />\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<transition cond=\"Var1 is not None\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test552.txt",
    "content": "<root></root>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test553.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- we test that the processor does not dispatch the event if\nevaluation\nof <send>'s args causes an error..  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <!-- timeout event -->\n      <send event=\"timeout\" delay=\"3s\" />\n      <!-- include a non-existing var in the namelist -->\n      <send event=\"event1\" namelist=\"Var2\" />\n    </onentry>\n\n    <!-- if we get the timeout before event1, we assume that event1 hasn't been sent\n We ignore the error event here because this assertion doesn't mention it -->\n    <transition event=\"timeout\" target=\"pass\" />\n    <transition event=\"event1\" target=\"fail\" />\n  </state>\n\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test554.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that if the evaluation of <invoke>'s args causes an error,\nthe\ninvocation is cancelled.  In this test, that means that we don't get done.invoke\nbefore the timer goes off.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timer\" delay=\"2s\" />\n    </onentry>\n\n    <!-- namelist references an undeclared variable -->\n    <invoke type=\"http://www.w3.org/TR/scxml/\" namelist=\"Var2\">\n      <content>\n        <scxml initial=\"subFinal\" version=\"1.0\" datamodel=\"python\">\n          <final id=\"subFinal\" />\n        </scxml>\n      </content>\n    </invoke>\n    <transition event=\"timer\" target=\"pass\" />\n    <transition event=\"done.invoke\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test570.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- test that we generate done.state.id when all a parallel state's\nchildren are in final states -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"p0\" datamodel=\"python\" version=\"1.0\">\n\n  <parallel id=\"p0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <raise event=\"e1\" />\n      <raise event=\"e2\" />\n    </onentry>\n    <!-- we should get this event before done.state.p0 -->\n    <transition event=\"done.state.p0s2\" target=\"s1\" />\n\n    <state id=\"p0s1\" initial=\"p0s11\">\n      <state id=\"p0s11\">\n        <transition event=\"e1\" target=\"p0s1final\" />\n      </state>\n      <final id=\"p0s1final\" />\n    </state>\n\n    <state id=\"p0s2\" initial=\"p0s21\">\n      <state id=\"p0s21\">\n        <transition event=\"e2\" target=\"p0s2final\" />\n      </state>\n      <final id=\"p0s2final\" />\n    </state>\n\n  </parallel>\n\n  <state id=\"s1\">\n    <!-- if we get done.state.p0, success -->\n    <transition event=\"done.state.p0\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test576.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that the 'initial' value of scxml is respected.  We set the value to deeply nested\nnon-default parallel siblings and\ntest that both are entered. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s11p112 s11p122\" datamodel=\"python\" version=\"1.0\">\n  <state id=\"s0\">\n    <transition target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <onentry>\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"timeout\" target=\"fail\" />\n    <state id=\"s11\" initial=\"s111\">\n      <state id=\"s111\" />\n      <parallel id=\"s11p1\">\n        <state id=\"s11p11\" initial=\"s11p111\">\n          <state id=\"s11p111\" />\n          <state id=\"s11p112\">\n            <onentry>\n              <raise event=\"In-s11p112\" />\n            </onentry>\n          </state>\n        </state>\n        <state id=\"s11p12\" initial=\"s11p121\">\n          <state id=\"s11p121\" />\n          <state id=\"s11p122\">\n            <transition event=\"In-s11p112\" target=\"pass\" />\n          </state>\n        </state>\n      </parallel>\n    </state>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test579.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that default history content is executed correctly.   The Process MUST execute any\nexecutable content in the transition after the parent state's onentry handlers, and, in the case\nwhere the history pseudo-state is the target of an <initial> transition,\nthe executable content inside the <initial> transition.  However the Processor MUST\nexecute this content only if there is no stored history.  Once the history state's\nparent state has been visited and exited, the default history content must not be executed -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"s0\" datamodel=\"python\">\n  <state id=\"s0\">\n    <datamodel>\n      <data id=\"Var1\" expr=\"0\" />\n    </datamodel>\n    <initial>\n      <transition target=\"sh1\">\n        <raise event=\"event2\" />\n      </transition>\n    </initial>\n    <onentry>\n      <send delayexpr=\"'1s'\" event=\"timeout\" />\n      <raise event=\"event1\" />\n    </onentry>\n    <onexit>\n      <assign location=\"Var1\" expr=\"Var1 + 1\" />\n    </onexit>\n    <history id=\"sh1\">\n      <transition target=\"s01\">\n        <raise event=\"event3\" />\n      </transition>\n    </history>\n    <state id=\"s01\">\n      <transition event=\"event1\" target=\"s02\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n    <state id=\"s02\">\n      <transition event=\"event2\" target=\"s03\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n    <state id=\"s03\">\n      <transition cond=\"Var1==0\" event=\"event3\" target=\"s0\" />\n      <transition cond=\"Var1==1\" event=\"event1\" target=\"s2\" />\n      <transition event=\"*\" target=\"fail\" />\n    </state>\n  </state>\n  <state id=\"s2\">\n    <transition event=\"event2\" target=\"s3\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s3\">\n    <transition event=\"event3\" target=\"fail\" />\n    <transition event=\"timeout\" target=\"pass\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/mandatory/test580.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that a history state never ends up part of the configuration -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" initial=\"p1\" datamodel=\"python\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n  </datamodel>\n  <parallel id=\"p1\">\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n    </onentry>\n    <state id=\"s0\">\n      <transition cond=\"In('sh1')\" target=\"fail\" />\n      <transition event=\"timeout\" target=\"fail\" />\n    </state>\n    <state id=\"s1\">\n      <initial>\n        <transition target=\"sh1\" />\n      </initial>\n      <history id=\"sh1\">\n        <transition target=\"s11\" />\n      </history>\n      <state id=\"s11\">\n        <transition cond=\"In('sh1')\" target=\"fail\" />\n        <transition target=\"s12\" />\n      </state>\n      <state id=\"s12\" />\n      <transition cond=\"In('sh1')\" target=\"fail\" />\n      <transition cond=\"Var1==0\" target=\"sh1\" />\n      <transition cond=\"Var1==1\" target=\"pass\" />\n      <onexit>\n        <assign location=\"Var1\" expr=\"Var1 + 1\" />\n      </onexit>\n    </state>\n  </parallel>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test193.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that omitting target and targetexpr of <send> when using the\nSCXML event i/o processor puts the event on the external queue.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"internal\" />\n      <!-- this should put event1 in the external queue -->\n      <send event=\"event1\" type=\"http://www.w3.org/TR/scxml/#SCXMLEventProcessor\" />\n      <send event=\"timeout\" delay=\"1s\" />\n    </onentry>\n    <transition event=\"event1\" target=\"fail\" />\n    <transition event=\"internal\" target=\"s1\" />\n  </state>\n  <state id=\"s1\">\n    <transition event=\"event1\" target=\"pass\" />\n    <transition event=\"timeout\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test201.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- we test that the processor supports the basic http event i/o processor.  This is an optional\ntest since platforms are not required to support basic http event i/o -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\"\n        targetexpr=\"_ioprocessors['basichttp']['location']\" event=\"event1\" />\n      <send event=\"timeout\" />\n    </onentry>\n    <transition event=\"event1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test278.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"python\">\n\n  <!-- test that a variable can be accessed from a state that is outside its lexical scope -->\n\n  <state id=\"s0\">\n    <transition cond=\"Var1==1\" target=\"pass\" />\n\n    <transition target=\"fail\" />\n    <transition target=\"s1\" event=\"foo\" /> <!-- included so the graph has a single component -->\n\n  </state>\n\n  <state id=\"s1\">\n    <datamodel>\n      <data id=\"Var1\" expr=\"1\" />\n    </datamodel>\n  </state>\n\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test444.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" name=\"ScxmlTest444\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n  xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n  that <data> creates a new python variable.-->\n  <datamodel>\n    <data expr=\"1\" id=\"var1\" />\n  </datamodel>\n  <state id=\"s0\"><!--test\n    that var1 can be used as an python variable-->\n    <transition cond=\"var1 + 1 == 2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test445.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" name=\"ScxmlTest445\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n  xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n  that python objects defined by <data> have value None if <data> does not assign a value-->\n  <datamodel>\n    <data id=\"var1\" />\n  </datamodel>\n  <state id=\"s0\">\n    <transition cond=\"var1==None\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test446.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- in the ECMA data model, test that if the child of <data> is JSON, the processor\n assigns it as the value of the var -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"var1\">[1, 2, 3]</data>\n    <data id=\"var2\" src=\"file:test446.txt\" />\n  </datamodel>\n  <state id=\"s0\">\n    <transition cond=\"var1 instanceof Array\" target=\"s1\" />\n    <transition target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition cond=\"var2 instanceof Array\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test446.txt",
    "content": "[1,2,3]\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test448.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--  test that all ecmascript objects are placed in a single global scope -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <!-- test that a parent state can access a variable defined in a child -->\n    <transition cond=\"var1==1\" target=\"s1\" />\n    <transition target=\"fail\" />\n    <state id=\"s01\">\n      <datamodel>\n        <data id=\"var1\" expr=\"1\" />\n      </datamodel>\n    </state>\n  </state>\n  <state id=\"s1\" initial=\"s01p\">\n    <parallel id=\"s01p\">\n      <state id=\"s01p1\">\n        <!-- test that we can access a variable defined in a parallel sibling state -->\n        <transition cond=\"var2==1\" target=\"pass\" />\n        <transition target=\"fail\" />\n      </state>\n      <state id=\"s01p2\">\n        <datamodel>\n          <data id=\"var2\" expr=\"1\" />\n        </datamodel>\n      </state>\n    </parallel>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test449.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--  test that ecmascript objects are converted to booleans inside cond -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <transition cond=\"'foo'\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test451.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- simple test of the in() predicate -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  version=\"1.0\" datamodel=\"ecmascript\" initial=\"p\">\n  <parallel id=\"p\">\n    <state id=\"s0\">\n      <transition cond=\"In('s1')\" target=\"pass\" />\n      <transition target=\"fail\" />\n    </state>\n    <state id=\"s1\" />\n  </parallel>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test452.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" name=\"ScxmlTest452\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n  xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n  that we can assign to any location in the datamodel.\nIn this case, we just test that we can assign\nto a substructure (not the top level variable).\nThis may not be the most idiomatic way to write the test-->\n  <datamodel>\n    <data expr=\"0\" id=\"foo\" />\n  </datamodel>\n  <script>def testobject():\n    class A:\n        bar = 0\n    return A()\n  </script>\n  <state id=\"s0\">\n    <onentry>\n      <assign expr=\"testobject()\" location=\"foo\" /><!--try\n      to assign to foo's bar property-->\n      <assign expr=\"1\" location=\"foo.bar\" />\n      <raise event=\"event1\" />\n    </onentry><!--test\n    that we have assigned to foo's bar property-->\n    <transition cond=\"foo.bar == 1\" event=\"event1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\">\n      <log expr=\"foo.bar\" />\n    </transition>\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test453.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" name=\"ScxmlTest453\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n  xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n  that we can use any python expression as a value expression.\nIn this case, we just test that we can assign\na function to a variable and then call it.-->\n  <datamodel>\n    <data expr=\"lambda invar: invar + 1\" id=\"var1\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <raise event=\"event1\" />\n    </onentry><!--test\n    that we can call the function-->\n    <transition cond=\"var1(2) == 3\" event=\"event1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test456.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest456\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--we can't test that _any_ python is valid inside <script>, so we\njust run a simple one and check that it can update the data model.-->\n\t<datamodel>\n\t\t<data expr=\"0\" id=\"Var1\"/>\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<script>\n      Var1+=1\n\t\t\t</script>\n\t\t</onentry>\n\t\t<transition cond=\"Var1==1\" target=\"pass\"/>\n\t\t<transition target=\"fail\"/>\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\"/>\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\"/>\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test457.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that an the legal iterable collections are arrays, namely objects that satisfy\ninstanceof(Array) in ECMAScript.\n \t     the legal values for the 'item' attribute on foreach are legal ECMAScript variable names.. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n    <data id=\"Var2\" />\n    <data id=\"Var3\" />\n    <data id=\"Var4\" expr=\"7\" />\n    <data id=\"Var5\" expr=\"[1,2,3]\" />\n    <data id=\"Var6\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <!-- invalid array, legal item -->\n      <foreach item=\"Var2\" index=\"Var3\" array=\"Var4\">\n        <assign location=\"Var1\" expr=\"Var1 + 1\" />\n      </foreach>\n      <raise event=\"foo\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <onentry>\n      <!-- illegal item, legal array -->\n      <foreach item=\"'continue'\" index=\"Var3\" array=\"Var5\">\n        <assign location=\"Var1\" expr=\"Var1 + 1\" />\n      </foreach>\n      <raise event=\"bar\" />\n    </onentry>\n    <transition event=\"error.execution\" target=\"s2\" />\n    <transition event=\"bar\" target=\"fail\" />\n  </state>\n  <state id=\"s2\">\n    <!-- check that var1 has its original value (so executable content never got executed -->\n    <transition cond=\"Var1==0\" target=\"s3\" />\n    <transition target=\"fail\" />\n  </state>\n  <!-- finally check that a legal array works properly -->\n  <state id=\"s3\">\n    <onentry>\n      <assign location=\"Var6\" expr=\"0\" />\n      <foreach item=\"Var2\" array=\"Var5\">\n        <assign location=\"Var6\" expr=\"Var6 + Var2\" />\n      </foreach>\n    </onentry>\n    <transition cond=\"Var6==6\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test459.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that foreach goes over the array in the right order.  since the array contains 1 2 3, we\ncompare the current\nvalue with the previous value, which is stored in var1. The current value should always be larger.\nIf\nit ever isn't, set Var4 to 0, indicating failure.  Also check that the final value of the index\nis 2 (meaning that the initial value was 0, not 1) -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"0\" />\n    <!-- contains the previous value -->\n    <data id=\"Var2\" />\n    <!-- the item which will contain the current value -->\n    <data id=\"Var3\" />\n    <!-- the index -->\n    <data id=\"Var4\" expr=\"[1,2,3]\" />\n    <data id=\"Var5\" expr=\"1\" />\n    <!-- 1 if success, 0 if failure -->\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <foreach item=\"Var2\" array=\"Var4\" index=\"Var3\">\n        <if cond=\"Var1&lt;Var2\">\n          <assign location=\"Var1\" expr=\"Var2\" />\n          <else />\n          <!-- values are out of order, record failure -->\n          <assign location=\"Var5\" expr=\"0\" />\n        </if>\n      </foreach>\n    </onentry>\n    <!-- check that var1 has its original value  -->\n    <transition cond=\"Var4==0 | Var3 != 2\" target=\"fail\" />\n    <transition target=\"pass\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test460.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" name=\"ScxmlTest460\" version=\"1.0\" xmlns=\"http://www.w3.org/2005/07/scxml\"\n  xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n  that <foreach> does a shallow copy, so that modifying the array does not change\nthe iteration behavior.-->\n  <datamodel>\n    <data expr=\"[1,2,3]\" id=\"Var1\" />\n    <data expr=\"0\" id=\"Var2\" /><!--counts\n    the number of iterations-->\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <foreach array=\"Var1\" item=\"Var3\">\n        <assign expr=\"Var1 + [4]\" location=\"Var1\" />\n        <assign expr=\"Var2 + 1\" location=\"Var2\" />\n      </foreach>\n    </onentry>\n    <transition cond=\"Var2==3\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test509.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest509\" version=\"1.0\"\n  xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--test\n  that Basic HTTP Event I/O processor uses POST method and that it can receive messages\nat the accessURI-->\n  <state id=\"s0\">\n    <onentry>\n      <send delay=\"2s\" event=\"timeout\" />\n      <send event=\"test\" targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\" />\n    </onentry><!--if\n    the event was send by http and we get it, we succeed-->\n    <transition cond=\"_event.raw.search('POST') !== -1\" event=\"test\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test510.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that Basic HTTP messages go into external queue.   -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <send event=\"test\" targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\" />\n      <!-- this creates an internal event -->\n      <raise event=\"internal\" />\n    </onentry>\n    <!-- we should get 'internal' first, then 'test' -->\n    <transition event=\"internal\" target=\"s1\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition event=\"test\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test518.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that namelist values get encoded as POST parameters.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"2\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <send event=\"test\" targetexpr=\"_ioprocessors['basichttp']['location']\" namelist=\"Var1\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\" />\n    </onentry>\n    <transition event=\"test\" cond=\"_event.raw.search(/Var1=2/) !== -1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test519.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that <param> values get encoded as POST parameters.  . -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <send event=\"test\" targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\">\n        <param name=\"param1\" expr=\"1\" />\n      </send>\n    </onentry>\n    <!-- if other end sends us back this event, we succeed -->\n    <transition event=\"test\" cond=\"_event.raw.search('param1=1') !== -1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test520.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that <content> gets sent as the body of the message.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <send targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\">\n        <content>this is some content</content>\n      </send>\n    </onentry>\n    <!-- if other end sends us back this event, we succeed.  Test for two common\n     ways of encoding -->\n    <transition event=\"HTTP.POST\" cond=\"_event.raw.search(/this+is+some+content/) !== -1\"\n      target=\"pass\" />\n    <transition event=\"HTTP.POST\" cond=\"_event.raw.search(/this%20is%20some%20content/) !== -1\"\n      target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test522.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that location field the entry for Basic HTTP Event I/O processor can be used\nto send a message to the processor -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <send event=\"test\" type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\"\n        targetexpr=\"_ioprocessors['basichttp']['location']\" />\n    </onentry>\n    <!-- the event we receive should be called 'test', but that's not actually\n     required for this test. Only that the send deliver some event to us.  So if\n     we get something other than timeout or error, we call it success -->\n    <transition event=\"timeout\" target=\"fail\" />\n    <transition event=\"error\" target=\"fail\" />\n    <transition event=\"*\" target=\"pass\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test531.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that the value of the <param> _scxmleventname gets used as the name\nof the raised event. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n      <send targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\">\n        <param name=\"_scxmleventname\" expr=\"'test'\" />\n      </send>\n    </onentry>\n    <!-- if we get an event named 'test' we succeed. Otherwise fail -->\n    <transition event=\"test\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test532.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that if _scxmleventname  is not present, the name of the HTTP method is used\nas the name of the resulting event. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n      <send targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\">\n        <!-- this content will be ignored, but it's here to make sure we have a message body -->\n        <content>some content</content>\n      </send>\n    </onentry>\n    <transition event=\"HTTP.POST\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test534.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that <send> 'event' value gets sent as the param _scxmleventname . -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"python\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"2s\" />\n      <send event=\"test\" targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\">\n      </send>\n    </onentry>\n    <!-- if other end sends us back this event, we succeed -->\n    <transition event=\"test\" cond=\"'_scxmleventname=test' in _event.raw\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test557.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest557\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--in\n\tthe Python data model, test that if the child of <data> is XML,\nor if XML is loaded via src=, the processor\nassigns it as the value of the var-->\n\t<datamodel>\n\t\t<data id=\"var1\">\n\t\t\t<books xmlns=\"\">\n\t\t\t\t<book title=\"title1\" />\n\t\t\t\t<book title=\"title2\" />\n\t\t\t</books>\n\t\t</data>\n\t\t<data id=\"var2\" src=\"file:test557.txt\" />\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<transition cond=\"var1.findall('book')[0].get('title') == 'title1'\" target=\"s1\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<state id=\"s1\">\n\t\t<transition cond=\"var2.findall('book')[1].get('title') == 'title2'\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test557.txt",
    "content": "<books xmlns=\"\">\n     <book title=\"title1\"/>\n     <book title=\"title2\"/>\n   </books>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test558.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest558\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--in\n\tthe Python data model, test that if the child of <data> is not XML, or if XML is loaded via\n\tsrc=,\n the processor treats the value as a string, does whitespace normalization and assigns it to the\n\tvar.-->\n\t<datamodel>\n\t\t<data id=\"var1\">\n\t\t\tthis is\n\t\t\ta string\n\t\t</data>\n\t\t<data id=\"var2\" src=\"file:test558.txt\" />\n\t</datamodel>\n\t<state id=\"s0\">\n\t\t<transition cond=\"var1 == 'this is a string'\" target=\"s1\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<state id=\"s1\">\n\t\t<transition cond=\"var2 == 'this is a string'\" target=\"pass\" />\n\t\t<transition target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test558.txt",
    "content": "\nthis  is\na string\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test560.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- in the ECMA data model, test that processor creates correct structure in\n _event.data when receiving KVPs in an event -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"foo\">\n        <param name=\"aParam\" expr=\"1\" />\n      </send>\n    </onentry>\n    <transition event=\"foo\" cond=\"_event.data.aParam == 1\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test561.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest561\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--in\n\tthe Python data model, test that processor creates an DOM object\n _event.data when receiving XML in an event-->\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<send event=\"foo\">\n\t\t\t\t<content>\n\t\t\t\t\t<books xmlns=\"\">\n\t\t\t\t\t\t<book title=\"title1\" />\n\t\t\t\t\t\t<book title=\"title2\" />\n\t\t\t\t\t</books>\n\t\t\t\t</content>\n\t\t\t</send>\n\t\t</onentry>\n\t\t<transition cond=\"_event.data.findall('book')[1].get('title') == 'title2'\" event=\"foo\"\n\t\t\ttarget=\"pass\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test562.scxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest562\" version=\"1.0\"\n\txmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--in\n\tthe Python data model, test that processor creates space normalized string in\n _event.data when receiving anything other than KVPs or XML in an event-->\n\t<state id=\"s0\">\n\t\t<onentry>\n\t\t\t<send event=\"foo\">\n\t\t\t\t<content>\n\t\t\t\t\tthis is a\n\t\t\t\t\tstring\n\t\t\t\t</content>\n\t\t\t</send>\n\t\t</onentry>\n\t\t<transition cond=\"_event.data == 'this is a string'\" event=\"foo\" target=\"pass\" />\n\t\t<transition event=\"*\" target=\"fail\" />\n\t</state>\n\t<final id=\"pass\">\n\t\t<onentry>\n\t\t\t<log expr=\"'pass'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n\t<final id=\"fail\">\n\t\t<onentry>\n\t\t\t<log expr=\"'fail'\" label=\"Outcome\" />\n\t\t</onentry>\n\t</final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test567.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that any content in the message other than _scxmleventname is used to populate\n_event.data.  -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <datamodel>\n    <data id=\"Var1\" expr=\"2\" />\n  </datamodel>\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"timeout\" delay=\"3s\" />\n      <!-- in this case, 'test' will be placed in _scxmleventname.  The <param> should\n be used to populate _event.data -->\n      <send event=\"test\" targetexpr=\"_ioprocessors['basichttp']['location']\"\n        type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\">\n        <param name=\"param1\" expr=\"2\" />\n      </send>\n    </onentry>\n    <!-- if we get this event, we succeed -->\n    <transition event=\"test\" target=\"s1\">\n      <assign location=\"Var1\" expr=\"_event.data.param1\" />\n    </transition>\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <state id=\"s1\">\n    <transition cond=\"Var1==2\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test569.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that location field is found inside entry for SCXML Event I/O processor in the ECMAScript\ndata model.  The tests for the relevant event i/o processors will test that it can be used to\nsend events. -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" version=\"1.0\" datamodel=\"ecmascript\">\n  <state id=\"s0\">\n    <transition cond=\"_ioprocessors['scxml'].location\" target=\"pass\" />\n    <transition target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test577.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test that that <send> without target in basichttp event i/o processor\ncauses error.communication to get added to internal queue . -->\n<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"\n  initial=\"s0\" datamodel=\"ecmascript\" version=\"1.0\">\n  <state id=\"s0\">\n    <onentry>\n      <!-- sent by scxml event i/o processor, added to external queue -->\n      <send event=\"event1\" />\n      <!-- should put error.communication on internal queue -->\n      <send event=\"test\" type=\"http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor\" />\n    </onentry>\n    <transition event=\"error.communication\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'pass'\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log label=\"Outcome\" expr=\"'fail'\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/scxml/w3c/optional/test578.scxml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<scxml datamodel=\"python\" initial=\"s0\" name=\"ScxmlTest578\" version=\"1.0\"\n  xmlns=\"http://www.w3.org/2005/07/scxml\" xmlns:conf=\"http://www.w3.org/2005/scxml-conformance\"><!--in\n  the ECMA data model, test that processor creates an ECMAScript object\n _event.data when receiving JSON in an event-->\n  <state id=\"s0\">\n    <onentry>\n      <send event=\"foo\">\n        <content>{ &quot;productName&quot; : &quot;bar&quot;, &quot;size&quot; : 27 }</content>\n      </send>\n    </onentry>\n    <transition cond=\"_event.data[&quot;productName&quot;] == 'bar'\" event=\"foo\" target=\"pass\" />\n    <transition event=\"*\" target=\"fail\" />\n  </state>\n  <final id=\"pass\">\n    <onentry>\n      <log expr=\"'pass'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n  <final id=\"fail\">\n    <onentry>\n      <log expr=\"'fail'\" label=\"Outcome\" />\n    </onentry>\n  </final>\n</scxml>\n"
  },
  {
    "path": "tests/test_actions.py",
    "content": "from statemachine.callbacks import CallbackGroup\nfrom statemachine.callbacks import CallbackSpec\n\n\nclass TestActions:\n    def test_should_return_all_before_results(self, AllActionsMachine):\n        import tests.examples.all_actions_machine  # noqa\n\n    def test_should_allow_actions_on_the_model(self):\n        # just importing, as the example has assertions\n        import tests.examples.order_control_rich_model_machine  # noqa\n\n    def test_should_should_compute_callbacks_meta_list(self, campaign_machine):\n        sm = campaign_machine()\n        assert list(sm.draft.enter) == [\n            CallbackSpec(\"on_enter_state\", CallbackGroup.ENTER, is_convention=True),\n            CallbackSpec(\"on_enter_draft\", CallbackGroup.ENTER, is_convention=True),\n        ]\n        assert list(sm.draft.exit) == [\n            CallbackSpec(\"on_exit_state\", CallbackGroup.EXIT, is_convention=True),\n            CallbackSpec(\"on_exit_draft\", CallbackGroup.EXIT, is_convention=True),\n        ]\n"
  },
  {
    "path": "tests/test_api_contract.py",
    "content": "\"\"\"Contract tests: observable behavior of public Configuration APIs.\n\nDocuments the exact values returned by each public API across all supported\ntopologies (flat, compound, parallel, complex parallel) and lifecycle phases\n(initial state, after transitions, final state).\n\nAPIs under test (StateChart):\n    sm.current_state_value  -- raw value stored on the model\n    sm.configuration_values -- OrderedSet of raw values\n    sm.configuration        -- OrderedSet[State]\n    sm.current_state        -- State or OrderedSet[State] (deprecated)\n\nAPI under test (Model):\n    model.state             -- raw attribute on the model object\n\"\"\"\n\nimport warnings\nfrom typing import Any\n\nimport pytest\nfrom statemachine.orderedset import OrderedSet\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n# ---------------------------------------------------------------------------\n# Model\n# ---------------------------------------------------------------------------\n\n\nclass Model:\n    \"\"\"Explicit model to verify raw state persistence independently.\"\"\"\n\n    def __init__(self):\n        self.state: Any = None\n\n\n# ---------------------------------------------------------------------------\n# Topologies\n# ---------------------------------------------------------------------------\n\n\nclass FlatSC(StateChart):\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State(final=True)\n\n    go = s1.to(s2)\n    finish = s2.to(s3)\n\n\nclass CompoundSC(StateChart):\n    class parent(State.Compound):\n        child1 = State(initial=True)\n        child2 = State()\n        move = child1.to(child2)\n\n    done = State(final=True)\n    leave = parent.to(done)\n\n\nclass ParallelSC(StateChart):\n    class regions(State.Parallel):\n        class region_a(State.Compound):\n            a1 = State(initial=True)\n            a2 = State()\n            go_a = a1.to(a2)\n\n        class region_b(State.Compound):\n            b1 = State(initial=True)\n            b2 = State()\n            go_b = b1.to(b2)\n\n\nclass ComplexParallelSC(StateChart):\n    class top(State.Parallel):\n        class left(State.Compound):\n            class nested(State.Compound):\n                l1 = State(initial=True)\n                l2 = State()\n                move_l = l1.to(l2)\n\n            left_done = State(final=True)\n            finish_left = nested.to(left_done)\n\n        class right(State.Compound):\n            r1 = State(initial=True)\n            r2 = State()\n            move_r = r1.to(r2)\n\n\n# ---------------------------------------------------------------------------\n# Assertion helper\n# ---------------------------------------------------------------------------\n\n\ndef assert_contract(sm, model, expected_ids: set):\n    \"\"\"Assert the full observable API contract.\n\n    When exactly one state is active, the model stores a scalar and\n    ``current_state`` returns a single ``State``.  When multiple states\n    are active (compound/parallel), the model stores an ``OrderedSet``\n    and ``current_state`` returns ``OrderedSet[State]``.\n    \"\"\"\n    scalar = len(expected_ids) == 1\n\n    # model.state and current_state_value point to the same object\n    assert model.state is sm.current_state_value\n\n    if scalar:\n        val = next(iter(expected_ids))\n        assert model.state == val\n        assert not isinstance(model.state, OrderedSet)\n    else:\n        assert isinstance(model.state, OrderedSet)\n        assert set(model.state) == expected_ids\n\n    # configuration_values -- always OrderedSet of raw values\n    assert isinstance(sm.configuration_values, OrderedSet)\n    assert set(sm.configuration_values) == expected_ids\n\n    # configuration -- always OrderedSet[State]\n    assert len(sm.configuration) == len(expected_ids)\n    assert {s.id for s in sm.configuration} == expected_ids\n\n    # current_state (deprecated) -- unwrapped when single\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\", DeprecationWarning)\n        cs = sm.current_state\n    if scalar:\n        assert not isinstance(cs, OrderedSet)\n        assert cs.id == next(iter(expected_ids))\n    else:\n        assert isinstance(cs, OrderedSet)\n        assert {s.id for s in cs} == expected_ids\n\n\n# ---------------------------------------------------------------------------\n# Main contract matrix: topology x lifecycle x engine\n# ---------------------------------------------------------------------------\n\n\nSCENARIOS = [\n    # -- Flat --\n    pytest.param(FlatSC, [], {\"s1\"}, id=\"flat-initial\"),\n    pytest.param(FlatSC, [\"go\"], {\"s2\"}, id=\"flat-after-go\"),\n    pytest.param(FlatSC, [\"go\", \"finish\"], {\"s3\"}, id=\"flat-final\"),\n    # -- Compound --\n    pytest.param(CompoundSC, [], {\"parent\", \"child1\"}, id=\"compound-initial\"),\n    pytest.param(CompoundSC, [\"move\"], {\"parent\", \"child2\"}, id=\"compound-inner-move\"),\n    pytest.param(CompoundSC, [\"leave\"], {\"done\"}, id=\"compound-exit\"),\n    # -- Parallel --\n    pytest.param(\n        ParallelSC,\n        [],\n        {\"regions\", \"region_a\", \"a1\", \"region_b\", \"b1\"},\n        id=\"parallel-initial\",\n    ),\n    pytest.param(\n        ParallelSC,\n        [\"go_a\"],\n        {\"regions\", \"region_a\", \"a2\", \"region_b\", \"b1\"},\n        id=\"parallel-one-region\",\n    ),\n    pytest.param(\n        ParallelSC,\n        [\"go_a\", \"go_b\"],\n        {\"regions\", \"region_a\", \"a2\", \"region_b\", \"b2\"},\n        id=\"parallel-both-regions\",\n    ),\n    # -- Complex parallel --\n    pytest.param(\n        ComplexParallelSC,\n        [],\n        {\"top\", \"left\", \"nested\", \"l1\", \"right\", \"r1\"},\n        id=\"complex-initial\",\n    ),\n    pytest.param(\n        ComplexParallelSC,\n        [\"move_l\"],\n        {\"top\", \"left\", \"nested\", \"l2\", \"right\", \"r1\"},\n        id=\"complex-nested-move\",\n    ),\n    pytest.param(\n        ComplexParallelSC,\n        [\"move_r\"],\n        {\"top\", \"left\", \"nested\", \"l1\", \"right\", \"r2\"},\n        id=\"complex-other-region\",\n    ),\n    pytest.param(\n        ComplexParallelSC,\n        [\"move_l\", \"move_r\"],\n        {\"top\", \"left\", \"nested\", \"l2\", \"right\", \"r2\"},\n        id=\"complex-both-regions\",\n    ),\n    pytest.param(\n        ComplexParallelSC,\n        [\"finish_left\"],\n        {\"top\", \"left\", \"left_done\", \"right\", \"r1\"},\n        id=\"complex-exit-nested\",\n    ),\n]\n\n\n@pytest.mark.parametrize((\"sc_class\", \"events\", \"expected_ids\"), SCENARIOS)\nasync def test_configuration_contract(sm_runner, sc_class, events, expected_ids):\n    model = Model()\n    sm = await sm_runner.start(sc_class, model=model)\n    for event in events:\n        await sm_runner.send(sm, event)\n    assert_contract(sm, model, expected_ids)\n\n\n# ---------------------------------------------------------------------------\n# Model setter contract\n# ---------------------------------------------------------------------------\n\nSETTER_SCENARIOS = [\n    pytest.param(FlatSC, \"s2\", {\"s2\"}, id=\"scalar-on-flat\"),\n    pytest.param(\n        CompoundSC,\n        OrderedSet([\"parent\", \"child2\"]),\n        {\"parent\", \"child2\"},\n        id=\"orderedset-on-compound\",\n    ),\n    pytest.param(CompoundSC, \"done\", {\"done\"}, id=\"scalar-collapses-orderedset\"),\n]\n\n\n@pytest.mark.parametrize((\"sc_class\", \"new_value\", \"expected_ids\"), SETTER_SCENARIOS)\nasync def test_setter_contract(sm_runner, sc_class, new_value, expected_ids):\n    model = Model()\n    sm = await sm_runner.start(sc_class, model=model)\n    sm.current_state_value = new_value\n    assert_contract(sm, model, expected_ids)\n\n\nasync def test_set_none_clears_configuration(sm_runner):\n    model = Model()\n    sm = await sm_runner.start(FlatSC, model=model)\n\n    sm.current_state_value = None\n\n    assert model.state is None\n    assert sm.current_state_value is None\n    assert sm.configuration_values == OrderedSet()\n    assert sm.configuration == OrderedSet()\n\n\n# ---------------------------------------------------------------------------\n# Uninitialized state (async-only: sync enters initial state in __init__)\n# ---------------------------------------------------------------------------\n\nUNINITIALIZED_SCENARIOS = [\n    pytest.param(FlatSC, {\"s1\"}, id=\"flat\"),\n    pytest.param(CompoundSC, {\"parent\", \"child1\"}, id=\"compound\"),\n    pytest.param(\n        ParallelSC,\n        {\"regions\", \"region_a\", \"a1\", \"region_b\", \"b1\"},\n        id=\"parallel\",\n    ),\n]\n\n\n@pytest.mark.parametrize((\"sc_class\", \"expected_ids\"), UNINITIALIZED_SCENARIOS)\nasync def test_uninitialized_then_activated(sc_class, expected_ids):\n    from tests.conftest import _AsyncListener\n\n    model = Model()\n    sm = sc_class(model=model, listeners=[_AsyncListener()])\n\n    # Before activation: all APIs reflect empty configuration\n    assert model.state is None\n    assert sm.current_state_value is None\n    assert sm.configuration_values == OrderedSet()\n    assert sm.configuration == OrderedSet()\n\n    # After activation: full contract holds\n    await sm.activate_initial_state()\n    assert_contract(sm, model, expected_ids)\n"
  },
  {
    "path": "tests/test_async.py",
    "content": "import re\n\nimport pytest\nfrom statemachine.exceptions import InvalidDefinition\nfrom statemachine.exceptions import InvalidStateValue\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.fixture()\ndef async_order_control_machine():  # noqa: C901\n    class OrderControl(StateChart):\n        allow_event_without_transition = False\n\n        waiting_for_payment = State(initial=True)\n        processing = State()\n        shipping = State()\n        completed = State(final=True)\n\n        add_to_order = waiting_for_payment.to(waiting_for_payment)\n        receive_payment = waiting_for_payment.to(\n            processing, cond=\"payments_enough\"\n        ) | waiting_for_payment.to(waiting_for_payment, unless=\"payments_enough\")\n        process_order = processing.to(shipping, cond=\"payment_received\")\n        ship_order = shipping.to(completed)\n\n        def __init__(self):\n            self.order_total = 0\n            self.payments = []\n            self.payment_received = False\n            super().__init__()\n\n        async def payments_enough(self, amount):\n            return sum(self.payments) + amount >= self.order_total\n\n        async def before_add_to_order(self, amount):\n            self.order_total += amount\n            return self.order_total\n\n        async def before_receive_payment(self, amount):\n            self.payments.append(amount)\n            return self.payments\n\n        async def after_receive_payment(self):\n            self.payment_received = True\n\n        async def on_enter_waiting_for_payment(self):\n            self.payment_received = False\n\n    return OrderControl\n\n\nasync def test_async_order_control_machine(async_order_control_machine):\n    sm = async_order_control_machine()\n\n    assert await sm.add_to_order(3) == 3\n    assert await sm.add_to_order(7) == 10\n\n    assert await sm.receive_payment(4) == [4]\n    assert sm.waiting_for_payment.is_active\n\n    with pytest.raises(sm.TransitionNotAllowed):\n        await sm.process_order()\n\n    assert sm.waiting_for_payment.is_active\n\n    assert await sm.receive_payment(6) == [4, 6]\n    await sm.process_order()\n\n    await sm.ship_order()\n    assert sm.order_total == 10\n    assert sm.payments == [4, 6]\n    assert sm.completed.is_active\n\n\ndef test_async_state_from_sync_context(async_order_control_machine):\n    \"\"\"Test that an async state machine can be used from a synchronous context\"\"\"\n\n    sm = async_order_control_machine()\n\n    assert sm.add_to_order(3) == 3\n    assert sm.add_to_order(7) == 10\n\n    assert sm.receive_payment(4) == [4]\n    assert sm.waiting_for_payment.is_active\n\n    with pytest.raises(sm.TransitionNotAllowed):\n        sm.process_order()\n\n    assert sm.waiting_for_payment.is_active\n\n    assert sm.send(\"receive_payment\", 6) == [4, 6]  # test the sync version of the `.send()` method\n    sm.send(\"process_order\")  # test the sync version of the `.send()` method\n\n    sm.ship_order()\n    assert sm.order_total == 10\n    assert sm.payments == [4, 6]\n    assert sm.completed.is_active\n\n\nclass AsyncConditionExpressionMachine(StateChart):\n    \"\"\"Regression test for issue #535: async conditions in boolean expressions.\"\"\"\n\n    allow_event_without_transition = False\n\n    s1 = State(initial=True)\n\n    go_not = s1.to.itself(cond=\"not cond_false\")\n    go_and = s1.to.itself(cond=\"cond_true and cond_true\")\n    go_or_false_first = s1.to.itself(cond=\"cond_false or cond_true\")\n    go_or_true_first = s1.to.itself(cond=\"cond_true or cond_false\")\n    go_blocked = s1.to.itself(cond=\"not cond_true\")\n    go_and_blocked = s1.to.itself(cond=\"cond_true and cond_false\")\n    go_or_both_false = s1.to.itself(cond=\"cond_false or cond_false\")\n\n    async def cond_true(self):\n        return True\n\n    async def cond_false(self):\n        return False\n\n    async def on_enter_state(self, target):\n        \"\"\"Async callback to ensure the SM uses AsyncEngine.\"\"\"\n\n\nasync def test_async_condition_not(recwarn):\n    \"\"\"Issue #535: 'not cond_false' should allow the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    await sm.go_not()\n    assert sm.s1.is_active\n    assert not any(\"coroutine\" in str(w.message) for w in recwarn.list)\n\n\nasync def test_async_condition_not_blocked():\n    \"\"\"Issue #535: 'not cond_true' should block the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    with pytest.raises(sm.TransitionNotAllowed):\n        await sm.go_blocked()\n\n\nasync def test_async_condition_and():\n    \"\"\"Issue #535: 'cond_true and cond_true' should allow the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    await sm.go_and()\n    assert sm.s1.is_active\n\n\nasync def test_async_condition_and_blocked():\n    \"\"\"Issue #535: 'cond_true and cond_false' should block the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    with pytest.raises(sm.TransitionNotAllowed):\n        await sm.go_and_blocked()\n\n\nasync def test_async_condition_or_false_first():\n    \"\"\"Issue #535: 'cond_false or cond_true' should allow the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    await sm.go_or_false_first()\n    assert sm.s1.is_active\n\n\nasync def test_async_condition_or_true_first():\n    \"\"\"'cond_true or cond_false' should allow the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    await sm.go_or_true_first()\n    assert sm.s1.is_active\n\n\nasync def test_async_condition_or_both_false():\n    \"\"\"'cond_false or cond_false' should block the transition.\"\"\"\n    sm = AsyncConditionExpressionMachine()\n    await sm.activate_initial_state()\n    with pytest.raises(sm.TransitionNotAllowed):\n        await sm.go_or_both_false()\n\n\nasync def test_async_state_should_be_initialized(async_order_control_machine):\n    \"\"\"Test that the state machine is initialized before any event is triggered\n\n    Given how async works on python, there's no built-in way to activate the initial state that\n    may depend on async code from the StateMachine.__init__ method.\n\n    We do a `_ensure_is_initialized()` check before each event, but to check the current state\n    just before the state machine is created, the user must await the activation of the initial\n    state explicitly.\n    \"\"\"\n\n    sm = async_order_control_machine()\n    import warnings\n\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\", DeprecationWarning)\n        with pytest.raises(\n            InvalidStateValue,\n            match=re.escape(\n                r\"There's no current state set. In async code, \"\n                r\"did you activate the initial state? (e.g., `await sm.activate_initial_state()`)\"\n            ),\n        ):\n            sm.current_state  # noqa: B018\n\n    await sm.activate_initial_state()\n    assert sm.waiting_for_payment.is_active\n\n\n@pytest.mark.timeout(5)\nasync def test_async_catch_errors_as_events_in_condition():\n    \"\"\"Async engine catches errors in conditions with catch_errors_as_events.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n        error_state = State(final=True)\n\n        go = s1.to(s2, cond=\"bad_cond\")\n        error_execution = s1.to(error_state)\n\n        def bad_cond(self, **kwargs):\n            raise RuntimeError(\"Condition boom\")\n\n    sm = SM()\n    sm.send(\"go\")\n    assert sm.configuration == {sm.error_state}\n\n\n@pytest.mark.timeout(5)\nasync def test_async_catch_errors_as_events_in_transition():\n    \"\"\"Async engine catches errors in transition callbacks with catch_errors_as_events.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State()\n        error_state = State(final=True)\n\n        go = s1.to(s2, on=\"bad_action\")\n        finish = s2.to(error_state)\n        # Transition 'on' content error is caught per-block, so the transition\n        # completes to s2.  error.execution fires from s2.\n        error_execution = s1.to(error_state) | s2.to(error_state)\n\n        def bad_action(self, **kwargs):\n            raise RuntimeError(\"Transition boom\")\n\n    sm = SM()\n    sm.send(\"go\")\n    assert sm.configuration == {sm.error_state}\n\n\n@pytest.mark.timeout(5)\nasync def test_async_catch_errors_as_events_in_after():\n    \"\"\"Async engine catches errors in after callbacks with catch_errors_as_events.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State()\n        error_state = State(final=True)\n\n        go = s1.to(s2)\n        error_execution = s2.to(error_state)\n\n        def after_go(self, **kwargs):\n            raise RuntimeError(\"After boom\")\n\n    sm = SM()\n    sm.send(\"go\")\n    assert sm.configuration == {sm.error_state}\n\n\n@pytest.mark.timeout(5)\nasync def test_async_catch_errors_as_events_in_before():\n    \"\"\"Async engine catches errors in before callbacks with catch_errors_as_events.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        error_state = State(final=True)\n\n        go = s1.to(s1)\n        error_execution = s1.to(error_state)\n\n        def before_go(self, **kwargs):\n            raise RuntimeError(\"Before boom\")\n\n        async def on_enter_state(self, **kwargs):\n            \"\"\"Async callback to force the async engine.\"\"\"\n\n    sm = SM()\n    await sm.activate_initial_state()\n    await sm.go()\n    assert sm.configuration == {sm.error_state}\n\n\n@pytest.mark.timeout(5)\nasync def test_async_invalid_definition_in_transition_propagates():\n    \"\"\"InvalidDefinition in async transition propagates.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2, on=\"bad_action\")\n\n        def bad_action(self, **kwargs):\n            raise InvalidDefinition(\"Bad async\")\n\n    sm = SM()\n    with pytest.raises(InvalidDefinition, match=\"Bad async\"):\n        sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\nasync def test_async_invalid_definition_in_after_propagates():\n    \"\"\"InvalidDefinition in async after callback propagates.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n        def after_go(self, **kwargs):\n            raise InvalidDefinition(\"Bad async after\")\n\n    sm = SM()\n    with pytest.raises(InvalidDefinition, match=\"Bad async after\"):\n        sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\nasync def test_async_runtime_error_in_after_without_catch_errors_as_events():\n    \"\"\"RuntimeError in async after callback without catch_errors_as_events propagates.\"\"\"\n\n    class SM(StateChart):\n        catch_errors_as_events = False\n\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n        def after_go(self, **kwargs):\n            raise RuntimeError(\"Async after boom\")\n\n    sm = SM()\n    with pytest.raises(RuntimeError, match=\"Async after boom\"):\n        sm.send(\"go\")\n\n\n# --- Actual async engine tests (async callbacks trigger AsyncEngine) ---\n# Note: async engine catch_errors_as_events with async callbacks has a known limitation:\n# _send_error_execution calls sm.send() which returns an unawaited coroutine.\n# The tests below cover the paths that DO work in the async engine.\n\n\n@pytest.mark.timeout(5)\nasync def test_async_engine_invalid_definition_in_condition_propagates():\n    \"\"\"AsyncEngine: InvalidDefinition in async condition always propagates.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2, cond=\"bad_cond\")\n\n        async def bad_cond(self, **kwargs):\n            raise InvalidDefinition(\"Async bad definition\")\n\n    sm = SM()\n    await sm.activate_initial_state()\n    with pytest.raises(InvalidDefinition, match=\"Async bad definition\"):\n        await sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\nasync def test_async_engine_invalid_definition_in_transition_propagates():\n    \"\"\"AsyncEngine: InvalidDefinition in async transition execution always propagates.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2, on=\"bad_action\")\n\n        async def bad_action(self, **kwargs):\n            raise InvalidDefinition(\"Async bad transition\")\n\n    sm = SM()\n    await sm.activate_initial_state()\n    with pytest.raises(InvalidDefinition, match=\"Async bad transition\"):\n        await sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\nasync def test_async_engine_invalid_definition_in_after_propagates():\n    \"\"\"AsyncEngine: InvalidDefinition in async after callback propagates.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n        async def after_go(self, **kwargs):\n            raise InvalidDefinition(\"Async bad after\")\n\n    sm = SM()\n    await sm.activate_initial_state()\n    with pytest.raises(InvalidDefinition, match=\"Async bad after\"):\n        await sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\nasync def test_async_engine_runtime_error_in_after_without_catch_errors_as_events_propagates():\n    \"\"\"AsyncEngine: RuntimeError in async after callback without catch_errors_as_events raises.\"\"\"\n\n    class SM(StateChart):\n        catch_errors_as_events = False\n\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n        async def after_go(self, **kwargs):\n            raise RuntimeError(\"Async after boom no catch\")\n\n    sm = SM()\n    await sm.activate_initial_state()\n    with pytest.raises(RuntimeError, match=\"Async after boom no catch\"):\n        await sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\nasync def test_async_engine_start_noop_when_already_initialized():\n    \"\"\"BaseEngine.start() is a no-op when state machine is already initialized.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n        async def on_go(\n            self,\n        ): ...  # No-op: presence of async callback triggers AsyncEngine selection\n\n    sm = SM()\n    await sm.activate_initial_state()\n    assert sm.current_state_value is not None\n    sm._engine.start()  # Should return early\n    assert sm.s1.is_active\n\n\nclass TestAsyncEnabledEvents:\n    async def test_passing_async_condition(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"is_ready\")\n\n            async def is_ready(self):\n                return True\n\n        sm = MyMachine()\n        await sm.activate_initial_state()\n        assert [e.id for e in await sm.enabled_events()] == [\"go\"]\n\n    async def test_failing_async_condition(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"is_ready\")\n\n            async def is_ready(self):\n                return False\n\n        sm = MyMachine()\n        await sm.activate_initial_state()\n        assert await sm.enabled_events() == []\n\n    async def test_kwargs_forwarded_to_async_conditions(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"check_value\")\n\n            async def check_value(self, value=0):\n                return value > 10\n\n        sm = MyMachine()\n        await sm.activate_initial_state()\n        assert await sm.enabled_events() == []\n        assert [e.id for e in await sm.enabled_events(value=20)] == [\"go\"]\n\n    async def test_async_condition_exception_treated_as_enabled(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"bad_cond\")\n\n            async def bad_cond(self):\n                raise RuntimeError(\"boom\")\n\n        sm = MyMachine()\n        await sm.activate_initial_state()\n        assert [e.id for e in await sm.enabled_events()] == [\"go\"]\n\n    async def test_duplicate_event_across_transitions_deduplicated(self):\n        \"\"\"Same event on multiple passing transitions appears only once.\"\"\"\n\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n            s2 = State(final=True)\n\n            go = s0.to(s1, cond=\"cond_a\") | s0.to(s2, cond=\"cond_b\")\n\n            async def cond_a(self):\n                return True\n\n            async def cond_b(self):\n                return True\n\n        sm = MyMachine()\n        await sm.activate_initial_state()\n        ids = [e.id for e in await sm.enabled_events()]\n        assert ids == [\"go\"]\n        assert len(ids) == 1\n\n    async def test_mixed_enabled_and_disabled_async(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n            s2 = State(final=True)\n\n            go = s0.to(s1, cond=\"cond_true\")\n            stop = s0.to(s2, cond=\"cond_false\")\n\n            async def cond_true(self):\n                return True\n\n            async def cond_false(self):\n                return False\n\n        sm = MyMachine()\n        await sm.activate_initial_state()\n        assert [e.id for e in await sm.enabled_events()] == [\"go\"]\n"
  },
  {
    "path": "tests/test_async_futures.py",
    "content": "\"\"\"Tests for future-based result routing in the async engine.\n\nWhen multiple coroutines send events concurrently, only one acquires the\nprocessing lock. The others must still receive their own event's result (or\nexception) via an ``asyncio.Future`` attached to each ``TriggerData``.\n\nSee: https://github.com/fgmacedo/python-statemachine/issues/509\n\"\"\"\n\nimport asyncio\n\nimport pytest\nfrom statemachine.engines.base import EventQueue\nfrom statemachine.event_data import TriggerData\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n# ---------------------------------------------------------------------------\n# Fixtures / helpers\n# ---------------------------------------------------------------------------\n\n\nclass TrafficLight(StateChart):\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n\n    slow_down = green.to(yellow)\n    stop = yellow.to(red)\n    go = red.to(green)\n\n    async def on_slow_down(self):\n        return \"slowing\"\n\n    async def on_stop(self):\n        return \"stopped\"\n\n    async def on_go(self):\n        return \"going\"\n\n\nclass FailingMachine(StateChart):\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State(final=True)\n\n    ok = s1.to(s2)\n    fail = s2.to(s3)\n\n    async def on_ok(self):\n        return \"ok_result\"\n\n    async def on_fail(self):\n        raise RuntimeError(\"boom\")\n\n\n# ---------------------------------------------------------------------------\n# Tests\n# ---------------------------------------------------------------------------\n\n\nclass TestConcurrentSendsGetCorrectResults:\n    \"\"\"asyncio.gather(sm.send(\"a\"), sm.send(\"b\")) — each caller gets its own result.\"\"\"\n\n    @pytest.mark.asyncio()\n    async def test_sequential_sends(self):\n        \"\"\"Baseline: sequential sends return correct results.\"\"\"\n        sm = TrafficLight()\n        await sm.activate_initial_state()\n\n        r1 = await sm.send(\"slow_down\")\n        assert r1 == \"slowing\"\n\n        r2 = await sm.send(\"stop\")\n        assert r2 == \"stopped\"\n\n    @pytest.mark.asyncio()\n    async def test_single_async_caller_gets_result(self):\n        \"\"\"Single async caller gets its callback result (backward compat).\"\"\"\n        sm = TrafficLight()\n        await sm.activate_initial_state()\n\n        result = await sm.slow_down()\n        assert result == \"slowing\"\n\n\nclass TestExceptionRouting:\n    \"\"\"Exceptions from one event must be routed to the correct caller.\"\"\"\n\n    @pytest.mark.asyncio()\n    async def test_exception_reaches_caller(self):\n        \"\"\"When catch_errors_as_events=False (not default for StateChart), the\n        exception propagates to the caller of that event.\"\"\"\n\n        class FailingSC(StateChart):\n            catch_errors_as_events = False\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n            async def on_go(self):\n                raise ValueError(\"broken\")\n\n        sm = FailingSC()\n        await sm.activate_initial_state()\n\n        with pytest.raises(ValueError, match=\"broken\"):\n            await sm.send(\"go\")\n\n\nclass TestTransitionNotAllowedRouting:\n    \"\"\"TransitionNotAllowed from an unknown event reaches the correct caller.\"\"\"\n\n    @pytest.mark.asyncio()\n    async def test_transition_not_allowed(self):\n        class StrictSC(StateChart):\n            allow_event_without_transition = False\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n            async def on_go(self):\n                return \"went\"\n\n        sm = StrictSC()\n        await sm.activate_initial_state()\n\n        # \"go\" works\n        result = await sm.send(\"go\")\n        assert result == \"went\"\n\n        # Now in s2, \"go\" has no transition\n        with pytest.raises(sm.TransitionNotAllowed):\n            await sm.send(\"go\")\n\n\nclass TestFutureEdgeCases:\n    \"\"\"Edge cases for future-based routing.\"\"\"\n\n    @pytest.mark.asyncio()\n    async def test_initial_activation_no_future(self):\n        \"\"\"activate_initial_state has no caller_trigger, should work fine.\"\"\"\n        sm = TrafficLight()\n        await sm.activate_initial_state()\n        assert \"green\" in sm.configuration_values\n\n    @pytest.mark.asyncio()\n    async def test_allow_event_without_transition_resolves_none(self):\n        \"\"\"When allow_event_without_transition=True and no transition matches,\n        the caller should get None (not hang).\"\"\"\n        sm = TrafficLight()\n        await sm.activate_initial_state()\n\n        # \"stop\" is not valid from \"green\", but allow_event_without_transition=True\n        result = await sm.send(\"stop\")\n        assert result is None\n\n    @pytest.mark.asyncio()\n    async def test_concurrent_sends_via_gather(self):\n        \"\"\"Two coroutines sending events concurrently via asyncio.gather.\n\n        One coroutine will hold the lock; the other awaits its future.\n        Both should get their own results.\n        \"\"\"\n\n        class SlowMachine(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State(final=True)\n\n            step1 = s1.to(s2)\n            step2 = s2.to(s3)\n\n            async def on_step1(self):\n                # Yield control so the second coroutine can enqueue its event\n                await asyncio.sleep(0)\n                return \"result_1\"\n\n            async def on_step2(self):\n                return \"result_2\"\n\n        sm = SlowMachine()\n        await sm.activate_initial_state()\n\n        r1, r2 = await asyncio.gather(\n            sm.send(\"step1\"),\n            sm.send(\"step2\"),\n        )\n\n        assert r1 == \"result_1\"\n        assert r2 == \"result_2\"\n\n    @pytest.mark.asyncio()\n    async def test_concurrent_sends_exception_with_catch_errors_as_events_off(self):\n        \"\"\"When catch_errors_as_events=False and one event raises, the exception\n        is routed to that caller's future; the other caller is unaffected.\n\n        With catch_errors_as_events=False, the exception propagates and the\n        processing loop clears the external queue, so the second event is\n        never processed.\n        \"\"\"\n\n        class ConcurrentFailMachine(StateChart):\n            catch_errors_as_events = False\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State(final=True)\n\n            step1 = s1.to(s2)\n            step2 = s2.to(s3)\n\n            async def on_step1(self):\n                await asyncio.sleep(0)\n                raise RuntimeError(\"step1 failed\")\n\n            async def on_step2(self):\n                return \"step2_ok\"\n\n        sm = ConcurrentFailMachine()\n        await sm.activate_initial_state()\n\n        # step1 raises — the exception should reach step1's caller via its future.\n        # step2 was queued but the processing loop rejects all pending futures\n        # and clears the queue on exception.\n        r1, r2 = await asyncio.gather(\n            sm.send(\"step1\"),\n            sm.send(\"step2\"),\n            return_exceptions=True,\n        )\n\n        # step1's caller gets the RuntimeError\n        assert isinstance(r1, RuntimeError)\n        assert str(r1) == \"step1 failed\"\n        # step2 also gets the RuntimeError (pending future rejected with same exception)\n        assert isinstance(r2, RuntimeError)\n        assert str(r2) == \"step1 failed\"\n\n    @pytest.mark.asyncio()\n    async def test_separate_tasks_with_slow_callback(self):\n        \"\"\"Reproduces the scenario from issue #509: two separate asyncio tasks\n        send events to the same state machine. The first callback does a slow\n        ``await asyncio.sleep()``, yielding control so the second task can\n        enqueue its event. Both tasks must receive their own results.\n\n        This specifically tests that concurrent external tasks (as opposed to\n        reentrant calls from within callbacks) correctly get futures and don't\n        return ``None``.\n        \"\"\"\n\n        class SlowSC(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n\n            noop = s1.to(s2)\n            noop2 = s2.to.itself()\n\n            async def on_noop(self, name):\n                await asyncio.sleep(0.01)\n                return f\"noop done by {name}\"\n\n            async def on_noop2(self, name):\n                return f\"noop2 done by {name}\"\n\n        sm = SlowSC()\n        await sm.activate_initial_state()\n\n        results = {}\n\n        async def fn1():\n            results[\"fn1\"] = await sm.send(\"noop\", \"fn1\")\n\n        async def fn2():\n            # Small delay so fn1 acquires the lock first\n            await asyncio.sleep(0.005)\n            results[\"fn2\"] = await sm.send(\"noop2\", \"fn2\")\n\n        await asyncio.gather(fn1(), fn2())\n\n        assert results[\"fn1\"] == \"noop done by fn1\"\n        assert results[\"fn2\"] == \"noop2 done by fn2\"\n\n    @pytest.mark.asyncio()\n    async def test_separate_tasks_validator_exception_routing(self):\n        \"\"\"Issue #509 scenario: validator exception must reach the correct\n        caller task, not the task that holds the processing lock.\n        \"\"\"\n\n        class ValidatorSC(StateChart):\n            catch_errors_as_events = False\n            s1 = State(initial=True)\n            s2 = State()\n\n            noop = s1.to(s2)\n            noop2 = s2.to.itself(validators=\"check_allowed\")\n\n            async def on_noop(self):\n                await asyncio.sleep(0.01)\n                return \"noop ok\"\n\n            def check_allowed(self):\n                raise ValueError(\"noop2 is not allowed\")\n\n        sm = ValidatorSC()\n        await sm.activate_initial_state()\n\n        results = {}\n        errors = {}\n\n        async def fn1():\n            results[\"fn1\"] = await sm.send(\"noop\")\n\n        async def fn2():\n            await asyncio.sleep(0.005)\n            try:\n                await sm.send(\"noop2\")\n            except ValueError as e:\n                errors[\"fn2\"] = e\n\n        await asyncio.gather(fn1(), fn2())\n\n        assert results[\"fn1\"] == \"noop ok\"\n        assert \"fn2\" in errors\n        assert str(errors[\"fn2\"]) == \"noop2 is not allowed\"\n\n\nclass TestEventQueueRejectFutures:\n    \"\"\"Unit tests for EventQueue.reject_futures.\"\"\"\n\n    def test_reject_futures_skips_items_without_future(self):\n        \"\"\"Items with future=None are silently skipped.\"\"\"\n        sm = TrafficLight()\n\n        queue = EventQueue()\n        td = TriggerData(machine=sm, event=None)\n        assert td.future is None\n        queue.put(td)\n\n        queue.reject_futures(RuntimeError(\"boom\"))\n        # No exception raised, item still in queue\n        assert not queue.is_empty()\n"
  },
  {
    "path": "tests/test_callbacks.py",
    "content": "from unittest import mock\n\nimport pytest\nfrom statemachine.callbacks import CallbackGroup\nfrom statemachine.callbacks import CallbacksExecutor\nfrom statemachine.callbacks import CallbackSpec\nfrom statemachine.callbacks import CallbackSpecList\nfrom statemachine.callbacks import CallbacksRegistry\nfrom statemachine.dispatcher import resolver_factory_from_objects\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.fixture()\ndef ObjectWithCallbacks():\n    class ObjectWithCallbacks:\n        def __init__(self):\n            super().__init__()\n            self.name = \"statemachine\"\n            self.callbacks = CallbackSpecList().add(\n                [\"life_meaning\", \"name\", \"a_method\"],\n                group=CallbackGroup.ON,\n            )\n            self.can_be_called = self.callbacks.grouper(CallbackGroup.ON)\n            self.registry = CallbacksRegistry()\n            resolver_factory_from_objects(self).resolve(self.callbacks, registry=self.registry)\n\n        @property\n        def life_meaning(self):\n            return 42\n\n        def a_method(self, *args, **kwargs):\n            return args, kwargs\n\n    return ObjectWithCallbacks\n\n\nclass TestCallbacksMachinery:\n    def test_callback_meta_is_hashable(self):\n        wrapper = CallbackSpec(\"something\", group=CallbackGroup.ON)\n        set().add(wrapper)\n\n    def test_can_add_callback_that_is_a_string(self):\n        specs = CallbackSpecList()\n        func = mock.Mock()\n\n        registry = CallbacksRegistry()\n\n        class MyObject:\n            def my_method(self, *args, **kwargs):\n                return func(\"my_method\", *args, **kwargs)\n\n            def other_method(self, *args, **kwargs):\n                return func(\"other_method\", *args, **kwargs)\n\n            def last_one(self, *args, **kwargs):\n                return func(\"last_one\", *args, **kwargs)\n\n        obj = MyObject()\n\n        specs.add(\"my_method\", group=CallbackGroup.ON).add(\"other_method\", group=CallbackGroup.ON)\n        specs.add(\"last_one\", group=CallbackGroup.ON)\n\n        resolver_factory_from_objects(obj).resolve(specs, registry)\n\n        registry[CallbackGroup.ON.build_key(specs)].call(1, 2, 3, a=\"x\", b=\"y\")\n\n        assert func.call_args_list == [\n            mock.call(\"my_method\", 1, 2, 3, a=\"x\", b=\"y\"),\n            mock.call(\"other_method\", 1, 2, 3, a=\"x\", b=\"y\"),\n            mock.call(\"last_one\", 1, 2, 3, a=\"x\", b=\"y\"),\n        ]\n\n    def test_callbacks_are_iterable(self):\n        specs = CallbackSpecList()\n\n        specs.add(\"my_method\", 1).add(\"other_method\", 1)\n        specs.add(\"last_one\", 1)\n\n        assert [c.func for c in specs] == [\"my_method\", \"other_method\", \"last_one\"]\n\n    def test_add_many_callbacks_at_once(self):\n        specs = CallbackSpecList()\n        method_names = [\"my_method\", \"other_method\", \"last_one\"]\n\n        specs.add(method_names, group=CallbackGroup.ON)\n\n        assert [c.func for c in specs] == method_names\n\n    @pytest.mark.parametrize(\"is_convention\", [False, True])\n    def test_raise_error_if_didnt_found_attr(self, is_convention):\n        specs = CallbackSpecList()\n        registry = CallbacksRegistry()\n\n        specs.add(\n            \"this_does_no_exist\",\n            group=CallbackGroup.ON,\n            is_convention=is_convention,\n        )\n        resolver_factory_from_objects(self).resolve(specs, registry=registry)\n\n        if is_convention:\n            registry.check(specs)\n        else:\n            with pytest.raises(InvalidDefinition):\n                registry.check(specs)\n\n    def test_collect_results(self):\n        specs = CallbackSpecList()\n        registry = CallbacksRegistry()\n\n        def func1():\n            return 10\n\n        def func2():\n            return (\"a\", True)\n\n        def func3():\n            return {\"key\": \"value\"}\n\n        specs.add([func1, func2, func3], group=CallbackGroup.ON)\n        resolver_factory_from_objects(object()).resolve(specs, registry=registry)\n\n        results = registry[CallbackGroup.ON.build_key(specs)].call(1, 2, 3, a=\"x\", b=\"y\")\n\n        assert results == [\n            10,\n            (\"a\", True),\n            {\"key\": \"value\"},\n        ]\n\n    def test_callbacks_values_resolution(self, ObjectWithCallbacks):\n        x = ObjectWithCallbacks()\n        assert x.registry[CallbackGroup.ON.build_key(x.callbacks)].call(xablau=True) == [\n            42,\n            \"statemachine\",\n            ((), {\"xablau\": True}),\n        ]\n\n\nclass TestCallbacksAsDecorator:\n    def test_decorate_unbounded_function(self, ObjectWithCallbacks):\n        x = ObjectWithCallbacks()\n\n        @x.can_be_called\n        def hero_lowercase(hero):\n            return hero.lower()\n\n        @x.can_be_called\n        def race_uppercase(race):\n            return race.upper()\n\n        resolver_factory_from_objects(x).resolve(x.callbacks, registry=x.registry)\n\n        assert x.registry[CallbackGroup.ON.build_key(x.callbacks)].call(\n            hero=\"Gandalf\", race=\"Maia\"\n        ) == [\n            42,\n            \"statemachine\",\n            ((), {\"hero\": \"Gandalf\", \"race\": \"Maia\"}),\n            \"gandalf\",\n            \"MAIA\",\n        ]\n\n        assert race_uppercase(\"Hobbit\") == \"HOBBIT\"\n\n    def test_decorate_unbounded_machine_methods(self):\n        class MiniHeroJourneyMachine(StateChart):\n            ordinary_world = State(initial=True)\n            call_to_adventure = State(final=True)\n            refusal_of_call = State(final=True)\n\n            adventure_called = ordinary_world.to(call_to_adventure)\n\n            def __init__(self, *args, **kwargs):\n                self.spy = mock.Mock(side_effect=lambda *x: x)\n                super().__init__(*args, **kwargs)\n\n            @ordinary_world.enter\n            def enter_ordinary_world(self):\n                \"\"\"This is the hero's life before they begin their journey. It is their \"normal\"\n                world, where they are comfortable and familiar.\n                \"\"\"\n                self.spy(\"enter_ordinary_world\")\n\n            @call_to_adventure.enter\n            def enter_call_to_adventure(self, request):\n                \"\"\"Something happens that forces the hero to leave their ordinary world and embark\n                on a journey. This might be a direct call, like a prophecy or a request for help,\n                or it might be a more subtle nudge, like a feeling of restlessness or a sense of\n                something missing in their life.\"\"\"\n                self.spy(\"call_to_adventure\", request)\n\n            @ordinary_world.to(refusal_of_call)\n            def refuse_call(self, reason):\n                self.spy(\"refuse_call\", reason)\n\n        sm = MiniHeroJourneyMachine()\n        sm.adventure_called(request=\"The darkness is coming\")\n        assert sm.spy.call_args_list == [\n            mock.call(\"enter_ordinary_world\"),\n            mock.call(\"call_to_adventure\", \"The darkness is coming\"),\n        ]\n\n        sm = MiniHeroJourneyMachine()\n        sm.refuse_call(reason=\"Not prepared yet\")\n        assert sm.spy.call_args_list == [\n            mock.call(\"enter_ordinary_world\"),\n            mock.call(\"refuse_call\", \"Not prepared yet\"),\n        ]\n\n\nclass TestIssue406:\n    \"\"\"\n    A StateMachine that exercises the example given on issue\n    #[406](https://github.com/fgmacedo/python-statemachine/issues/406).\n\n    In this example, the event callback must be registered only once.\n    \"\"\"\n\n    def test_issue_406(self, mocker):\n        mock = mocker.Mock()\n\n        class ExampleStateMachine(StateChart):\n            created = State(initial=True)\n            inited = State(final=True)\n\n            initialize = created.to(inited)\n\n            @initialize.before\n            def before_initialize(self):\n                mock(\"before init\")\n\n            @initialize.on\n            def on_initialize(self):\n                mock(\"on init\")\n\n        sm = ExampleStateMachine()\n        sm.initialize()\n\n        assert mock.call_args_list == [\n            mocker.call(\"before init\"),\n            mocker.call(\"on init\"),\n        ]\n\n\nclass TestIssue417:\n    \"\"\"\n    A StateMachine that exercises the example given on issue\n    #[417](https://github.com/fgmacedo/python-statemachine/issues/417).\n    \"\"\"\n\n    @pytest.fixture()\n    def mock_calls(self, mocker):\n        return mocker.Mock()\n\n    @pytest.fixture()\n    def model_class(self):\n        class Model:\n            def __init__(self, counter: int = 0):\n                self.state = None\n                self.counter = counter\n\n            def can_be_started_on_model(self) -> bool:\n                return self.counter > 0\n\n            @property\n            def can_be_started_as_property_on_model(self) -> bool:\n                return self.counter > 1\n\n            @property\n            def can_be_started_as_property_str_on_model(self) -> bool:\n                return self.counter > 2\n\n        return Model\n\n    @pytest.fixture()\n    def sm_class(self, model_class, mock_calls):\n        class ExampleStateMachine(StateChart):\n            allow_event_without_transition = False\n            catch_errors_as_events = False\n\n            created = State(initial=True)\n            started = State(final=True)\n\n            def can_be_started(self) -> bool:\n                return self.counter > 0\n\n            @property\n            def can_be_started_as_property(self) -> bool:\n                return self.counter > 1\n\n            @property\n            def can_be_started_as_property_str(self) -> bool:\n                return self.counter > 2\n\n            start = created.to(\n                started,\n                cond=[\n                    can_be_started,\n                    can_be_started_as_property,\n                    \"can_be_started_as_property_str\",\n                    model_class.can_be_started_on_model,\n                    model_class.can_be_started_as_property_on_model,\n                    \"can_be_started_as_property_str_on_model\",\n                ],\n            )\n\n            def __init__(self, model=None, counter: int = 0):\n                self.counter = counter\n                super().__init__(model=model)\n\n            def on_start(self):\n                mock_calls(\"started\")\n\n        return ExampleStateMachine\n\n    def test_issue_417_cannot_start(self, model_class, sm_class, mock_calls):\n        model = model_class(0)\n        sm = sm_class(model, 0)\n        with pytest.raises(sm.TransitionNotAllowed, match=\"Can't Start when in Created\"):\n            sm.start()\n\n        mock_calls.assert_not_called()\n\n    def test_issue_417_can_start(self, model_class, sm_class, mock_calls, mocker):\n        model = model_class(3)\n        sm = sm_class(model, 3)\n        sm.start()\n\n        assert mock_calls.call_args_list == [\n            mocker.call(\"started\"),\n        ]\n\n    def test_raise_exception_if_property_is_not_found(self):\n        class StrangeObject:\n            @property\n            def this_cannot_resolve(self) -> bool:\n                return True\n\n        class ExampleStateMachine(StateChart):\n            catch_errors_as_events = False\n\n            created = State(initial=True)\n            started = State(final=True)\n            start = created.to(started, cond=[StrangeObject.this_cannot_resolve])\n\n        with pytest.raises(\n            InvalidDefinition,\n            match=\"Error on transition start from Created to Started when resolving callbacks\",\n        ):\n            ExampleStateMachine()\n\n\nclass TestVisitConditionFalse:\n    \"\"\"visit/async_visit skip callbacks whose condition returns False.\"\"\"\n\n    def test_visit_skips_when_condition_is_false(self):\n        visited = []\n        spec = CallbackSpec(\n            \"never_called\",\n            group=CallbackGroup.INVOKE,\n            is_convention=True,\n            cond=lambda *a, **kw: False,\n        )\n        executor = CallbacksExecutor()\n        executor.add(\"test_key\", spec, lambda: lambda **kw: True)\n\n        executor.visit(lambda cb, *a, **kw: visited.append(str(cb)))\n        assert visited == []\n\n    async def test_async_visit_skips_when_condition_is_false(self):\n        visited = []\n        spec = CallbackSpec(\n            \"never_called\",\n            group=CallbackGroup.INVOKE,\n            is_convention=True,\n            cond=lambda *a, **kw: False,\n        )\n        executor = CallbacksExecutor()\n        executor.add(\"test_key\", spec, lambda: lambda **kw: True)\n\n        await executor.async_visit(lambda cb, *a, **kw: visited.append(str(cb)))\n        assert visited == []\n"
  },
  {
    "path": "tests/test_callbacks_isolation.py",
    "content": "import pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.fixture()\ndef simple_sm_cls():\n    class TestStateMachine(StateChart):\n        allow_event_without_transition = True\n\n        # States\n        initial = State(initial=True)\n        final = State(final=True, enter=\"do_enter_final\")\n\n        finish = initial.to(final, cond=\"can_finish\", on=\"do_finish\")\n\n        def __init__(self, name):\n            self.name = name\n            self.can_finish = False\n            self.finalized = False\n            super().__init__()\n\n        def do_finish(self):\n            return self.name, self.can_finish\n\n        def do_enter_final(self):\n            self.finalized = True\n\n    return TestStateMachine\n\n\nclass TestCallbacksIsolation:\n    def test_should_conditions_be_isolated(self, simple_sm_cls):\n        sm1 = simple_sm_cls(\"sm1\")\n        sm2 = simple_sm_cls(\"sm2\")\n        sm3 = simple_sm_cls(\"sm3\")\n\n        sm1.can_finish = True\n        sm1.send(\"finish\")\n        sm2.send(\"finish\")\n        sm3.send(\"finish\")\n\n        assert sm1.final.is_active\n        assert sm2.initial.is_active\n        assert sm2.initial.is_active\n\n    def test_should_actions_be_isolated(self, simple_sm_cls):\n        sm1 = simple_sm_cls(\"sm1\")\n        sm2 = simple_sm_cls(\"sm2\")\n\n        sm1.can_finish = True\n        sm2.can_finish = True\n\n        sm1_initial = sm1.initial\n        sm1_final = sm1.final\n\n        assert sm2.finish() == (\"sm2\", True)\n\n        assert not sm2.initial.is_active\n        assert sm2.final.is_active\n        assert sm2.finalized is True\n\n        assert sm1_initial.is_active\n        assert not sm1_final.is_active\n        assert sm1.finalized is False\n\n        assert sm1.initial.is_active\n        assert not sm1.final.is_active\n\n        assert sm1.finish() == (\"sm1\", True)\n\n        assert sm1.finalized is True\n        assert not sm1.initial.is_active\n        assert sm1.final.is_active\n"
  },
  {
    "path": "tests/test_class_listeners.py",
    "content": "import pickle\nfrom functools import partial\n\nimport pytest\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass RecordingListener:\n    \"\"\"Listener that records transitions for testing.\"\"\"\n\n    def __init__(self):\n        self.transitions = []\n\n    def after_transition(self, event, source, target):\n        self.transitions.append((event, source.id, target.id))\n\n\nclass SetupListener:\n    \"\"\"Listener that uses setup() to receive runtime dependencies.\"\"\"\n\n    def __init__(self):\n        self.session = None\n        self.transitions = []\n\n    def setup(self, sm, session=None, **kwargs):\n        self.session = session\n\n    def after_transition(self, event, source, target):\n        self.transitions.append((event, source.id, target.id, self.session))\n\n\nclass TestClassLevelListeners:\n    def test_class_level_listener_callable_creates_per_instance(self):\n        class MyChart(StateChart):\n            listeners = [RecordingListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm1 = MyChart()\n        sm2 = MyChart()\n\n        sm1.send(\"go\")\n\n        # Each SM gets its own listener instance\n        assert len(sm1.active_listeners) == 1\n        assert len(sm2.active_listeners) == 1\n        assert sm1.active_listeners[0] is not sm2.active_listeners[0]\n\n        # Only sm1 should have the transition recorded\n        assert sm1.active_listeners[0].transitions == [(\"go\", \"s1\", \"s2\")]\n        assert sm2.active_listeners[0].transitions == []\n\n    def test_class_level_listener_shared_instance(self):\n        shared = RecordingListener()\n\n        class MyChart(StateChart):\n            listeners = [shared]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm1 = MyChart()\n        sm2 = MyChart()\n\n        sm1.send(\"go\")\n        sm2.send(\"go\")\n\n        # Both SMs share the same listener instance\n        assert sm1.active_listeners[0] is shared\n        assert sm2.active_listeners[0] is shared\n        assert len(shared.transitions) == 2\n\n    def test_class_level_listener_partial(self):\n        class ConfigurableListener:\n            def __init__(self, prefix=\"default\"):\n                self.prefix = prefix\n                self.messages = []\n\n            def after_transition(self, event, source, target):\n                self.messages.append(f\"{self.prefix}: {source.id} -> {target.id}\")\n\n        class MyChart(StateChart):\n            listeners = [partial(ConfigurableListener, prefix=\"custom\")]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart()\n        sm.send(\"go\")\n\n        listener = sm.active_listeners[0]\n        assert listener.prefix == \"custom\"\n        assert listener.messages == [\"custom: s1 -> s2\"]\n\n    def test_class_level_listener_lambda(self):\n        class SimpleListener:\n            def __init__(self, tag):\n                self.tag = tag\n\n        class MyChart(StateChart):\n            listeners = [lambda: SimpleListener(\"from_lambda\")]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart()\n        assert sm.active_listeners[0].tag == \"from_lambda\"\n\n    def test_runtime_listeners_merge_with_class_level(self):\n        class MyChart(StateChart):\n            listeners = [RecordingListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        runtime_listener = RecordingListener()\n        sm = MyChart(listeners=[runtime_listener])\n\n        sm.send(\"go\")\n\n        assert len(sm.active_listeners) == 2\n\n        # Both listeners should have recorded\n        for listener in sm.active_listeners:\n            assert listener.transitions == [(\"go\", \"s1\", \"s2\")]\n\n        # Runtime listener is the one we passed in\n        assert runtime_listener in sm.active_listeners\n\n\nclass TestClassListenerInheritance:\n    def test_child_extends_parent_listeners(self):\n        class ParentListener:\n            pass\n\n        class ChildListener:\n            pass\n\n        class Parent(StateChart):\n            listeners = [ParentListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        class Child(Parent):\n            listeners = [ChildListener]\n\n        sm = Child()\n        assert len(sm.active_listeners) == 2\n        assert isinstance(sm.active_listeners[0], ParentListener)\n        assert isinstance(sm.active_listeners[1], ChildListener)\n\n    def test_child_replaces_parent_listeners(self):\n        class ParentListener:\n            pass\n\n        class ChildListener:\n            pass\n\n        class Parent(StateChart):\n            listeners = [ParentListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        class Child(Parent):\n            listeners_inherit = False\n            listeners = [ChildListener]\n\n        sm = Child()\n        assert len(sm.active_listeners) == 1\n        assert isinstance(sm.active_listeners[0], ChildListener)\n\n    def test_grandchild_inherits_full_chain(self):\n        class L1:\n            pass\n\n        class L2:\n            pass\n\n        class L3:\n            pass\n\n        class Base(StateChart):\n            listeners = [L1]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        class Mid(Base):\n            listeners = [L2]\n\n        class Leaf(Mid):\n            listeners = [L3]\n\n        sm = Leaf()\n        assert len(sm.active_listeners) == 3\n        assert isinstance(sm.active_listeners[0], L1)\n        assert isinstance(sm.active_listeners[1], L2)\n        assert isinstance(sm.active_listeners[2], L3)\n\n    def test_no_listeners_declared_inherits_parent(self):\n        class ParentListener:\n            pass\n\n        class Parent(StateChart):\n            listeners = [ParentListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        class Child(Parent):\n            pass\n\n        sm = Child()\n        assert len(sm.active_listeners) == 1\n        assert isinstance(sm.active_listeners[0], ParentListener)\n\n\nclass TestListenerSetupProtocol:\n    def test_setup_receives_kwargs(self):\n        class MyChart(StateChart):\n            listeners = [SetupListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart(session=\"my_db_session\")\n        listener = sm.active_listeners[0]\n        assert listener.session == \"my_db_session\"\n\n    def test_setup_ignores_unknown_kwargs(self):\n        class MyChart(StateChart):\n            listeners = [SetupListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart(session=\"db\", unknown_arg=\"ignored\")\n        listener = sm.active_listeners[0]\n        assert listener.session == \"db\"\n\n    def test_setup_not_called_on_shared_instances(self):\n        shared = SetupListener()\n\n        class MyChart(StateChart):\n            listeners = [shared]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        MyChart(session=\"db\")\n        # Shared instance should NOT have setup() called\n        assert shared.session is None\n\n    def test_multiple_listeners_with_different_deps(self):\n        class DBListener:\n            def __init__(self):\n                self.session = None\n\n            def setup(self, sm, session=None, **kwargs):\n                self.session = session\n\n        class CacheListener:\n            def __init__(self):\n                self.redis = None\n\n            def setup(self, sm, redis=None, **kwargs):\n                self.redis = redis\n\n        class MyChart(StateChart):\n            listeners = [DBListener, CacheListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart(session=\"db_conn\", redis=\"redis_conn\")\n        db, cache = sm.active_listeners\n        assert db.session == \"db_conn\"\n        assert cache.redis == \"redis_conn\"\n\n    def test_setup_receives_sm_instance(self):\n        class IntrospectiveListener:\n            def __init__(self):\n                self.sm = None\n\n            def setup(self, sm, **kwargs):\n                self.sm = sm\n\n        class MyChart(StateChart):\n            listeners = [IntrospectiveListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart()\n        listener = sm.active_listeners[0]\n        assert listener.sm is sm\n\n    def test_setup_optional_kwargs_default_to_none(self):\n        class MyChart(StateChart):\n            listeners = [SetupListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart()  # No session kwarg provided\n        listener = sm.active_listeners[0]\n        assert listener.session is None\n\n    def test_setup_required_kwarg_missing_raises_error(self):\n        class StrictListener:\n            def setup(self, sm, session):\n                self.session = session\n\n        class MyChart(StateChart):\n            listeners = [StrictListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        with pytest.raises(TypeError, match=\"Error calling setup.*StrictListener\"):\n            MyChart()\n\n    def test_setup_required_kwarg_provided(self):\n        class StrictListener:\n            def setup(self, sm, session):\n                self.session = session\n\n        class MyChart(StateChart):\n            listeners = [StrictListener]\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart(session=\"db_conn\")\n        assert sm.active_listeners[0].session == \"db_conn\"\n\n\nclass TestListenerValidation:\n    def test_rejects_none_in_listeners(self):\n        with pytest.raises(InvalidDefinition, match=\"Invalid entry\"):\n\n            class MyChart(StateChart):\n                listeners = [None]\n\n                s1 = State(initial=True)\n                s2 = State(final=True)\n                go = s1.to(s2)\n\n    def test_rejects_string_in_listeners(self):\n        with pytest.raises(InvalidDefinition, match=\"Invalid entry\"):\n\n            class MyChart(StateChart):\n                listeners = [\"not_a_listener\"]\n\n                s1 = State(initial=True)\n                s2 = State(final=True)\n                go = s1.to(s2)\n\n    def test_rejects_number_in_listeners(self):\n        with pytest.raises(InvalidDefinition, match=\"Invalid entry\"):\n\n            class MyChart(StateChart):\n                listeners = [42]\n\n                s1 = State(initial=True)\n                s2 = State(final=True)\n                go = s1.to(s2)\n\n    def test_rejects_bool_in_listeners(self):\n        with pytest.raises(InvalidDefinition, match=\"Invalid entry\"):\n\n            class MyChart(StateChart):\n                listeners = [True]\n\n                s1 = State(initial=True)\n                s2 = State(final=True)\n                go = s1.to(s2)\n\n\nclass _PickleChart(StateChart):\n    listeners = [RecordingListener]\n\n    s1 = State(initial=True)\n    s2 = State(final=True)\n    go = s1.to(s2)\n\n\nclass _PickleMultiStepChart(StateChart):\n    listeners = [RecordingListener]\n\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State(final=True)\n    step1 = s1.to(s2)\n    step2 = s2.to(s3)\n\n\nclass TestListenerSerialization:\n    def test_pickle_with_class_listeners(self):\n        sm = _PickleChart()\n        sm.send(\"go\")\n\n        data = pickle.dumps(sm)\n        sm2 = pickle.loads(data)\n\n        # Class listener instances are preserved through serialization\n        assert len(sm2.active_listeners) == 1\n        assert sm2.active_listeners[0].transitions == [(\"go\", \"s1\", \"s2\")]\n        assert \"s2\" in sm2.configuration_values\n\n    def test_pickle_does_not_duplicate_class_listeners(self):\n        sm = _PickleChart()\n        assert len(sm.active_listeners) == 1\n\n        data = pickle.dumps(sm)\n        sm2 = pickle.loads(data)\n\n        # Must not duplicate class listeners after deserialization\n        assert len(sm2.active_listeners) == 1\n\n    def test_pickle_with_runtime_listeners(self):\n        runtime = RecordingListener()\n        sm = _PickleMultiStepChart(listeners=[runtime])\n        sm.send(\"step1\")\n\n        data = pickle.dumps(sm)\n        sm2 = pickle.loads(data)\n\n        # After deserialization, both class and runtime listeners are re-registered\n        assert \"s2\" in sm2.configuration_values\n        sm2.send(\"step2\")\n        assert \"s3\" in sm2.configuration_values\n\n\nclass TestEmptyClassListeners:\n    def test_no_listeners_attribute(self):\n        class MyChart(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart()\n        assert sm.active_listeners == []\n\n    def test_empty_listeners_list(self):\n        class MyChart(StateChart):\n            listeners = []\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        sm = MyChart()\n        assert sm.active_listeners == []\n"
  },
  {
    "path": "tests/test_conditions_algebra.py",
    "content": "import pytest\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass AnyConditionSM(StateChart):\n    allow_event_without_transition = False\n    catch_errors_as_events = False\n\n    start = State(initial=True)\n    end = State(final=True)\n\n    submit = start.to(end, cond=\"used_money or used_credit\")\n\n    used_money: bool = False\n    used_credit: bool = False\n\n\ndef test_conditions_algebra_any_false():\n    sm = AnyConditionSM()\n    with pytest.raises(sm.TransitionNotAllowed):\n        sm.submit()\n\n    assert sm.start.is_active\n\n\ndef test_conditions_algebra_any_left_true():\n    sm = AnyConditionSM()\n    sm.used_money = True\n    sm.submit()\n    assert sm.end.is_active\n\n\ndef test_conditions_algebra_any_right_true():\n    sm = AnyConditionSM()\n    sm.used_credit = True\n    sm.submit()\n    assert sm.end.is_active\n\n\ndef test_should_raise_invalid_definition_if_cond_is_not_valid_sintax():\n    class AnyConditionSM(StateChart):\n        start = State(initial=True)\n        end = State(final=True)\n\n        submit = start.to(end, cond=\"used_money xxx\")\n\n        used_money: bool = False\n        used_credit: bool = False\n\n    with pytest.raises(InvalidDefinition, match=\"Failed to parse boolean expression\"):\n        AnyConditionSM()\n\n\ndef test_should_raise_invalid_definition_if_cond_is_not_found():\n    class AnyConditionSM(StateChart):\n        start = State(initial=True)\n        end = State(final=True)\n\n        submit = start.to(end, cond=\"used_money and xxx\")\n\n        used_money: bool = False\n        used_credit: bool = False\n\n    with pytest.raises(InvalidDefinition, match=\"Did not found name 'xxx'\"):\n        AnyConditionSM()\n"
  },
  {
    "path": "tests/test_configuration.py",
    "content": "\"\"\"Tests for the Configuration class internals.\n\nThese tests cover branches in statemachine/configuration.py that are not\nexercised by the higher-level state machine tests.\n\"\"\"\n\nimport warnings\n\nfrom statemachine.orderedset import OrderedSet\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass ParallelSM(StateChart):\n    \"\"\"A parallel state chart for testing multi-element configuration.\"\"\"\n\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State(final=True)\n\n    go = s1.to(s2)\n    finish = s2.to(s3)\n\n\nclass TestConfigurationStatesSetter:\n    def test_set_empty_configuration(self):\n        sm = ParallelSM()\n        assert len(sm.configuration) > 0\n\n        sm.configuration = OrderedSet()\n        assert sm.current_state_value is None\n\n    def test_set_multi_element_configuration(self):\n        sm = ParallelSM()\n        s1_inst = sm.s1\n        s2_inst = sm.s2\n\n        sm.configuration = OrderedSet([s1_inst, s2_inst])\n        assert isinstance(sm.current_state_value, OrderedSet)\n        assert sm.current_state_value == OrderedSet([ParallelSM.s1.value, ParallelSM.s2.value])\n\n\nclass TestConfigurationValueSetter:\n    def test_set_value_none_writes_none_to_model(self):\n        sm = ParallelSM()\n        assert sm.current_state_value is not None\n\n        sm.current_state_value = None\n        assert sm.current_state_value is None\n        assert sm.configuration_values == OrderedSet()\n\n    def test_set_value_plain_set_coerces_to_ordered_set(self):\n        sm = ParallelSM()\n        s1_val = ParallelSM.s1.value\n        s2_val = ParallelSM.s2.value\n\n        # Assign a plain set (MutableSet but not OrderedSet)\n        sm.current_state_value = {s1_val, s2_val}\n        # Model should store an OrderedSet (denormalized back to it)\n        assert isinstance(sm.current_state_value, OrderedSet)\n        assert sm.current_state_value == OrderedSet([s1_val, s2_val])\n\n\nclass TestReadFromModelNonOrderedSet:\n    def test_read_from_model_coerces_plain_set(self):\n        \"\"\"When the model stores a plain set, _read_from_model coerces it.\"\"\"\n        sm = ParallelSM()\n        s1_val = ParallelSM.s1.value\n        s2_val = ParallelSM.s2.value\n\n        # Bypass the value setter to place a plain set on the model\n        setattr(sm._config._model, sm._config._state_field, {s1_val, s2_val})\n\n        values = sm._config._read_from_model()\n        assert isinstance(values, OrderedSet)\n        assert values == OrderedSet([s1_val, s2_val])\n\n\nclass TestConfigurationDiscard:\n    def test_discard_nonmatching_scalar(self):\n        sm = ParallelSM()\n        # current value is s1 (scalar)\n        assert sm.current_state_value == ParallelSM.s1.value\n\n        # discard s2 — should be a no-op since s2 is not active\n        sm._config.discard(ParallelSM.s2)\n        assert sm.current_state_value == ParallelSM.s1.value\n\n\nclass TestConfigurationCurrentState:\n    def test_current_state_with_multiple_active_states(self):\n        sm = ParallelSM()\n        s1_inst = sm.s1\n        s2_inst = sm.s2\n        sm.configuration = OrderedSet([s1_inst, s2_inst])\n\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", DeprecationWarning)\n            result = sm.current_state\n        assert isinstance(result, OrderedSet)\n        assert len(result) == 2\n\n\n# ---------------------------------------------------------------------------\n# Regression tests: add()/discard() must go through the property setter\n# so that models with deserializing properties persist the updated value.\n# ---------------------------------------------------------------------------\n\n\nclass SerializingModel:\n    \"\"\"A model that serializes/deserializes state on every access,\n    simulating a DB-backed property (e.g., Django model field).\n    \"\"\"\n\n    def __init__(self):\n        self._raw: str | None = None\n\n    @property\n    def state(self):\n        if self._raw is None:\n            return None\n        parts = self._raw.split(\",\")\n        if len(parts) == 1:\n            return parts[0]\n        return OrderedSet(parts)\n\n    @state.setter\n    def state(self, value):\n        if value is None:\n            self._raw = None\n        elif isinstance(value, OrderedSet):\n            self._raw = \",\".join(str(v) for v in value)\n        else:\n            self._raw = str(value)\n\n\nclass WarSC(StateChart):\n    \"\"\"Parallel state chart with two regions for testing.\"\"\"\n\n    class war(State.Parallel):\n        class region_a(State.Compound):\n            a1 = State(initial=True)\n            a2 = State()\n            move_a = a1.to(a2)\n\n        class region_b(State.Compound):\n            b1 = State(initial=True)\n            b2 = State()\n            move_b = b1.to(b2)\n\n\nclass TestAddDiscard:\n    \"\"\"Verify add()/discard() always write back through model setter.\"\"\"\n\n    def test_add_calls_setter_on_serializing_model(self):\n        model = SerializingModel()\n        sm = WarSC(model=model)\n\n        # After initial entry, all parallel states should be active\n        config_values = sm.configuration_values\n        assert len(config_values) == 5  # war, region_a, a1, region_b, b1\n\n    def test_discard_calls_setter_on_serializing_model(self):\n        model = SerializingModel()\n        sm = WarSC(model=model)\n\n        initial_count = len(sm.configuration_values)\n        assert initial_count == 5\n\n        # Trigger a transition in region_a: a1 -> a2\n        sm.send(\"move_a\")\n        config_values = sm.configuration_values\n        # a1 should be replaced by a2; still 5 states\n        assert len(config_values) == 5\n        assert \"a2\" in config_values\n        assert \"a1\" not in config_values\n\n    def test_parallel_lifecycle_with_serializing_model(self):\n        model = SerializingModel()\n        sm = WarSC(model=model)\n\n        # Move both regions\n        sm.send(\"move_a\")\n        sm.send(\"move_b\")\n\n        config_values = sm.configuration_values\n        assert len(config_values) == 5\n        assert \"a2\" in config_values\n        assert \"b2\" in config_values\n        assert \"a1\" not in config_values\n        assert \"b1\" not in config_values\n\n    def test_state_restoration_from_serialized_model(self):\n        model = SerializingModel()\n        sm = WarSC(model=model)\n        sm.send(\"move_a\")\n\n        # Save the raw state\n        raw_state = model._raw\n\n        # Create a new model with the same raw state and a new SM\n        model2 = SerializingModel()\n        model2._raw = raw_state\n        sm2 = WarSC(model=model2)\n\n        assert sm2.configuration_values == sm.configuration_values\n\n    async def test_parallel_with_serializing_model_both_engines(self, sm_runner):\n        model = SerializingModel()\n        sm = await sm_runner.start(WarSC, model=model)\n\n        assert len(sm.configuration_values) == 5\n\n        await sm_runner.send(sm, \"move_a\")\n        assert \"a2\" in sm.configuration_values\n        assert len(sm.configuration_values) == 5\n"
  },
  {
    "path": "tests/test_contrib_diagram.py",
    "content": "import re\nfrom contextlib import contextmanager\nfrom unittest import mock\nfrom xml.etree import ElementTree\n\nimport pytest\nfrom docutils import nodes\nfrom statemachine.contrib.diagram import DotGraphMachine\nfrom statemachine.contrib.diagram import main\nfrom statemachine.contrib.diagram import quickchart_write_svg\nfrom statemachine.contrib.diagram.extract import _format_event_names\nfrom statemachine.contrib.diagram.model import ActionType\nfrom statemachine.contrib.diagram.model import StateType\nfrom statemachine.contrib.diagram.renderers.dot import DotRenderer\nfrom statemachine.event import Event\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\npytestmark = pytest.mark.usefixtures(\"requires_dot_installed\")\n\nSVG_NS = {\"svg\": \"http://www.w3.org/2000/svg\"}\n\n\ndef _parse_svg(graph):\n    \"\"\"Generate SVG from a pydot graph and parse it as XML.\"\"\"\n    svg_bytes = graph.create_svg()\n    return ElementTree.fromstring(svg_bytes)\n\n\ndef _find_state_node(svg_root, state_id):\n    \"\"\"Find the SVG <g> element for a state node by its title text.\"\"\"\n    for g in svg_root.iter(\"{http://www.w3.org/2000/svg}g\"):\n        if g.get(\"class\") != \"node\":\n            continue\n        title = g.find(\"{http://www.w3.org/2000/svg}title\")\n        if title is not None and title.text == state_id:\n            return g\n    return None\n\n\ndef _has_rectangular_fill(node_g):\n    \"\"\"Check if a node group has a <polygon> with a colored fill.\n\n    A <polygon> fill inside a state node means the background is rectangular\n    (no rounded corners), which is a visual regression — state backgrounds\n    should use <path> with curves to match the rounded border.\n\n    Ignores white fills and arrow-related polygons (which are in edge groups).\n    \"\"\"\n    for polygon in node_g.findall(\"{http://www.w3.org/2000/svg}polygon\"):\n        fill = polygon.get(\"fill\", \"none\")\n        if fill not in (\"none\", \"white\", \"black\", \"#ffffff\"):\n            return True\n    return False\n\n\ndef _path_has_curves(d_attr):\n    \"\"\"Check if an SVG path `d` attribute contains curve commands (C, c, Q, q, A, a).\n\n    Rounded corners are drawn with cubic Bezier curves (C command).\n    A rectangular shape only has M (move) and L (line) commands.\n    \"\"\"\n    return bool(re.search(r\"[CcQqAa]\", d_attr))\n\n\n@pytest.fixture(\n    params=[\n        (\n            \"_repr_svg_\",\n            '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\\n<!DOCTYPE svg',\n        ),\n        (\n            \"_repr_html_\",\n            '<div class=\"statemachine\"><?xml version=\"1.0\" encoding=\"UTF-8\" standalone=',\n        ),\n    ]\n)\ndef expected_reprs(request):\n    return request.param\n\n\n@pytest.mark.parametrize(\n    \"machine_name\",\n    [\n        \"AllActionsMachine\",\n        \"OrderControl\",\n    ],\n)\ndef test_machine_repr_custom_(request, machine_name, expected_reprs):\n    machine_cls = request.getfixturevalue(machine_name)\n    machine = machine_cls()\n\n    magic_method, expected_repr = expected_reprs\n    repr = getattr(machine, magic_method)()\n    assert repr.startswith(expected_repr)\n\n\ndef test_machine_dot(OrderControl):\n    machine = OrderControl()\n\n    graph = DotGraphMachine(machine)\n    dot = graph()\n\n    dot_str = dot.to_string()  # or dot.to_string()\n    assert dot_str.startswith(\"digraph OrderControl {\")\n\n\nclass TestDiagramCmdLine:\n    def test_generate_image(self, tmp_path):\n        out = tmp_path / \"sm.svg\"\n\n        main([\"tests.examples.traffic_light_machine.TrafficLightMachine\", str(out)])\n\n        assert out.read_text().startswith(\n            '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\\n<!DOCTYPE svg'\n        )\n\n    def test_generate_image_from_module_path(self, tmp_path):\n        \"\"\"Accept a module path without the class name and auto-discover the SM class.\"\"\"\n        out = tmp_path / \"sm.svg\"\n\n        main([\"tests.examples.traffic_light_machine\", str(out)])\n\n        assert out.read_text().startswith(\n            '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\\n<!DOCTYPE svg'\n        )\n\n    def test_generate_complain_about_bad_sm_path(self, capsys, tmp_path):\n        out = tmp_path / \"sm.svg\"\n\n        expected_error = \"TrafficLightMachineXXX is not a subclass of StateMachine\"\n        with pytest.raises(ValueError, match=expected_error):\n            main(\n                [\n                    \"tests.examples.traffic_light_machine.TrafficLightMachineXXX\",\n                    str(out),\n                ]\n            )\n\n    def test_generate_image_with_events(self, tmp_path):\n        \"\"\"CLI --events instantiates the machine and sends events before rendering.\"\"\"\n        out = tmp_path / \"sm.png\"\n\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                str(out),\n                \"--events\",\n                \"cycle\",\n                \"cycle\",\n                \"cycle\",\n            ]\n        )\n\n        assert out.exists()\n        assert out.stat().st_size > 0\n\n    def test_generate_complain_about_module_without_sm(self, tmp_path):\n        out = tmp_path / \"sm.svg\"\n\n        expected_error = \"No StateMachine subclass found in module\"\n        with pytest.raises(ValueError, match=expected_error):\n            main([\"tests.examples\", str(out)])\n\n    def test_format_mermaid(self, tmp_path):\n        out = tmp_path / \"sm.mmd\"\n\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                str(out),\n                \"--format\",\n                \"mermaid\",\n            ]\n        )\n\n        content = out.read_text()\n        assert \"stateDiagram-v2\" in content\n        assert \"green --> yellow : Cycle\" in content\n\n    def test_format_md(self, tmp_path):\n        out = tmp_path / \"sm.md\"\n\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                str(out),\n                \"--format\",\n                \"md\",\n            ]\n        )\n\n        content = out.read_text()\n        assert \"| State\" in content\n        assert \"Cycle\" in content\n\n    def test_format_rst(self, tmp_path):\n        out = tmp_path / \"sm.rst\"\n\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                str(out),\n                \"--format\",\n                \"rst\",\n            ]\n        )\n\n        content = out.read_text()\n        assert \"+---\" in content\n        assert \"Cycle\" in content\n\n    def test_format_mermaid_stdout(self, capsys):\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                \"-\",\n                \"--format\",\n                \"mermaid\",\n            ]\n        )\n\n        captured = capsys.readouterr()\n        assert \"stateDiagram-v2\" in captured.out\n\n    def test_format_md_stdout(self, capsys):\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                \"-\",\n                \"--format\",\n                \"md\",\n            ]\n        )\n\n        captured = capsys.readouterr()\n        assert \"| State\" in captured.out\n\n    def test_stdout_default_svg(self, capsys):\n        \"\"\"Default format to stdout writes SVG bytes.\"\"\"\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                \"-\",\n            ]\n        )\n\n        captured = capsys.readouterr()\n        assert \"<svg\" in captured.out or b\"<svg\" in captured.out.encode()\n\n    def test_format_mermaid_with_events(self, tmp_path):\n        out = tmp_path / \"sm.mmd\"\n\n        main(\n            [\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                str(out),\n                \"--format\",\n                \"mermaid\",\n                \"--events\",\n                \"cycle\",\n            ]\n        )\n\n        content = out.read_text()\n        assert \"yellow:::active\" in content\n\n\nclass TestQuickChart:\n    @contextmanager\n    def mock_quickchart(self, origin_img_path):\n        with open(origin_img_path) as f:\n            expected_image = f.read()\n\n        with mock.patch(\"statemachine.contrib.diagram.urlopen\", spec=True) as p:\n            p().read.side_effect = lambda: expected_image.encode()\n            yield p\n\n    def test_should_call_write_svg(self, OrderControl):\n        sm = OrderControl()\n        with self.mock_quickchart(\"docs/images/_oc_machine_processing.svg\"):\n            quickchart_write_svg(sm, \"docs/images/oc_machine_processing.svg\")\n\n\ndef test_compound_state_diagram():\n    \"\"\"Diagram renders compound state subgraphs.\"\"\"\n\n    class SM(StateChart):\n        class parent(State.Compound, name=\"Parent\"):\n            child1 = State(initial=True)\n            child2 = State(final=True)\n\n            go = child1.to(child2)\n\n        start = State(initial=True)\n        end = State(final=True)\n\n        enter = start.to(parent)\n        finish = parent.to(end)\n\n    graph = DotGraphMachine(SM)\n    result = graph()\n    assert result is not None\n    dot = result.to_string()\n    assert \"cluster_parent\" in dot\n\n\ndef test_parallel_state_diagram():\n    \"\"\"Diagram renders parallel state with dashed style.\"\"\"\n\n    class SM(StateChart):\n        class p(State.Parallel, name=\"p\"):\n            class r1(State.Compound, name=\"r1\"):\n                a = State(initial=True)\n                a_done = State(final=True)\n                finish_a = a.to(a_done)\n\n            class r2(State.Compound, name=\"r2\"):\n                b = State(initial=True)\n                b_done = State(final=True)\n                finish_b = b.to(b_done)\n\n        start = State(initial=True)\n        begin = start.to(p)\n\n    graph = DotGraphMachine(SM)\n    result = graph()\n    dot = result.to_string()\n    assert \"cluster_p\" in dot\n    assert \"cluster_r1\" in dot\n    assert \"cluster_r2\" in dot\n\n\ndef test_nested_compound_state_diagram():\n    \"\"\"Diagram renders nested compound states.\"\"\"\n\n    class SM(StateChart):\n        class outer(State.Compound, name=\"Outer\"):\n            class inner(State.Compound, name=\"Inner\"):\n                deep = State(initial=True)\n                deep_final = State(final=True)\n                go_deep = deep.to(deep_final)\n\n            start_inner = State(initial=True)\n            to_inner = start_inner.to(inner)\n\n        begin = State(initial=True)\n        enter = begin.to(outer)\n\n    graph = DotGraphMachine(SM)\n    result = graph()\n    dot = result.to_string()\n    assert \"cluster_outer\" in dot\n    assert \"cluster_inner\" in dot\n\n\ndef test_subgraph_dashed_style_for_parallel_parent():\n    \"\"\"Subgraph uses dashed border when parent state is parallel.\"\"\"\n\n    class SM(StateChart):\n        class p(State.Parallel, name=\"p\"):\n            class r1(State.Compound, name=\"r1\"):\n                a = State(initial=True)\n\n        start = State(initial=True)\n        begin = start.to(p)\n\n    dot = DotGraphMachine(SM)().to_string()\n    # The region subgraph inside a parallel state should have dashed style\n    assert \"dashed\" in dot\n\n\ndef test_initial_edge_with_compound_state_has_lhead():\n    \"\"\"Initial edge to a compound state sets lhead cluster attribute.\"\"\"\n\n    class SM(StateChart):\n        class parent(State.Compound, name=\"Parent\"):\n            child1 = State(initial=True)\n            child2 = State(final=True)\n            go = child1.to(child2)\n\n        start = State(initial=True)\n        enter = start.to(parent)\n\n    dot = DotGraphMachine(SM)().to_string()\n    assert \"lhead=cluster_parent\" in dot\n\n\ndef test_initial_edge_inside_compound_subgraph():\n    \"\"\"Compound substate has an initial edge from dot to initial child.\"\"\"\n\n    class SM(StateChart):\n        class parent(State.Compound, name=\"Parent\"):\n            child1 = State(initial=True)\n            child2 = State(final=True)\n\n            go = child1.to(child2)\n\n        start = State(initial=True)\n        end = State(final=True)\n\n        enter = start.to(parent)\n        finish = parent.to(end)\n\n    graph = DotGraphMachine(SM)\n    dot = graph().to_string()\n    # The compound subgraph should contain an initial point node and an edge to child1\n    assert \"parent_anchor\" in dot\n    assert \"child1\" in dot\n    # Verify the initial edge exists (from the black-dot initial node to child1)\n    # The implicit initial transition from the compound state itself is NOT rendered\n    # as an edge — it is represented only by the black-dot initial node inside the cluster.\n    assert \"parent_anchor -> child1\" not in dot\n    assert \"-> child1\" in dot\n\n\ndef test_history_state_shallow_diagram():\n    \"\"\"DOT output contains an 'H' circle node for shallow history state.\"\"\"\n    from statemachine.contrib.diagram.model import DiagramState\n\n    state = DiagramState(id=\"h_shallow\", name=\"H\", type=StateType.HISTORY_SHALLOW)\n    renderer = DotRenderer()\n    node = renderer._create_history_node(state)\n    attrs = node.obj_dict[\"attributes\"]\n    assert attrs[\"label\"] in (\"H\", '\"H\"')\n    assert attrs[\"shape\"] == \"circle\"\n\n\ndef test_history_state_deep_diagram():\n    \"\"\"DOT output contains an 'H*' circle node for deep history state.\"\"\"\n    from statemachine.contrib.diagram.model import DiagramState\n\n    state = DiagramState(id=\"h_deep\", name=\"H*\", type=StateType.HISTORY_DEEP)\n    renderer = DotRenderer()\n    node = renderer._create_history_node(state)\n    dot_str = node.to_string()\n    assert \"H*\" in dot_str\n    assert \"circle\" in dot_str\n\n\ndef test_history_state_default_transition():\n    \"\"\"History state's default transition appears as an edge in the diagram.\"\"\"\n    from statemachine.contrib.diagram.model import DiagramTransition\n\n    transition = DiagramTransition(source=\"hist\", targets=[\"child1\"], event=\"\")\n    renderer = DotRenderer()\n    renderer._compound_ids = set()\n    edges = renderer._create_edges(transition)\n    assert len(edges) == 1\n    edge = edges[0]\n    assert edge.obj_dict[\"points\"] == (\"hist\", \"child1\")\n\n\ndef test_parallel_state_label_indicator():\n    \"\"\"Parallel subgraph label includes a visual indicator.\"\"\"\n\n    class SM(StateChart):\n        class p(State.Parallel, name=\"p\"):\n            class r1(State.Compound, name=\"r1\"):\n                a = State(initial=True)\n\n            class r2(State.Compound, name=\"r2\"):\n                b = State(initial=True)\n\n        start = State(initial=True)\n        begin = start.to(p)\n\n    graph = DotGraphMachine(SM)\n    dot = graph().to_string()\n    # The parallel state label should contain an HTML-like label with the indicator\n    assert \"&#9783;\" in dot\n\n\ndef test_history_state_in_graph_states():\n    \"\"\"History pseudo-state nodes appear in the full graph output.\"\"\"\n    from tests.examples.statechart_history_machine import PersonalityMachine\n\n    graph = DotGraphMachine(PersonalityMachine)\n    dot = graph().to_string()\n    # History node should render as an 'H' circle\n    assert '\"H\"' in dot or \"H\" in dot\n\n\ndef test_multi_target_transition_diagram():\n    \"\"\"Edges are created for all targets of a multi-target transition.\"\"\"\n    from statemachine.contrib.diagram.model import DiagramTransition\n\n    transition = DiagramTransition(source=\"source\", targets=[\"target1\", \"target2\"], event=\"go\")\n    renderer = DotRenderer()\n    renderer._compound_ids = set()\n    edges = renderer._create_edges(transition)\n    assert len(edges) == 2\n    assert edges[0].obj_dict[\"points\"] == (\"source\", \"target1\")\n    assert edges[1].obj_dict[\"points\"] == (\"source\", \"target2\")\n    # Only the first edge gets a label\n    assert \"go\" in edges[0].obj_dict[\"attributes\"][\"label\"]\n    assert edges[1].obj_dict[\"attributes\"][\"label\"] == \"\"\n\n\ndef test_compound_and_parallel_mixed():\n    \"\"\"Full diagram with compound and parallel states renders without error.\"\"\"\n\n    class SM(StateChart):\n        class top(State.Compound, name=\"Top\"):\n            class par(State.Parallel, name=\"Par\"):\n                class region1(State.Compound, name=\"Region1\"):\n                    r1_a = State(initial=True)\n                    r1_b = State(final=True)\n                    r1_go = r1_a.to(r1_b)\n\n                class region2(State.Compound, name=\"Region2\"):\n                    r2_a = State(initial=True)\n                    r2_b = State(final=True)\n                    r2_go = r2_a.to(r2_b)\n\n            entry = State(initial=True)\n            start_par = entry.to(par)\n\n        begin = State(initial=True)\n        enter_top = begin.to(top)\n\n    graph = DotGraphMachine(SM)\n    dot = graph().to_string()\n    assert \"cluster_top\" in dot\n    assert \"cluster_par\" in dot\n    assert \"cluster_region1\" in dot\n    assert \"cluster_region2\" in dot\n    # Parallel indicator\n    assert \"&#9783;\" in dot\n    # Implicit initial transitions from compound states are NOT rendered as edges —\n    # they are represented by the black-dot initial node inside each cluster.\n    assert \"top_anchor -> entry\" not in dot\n    assert \"-> entry\" in dot\n\n\nclass TestSVGShapeConsistency:\n    \"\"\"Verify that active and inactive states render with the same shape in SVG.\n\n    These tests parse the generated SVG to catch visual regressions that are\n    hard to spot by inspecting DOT source alone. For example, using `bgcolor`\n    on a `<td>` instead of a `<table>` causes Graphviz to render a rectangular\n    `<polygon>` behind a rounded `<path>` border — the DOT looks fine but the\n    visual result is broken.\n    \"\"\"\n\n    def test_active_state_has_no_rectangular_fill(self):\n        \"\"\"Active state background must use rounded <path>, not rectangular <polygon>.\"\"\"\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()  # starts in Green\n        graph = DotGraphMachine(sm).get_graph()\n        svg = _parse_svg(graph)\n\n        green_node = _find_state_node(svg, \"green\")\n        assert green_node is not None, \"Could not find 'green' node in SVG\"\n        assert not _has_rectangular_fill(green_node), (\n            \"Active state 'green' has a rectangular <polygon> fill — \"\n            \"expected a rounded <path> fill to match the border shape\"\n        )\n\n    def test_active_and_inactive_states_use_same_svg_element_type(self):\n        \"\"\"Active and inactive states must both render as rounded <path> elements.\n\n        With ``shape=rectangle`` + ``style=\"rounded, filled\"``, Graphviz renders\n        each state as a single ``<path>`` with cubic Bezier curves (``C`` commands)\n        for rounded corners. Both the fill and stroke are in the same ``<path>``.\n\n        A regression would be if the active state rendered differently — e.g., a\n        rectangular ``<polygon>`` for the fill behind a rounded ``<path>`` border.\n        \"\"\"\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        graph = DotGraphMachine(sm).get_graph()\n        svg = _parse_svg(graph)\n\n        for state_id in (\"green\", \"yellow\", \"red\"):\n            node = _find_state_node(svg, state_id)\n            assert node is not None, f\"Could not find '{state_id}' node in SVG\"\n\n            # Each state should have at least one <path> with rounded curves\n            paths = node.findall(\"{http://www.w3.org/2000/svg}path\")\n            assert len(paths) >= 1, (\n                f\"State '{state_id}' should have at least 1 <path>, found {len(paths)}\"\n            )\n            for p in paths:\n                assert _path_has_curves(p.get(\"d\", \"\")), (\n                    f\"State '{state_id}' has a <path> without curves — not rounded\"\n                )\n\n    def test_no_state_node_has_rectangular_colored_fill(self):\n        \"\"\"No state in the diagram should have a rectangular <polygon> colored fill.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State(final=True)\n            go = s1.to(s2)\n            finish = s2.to(s3)\n\n        sm = SM()\n        sm.go()  # move to s2\n        graph = DotGraphMachine(sm).get_graph()\n        svg = _parse_svg(graph)\n\n        for state_id in (\"s1\", \"s2\", \"s3\"):\n            node = _find_state_node(svg, state_id)\n            if node is None:\n                continue\n            assert not _has_rectangular_fill(node), (\n                f\"State '{state_id}' has a rectangular <polygon> colored fill\"\n            )\n\n\nclass TestExtract:\n    \"\"\"Tests for extract.py edge cases.\"\"\"\n\n    def test_deep_history_state_type(self):\n        \"\"\"Deep history state is correctly typed in the extracted graph.\"\"\"\n        from statemachine.contrib.diagram.extract import extract\n\n        from tests.machines.showcase_deep_history import DeepHistorySC\n\n        graph = extract(DeepHistorySC)\n        # Find the history state in the outer compound's children\n        outer = next(s for s in graph.states if s.id == \"outer\")\n        h_state = next(s for s in outer.children if s.type == StateType.HISTORY_DEEP)\n        assert h_state is not None\n\n    def test_internal_transition_actions_extracted(self):\n        \"\"\"Internal transitions with actions are extracted into state actions.\"\"\"\n        from statemachine.contrib.diagram.extract import extract\n\n        from tests.machines.showcase_internal import InternalSC\n\n        graph = extract(InternalSC)\n        monitoring = next(s for s in graph.states if s.id == \"monitoring\")\n        internal_actions = [a for a in monitoring.actions if a.type == ActionType.INTERNAL]\n        assert len(internal_actions) >= 1\n        assert any(\"check\" in a.body for a in internal_actions)\n\n    def test_internal_transition_skipped_in_bidirectional(self):\n        \"\"\"Internal transitions are skipped in _collect_bidirectional_compound_ids.\"\"\"\n        from statemachine.contrib.diagram.extract import extract\n\n        class SM(StateChart):\n            class parent(State.Compound, name=\"Parent\"):\n                child1 = State(initial=True)\n                child2 = State(final=True)\n\n                def log(self): ...\n\n                check = child1.to.itself(internal=True, on=\"log\")\n                go = child1.to(child2)\n\n            start = State(initial=True)\n            end = State(final=True)\n\n            enter = start.to(parent)\n            finish = parent.to(end)\n\n        graph = extract(SM)\n        # parent has both incoming and outgoing, so it should be bidirectional\n        assert \"parent\" in graph.bidirectional_compound_ids\n\n    def test_internal_transition_without_action(self):\n        \"\"\"Internal transition without on action has no internal action in diagram.\"\"\"\n        from statemachine.contrib.diagram.extract import extract\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            noop = s1.to.itself(internal=True)\n            go = s1.to(s2)\n\n        graph = extract(SM)\n        s1 = next(s for s in graph.states if s.id == \"s1\")\n        internal_actions = [a for a in s1.actions if a.type == ActionType.INTERNAL]\n        assert internal_actions == []\n\n    def test_extract_invalid_type_raises(self):\n        \"\"\"extract() raises TypeError for invalid input.\"\"\"\n        from statemachine.contrib.diagram.extract import extract\n\n        with pytest.raises(TypeError, match=\"Expected a StateChart\"):\n            extract(\"not a machine\")  # type: ignore[arg-type]\n\n    def test_resolve_initial_fallback(self):\n        \"\"\"When no explicit initial, first candidate gets is_initial=True.\"\"\"\n        from statemachine.contrib.diagram.extract import _resolve_initial_states\n        from statemachine.contrib.diagram.model import DiagramState\n\n        states = [\n            DiagramState(id=\"a\", name=\"A\", type=StateType.REGULAR),\n            DiagramState(id=\"b\", name=\"B\", type=StateType.REGULAR),\n        ]\n        _resolve_initial_states(states)\n        assert states[0].is_initial is True\n\n\nclass TestFormatEventNames:\n    \"\"\"Tests for _format_event_names — alias filtering for diagram display.\"\"\"\n\n    def test_simple_event_uses_name(self):\n        \"\"\"A plain event displays its human-readable name.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        t = SM.s1.transitions[0]\n        assert _format_event_names(t) == \"Go\"\n\n    def test_done_state_alias_filtered(self):\n        \"\"\"done_state_X registers both underscore and dot forms; only underscore is shown.\"\"\"\n\n        class SM(StateChart):\n            class parent(State.Compound):\n                child = State(initial=True)\n                done = State(final=True)\n                finish = child.to(done)\n\n            end = State(final=True)\n            done_state_parent = parent.to(end)\n\n        t = next(t for t in SM.parent.transitions if t.event and \"done_state\" in t.event)\n        result = _format_event_names(t)\n        assert result == \"Done state parent\"\n        assert \"done.state\" not in result\n\n    def test_done_invoke_alias_filtered(self):\n        \"\"\"done_invoke_X alias filtering works the same as done_state_X.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            done_invoke_child = s1.to(s2)\n\n        t = SM.s1.transitions[0]\n        result = _format_event_names(t)\n        assert result == \"Done invoke child\"\n        assert \"done.invoke\" not in result\n\n    def test_error_alias_filtered(self):\n        \"\"\"error_X registers both error_X and error.X; only underscore is shown.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            error_execution = s1.to(s2)\n\n        t = SM.s1.transitions[0]\n        result = _format_event_names(t)\n        assert result == \"Error execution\"\n        assert \"error.execution\" not in result\n\n    def test_multiple_distinct_events_preserved(self):\n        \"\"\"Multiple distinct events on one transition are all preserved.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n            also = s1.to(s2)\n\n        # Add a second event to the first transition\n        t = SM.s1.transitions[0]\n        t.add_event(\"also\")\n        result = _format_event_names(t)\n        assert \"Go\" in result\n        assert \"Also\" in result\n\n    def test_eventless_transition_returns_empty(self):\n        \"\"\"A transition with no events returns an empty string.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            s1.to(s2, cond=\"always_true\")\n\n            def always_true(self):\n                return True\n\n        # Find the eventless transition\n        t = next(t for t in SM.s1.transitions if not list(t.events))\n        assert _format_event_names(t) == \"\"\n\n    def test_dot_only_event_preserved(self):\n        \"\"\"An event whose ID contains dots but has no underscore alias is preserved.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n            go = s1.to(s2)\n\n        from statemachine.transition import Transition\n\n        t = Transition(source=SM.s1, target=SM.s2, event=\"custom.event\")\n        assert _format_event_names(t) == \"Custom event\"\n\n    def test_explicit_event_name_displayed(self):\n        \"\"\"An Event with an explicit name= shows the human-readable name.\"\"\"\n\n        class SM(StateChart):\n            active = State(initial=True)\n            suspended = State(final=True)\n\n            suspend = Event(\n                active.to(suspended),\n                name=\"Human Suspend\",\n            )\n\n        t = SM.active.transitions[0]\n        assert _format_event_names(t) == \"Human Suspend\"\n\n\nclass TestDotRendererEdgeCases:\n    \"\"\"Tests for dot.py edge cases.\"\"\"\n\n    def test_compound_state_with_actions_label(self):\n        \"\"\"Compound state with entry/exit actions renders action rows in label.\"\"\"\n\n        class SM(StateChart):\n            class parent(State.Compound, name=\"Parent\"):\n                child = State(initial=True)\n\n                def on_enter_parent(self): ...\n\n            start = State(initial=True)\n            enter = start.to(parent)\n\n        dot = DotGraphMachine(SM)().to_string()\n        # The compound label should contain the entry action\n        assert \"entry\" in dot.lower() or \"on_enter_parent\" in dot\n\n    def test_internal_action_format(self):\n        \"\"\"Internal action uses body directly (no 'entry /' prefix).\"\"\"\n        renderer = DotRenderer()\n        from statemachine.contrib.diagram.model import DiagramAction\n\n        action = DiagramAction(type=ActionType.INTERNAL, body=\"check / log_status\")\n        result = renderer._format_action(action)\n        assert result == \"check / log_status\"\n\n    def test_targetless_transition_self_loop(self):\n        \"\"\"Transition with no target falls back to source as destination.\"\"\"\n        from statemachine.contrib.diagram.model import DiagramTransition\n\n        transition = DiagramTransition(source=\"s1\", targets=[], event=\"tick\")\n        renderer = DotRenderer()\n        renderer._compound_ids = set()\n        edges = renderer._create_edges(transition)\n        assert len(edges) == 1\n        # With no targets, target_ids becomes [None], and dst becomes source\n        assert edges[0].obj_dict[\"points\"][1] == \"s1\"\n\n    def test_compound_edge_anchor_non_bidirectional(self):\n        \"\"\"Non-bidirectional compound state uses generic _anchor node.\"\"\"\n        renderer = DotRenderer()\n        renderer._compound_bidir_ids = {\"other\"}\n        result = renderer._compound_edge_anchor(\"my_state\", \"out\")\n        assert result == \"my_state_anchor\"\n\n\nclass TestDiagramMainModule:\n    \"\"\"Tests for __main__.py.\"\"\"\n\n    def test_main_module_execution(self, tmp_path):\n        \"\"\"python -m statemachine.contrib.diagram works.\"\"\"\n        import runpy\n\n        out = tmp_path / \"sm.svg\"\n        with mock.patch(\n            \"sys.argv\",\n            [\n                \"statemachine.contrib.diagram\",\n                \"tests.examples.traffic_light_machine.TrafficLightMachine\",\n                str(out),\n            ],\n        ):\n            with pytest.raises(SystemExit) as exc_info:\n                runpy.run_module(\n                    \"statemachine.contrib.diagram\", run_name=\"__main__\", alter_sys=True\n                )\n            assert exc_info.value.code is None\n        assert out.exists()\n\n\nclass TestSphinxDirective:\n    \"\"\"Unit tests for the statemachine-diagram Sphinx directive.\"\"\"\n\n    def test_parse_events(self):\n        from statemachine.contrib.diagram.sphinx_ext import _parse_events\n\n        assert _parse_events(\"start, ship\") == [\"start\", \"ship\"]\n        assert _parse_events(\"single\") == [\"single\"]\n        assert _parse_events(\" a , b , c \") == [\"a\", \"b\", \"c\"]\n        assert _parse_events(\"\") == []\n\n    def test_import_and_render_class(self, tmp_path):\n        \"\"\"Directive logic: import a class and generate SVG.\"\"\"\n        from statemachine.contrib.diagram import DotGraphMachine\n        from statemachine.contrib.diagram import import_sm\n\n        sm_class = import_sm(\"tests.examples.order_control_machine.OrderControl\")\n        graph = DotGraphMachine(sm_class).get_graph()\n        svg_bytes = graph.create_svg()\n        assert svg_bytes.startswith(b\"<?xml\")\n\n    def test_import_and_render_with_events(self, tmp_path):\n        \"\"\"Directive logic: import, instantiate, send events, render SVG.\"\"\"\n        from statemachine.contrib.diagram import DotGraphMachine\n        from statemachine.contrib.diagram import import_sm\n        from statemachine.contrib.diagram.sphinx_ext import _parse_events\n\n        sm_class = import_sm(\"tests.examples.traffic_light_machine.TrafficLightMachine\")\n        machine = sm_class()\n        for event_name in _parse_events(\"cycle\"):\n            machine.send(event_name)\n\n        graph = DotGraphMachine(machine).get_graph()\n        svg_bytes = graph.create_svg()\n        assert svg_bytes.startswith(b\"<?xml\")\n\n    def test_import_invalid_qualname(self):\n        \"\"\"import_sm raises for invalid class paths.\"\"\"\n        from statemachine.contrib.diagram import import_sm\n\n        with pytest.raises((ValueError, ModuleNotFoundError)):\n            import_sm(\"nonexistent.module.SomeMachine\")\n\n\nclass TestSplitLength:\n    \"\"\"Tests for the _split_length helper.\"\"\"\n\n    def test_split_pt(self):\n        from statemachine.contrib.diagram.sphinx_ext import _split_length\n\n        value, unit = _split_length(\"702pt\")\n        assert value == pytest.approx(702.0)\n        assert unit == \"pt\"\n\n    def test_split_px(self):\n        from statemachine.contrib.diagram.sphinx_ext import _split_length\n\n        value, unit = _split_length(\"100px\")\n        assert value == pytest.approx(100.0)\n        assert unit == \"px\"\n\n    def test_split_float(self):\n        from statemachine.contrib.diagram.sphinx_ext import _split_length\n\n        value, unit = _split_length(\"12.5em\")\n        assert value == pytest.approx(12.5)\n        assert unit == \"em\"\n\n    def test_split_no_match(self):\n        from statemachine.contrib.diagram.sphinx_ext import _split_length\n\n        value, unit = _split_length(\"auto\")\n        assert value == pytest.approx(0.0)\n        assert unit == \"auto\"\n\n\nclass TestAlignSpec:\n    \"\"\"Tests for _align_spec option validator.\"\"\"\n\n    def test_valid_values(self):\n        from statemachine.contrib.diagram.sphinx_ext import _align_spec\n\n        assert _align_spec(\"left\") == \"left\"\n        assert _align_spec(\"center\") == \"center\"\n        assert _align_spec(\"right\") == \"right\"\n\n    def test_invalid_value(self):\n        from statemachine.contrib.diagram.sphinx_ext import _align_spec\n\n        with pytest.raises(ValueError, match=\"top\"):\n            _align_spec(\"top\")\n\n\nclass TestSetup:\n    \"\"\"Tests for the Sphinx extension setup function.\"\"\"\n\n    def test_setup_returns_metadata(self):\n        from statemachine.contrib.diagram.sphinx_ext import setup\n\n        app = mock.MagicMock()\n        result = setup(app)\n        assert result[\"version\"] == \"0.1\"\n        assert result[\"parallel_read_safe\"] is True\n        assert result[\"parallel_write_safe\"] is True\n        app.add_directive.assert_called_once_with(\"statemachine-diagram\", mock.ANY)\n\n\nclass TestPrepareSvg:\n    \"\"\"Tests for StateMachineDiagram._prepare_svg.\"\"\"\n\n    def _make_directive(self, options=None):\n        from statemachine.contrib.diagram.sphinx_ext import StateMachineDiagram\n\n        directive = StateMachineDiagram.__new__(StateMachineDiagram)\n        directive.options = options or {}\n        directive.arguments = [\"test.Module\"]\n        return directive\n\n    def test_strips_xml_prologue(self):\n        svg_text = (\n            '<?xml version=\"1.0\"?>\\n<!DOCTYPE svg>\\n'\n            '<svg width=\"100pt\" height=\"50pt\" viewBox=\"0 0 100 50\">'\n            \"<circle/></svg>\"\n        )\n        directive = self._make_directive()\n        svg_tag, _, _ = directive._prepare_svg(svg_text)\n\n        assert not svg_tag.startswith(\"<?xml\")\n        assert svg_tag.startswith(\"<svg\")\n        assert \"</svg>\" in svg_tag\n\n    def test_extracts_intrinsic_dimensions(self):\n        svg_text = '<svg width=\"702pt\" height=\"170pt\"><rect/></svg>'\n        directive = self._make_directive()\n        _, w, h = directive._prepare_svg(svg_text)\n\n        assert w == \"702pt\"\n        assert h == \"170pt\"\n\n    def test_removes_fixed_dimensions(self):\n        svg_text = '<svg width=\"702pt\" height=\"170pt\" viewBox=\"0 0 702 170\"><rect/></svg>'\n        directive = self._make_directive()\n        svg_tag, _, _ = directive._prepare_svg(svg_text)\n\n        assert 'width=\"702pt\"' not in svg_tag\n        assert 'height=\"170pt\"' not in svg_tag\n        assert \"viewBox\" in svg_tag\n\n    def test_handles_no_dimensions(self):\n        svg_text = '<svg viewBox=\"0 0 100 50\"><rect/></svg>'\n        directive = self._make_directive()\n        _, w, h = directive._prepare_svg(svg_text)\n\n        assert w == \"\"\n        assert h == \"\"\n\n    def test_handles_px_dimensions(self):\n        svg_text = '<svg width=\"200px\" height=\"100px\"><rect/></svg>'\n        directive = self._make_directive()\n        _, w, h = directive._prepare_svg(svg_text)\n\n        assert w == \"200px\"\n        assert h == \"100px\"\n\n\nclass TestBuildSvgStyles:\n    \"\"\"Tests for StateMachineDiagram._build_svg_styles.\"\"\"\n\n    def _make_directive(self, options=None):\n        from statemachine.contrib.diagram.sphinx_ext import StateMachineDiagram\n\n        directive = StateMachineDiagram.__new__(StateMachineDiagram)\n        directive.options = options or {}\n        return directive\n\n    def test_intrinsic_width_as_max_width(self):\n        directive = self._make_directive()\n        result = directive._build_svg_styles(\"702pt\", \"170pt\")\n        assert \"max-width: 702pt\" in result\n        assert \"height: auto\" in result\n\n    def test_explicit_width(self):\n        directive = self._make_directive({\"width\": \"400px\"})\n        result = directive._build_svg_styles(\"702pt\", \"170pt\")\n        assert \"width: 400px\" in result\n        assert \"max-width\" not in result\n\n    def test_explicit_height(self):\n        directive = self._make_directive({\"height\": \"200px\"})\n        result = directive._build_svg_styles(\"702pt\", \"170pt\")\n        assert \"height: 200px\" in result\n        assert \"height: auto\" not in result\n\n    def test_scale(self):\n        directive = self._make_directive({\"scale\": \"50%\"})\n        result = directive._build_svg_styles(\"702pt\", \"170pt\")\n        assert \"width: 351.0pt\" in result\n        assert \"height: 85.0pt\" in result\n\n    def test_scale_without_intrinsic(self):\n        directive = self._make_directive({\"scale\": \"50%\"})\n        result = directive._build_svg_styles(\"\", \"\")\n        # No width/height when no intrinsic dimensions to scale\n        assert \"max-width\" not in result\n        assert \"height: auto\" in result\n\n    def test_no_dimensions(self):\n        directive = self._make_directive()\n        result = directive._build_svg_styles(\"\", \"\")\n        assert \"height: auto\" in result\n\n    def test_explicit_width_overrides_scale(self):\n        directive = self._make_directive({\"width\": \"300px\", \"scale\": \"50%\"})\n        result = directive._build_svg_styles(\"702pt\", \"170pt\")\n        assert \"width: 300px\" in result\n        assert \"351\" not in result\n\n\nclass TestBuildWrapperClasses:\n    \"\"\"Tests for StateMachineDiagram._build_wrapper_classes.\"\"\"\n\n    def _make_directive(self, options=None):\n        from statemachine.contrib.diagram.sphinx_ext import StateMachineDiagram\n\n        directive = StateMachineDiagram.__new__(StateMachineDiagram)\n        directive.options = options or {}\n        return directive\n\n    def test_default_center_align(self):\n        directive = self._make_directive()\n        classes = directive._build_wrapper_classes()\n        assert classes == [\"statemachine-diagram\", \"align-center\"]\n\n    def test_custom_align(self):\n        directive = self._make_directive({\"align\": \"left\"})\n        classes = directive._build_wrapper_classes()\n        assert classes == [\"statemachine-diagram\", \"align-left\"]\n\n    def test_extra_css_classes(self):\n        directive = self._make_directive({\"class\": [\"my-class\", \"another\"]})\n        classes = directive._build_wrapper_classes()\n        assert classes == [\"statemachine-diagram\", \"align-center\", \"my-class\", \"another\"]\n\n\nclass TestResolveTarget:\n    \"\"\"Tests for StateMachineDiagram._resolve_target.\"\"\"\n\n    def _make_directive(self, options=None, tmp_path=None):\n        from statemachine.contrib.diagram.sphinx_ext import StateMachineDiagram\n\n        directive = StateMachineDiagram.__new__(StateMachineDiagram)\n        directive.options = options or {}\n        directive.arguments = [\"my.module.MyMachine\"]\n        if tmp_path is not None:\n            directive.state = mock.MagicMock()\n            directive.state.document.settings.env.app.outdir = str(tmp_path)\n        return directive\n\n    def test_no_target_option(self):\n        directive = self._make_directive()\n        assert directive._resolve_target(\"<svg/>\") == \"\"\n\n    def test_explicit_target_url(self):\n        directive = self._make_directive({\"target\": \"https://example.com/diagram.svg\"})\n        assert directive._resolve_target(\"<svg/>\") == \"https://example.com/diagram.svg\"\n\n    def test_empty_target_generates_file(self, tmp_path):\n        directive = self._make_directive({\"target\": \"\"}, tmp_path=tmp_path)\n        svg_data = \"<svg><rect/></svg>\"\n        result = directive._resolve_target(svg_data)\n\n        assert result.startswith(\"/_images/statemachine-\")\n        assert result.endswith(\".svg\")\n\n        # Verify the file was written\n        images_dir = tmp_path / \"_images\"\n        svg_files = list(images_dir.glob(\"statemachine-*.svg\"))\n        assert len(svg_files) == 1\n        assert svg_files[0].read_text(encoding=\"utf-8\") == svg_data\n\n    def test_empty_target_deterministic_filename(self, tmp_path):\n        \"\"\"Same qualname + events produces the same filename.\"\"\"\n        directive1 = self._make_directive({\"target\": \"\", \"events\": \"go\"}, tmp_path=tmp_path)\n        directive2 = self._make_directive({\"target\": \"\", \"events\": \"go\"}, tmp_path=tmp_path)\n        result1 = directive1._resolve_target(\"<svg>1</svg>\")\n        result2 = directive2._resolve_target(\"<svg>2</svg>\")\n        assert result1 == result2\n\n    def test_different_events_different_filename(self, tmp_path):\n        \"\"\"Different events produce different filenames.\"\"\"\n        d1 = self._make_directive({\"target\": \"\", \"events\": \"a\"}, tmp_path=tmp_path)\n        d2 = self._make_directive({\"target\": \"\", \"events\": \"b\"}, tmp_path=tmp_path)\n        assert d1._resolve_target(\"<svg/>\") != d2._resolve_target(\"<svg/>\")\n\n\nclass TestDirectiveRun:\n    \"\"\"Integration tests for StateMachineDiagram.run().\"\"\"\n\n    _QUALNAME = \"tests.examples.traffic_light_machine.TrafficLightMachine\"\n\n    def _make_directive(self, tmp_path, options=None):\n        from statemachine.contrib.diagram.sphinx_ext import StateMachineDiagram\n\n        directive = StateMachineDiagram.__new__(StateMachineDiagram)\n        directive.options = options or {}\n        directive.lineno = 1\n        directive.state_machine = mock.MagicMock()\n        directive.state = mock.MagicMock()\n        directive.state.document.settings.env.app.outdir = str(tmp_path)\n        directive.content_offset = 0\n        return directive\n\n    def _run(self, tmp_path, qualname=None, options=None):\n        directive = self._make_directive(tmp_path, options=options)\n        directive.arguments = [qualname or self._QUALNAME]\n        return directive, directive.run()\n\n    def test_render_class_diagram(self, tmp_path):\n        \"\"\"Renders a class diagram (no events) as inline SVG.\"\"\"\n        _, result = self._run(tmp_path)\n\n        assert len(result) == 1\n        node = result[0]\n        assert isinstance(node, nodes.raw)\n        assert node[\"format\"] == \"html\"\n        html = node.astext()\n        assert \"<svg\" in html\n        assert \"statemachine-diagram\" in html\n        assert \"align-center\" in html\n\n    def test_render_with_events(self, tmp_path):\n        \"\"\"Renders a diagram after sending events.\"\"\"\n        _, result = self._run(tmp_path, options={\"events\": \"cycle\"})\n\n        assert len(result) == 1\n        assert isinstance(result[0], nodes.raw)\n        assert \"<svg\" in result[0].astext()\n\n    def test_render_with_empty_events(self, tmp_path):\n        \"\"\"Empty :events: instantiates the machine (highlights initial state).\"\"\"\n        _, result = self._run(tmp_path, options={\"events\": \"\"})\n\n        assert len(result) == 1\n        assert isinstance(result[0], nodes.raw)\n\n    def test_render_with_caption(self, tmp_path):\n        \"\"\"Caption wraps the diagram in a <figure> element.\"\"\"\n        _, result = self._run(tmp_path, options={\"caption\": \"My caption\"})\n\n        html = result[0].astext()\n        assert \"<figure\" in html\n        assert \"<figcaption>My caption</figcaption>\" in html\n\n    def test_render_with_figclass(self, tmp_path):\n        \"\"\"figclass adds extra CSS classes to the figure wrapper.\"\"\"\n        _, result = self._run(tmp_path, options={\"caption\": \"Test\", \"figclass\": [\"extra-fig\"]})\n\n        assert \"extra-fig\" in result[0].astext()\n\n    def test_render_with_alt(self, tmp_path):\n        \"\"\"Custom alt text appears in aria-label.\"\"\"\n        _, result = self._run(tmp_path, options={\"alt\": \"Traffic light diagram\"})\n\n        assert 'aria-label=\"Traffic light diagram\"' in result[0].astext()\n\n    def test_render_default_alt(self, tmp_path):\n        \"\"\"Default alt text uses the class name from the qualname.\"\"\"\n        _, result = self._run(tmp_path)\n\n        assert 'aria-label=\"TrafficLightMachine\"' in result[0].astext()\n\n    def test_render_with_explicit_target(self, tmp_path):\n        \"\"\"Explicit target wraps diagram in a link.\"\"\"\n        _, result = self._run(tmp_path, options={\"target\": \"https://example.com\"})\n\n        html = result[0].astext()\n        assert 'href=\"https://example.com\"' in html\n        assert 'target=\"_blank\"' in html\n\n    def test_render_with_empty_target(self, tmp_path):\n        \"\"\"Empty target auto-generates a zoom SVG file.\"\"\"\n        _, result = self._run(tmp_path, options={\"target\": \"\"})\n\n        assert 'href=\"/_images/statemachine-' in result[0].astext()\n        images_dir = tmp_path / \"_images\"\n        assert any(images_dir.glob(\"statemachine-*.svg\"))\n\n    def test_render_with_align(self, tmp_path):\n        \"\"\"Align option controls CSS class.\"\"\"\n        _, result = self._run(tmp_path, options={\"align\": \"left\"})\n\n        assert \"align-left\" in result[0].astext()\n\n    def test_render_with_width(self, tmp_path):\n        \"\"\"Width option is applied as inline style.\"\"\"\n        _, result = self._run(tmp_path, options={\"width\": \"400px\"})\n\n        assert \"width: 400px\" in result[0].astext()\n\n    def test_render_with_name(self, tmp_path):\n        \"\"\"Name option calls add_name for cross-referencing.\"\"\"\n        directive = self._make_directive(tmp_path, options={\"name\": \"my-diagram\"})\n        directive.arguments = [self._QUALNAME]\n        result = directive.run()\n\n        assert len(result) == 1\n\n    def test_render_with_class(self, tmp_path):\n        \"\"\"Custom CSS classes appear in the wrapper.\"\"\"\n        _, result = self._run(tmp_path, options={\"class\": [\"custom-class\"]})\n\n        assert \"custom-class\" in result[0].astext()\n\n    def test_invalid_qualname_returns_warning(self, tmp_path):\n        \"\"\"Invalid qualname returns a warning node.\"\"\"\n        directive, result = self._run(tmp_path, qualname=\"nonexistent.module.BadMachine\")\n\n        assert len(result) == 1\n        directive.state_machine.reporter.warning.assert_called_once()\n        call_args = directive.state_machine.reporter.warning.call_args\n        assert \"could not import\" in call_args[0][0]\n\n    def test_render_failure_returns_warning(self, tmp_path):\n        \"\"\"Diagram generation failure returns a warning node.\"\"\"\n        with mock.patch(\n            \"statemachine.contrib.diagram.DotGraphMachine\",\n            side_effect=RuntimeError(\"render failed\"),\n        ):\n            directive, result = self._run(tmp_path)\n\n        assert len(result) == 1\n        directive.state_machine.reporter.warning.assert_called_once()\n        call_args = directive.state_machine.reporter.warning.call_args\n        assert \"failed to generate\" in call_args[0][0]\n\n    def test_render_without_caption_uses_div(self, tmp_path):\n        \"\"\"Without caption, the wrapper is a plain <div>.\"\"\"\n        _, result = self._run(tmp_path)\n\n        html = result[0].astext()\n        assert \"<figure\" not in html\n        assert \"<div\" in html\n\n\nclass TestGraphMethod:\n    \"\"\"Test the ``_graph()`` convenience method on state machine instances.\"\"\"\n\n    def test_graph_returns_pydot_dot(self):\n        import pydot\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        graph = sm._graph()\n        assert isinstance(graph, pydot.Dot)\n\n    def test_graph_reflects_active_state(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        sm.send(\"cycle\")\n        svg_root = _parse_svg(sm._graph())\n        yellow_node = _find_state_node(svg_root, \"yellow\")\n        assert yellow_node is not None\n\n\nclass TestFormat:\n    \"\"\"Tests for StateChart.__format__ (instance and class).\"\"\"\n\n    def test_format_mermaid_instance(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = f\"{sm:mermaid}\"\n        assert \"stateDiagram-v2\" in result\n        assert \"green:::active\" in result\n\n    def test_format_mermaid_class(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = f\"{TrafficLightMachine:mermaid}\"\n        assert \"stateDiagram-v2\" in result\n        assert \"green --> yellow : Cycle\" in result\n\n    def test_format_md_instance(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = f\"{sm:md}\"\n        assert \"| State\" in result\n        assert \"Cycle\" in result\n\n    def test_format_md_class(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = f\"{TrafficLightMachine:md}\"\n        assert \"| State\" in result\n\n    def test_format_markdown_alias(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = format(TrafficLightMachine, \"markdown\")\n        assert \"| State\" in result\n\n    def test_format_rst_instance(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = f\"{sm:rst}\"\n        assert \"+---\" in result\n\n    def test_format_rst_class(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = f\"{TrafficLightMachine:rst}\"\n        assert \"+---\" in result\n\n    def test_format_dot_instance(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = f\"{sm:dot}\"\n        assert result.startswith(\"digraph TrafficLightMachine {\")\n        assert \"green\" in result\n\n    def test_format_dot_class(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = f\"{TrafficLightMachine:dot}\"\n        assert result.startswith(\"digraph TrafficLightMachine {\")\n\n    def test_format_empty_falls_back_to_repr(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = f\"{sm:}\"\n        assert \"TrafficLightMachine(\" in result\n\n    def test_format_empty_class(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = f\"{TrafficLightMachine:}\"\n        assert \"TrafficLightMachine\" in result\n\n    def test_format_invalid_raises(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        with pytest.raises(ValueError, match=\"Unsupported format\"):\n            f\"{sm:invalid}\"\n\n    def test_format_invalid_class_raises(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        with pytest.raises(ValueError, match=\"Unsupported format\"):\n            f\"{TrafficLightMachine:invalid}\"\n\n\nclass TestDocstringExpansion:\n    \"\"\"Tests for {statechart:FORMAT} placeholder expansion in docstrings.\"\"\"\n\n    def test_md_placeholder(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            \"\"\"Machine.\n\n            {statechart:md}\n            \"\"\"\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        assert \"| State\" in MyMachine.__doc__\n        assert \"{statechart:md}\" not in MyMachine.__doc__\n\n    def test_rst_placeholder(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            \"\"\"Machine.\n\n            {statechart:rst}\n            \"\"\"\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        assert \"+---\" in MyMachine.__doc__\n        assert \"{statechart:rst}\" not in MyMachine.__doc__\n\n    def test_mermaid_placeholder(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            \"\"\"{statechart:mermaid}\"\"\"\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        assert \"stateDiagram-v2\" in MyMachine.__doc__\n\n    def test_no_placeholder_unchanged(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            \"\"\"Just a plain docstring.\"\"\"\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        assert MyMachine.__doc__ == \"Just a plain docstring.\"\n\n    def test_no_docstring(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        assert MyMachine.__doc__ is None\n\n    def test_indentation_preserved(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            __doc__ = \"Doc.\\n\\n    Table:\\n\\n    {statechart:md}\\n\\n    End.\\n\"\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        lines = MyMachine.__doc__.split(\"\\n\")\n        table_lines = [line for line in lines if \"|\" in line]\n        for line in table_lines:\n            assert line.startswith(\"    |\")\n        assert \"End.\" in MyMachine.__doc__\n\n    def test_multiple_placeholders(self):\n        from statemachine.state import State\n        from statemachine.statemachine import StateChart\n\n        class MyMachine(StateChart):\n            \"\"\"MD: {statechart:md}\n\n            Mermaid: {statechart:mermaid}\n            \"\"\"\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        assert \"| State\" in MyMachine.__doc__\n        assert \"stateDiagram-v2\" in MyMachine.__doc__\n\n\nclass TestFormatter:\n    \"\"\"Tests for the Formatter facade (render, register_format, supported_formats).\"\"\"\n\n    def test_render_mermaid(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = formatter.render(TrafficLightMachine, \"mermaid\")\n        assert \"stateDiagram-v2\" in result\n\n    def test_render_dot(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = formatter.render(TrafficLightMachine, \"dot\")\n        assert result.startswith(\"digraph TrafficLightMachine {\")\n\n    def test_render_svg(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = formatter.render(TrafficLightMachine, \"svg\")\n        assert isinstance(result, str)\n        assert \"<svg\" in result\n        assert \"green\" in result\n\n    def test_render_svg_instance(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = formatter.render(sm, \"svg\")\n        assert \"<svg\" in result\n        # Active state should be highlighted\n        assert \"turquoise\" in result\n\n    def test_format_svg(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = f\"{TrafficLightMachine:svg}\"\n        assert \"<svg\" in result\n\n    def test_render_md(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = formatter.render(TrafficLightMachine, \"md\")\n        assert \"| State\" in result\n\n    def test_render_markdown_alias(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        assert formatter.render(TrafficLightMachine, \"markdown\") == formatter.render(\n            TrafficLightMachine, \"md\"\n        )\n\n    def test_render_rst(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = formatter.render(TrafficLightMachine, \"rst\")\n        assert \"+---\" in result\n\n    def test_render_empty_repr_instance(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        assert formatter.render(sm, \"\") == repr(sm)\n\n    def test_render_empty_repr_class(self):\n        from statemachine.contrib.diagram import formatter\n\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        assert formatter.render(TrafficLightMachine, \"\") == repr(TrafficLightMachine)\n\n    def test_render_invalid_raises(self):\n        from statemachine.contrib.diagram import formatter\n\n        with pytest.raises(ValueError, match=\"Unsupported format\"):\n            formatter.render(object(), \"invalid\")\n\n    def test_supported_formats(self):\n        from statemachine.contrib.diagram import formatter\n\n        fmts = formatter.supported_formats()\n        assert \"dot\" in fmts\n        assert \"svg\" in fmts\n        assert \"mermaid\" in fmts\n        assert \"md\" in fmts\n        assert \"markdown\" in fmts\n        assert \"rst\" in fmts\n\n    def test_register_custom_format(self):\n        from statemachine.contrib.diagram import formatter\n\n        @formatter.register_format(\"_test_custom\")\n        def _render_custom(machine_or_class):\n            return \"custom output\"\n\n        try:\n            assert formatter.render(object(), \"_test_custom\") == \"custom output\"\n        finally:\n            formatter._formats.pop(\"_test_custom\", None)\n\n    def test_register_format_with_aliases(self):\n        from statemachine.contrib.diagram import formatter\n\n        @formatter.register_format(\"_test_alias\", \"_test_alias2\")\n        def _render_alias_test(machine_or_class):\n            return \"alias output\"\n\n        try:\n            assert formatter.render(object(), \"_test_alias\") == \"alias output\"\n            assert formatter.render(object(), \"_test_alias2\") == \"alias output\"\n        finally:\n            formatter._formats.pop(\"_test_alias\", None)\n            formatter._formats.pop(\"_test_alias2\", None)\n\n    def test_error_message_lists_primary_formats(self):\n        from statemachine.contrib.diagram import formatter\n\n        with pytest.raises(ValueError, match=\"'dot'\") as exc_info:\n            formatter.render(object(), \"nonexistent\")\n        msg = str(exc_info.value)\n        # Should list primary names, not aliases\n        assert \"'mermaid'\" in msg\n        assert \"'md'\" in msg\n        assert \"'rst'\" in msg\n        # \"markdown\" is an alias, should not appear in error message\n        assert \"'markdown'\" not in msg\n\n\nclass TestDirectiveMermaidFormat:\n    \"\"\"Tests for the :format: mermaid Sphinx directive option.\"\"\"\n\n    _QUALNAME = \"tests.examples.traffic_light_machine.TrafficLightMachine\"\n\n    def _make_directive(self, tmp_path, options=None):\n        from statemachine.contrib.diagram.sphinx_ext import StateMachineDiagram\n\n        directive = StateMachineDiagram.__new__(StateMachineDiagram)\n        directive.options = options or {}\n        directive.lineno = 1\n        directive.state_machine = mock.MagicMock()\n        directive.state = mock.MagicMock()\n        directive.state.document.settings.env.app.outdir = str(tmp_path)\n        directive.content_offset = 0\n        return directive\n\n    def _run(self, tmp_path, qualname=None, options=None):\n        directive = self._make_directive(tmp_path, options=options)\n        directive.arguments = [qualname or self._QUALNAME]\n        return directive, directive.run()\n\n    def test_mermaid_format_with_sphinxcontrib(self, tmp_path):\n        \"\"\"When sphinxcontrib-mermaid is available, emits a mermaid node.\"\"\"\n        from sphinxcontrib.mermaid import mermaid as MermaidNode  # type: ignore[import-untyped]\n\n        _, result = self._run(tmp_path, options={\"format\": \"mermaid\"})\n        assert len(result) == 1\n        node = result[0]\n        assert isinstance(node, MermaidNode)\n        assert \"stateDiagram-v2\" in node[\"code\"]\n\n    def test_mermaid_format_with_caption(self, tmp_path):\n        \"\"\"Mermaid format with caption wraps in figure node.\"\"\"\n        from sphinxcontrib.mermaid import mermaid as MermaidNode  # type: ignore[import-untyped]\n\n        _, result = self._run(tmp_path, options={\"format\": \"mermaid\", \"caption\": \"My Diagram\"})\n        assert len(result) == 1\n        node = result[0]\n        assert isinstance(node, nodes.figure)\n        # Figure should contain a mermaid node and a caption\n        mermaid_children = [c for c in node.children if isinstance(c, MermaidNode)]\n        assert len(mermaid_children) == 1\n        caption_children = [c for c in node.children if isinstance(c, nodes.caption)]\n        assert len(caption_children) == 1\n        assert caption_children[0].astext() == \"My Diagram\"\n\n    def test_mermaid_format_with_caption_and_name(self, tmp_path):\n        \"\"\"Mermaid format with caption and :name: calls add_name on the figure.\"\"\"\n        _, result = self._run(\n            tmp_path, options={\"format\": \"mermaid\", \"caption\": \"My Diagram\", \"name\": \"fig-sm\"}\n        )\n        assert len(result) == 1\n        assert isinstance(result[0], nodes.figure)\n\n    def test_mermaid_format_with_name_no_caption(self, tmp_path):\n        \"\"\"Mermaid format with :name: but no caption calls add_name on the mermaid node.\"\"\"\n        from sphinxcontrib.mermaid import mermaid as MermaidNode  # type: ignore[import-untyped]\n\n        _, result = self._run(tmp_path, options={\"format\": \"mermaid\", \"name\": \"fig-sm\"})\n        assert len(result) == 1\n        assert isinstance(result[0], MermaidNode)\n\n    def test_mermaid_format_fallback_no_sphinxcontrib(self, tmp_path):\n        \"\"\"When sphinxcontrib-mermaid is not available, falls back to code block.\"\"\"\n        import sys\n\n        saved = sys.modules.get(\"sphinxcontrib.mermaid\")\n        sys.modules[\"sphinxcontrib.mermaid\"] = None  # type: ignore[assignment]\n        try:\n            _, result = self._run(tmp_path, options={\"format\": \"mermaid\"})\n        finally:\n            if saved is not None:\n                sys.modules[\"sphinxcontrib.mermaid\"] = saved\n            else:\n                sys.modules.pop(\"sphinxcontrib.mermaid\", None)\n\n        assert len(result) == 1\n        node = result[0]\n        assert isinstance(node, nodes.literal_block)\n        assert \"stateDiagram-v2\" in node.astext()\n\n    def test_mermaid_render_failure_returns_warning(self, tmp_path):\n        \"\"\"Mermaid generation failure returns a warning node.\"\"\"\n        with mock.patch(\n            \"statemachine.contrib.diagram.MermaidGraphMachine\",\n            side_effect=RuntimeError(\"render failed\"),\n        ):\n            directive, result = self._run(tmp_path, options={\"format\": \"mermaid\"})\n\n        assert len(result) == 1\n        directive.state_machine.reporter.warning.assert_called_once()\n        call_args = directive.state_machine.reporter.warning.call_args\n        assert \"failed to generate mermaid\" in call_args[0][0]\n"
  },
  {
    "path": "tests/test_contrib_timeout.py",
    "content": "\"\"\"Tests for the timeout contrib module.\"\"\"\n\nimport threading\n\nimport pytest\nfrom statemachine.contrib.timeout import _Timeout\nfrom statemachine.contrib.timeout import timeout\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass TestTimeoutValidation:\n    def test_positive_duration(self):\n        t = timeout(5)\n        assert isinstance(t, _Timeout)\n        assert t.duration == 5\n\n    def test_zero_duration_raises(self):\n        with pytest.raises(ValueError, match=\"must be positive\"):\n            timeout(0)\n\n    def test_negative_duration_raises(self):\n        with pytest.raises(ValueError, match=\"must be positive\"):\n            timeout(-1)\n\n    def test_repr_without_on(self):\n        assert repr(timeout(5)) == \"timeout(5)\"\n\n    def test_repr_with_on(self):\n        assert repr(timeout(3.5, on=\"expired\")) == \"timeout(3.5, on='expired')\"\n\n\nclass TestTimeoutBasic:\n    \"\"\"Timeout fires done.invoke.<state> when no custom event is specified.\"\"\"\n\n    async def test_timeout_fires_done_invoke(self, sm_runner):\n        class SM(StateChart):\n            waiting = State(initial=True, invoke=timeout(0.05))\n            done = State(final=True)\n            done_invoke_waiting = waiting.to(done)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"done\" in sm.configuration_values\n\n    async def test_timeout_cancelled_on_early_exit(self, sm_runner):\n        \"\"\"If the machine transitions out before the timeout, nothing fires.\"\"\"\n\n        class SM(StateChart):\n            waiting = State(initial=True, invoke=timeout(10))\n            other = State(final=True)\n            go = waiting.to(other)\n            # No done_invoke_waiting — would fail if timeout fired unexpectedly\n            done_invoke_waiting = waiting.to(waiting)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.send(sm, \"go\")\n\n        assert \"other\" in sm.configuration_values\n\n\nclass TestTimeoutCustomEvent:\n    \"\"\"Timeout fires a custom event via the `on` parameter.\"\"\"\n\n    async def test_custom_event_fires(self, sm_runner):\n        class SM(StateChart):\n            waiting = State(initial=True, invoke=timeout(0.05, on=\"expired\"))\n            timed_out = State(final=True)\n            expired = waiting.to(timed_out)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"timed_out\" in sm.configuration_values\n\n    async def test_custom_event_cancelled_on_early_exit(self, sm_runner):\n        class SM(StateChart):\n            waiting = State(initial=True, invoke=timeout(10, on=\"expired\"))\n            other = State(final=True)\n            go = waiting.to(other)\n            expired = waiting.to(waiting)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.send(sm, \"go\")\n\n        assert \"other\" in sm.configuration_values\n\n\nclass TestTimeoutComposition:\n    \"\"\"Timeout combined with other invoke handlers — first to complete wins.\"\"\"\n\n    async def test_invoke_completes_before_timeout(self, sm_runner):\n        \"\"\"A fast invoke handler transitions out, cancelling the timeout.\"\"\"\n\n        def fast_handler():\n            return \"fast_result\"\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=[fast_handler, timeout(10, on=\"too_slow\")])\n            ready = State(final=True)\n            stuck = State(final=True)\n            done_invoke_loading = loading.to(ready)\n            too_slow = loading.to(stuck)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n\n    async def test_timeout_fires_before_slow_invoke(self, sm_runner):\n        \"\"\"Timeout fires while a slow invoke handler is still running.\"\"\"\n        handler_cancelled = threading.Event()\n\n        class SlowHandler:\n            def run(self, ctx):\n                # Wait until cancelled (state exit) — simulates long-running work\n                ctx.cancelled.wait()\n                handler_cancelled.set()\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=[SlowHandler(), timeout(0.05, on=\"too_slow\")])\n            ready = State(final=True)\n            stuck = State(final=True)\n            done_invoke_loading = loading.to(ready)\n            too_slow = loading.to(stuck)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"stuck\" in sm.configuration_values\n        # The slow handler should have been cancelled when the state exited\n        handler_cancelled.wait(timeout=2)\n        assert handler_cancelled.is_set()\n"
  },
  {
    "path": "tests/test_copy.py",
    "content": "import asyncio\nimport logging\nimport pickle\nfrom copy import deepcopy\nfrom enum import Enum\nfrom enum import auto\n\nimport pytest\nfrom statemachine.states import States\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\nlogger = logging.getLogger(__name__)\n\n\ndef copy_pickle(obj):\n    return pickle.loads(pickle.dumps(obj))\n\n\n@pytest.fixture(params=[deepcopy, copy_pickle], ids=[\"deepcopy\", \"pickle\"])\ndef copy_method(request):\n    return request.param\n\n\nclass GameStates(str, Enum):\n    GAME_START = auto()\n    GAME_PLAYING = auto()\n    TURN_END = auto()\n    GAME_END = auto()\n\n\nclass GameStateMachine(StateChart):\n    s = States.from_enum(GameStates, initial=GameStates.GAME_START, final=GameStates.GAME_END)\n\n    play = s.GAME_START.to(s.GAME_PLAYING)\n    stop = s.GAME_PLAYING.to(s.TURN_END)\n    end_game = s.TURN_END.to(s.GAME_END)\n\n    @end_game.cond\n    def game_is_over(self) -> bool:\n        return True\n\n    advance_round = end_game | s.TURN_END.to(s.GAME_END)\n\n\nclass MyStateMachine(StateChart):\n    created = State(initial=True)\n    started = State(final=True)\n\n    start = created.to(started)\n\n    def __init__(self):\n        super().__init__()\n        self.custom = 1\n        self.value = [1, 2, 3]\n\n\nclass MySM(StateChart):\n    draft = State(\"Draft\", initial=True, value=\"draft\")\n    published = State(\"Published\", value=\"published\", final=True)\n\n    publish = draft.to(published, cond=\"let_me_be_visible\")\n\n    def let_me_be_visible(self):\n        return True\n\n\nclass MyModel:\n    def __init__(self, name: str) -> None:\n        self.name = name\n        self._let_me_be_visible = False\n\n    def __repr__(self) -> str:\n        return f\"{type(self).__name__}@{id(self)}({self.name!r})\"\n\n    @property\n    def let_me_be_visible(self):\n        return self._let_me_be_visible\n\n    @let_me_be_visible.setter\n    def let_me_be_visible(self, value):\n        self._let_me_be_visible = value\n\n\ndef test_copy(copy_method):\n    sm = MySM(MyModel(\"main_model\"))\n    sm2 = copy_method(sm)\n\n    assert sm.model is not sm2.model\n    assert sm.model.name == sm2.model.name\n    assert sm2.draft.is_active\n\n    sm2.model.let_me_be_visible = True\n    sm2.send(\"publish\")\n    assert sm2.published.is_active\n\n\ndef test_copy_with_listeners(copy_method):\n    model1 = MyModel(\"main_model\")\n    sm1 = MySM(model1)\n\n    listener_1 = MyModel(\"observer_1\")\n    listener_2 = MyModel(\"observer_2\")\n    sm1.add_listener(listener_1)\n    sm1.add_listener(listener_2)\n\n    sm2 = copy_method(sm1)\n\n    assert sm1.model is not sm2.model\n    assert len(sm1._listeners) == len(sm2._listeners)\n    assert all(\n        listener.name == copied_listener.name\n        # zip(strict=True) requires python 3.10\n        for listener, copied_listener in zip(sm1._listeners.values(), sm2._listeners.values())  # noqa: B905\n    )\n\n    sm2.model.let_me_be_visible = True\n    for listener in sm2._listeners.values():\n        listener.let_me_be_visible = True\n\n    sm2.send(\"publish\")\n    assert sm2.published.is_active\n\n\ndef test_copy_with_enum(copy_method):\n    sm = GameStateMachine()\n    sm.play()\n    assert GameStates.GAME_PLAYING in sm.configuration_values\n\n    sm2 = copy_method(sm)\n    assert GameStates.GAME_PLAYING in sm2.configuration_values\n\n\ndef test_copy_with_custom_init_and_vars(copy_method):\n    sm = MyStateMachine()\n    sm.start()\n\n    sm2 = copy_method(sm)\n    assert sm2.custom == 1\n    assert sm2.value == [1, 2, 3]\n    assert sm2.started.is_active\n\n\nclass AsyncTrafficLightMachine(StateChart):\n    green = State(initial=True)\n    yellow = State()\n    red = State()\n\n    cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n    async def on_enter_state(self, target):\n        \"\"\"Async callback to ensure the SM uses AsyncEngine.\"\"\"\n\n\ndef test_copy_async_statemachine_before_activation(copy_method):\n    \"\"\"Regression test for issue #544: async SM fails after pickle/deepcopy.\n\n    When an async SM is copied before activation, the copy must still be\n    activatable because ``__setstate__`` re-enqueues the ``__initial__`` event.\n    \"\"\"\n    sm = AsyncTrafficLightMachine()\n    sm_copy = copy_method(sm)\n\n    async def verify():\n        await sm_copy.activate_initial_state()\n        assert sm_copy.green.is_active\n        await sm_copy.cycle()\n        assert sm_copy.yellow.is_active\n\n    asyncio.run(verify())\n\n\ndef test_copy_async_statemachine_after_activation(copy_method):\n    \"\"\"Copying an async SM that is already activated preserves its current state.\"\"\"\n\n    async def setup_and_verify():\n        sm = AsyncTrafficLightMachine()\n        await sm.activate_initial_state()\n        await sm.cycle()\n        assert sm.yellow.is_active\n\n        sm_copy = copy_method(sm)\n\n        await sm_copy.activate_initial_state()\n        assert sm_copy.yellow.is_active\n        await sm_copy.cycle()\n        assert sm_copy.red.is_active\n\n    asyncio.run(setup_and_verify())\n"
  },
  {
    "path": "tests/test_dispatcher.py",
    "content": "import pytest\nfrom statemachine.callbacks import CallbackGroup\nfrom statemachine.callbacks import CallbackSpec\nfrom statemachine.dispatcher import Listener\nfrom statemachine.dispatcher import Listeners\nfrom statemachine.dispatcher import resolver_factory_from_objects\nfrom statemachine.exceptions import InvalidDefinition\nfrom statemachine.state import State\nfrom statemachine.statemachine import StateChart\n\n\ndef _take_first_callable(iterable):\n    _key, builder = next(iterable)\n    return builder()\n\n\nclass Person:\n    def __init__(self, first_name, last_name, legal_document=None):\n        self.first_name = first_name\n        self.last_name = last_name\n        self.legal_document = legal_document\n\n    def get_full_name(self):\n        return f\"{self.first_name} {self.last_name}\"\n\n\nclass Organization:\n    def __init__(self, name, legal_document):\n        self.name = name\n        self.legal_document = legal_document\n\n    def get_full_name(self):\n        return self.name\n\n\nclass TestEnsureCallable:\n    @pytest.fixture(\n        params=[\n            pytest.param([], id=\"no-args\"),\n            pytest.param([24, True, \"Go!\"], id=\"with-args\"),\n        ]\n    )\n    def args(self, request):\n        return request.param\n\n    @pytest.fixture(\n        params=[\n            pytest.param({}, id=\"no-kwargs\"),\n            pytest.param({\"a\": \"x\", \"b\": \"y\"}, id=\"with-kwargs\"),\n        ]\n    )\n    def kwargs(self, request):\n        return request.param\n\n    def test_return_same_object_if_already_a_callable(self):\n        model = Person(\"Frodo\", \"Bolseiro\")\n        expected = model.get_full_name\n        actual = _take_first_callable(\n            resolver_factory_from_objects([]).search(\n                CallbackSpec(model.get_full_name, group=CallbackGroup.ON)\n            )\n        )\n        assert actual.__name__ == expected.__name__\n        assert actual.__doc__ == expected.__doc__\n\n    def test_retrieve_a_method_from_its_name(self, args, kwargs):\n        model = Person(\"Frodo\", \"Bolseiro\")\n        expected = model.get_full_name\n        method = _take_first_callable(\n            Listeners.from_listeners([Listener.from_obj(model)]).search(\n                CallbackSpec(\"get_full_name\", group=CallbackGroup.ON),\n            )\n        )\n\n        assert method.__name__ == expected.__name__\n        assert method.__doc__ == expected.__doc__\n        assert method(*args, **kwargs) == \"Frodo Bolseiro\"\n\n    def test_retrieve_a_callable_from_a_property_name(self, args, kwargs):\n        model = Person(\"Frodo\", \"Bolseiro\")\n\n        method = _take_first_callable(\n            Listeners.from_listeners([Listener.from_obj(model)]).search(\n                CallbackSpec(\"first_name\", group=CallbackGroup.ON),\n            )\n        )\n\n        assert method(*args, **kwargs) == \"Frodo\"\n\n    def test_retrieve_callable_from_a_property_name_that_should_keep_reference(self, args, kwargs):\n        model = Person(\"Frodo\", \"Bolseiro\")\n\n        method = _take_first_callable(\n            Listeners.from_listeners([Listener.from_obj(model)]).search(\n                CallbackSpec(\"first_name\", group=CallbackGroup.ON),\n            )\n        )\n\n        model.first_name = \"Bilbo\"\n\n        assert method(*args, **kwargs) == \"Bilbo\"\n\n\nclass TestResolverFactory:\n    @pytest.mark.parametrize(\n        (\"attr\", \"expected_value\"),\n        [\n            (\"first_name\", \"Frodo\"),\n            (\"last_name\", \"Bolseiro\"),\n            (\"legal_document\", \"cnpj\"),\n            (\"get_full_name\", \"The Lord fo the Rings\"),\n        ],\n    )\n    def test_should_chain_resolutions(self, attr, expected_value):\n        person = Person(\"Frodo\", \"Bolseiro\", \"cpf\")\n        org = Organization(\"The Lord fo the Rings\", \"cnpj\")\n\n        resolver = resolver_factory_from_objects(org, person)\n        resolved_method = _take_first_callable(\n            resolver.search(CallbackSpec(attr, group=CallbackGroup.ON))\n        )\n        assert resolved_method() == expected_value\n\n    @pytest.mark.parametrize(\n        (\"attr\", \"expected_value\"),\n        [\n            (\"first_name\", \"Frodo\"),\n            (\"last_name\", \"Bolseiro\"),\n            (\"legal_document\", \"cnpj\"),\n            (\"get_full_name\", \"Frodo Bolseiro\"),\n        ],\n    )\n    def test_should_ignore_list_of_attrs(self, attr, expected_value):\n        person = Person(\"Frodo\", \"Bolseiro\", \"cpf\")\n        org = Organization(\"The Lord fo the Rings\", \"cnpj\")\n\n        org_config = Listener.from_obj(org, {\"get_full_name\"})\n\n        resolver = resolver_factory_from_objects(org_config, person)\n        resolved_method = _take_first_callable(\n            resolver.search(CallbackSpec(attr, group=CallbackGroup.ON))\n        )\n        assert resolved_method() == expected_value\n\n\nclass TestSearchProperty:\n    def test_not_found_property_with_same_name(self):\n        class StrangeObject:\n            @property\n            def can_change_to_start(self):\n                return False\n\n        class StartMachine(StateChart):\n            catch_errors_as_events = False\n\n            created = State(initial=True)\n            started = State(final=True)\n\n            start = created.to(started, cond=StrangeObject.can_change_to_start)\n\n            def can_change_to_start(self):\n                return True\n\n        with pytest.raises(InvalidDefinition, match=\"not found name\"):\n            StartMachine()\n"
  },
  {
    "path": "tests/test_error_execution.py",
    "content": "import pytest\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\nfrom tests.machines.error.error_convention_event import ErrorConventionEventSC\nfrom tests.machines.error.error_convention_transition_list import ErrorConventionTransitionListSC\nfrom tests.machines.error.error_in_action_sc import ErrorInActionSC\nfrom tests.machines.error.error_in_action_sm_with_flag import ErrorInActionSMWithFlag\nfrom tests.machines.error.error_in_after_sc import ErrorInAfterSC\nfrom tests.machines.error.error_in_error_handler_sc import ErrorInErrorHandlerSC\nfrom tests.machines.error.error_in_guard_sc import ErrorInGuardSC\nfrom tests.machines.error.error_in_guard_sm import ErrorInGuardSM\nfrom tests.machines.error.error_in_on_enter_sc import ErrorInOnEnterSC\n\n\ndef test_exception_in_guard_sends_error_execution():\n    \"\"\"Exception in guard returns False and sends error.execution event.\"\"\"\n    sm = ErrorInGuardSC()\n    assert sm.configuration == {sm.initial}\n\n    sm.send(\"go\")\n\n    # The bad_guard raises, so error.execution is sent, transitioning to error_state\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_exception_in_on_enter_sends_error_execution():\n    \"\"\"Exception in on_enter sends error.execution and rolls back configuration.\"\"\"\n    sm = ErrorInOnEnterSC()\n    assert sm.configuration == {sm.s1}\n\n    sm.send(\"go\")\n\n    # on_enter_s2 raises, config is rolled back to s1, then error.execution fires\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_exception_in_action_sends_error_execution():\n    \"\"\"Exception in transition 'on' action sends error.execution.\"\"\"\n    sm = ErrorInActionSC()\n    assert sm.configuration == {sm.s1}\n\n    sm.send(\"go\")\n\n    # bad_action raises during transition, config rolls back to s1,\n    # then error.execution fires\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_exception_in_after_sends_error_execution_no_rollback():\n    \"\"\"Exception in 'after' action sends error.execution but does NOT roll back.\"\"\"\n    sm = ErrorInAfterSC()\n    assert sm.configuration == {sm.s1}\n\n    sm.send(\"go\")\n\n    # Transition s1->s2 completes, then bad_after raises,\n    # error.execution fires from s2 -> error_state\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_statemachine_exception_propagates():\n    \"\"\"StateChart with catch_errors_as_events=False should propagate exceptions normally.\"\"\"\n    sm = ErrorInGuardSM()\n    assert sm.configuration == {sm.initial}\n\n    # The bad_guard raises RuntimeError, which should propagate\n    with pytest.raises(RuntimeError, match=\"guard failed\"):\n        sm.send(\"go\")\n\n\ndef test_invalid_definition_always_propagates():\n    \"\"\"InvalidDefinition should always propagate regardless of catch_errors_as_events.\"\"\"\n\n    class BadDefinitionSC(StateChart):\n        s1 = State(\"s1\", initial=True)\n        s2 = State(\"s2\", final=True)\n\n        go = s1.to(s2, cond=\"bad_cond\")\n\n        def bad_cond(self):\n            raise InvalidDefinition(\"bad definition\")\n\n    sm = BadDefinitionSC()\n    with pytest.raises(InvalidDefinition, match=\"bad definition\"):\n        sm.send(\"go\")\n\n\ndef test_error_in_error_handler_no_infinite_loop():\n    \"\"\"Error while processing error.execution should not cause infinite loop.\"\"\"\n    sm = ErrorInErrorHandlerSC()\n    assert sm.configuration == {sm.s1}\n\n    # bad_action raises -> caught per-block, transition completes to s2 ->\n    # error.execution fires -> bad_error_handler raises during error.execution\n    # processing -> rolled back, second error ignored (logged as warning)\n    sm.send(\"go\")\n\n    # Transition 'on' content error is caught per-block (SCXML spec),\n    # so the transition s1->s2 completes. error.execution fires from s2,\n    # bad_error_handler raises, which is ignored during error.execution.\n    assert sm.configuration == {sm.s2}\n\n\ndef test_statemachine_with_catch_errors_as_events_true():\n    \"\"\"StateChart (catch_errors_as_events=True by default) should catch errors.\"\"\"\n    sm = ErrorInActionSMWithFlag()\n    assert sm.configuration == {sm.s1}\n\n    sm.send(\"go\")\n\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_error_data_available_in_error_execution_handler():\n    \"\"\"The error object should be available in the error.execution event kwargs.\"\"\"\n    received_errors = []\n\n    class ErrorDataSC(StateChart):\n        s1 = State(\"s1\", initial=True)\n        error_state = State(\"error_state\", final=True)\n\n        go = s1.to(s1, on=\"bad_action\")\n        error_execution = Event(s1.to(error_state, on=\"handle_error\"), id=\"error.execution\")\n\n        def bad_action(self):\n            raise RuntimeError(\"specific error message\")\n\n        def handle_error(self, error=None, **kwargs):\n            received_errors.append(error)\n\n    sm = ErrorDataSC()\n    sm.send(\"go\")\n\n    assert sm.configuration == {sm.error_state}\n    assert len(received_errors) == 1\n    assert isinstance(received_errors[0], RuntimeError)\n    assert str(received_errors[0]) == \"specific error message\"\n\n\n# --- Tests for error_ naming convention ---\n\n\ndef test_error_convention_with_transition_list():\n    \"\"\"Bare TransitionList with error_ prefix matches error.execution event.\"\"\"\n    sm = ErrorConventionTransitionListSC()\n    assert sm.configuration == {sm.s1}\n\n    sm.send(\"go\")\n\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_error_convention_with_event_no_explicit_id():\n    \"\"\"Event without explicit id with error_ prefix matches error.execution event.\"\"\"\n    sm = ErrorConventionEventSC()\n    assert sm.configuration == {sm.s1}\n\n    sm.send(\"go\")\n\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_error_convention_preserves_explicit_id():\n    \"\"\"Event with explicit id= should NOT be modified by naming convention.\"\"\"\n\n    class ExplicitIdSC(StateChart):\n        s1 = State(\"s1\", initial=True)\n        error_state = State(\"error_state\", final=True)\n\n        go = s1.to(s1, on=\"bad_action\")\n        error_execution = Event(s1.to(error_state), id=\"error.execution\")\n\n        def bad_action(self):\n            raise RuntimeError(\"action failed\")\n\n    sm = ExplicitIdSC()\n    sm.send(\"go\")\n    assert sm.configuration == {sm.error_state}\n\n\ndef test_non_error_prefix_unchanged():\n    \"\"\"Attributes NOT starting with error_ should not get dot-notation alias.\"\"\"\n\n    class NormalSC(StateChart):\n        s1 = State(\"s1\", initial=True)\n        s2 = State(\"s2\", final=True)\n\n        go = s1.to(s2)\n\n    sm = NormalSC()\n    # The 'go' event should only match 'go', not 'g.o'\n    sm.send(\"go\")\n    assert sm.configuration == {sm.s2}\n\n\n# --- LOTR-themed error_ convention and error handling edge cases ---\n\n\n@pytest.mark.timeout(5)\nclass TestErrorConventionLOTR:\n    \"\"\"Error handling and error_ naming convention using Lord of the Rings theme.\"\"\"\n\n    def test_ring_corrupts_bearer_convention_transition_list(self):\n        \"\"\"Frodo puts on the Ring (action fails) -> error.execution via bare TransitionList.\"\"\"\n\n        class FrodoJourney(StateChart):\n            the_shire = State(\"the_shire\", initial=True)\n            corrupted = State(\"corrupted\", final=True)\n\n            put_on_ring = the_shire.to(the_shire, on=\"bear_the_ring\")\n            error_execution = the_shire.to(corrupted)\n\n            def bear_the_ring(self):\n                raise RuntimeError(\"The Ring's corruption is too strong\")\n\n        sm = FrodoJourney()\n        assert sm.configuration == {sm.the_shire}\n        sm.send(\"put_on_ring\")\n        assert sm.configuration == {sm.corrupted}\n\n    def test_ring_corrupts_bearer_convention_event(self):\n        \"\"\"Same as above but using Event() without explicit id.\"\"\"\n\n        class FrodoJourney(StateChart):\n            the_shire = State(\"the_shire\", initial=True)\n            corrupted = State(\"corrupted\", final=True)\n\n            put_on_ring = the_shire.to(the_shire, on=\"bear_the_ring\")\n            error_execution = Event(the_shire.to(corrupted))\n\n            def bear_the_ring(self):\n                raise RuntimeError(\"The Ring's corruption is too strong\")\n\n        sm = FrodoJourney()\n        sm.send(\"put_on_ring\")\n        assert sm.configuration == {sm.corrupted}\n\n    def test_explicit_id_takes_precedence(self):\n        \"\"\"Explicit id='error.execution' is preserved, convention does not interfere.\"\"\"\n\n        class GandalfBattle(StateChart):\n            bridge = State(\"bridge\", initial=True)\n            fallen = State(\"fallen\", final=True)\n\n            fight_balrog = bridge.to(bridge, on=\"you_shall_not_pass\")\n            error_execution = Event(bridge.to(fallen), id=\"error.execution\")\n\n            def you_shall_not_pass(self):\n                raise RuntimeError(\"Balrog breaks the bridge\")\n\n        sm = GandalfBattle()\n        sm.send(\"fight_balrog\")\n        assert sm.configuration == {sm.fallen}\n\n    def test_error_data_passed_to_handler(self):\n        \"\"\"The original error is available in the error handler kwargs.\"\"\"\n        captured = []\n\n        class PalantirVision(StateChart):\n            seeing = State(\"seeing\", initial=True)\n            madness = State(\"madness\", final=True)\n\n            gaze = seeing.to(seeing, on=\"look_into_palantir\")\n            error_execution = seeing.to(madness, on=\"saurons_influence\")\n\n            def look_into_palantir(self):\n                raise RuntimeError(\"Sauron's eye burns\")\n\n            def saurons_influence(self, error=None, **kwargs):\n                captured.append(error)\n\n        sm = PalantirVision()\n        sm.send(\"gaze\")\n        assert sm.configuration == {sm.madness}\n        assert len(captured) == 1\n        assert str(captured[0]) == \"Sauron's eye burns\"\n\n    def test_error_in_guard_with_convention(self):\n        \"\"\"Error in a guard condition triggers error.execution via convention.\"\"\"\n\n        class GateOfMoria(StateChart):\n            outside = State(\"outside\", initial=True)\n            trapped = State(\"trapped\", final=True)\n\n            speak_friend = outside.to(outside, cond=\"know_password\") | outside.to(outside)\n            error_execution = outside.to(trapped)\n\n            def know_password(self):\n                raise RuntimeError(\"The Watcher attacks\")\n\n        sm = GateOfMoria()\n        sm.send(\"speak_friend\")\n        assert sm.configuration == {sm.trapped}\n\n    def test_error_in_on_enter_with_convention(self):\n        \"\"\"Error in on_enter triggers error.execution via convention.\"\"\"\n\n        class EnterMordor(StateChart):\n            ithilien = State(\"ithilien\", initial=True)\n            mordor = State(\"mordor\")\n            captured = State(\"captured\", final=True)\n\n            march = ithilien.to(mordor)\n            error_execution = ithilien.to(captured) | mordor.to(captured)\n\n            def on_enter_mordor(self):\n                raise RuntimeError(\"One does not simply walk into Mordor\")\n\n        sm = EnterMordor()\n        sm.send(\"march\")\n        assert sm.configuration == {sm.captured}\n\n    def test_error_in_after_with_convention(self):\n        \"\"\"Error in 'after' callback: transition completes, then error.execution fires.\"\"\"\n\n        class HelmDeep(StateChart):\n            defending = State(\"defending\", initial=True)\n            breached = State(\"breached\")\n            fallen = State(\"fallen\", final=True)\n\n            charge = defending.to(breached, after=\"wall_explodes\")\n            error_execution = breached.to(fallen)\n\n            def wall_explodes(self):\n                raise RuntimeError(\"Uruk-hai detonated the wall\")\n\n        sm = HelmDeep()\n        sm.send(\"charge\")\n        # 'after' runs after the transition completes (defending->breached),\n        # so error.execution fires from breached->fallen\n        assert sm.configuration == {sm.fallen}\n\n    def test_error_in_error_handler_no_loop_with_convention(self):\n        \"\"\"Error in error handler must NOT loop infinitely, even with convention.\"\"\"\n\n        class OneRingTemptation(StateChart):\n            carrying = State(\"carrying\", initial=True)\n            resisting = State(\"resisting\", final=True)\n\n            tempt = carrying.to(carrying, on=\"resist\")\n            error_execution = carrying.to(carrying, on=\"struggle\")\n            throw_ring = carrying.to(resisting)\n\n            def resist(self):\n                raise RuntimeError(\"The Ring whispers\")\n\n            def struggle(self):\n                raise RuntimeError(\"Cannot resist the Ring\")\n\n        sm = OneRingTemptation()\n        sm.send(\"tempt\")\n        # resist raises -> caught per-block, self-transition completes (carrying) ->\n        # error.execution fires -> struggle raises during error.execution ->\n        # rolled back, second error ignored -> stays in carrying\n        assert sm.configuration == {sm.carrying}\n\n    def test_multiple_source_states_with_convention(self):\n        \"\"\"error_execution from multiple states using | operator.\"\"\"\n\n        class FellowshipPath(StateChart):\n            rivendell = State(\"rivendell\", initial=True)\n            moria = State(\"moria\")\n            doom = State(\"doom\", final=True)\n\n            travel = rivendell.to(moria, on=\"enter_mines\")\n            error_execution = rivendell.to(doom) | moria.to(doom)\n\n            def enter_mines(self):\n                raise RuntimeError(\"The Balrog awakens\")\n\n        sm = FellowshipPath()\n        sm.send(\"travel\")\n        assert sm.configuration == {sm.doom}\n\n    def test_convention_with_self_transition_to_final(self):\n        \"\"\"Self-transition error leading to a different state via error handler.\"\"\"\n\n        class GollumDilemma(StateChart):\n            following = State(\"following\", initial=True)\n            betrayed = State(\"betrayed\", final=True)\n\n            precious = following.to(following, on=\"obsess\")\n            error_execution = following.to(betrayed)\n\n            def obsess(self):\n                raise RuntimeError(\"My precious!\")\n\n        sm = GollumDilemma()\n        sm.send(\"precious\")\n        assert sm.configuration == {sm.betrayed}\n\n    def test_statemachine_with_convention_and_flag(self):\n        \"\"\"StateChart (catch_errors_as_events=True by default) uses the error_ convention.\"\"\"\n\n        class SarumanBetrayal(StateChart):\n            white_council = State(\"white_council\", initial=True)\n            orthanc = State(\"orthanc\", final=True)\n\n            reveal = white_council.to(white_council, on=\"betray\")\n            error_execution = white_council.to(orthanc)\n\n            def betray(self):\n                raise RuntimeError(\"Saruman turns to Sauron\")\n\n        sm = SarumanBetrayal()\n        sm.send(\"reveal\")\n        assert sm.configuration == {sm.orthanc}\n\n    def test_statemachine_without_flag_propagates(self):\n        \"\"\"StateChart with catch_errors_as_events=False propagates errors even with convention.\"\"\"\n\n        class AragornSword(StateChart):\n            catch_errors_as_events = False\n\n            broken = State(\"broken\", initial=True)\n\n            reforge = broken.to(broken, on=\"attempt_reforge\")\n            error_execution = broken.to(broken)\n\n            def attempt_reforge(self):\n                raise RuntimeError(\"Narsil cannot be reforged yet\")\n\n        sm = AragornSword()\n        with pytest.raises(RuntimeError, match=\"Narsil cannot be reforged yet\"):\n            sm.send(\"reforge\")\n\n    def test_no_error_handler_defined(self):\n        \"\"\"error.execution fires but no matching transition -> silently ignored (StateChart).\"\"\"\n\n        class Treebeard(StateChart):\n            ent_moot = State(\"ent_moot\", initial=True)\n\n            deliberate = ent_moot.to(ent_moot, on=\"hasty_decision\")\n\n            def hasty_decision(self):\n                raise RuntimeError(\"Don't be hasty!\")\n\n        sm = Treebeard()\n        sm.send(\"deliberate\")\n        # No error_execution handler, so error.execution is ignored\n        # (allow_event_without_transition=True on StateChart)\n        assert sm.configuration == {sm.ent_moot}\n\n    def test_recovery_from_error_allows_further_transitions(self):\n        \"\"\"After handling error.execution, the machine can continue processing events.\"\"\"\n\n        class FrodoQuest(StateChart):\n            shire = State(\"shire\", initial=True)\n            journey = State(\"journey\")\n            mount_doom = State(\"mount_doom\", final=True)\n\n            depart = shire.to(shire, on=\"pack_bags\")\n            error_execution = shire.to(journey)\n            continue_quest = journey.to(mount_doom)\n\n            def pack_bags(self):\n                raise RuntimeError(\"Nazgul attack!\")\n\n        sm = FrodoQuest()\n        sm.send(\"depart\")\n        assert sm.configuration == {sm.journey}\n\n        # Machine is still alive, can process more events\n        sm.send(\"continue_quest\")\n        assert sm.configuration == {sm.mount_doom}\n\n    def test_error_nested_dots_convention(self):\n        \"\"\"error_communication_failed -> also matches error.communication.failed.\"\"\"\n\n        class BeaconOfGondor(StateChart):\n            waiting = State(\"waiting\", initial=True)\n            lit = State(\"lit\")\n            failed = State(\"failed\", final=True)\n\n            light_beacon = waiting.to(lit, on=\"kindle\")\n            error_communication_failed = lit.to(failed)\n\n            def kindle(self):\n                raise RuntimeError(\"The beacon wood is wet\")\n\n        sm = BeaconOfGondor()\n        sm.send(\"light_beacon\")\n        # Transition 'on' content error is caught per-block (SCXML spec),\n        # so waiting->lit completes. error.execution fires from lit, but\n        # error_communication_failed does NOT match error.execution.\n        # Error is unhandled and silently ignored (StateChart default).\n        assert sm.configuration == {sm.lit}\n\n    def test_multiple_errors_sequential(self):\n        \"\"\"Multiple events that fail are each handled by error.execution.\"\"\"\n        error_count = []\n\n        class BoromirLastStand(StateChart):\n            fighting = State(\"fighting\", initial=True)\n            wounded = State(\"wounded\")\n            fallen = State(\"fallen\", final=True)\n\n            strike = fighting.to(fighting, on=\"swing_sword\")\n            error_execution = fighting.to(wounded, on=\"take_arrow\") | wounded.to(\n                fallen, on=\"take_arrow\"\n            )\n            retreat = wounded.to(wounded)\n\n            def swing_sword(self):\n                raise RuntimeError(\"Arrow from Lurtz\")\n\n            def take_arrow(self, **kwargs):\n                error_count.append(1)\n\n        sm = BoromirLastStand()\n        sm.send(\"strike\")\n        assert sm.configuration == {sm.wounded}\n        assert len(error_count) == 1\n\n        # Second error from wounded state leads to fallen\n        sm.send(\"retreat\")  # no error, just moves wounded->wounded\n        assert sm.configuration == {sm.wounded}\n\n    def test_invalid_definition_propagates_despite_convention(self):\n        \"\"\"InvalidDefinition always propagates even with error_ convention.\"\"\"\n\n        class CursedRing(StateChart):\n            wearing = State(\"wearing\", initial=True)\n            corrupted = State(\"corrupted\", final=True)\n\n            use_ring = wearing.to(wearing, cond=\"ring_check\")\n            error_execution = wearing.to(corrupted)\n\n            def ring_check(self):\n                raise InvalidDefinition(\"Ring of Power has no valid definition\")\n\n        sm = CursedRing()\n        with pytest.raises(InvalidDefinition, match=\"Ring of Power\"):\n            sm.send(\"use_ring\")\n\n\n@pytest.mark.timeout(5)\nclass TestErrorHandlerBehaviorLOTR:\n    \"\"\"Advanced error handler behavior: on callbacks, conditions, flow control,\n    and error-in-handler scenarios. SCXML spec compliance.\n\n    All using Lord of the Rings theme.\n    \"\"\"\n\n    def test_on_callback_executes_on_error_transition(self):\n        \"\"\"An `on` callback on the error_execution transition is executed.\"\"\"\n        actions_log = []\n\n        class MirrorOfGaladriel(StateChart):\n            gazing = State(\"gazing\", initial=True)\n            shattered = State(\"shattered\", final=True)\n\n            look = gazing.to(gazing, on=\"peer_into_mirror\")\n            error_execution = gazing.to(shattered, on=\"vision_of_doom\")\n\n            def peer_into_mirror(self):\n                raise RuntimeError(\"Visions of Sauron\")\n\n            def vision_of_doom(self, **kwargs):\n                actions_log.append(\"vision_of_doom executed\")\n\n        sm = MirrorOfGaladriel()\n        sm.send(\"look\")\n        assert sm.configuration == {sm.shattered}\n        assert actions_log == [\"vision_of_doom executed\"]\n\n    def test_on_callback_receives_error_kwarg(self):\n        \"\"\"The `on` callback receives the original error via `error` kwarg.\"\"\"\n        captured = {}\n\n        class DeadMarshes(StateChart):\n            walking = State(\"walking\", initial=True)\n            lost = State(\"lost\", final=True)\n\n            follow_gollum = walking.to(walking, on=\"step_wrong\")\n            error_execution = walking.to(lost, on=\"fall_in_marsh\")\n\n            def step_wrong(self):\n                raise RuntimeError(\"The dead faces call\")\n\n            def fall_in_marsh(self, error=None, **kwargs):\n                captured[\"error\"] = error\n                captured[\"type\"] = type(error).__name__\n\n        sm = DeadMarshes()\n        sm.send(\"follow_gollum\")\n        assert sm.configuration == {sm.lost}\n        assert captured[\"type\"] == \"RuntimeError\"\n        assert str(captured[\"error\"]) == \"The dead faces call\"\n\n    def test_error_in_on_callback_of_error_handler_is_ignored(self):\n        \"\"\"If the `on` callback of error.execution raises, the second error is ignored.\n\n        Per SCXML spec: errors during error.execution processing must not recurse.\n        During error.execution, transition 'on' content errors propagate to\n        microstep(), which rolls back and ignores the second error.\n        \"\"\"\n\n        class MountDoom(StateChart):\n            climbing = State(\"climbing\", initial=True)\n            fallen_into_lava = State(\"fallen_into_lava\", final=True)\n\n            ascend = climbing.to(climbing, on=\"slip\")\n            error_execution = climbing.to(fallen_into_lava, on=\"gollum_intervenes\")\n            survive = climbing.to(fallen_into_lava)  # reachability\n\n            def slip(self):\n                raise RuntimeError(\"Rocks crumble\")\n\n            def gollum_intervenes(self):\n                raise RuntimeError(\"Gollum bites the finger!\")\n\n        sm = MountDoom()\n        sm.send(\"ascend\")\n        # slip raises -> caught per-block, self-transition completes (climbing) ->\n        # error.execution fires -> gollum_intervenes raises during error.execution ->\n        # rolled back to climbing, second error ignored\n        assert sm.configuration == {sm.climbing}\n\n    def test_condition_on_error_transition_routes_to_different_states(self):\n        \"\"\"Two error_execution transitions with different cond guards route errors\n        to different target states based on runtime conditions.\"\"\"\n\n        class BattleOfPelennor(StateChart):\n            fighting = State(\"fighting\", initial=True)\n            retreating = State(\"retreating\")\n            fallen = State(\"fallen\", final=True)\n\n            charge = fighting.to(fighting, on=\"attack\")\n            error_execution = fighting.to(retreating, cond=\"is_recoverable\") | fighting.to(fallen)\n            regroup = retreating.to(fighting)\n\n            is_minor_wound = False\n\n            def attack(self):\n                raise RuntimeError(\"Oliphant charges!\")\n\n            def is_recoverable(self, error=None, **kwargs):\n                return self.is_minor_wound\n\n        # Serious wound -> falls\n        sm = BattleOfPelennor()\n        sm.is_minor_wound = False\n        sm.send(\"charge\")\n        assert sm.configuration == {sm.fallen}\n\n        # Minor wound -> retreats\n        sm2 = BattleOfPelennor()\n        sm2.is_minor_wound = True\n        sm2.send(\"charge\")\n        assert sm2.configuration == {sm2.retreating}\n\n    def test_condition_inspects_error_type_to_route(self):\n        \"\"\"Conditions can inspect the error type to decide the error transition.\"\"\"\n\n        class PathsOfTheDead(StateChart):\n            entering = State(\"entering\", initial=True)\n            cursed = State(\"cursed\")\n            fled = State(\"fled\", final=True)\n            conquered = State(\"conquered\", final=True)\n\n            venture = entering.to(entering, on=\"face_the_dead\")\n            error_execution = entering.to(cursed, cond=\"is_fear\") | entering.to(conquered)\n            escape = cursed.to(fled)\n\n            def face_the_dead(self):\n                raise ValueError(\"The ghosts overwhelm with fear\")\n\n            def is_fear(self, error=None, **kwargs):\n                return isinstance(error, ValueError)\n\n        sm = PathsOfTheDead()\n        sm.send(\"venture\")\n        assert sm.configuration == {sm.cursed}\n\n    def test_condition_inspects_error_message_to_route(self):\n        \"\"\"Conditions can inspect the error message string.\"\"\"\n\n        class WeathertopAmbush(StateChart):\n            camping = State(\"camping\", initial=True)\n            wounded = State(\"wounded\")\n            safe = State(\"safe\", final=True)\n\n            rest = camping.to(camping, on=\"keep_watch\")\n            error_execution = camping.to(wounded, cond=\"is_morgul_blade\") | camping.to(safe)\n            heal = wounded.to(safe)\n\n            def keep_watch(self):\n                raise RuntimeError(\"Morgul blade strikes Frodo\")\n\n            def is_morgul_blade(self, error=None, **kwargs):\n                return error is not None and \"Morgul\" in str(error)\n\n        sm = WeathertopAmbush()\n        sm.send(\"rest\")\n        assert sm.configuration == {sm.wounded}\n\n    def test_error_handler_can_set_machine_attributes(self):\n        \"\"\"The `on` handler on error.execution can modify the state machine instance,\n        effectively controlling flow for subsequent transitions.\"\"\"\n        log = []\n\n        class IsengardSiege(StateChart):\n            besieging = State(\"besieging\", initial=True)\n            flooding = State(\"flooding\")\n            victory = State(\"victory\", final=True)\n\n            attack = besieging.to(besieging, on=\"ram_gates\")\n            error_execution = besieging.to(flooding, on=\"release_river\")\n            finish = flooding.to(victory)\n\n            def ram_gates(self):\n                raise RuntimeError(\"Gates too strong\")\n\n            def release_river(self, error=None, **kwargs):\n                log.append(f\"Ents release the river after: {error}\")\n                self.battle_outcome = \"flooded\"\n\n        sm = IsengardSiege()\n        sm.send(\"attack\")\n        assert sm.configuration == {sm.flooding}\n        assert sm.battle_outcome == \"flooded\"\n        assert len(log) == 1\n\n        sm.send(\"finish\")\n        assert sm.configuration == {sm.victory}\n\n    def test_error_recovery_then_second_error_handled(self):\n        \"\"\"After recovering from an error, a second error is also handled correctly.\"\"\"\n        errors_seen = []\n\n        class MinasTirithDefense(StateChart):\n            outer_wall = State(\"outer_wall\", initial=True)\n            inner_wall = State(\"inner_wall\")\n            citadel = State(\"citadel\", final=True)\n\n            defend_outer = outer_wall.to(outer_wall, on=\"hold_wall\")\n            error_execution = outer_wall.to(inner_wall, on=\"log_error\") | inner_wall.to(\n                citadel, on=\"log_error\"\n            )\n            defend_inner = inner_wall.to(inner_wall, on=\"hold_wall\")\n\n            def hold_wall(self):\n                raise RuntimeError(\"Wall breached!\")\n\n            def log_error(self, error=None, **kwargs):\n                errors_seen.append(str(error))\n\n        sm = MinasTirithDefense()\n\n        # First error: outer_wall -> inner_wall\n        sm.send(\"defend_outer\")\n        assert sm.configuration == {sm.inner_wall}\n        assert errors_seen == [\"Wall breached!\"]\n\n        # Second error: inner_wall -> citadel\n        sm.send(\"defend_inner\")\n        assert sm.configuration == {sm.citadel}\n        assert errors_seen == [\"Wall breached!\", \"Wall breached!\"]\n\n    def test_all_conditions_false_error_unhandled(self):\n        \"\"\"If all error_execution conditions are False, error.execution is silently ignored.\"\"\"\n\n        class Shelob(StateChart):\n            tunnel = State(\"tunnel\", initial=True)\n\n            sneak = tunnel.to(tunnel, on=\"enter_lair\")\n            error_execution = tunnel.to(tunnel, cond=\"never_true\")\n\n            def enter_lair(self):\n                raise RuntimeError(\"Shelob attacks!\")\n\n            def never_true(self, **kwargs):\n                return False\n\n        sm = Shelob()\n        sm.send(\"sneak\")\n        # No condition matched, error.execution ignored, stays in tunnel\n        assert sm.configuration == {sm.tunnel}\n\n    def test_error_in_before_callback_with_convention(self):\n        \"\"\"Error in a `before` callback is also caught and triggers error.execution.\"\"\"\n\n        class RivendellCouncil(StateChart):\n            debating = State(\"debating\", initial=True)\n            disbanded = State(\"disbanded\", final=True)\n\n            propose = debating.to(debating, before=\"check_ring\")\n            error_execution = debating.to(disbanded)\n\n            def check_ring(self):\n                raise RuntimeError(\"Gimli tries to destroy the Ring\")\n\n        sm = RivendellCouncil()\n        sm.send(\"propose\")\n        assert sm.configuration == {sm.disbanded}\n\n    def test_error_in_exit_callback_with_convention(self):\n        \"\"\"Error in on_exit is caught per-block and triggers error.execution.\"\"\"\n\n        class LothlorienDeparture(StateChart):\n            resting = State(\"resting\", initial=True)\n            river = State(\"river\")\n            lost = State(\"lost\", final=True)\n\n            depart = resting.to(river)\n            error_execution = resting.to(lost) | river.to(lost)\n\n            def on_exit_resting(self):\n                raise RuntimeError(\"Galadriel's gifts cause delay\")\n\n        sm = LothlorienDeparture()\n        sm.send(\"depart\")\n        assert sm.configuration == {sm.lost}\n\n\n@pytest.mark.timeout(5)\nclass TestEngineErrorPropagation:\n    def test_invalid_definition_in_enter_propagates(self):\n        \"\"\"InvalidDefinition during enter_states propagates and restores configuration.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            def on_enter_s2(self, **kwargs):\n                raise InvalidDefinition(\"Bad definition\")\n\n        sm = SM()\n        with pytest.raises(InvalidDefinition, match=\"Bad definition\"):\n            sm.send(\"go\")\n\n    def test_invalid_definition_in_after_propagates(self):\n        \"\"\"InvalidDefinition in after callback propagates.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            def after_go(self, **kwargs):\n                raise InvalidDefinition(\"Bad after\")\n\n        sm = SM()\n        with pytest.raises(InvalidDefinition, match=\"Bad after\"):\n            sm.send(\"go\")\n\n    def test_runtime_error_in_after_without_catch_errors_as_events_propagates(self):\n        \"\"\"RuntimeError in after callback without catch_errors_as_events raises.\"\"\"\n\n        class SM(StateChart):\n            catch_errors_as_events = False\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            def after_go(self, **kwargs):\n                raise RuntimeError(\"After boom\")\n\n        sm = SM()\n        with pytest.raises(RuntimeError, match=\"After boom\"):\n            sm.send(\"go\")\n\n    def test_runtime_error_in_after_with_catch_errors_as_events_handled(self):\n        \"\"\"RuntimeError in after callback with catch_errors_as_events is caught.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            error_state = State(final=True)\n\n            go = s1.to(s2)\n            error_execution = s2.to(error_state)\n\n            def after_go(self, **kwargs):\n                raise RuntimeError(\"After boom\")\n\n        sm = SM()\n        sm.send(\"go\")\n        assert sm.configuration == {sm.error_state}\n\n    def test_runtime_error_in_microstep_without_catch_errors_as_events(self):\n        \"\"\"RuntimeError in microstep without catch_errors_as_events raises.\"\"\"\n\n        class SM(StateChart):\n            catch_errors_as_events = False\n\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            def on_enter_s2(self, **kwargs):\n                raise RuntimeError(\"Microstep boom\")\n\n        sm = SM()\n        with pytest.raises(RuntimeError, match=\"Microstep boom\"):\n            sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\ndef test_internal_queue_processes_raised_events():\n    \"\"\"Internal events raised during processing are handled.\"\"\"\n\n    class SM(StateChart):\n        catch_errors_as_events = False\n\n        s1 = State(initial=True)\n        s2 = State()\n        s3 = State(final=True)\n\n        go = s1.to(s2)\n        next_step = s2.to(s3)\n\n        def on_enter_s2(self, **kwargs):\n            self.raise_(\"next_step\")\n\n    sm = SM()\n    sm.send(\"go\")\n    assert sm.s3.is_active\n\n\n@pytest.mark.timeout(5)\ndef test_engine_start_when_already_started():\n    \"\"\"start() is a no-op when state machine is already initialized.\"\"\"\n\n    class SM(StateChart):\n        catch_errors_as_events = False\n\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n    sm = SM()\n    sm._engine.start()\n    assert sm.s1.is_active\n\n\n@pytest.mark.timeout(5)\ndef test_error_in_internal_event_transition_caught_by_microstep():\n    \"\"\"Error in a transition triggered by an internal event is caught by _run_microstep.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State()\n        s3 = State()\n        error_state = State(final=True)\n\n        go = s1.to(s2)\n        step = s2.to(s3, on=\"bad_action\")\n        error_execution = s2.to(error_state) | s3.to(error_state)\n\n        def on_enter_s2(self, **kwargs):\n            self.raise_(\"step\")\n\n        def bad_action(self):\n            raise RuntimeError(\"Internal event error\")\n\n    sm = SM()\n    sm.send(\"go\")\n    assert sm.configuration == {sm.error_state}\n\n\n@pytest.mark.timeout(5)\ndef test_invalid_definition_in_internal_event_propagates():\n    \"\"\"InvalidDefinition in an internal event transition propagates through _run_microstep.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State()\n        s3 = State(final=True)\n        error_state = State(final=True)\n\n        go = s1.to(s2)\n        step = s2.to(s3, on=\"bad_action\")\n        error_execution = s2.to(error_state)\n\n        def on_enter_s2(self, **kwargs):\n            self.raise_(\"step\")\n\n        def bad_action(self):\n            raise InvalidDefinition(\"Internal event bad definition\")\n\n    sm = SM()\n    with pytest.raises(InvalidDefinition, match=\"Internal event bad definition\"):\n        sm.send(\"go\")\n\n\n@pytest.mark.timeout(5)\ndef test_runtime_error_in_internal_event_propagates_without_catch_errors_as_events():\n    \"\"\"RuntimeError in internal event propagates when catch_errors_as_events is False.\"\"\"\n\n    class SM(StateChart):\n        catch_errors_as_events = False\n\n        s1 = State(initial=True)\n        s2 = State()\n        s3 = State(final=True)\n\n        go = s1.to(s2)\n        step = s2.to(s3, on=\"bad_action\")\n\n        def on_enter_s2(self, **kwargs):\n            self.raise_(\"step\")\n\n        def bad_action(self):\n            raise RuntimeError(\"Internal event boom\")\n\n    sm = SM()\n    with pytest.raises(RuntimeError, match=\"Internal event boom\"):\n        sm.send(\"go\")\n"
  },
  {
    "path": "tests/test_events.py",
    "content": "import pytest\nfrom statemachine.event import Event\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\ndef test_assign_events_on_transitions():\n    class TrafficLightMachine(StateChart):\n        \"A traffic light machine\"\n\n        green = State(initial=True)\n        yellow = State()\n        red = State()\n\n        green.to(yellow, event=\"cycle slowdown\")\n        yellow.to(red, event=\"cycle stop\")\n        red.to(green, event=\"cycle go\")\n\n        def on_cycle(self, event_data, event: str):\n            assert event_data.event == event\n            return (\n                f\"Running {event} from {event_data.transition.source.id} to \"\n                f\"{event_data.transition.target.id}\"\n            )\n\n    sm = TrafficLightMachine()\n\n    assert sm.send(\"cycle\") == \"Running cycle from green to yellow\"\n    assert sm.send(\"cycle\") == \"Running cycle from yellow to red\"\n    assert sm.send(\"cycle\") == \"Running cycle from red to green\"\n\n\nclass TestExplicitEvent:\n    def test_accept_event_instance(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            start = Event(created.to(started))\n\n        assert [e.id for e in StartMachine.events] == [\"start\"]\n        assert [e.name for e in StartMachine.events] == [\"Start\"]\n        assert StartMachine.start.name == \"Start\"\n\n        sm = StartMachine()\n        sm.send(\"start\")\n        assert sm.started.is_active\n\n    def test_accept_event_name(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            start = Event(created.to(started), name=\"Start the machine\")\n\n        assert [e.id for e in StartMachine.events] == [\"start\"]\n        assert [e.name for e in StartMachine.events] == [\"Start the machine\"]\n        assert StartMachine.start.name == \"Start the machine\"\n\n    def test_derive_name_from_id(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            launch_the_machine = Event(created.to(started))\n\n        assert list(StartMachine.events) == [\"launch_the_machine\"]\n        assert [e.id for e in StartMachine.events] == [\"launch_the_machine\"]\n        assert [e.name for e in StartMachine.events] == [\"Launch the machine\"]\n        assert StartMachine.launch_the_machine.name == \"Launch the machine\"\n        assert str(StartMachine.launch_the_machine) == \"launch_the_machine\"\n        assert StartMachine.launch_the_machine == StartMachine.launch_the_machine.id\n\n    def test_not_derive_name_from_id_if_not_event_class(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            launch_the_machine = created.to(started)\n\n        assert list(StartMachine.events) == [\"launch_the_machine\"]\n        assert [e.id for e in StartMachine.events] == [\"launch_the_machine\"]\n        assert [e.name for e in StartMachine.events] == [\"Launch the machine\"]\n        assert StartMachine.launch_the_machine.name == \"Launch the machine\"\n        assert str(StartMachine.launch_the_machine) == \"launch_the_machine\"\n        assert StartMachine.launch_the_machine == StartMachine.launch_the_machine.id\n\n    def test_raise_invalid_definition_if_event_name_cannot_be_derived(self):\n        with pytest.raises(InvalidDefinition, match=\"has no id\"):\n\n            class StartMachine(StateChart):\n                created = State(initial=True)\n                started = State()\n\n                launch = Event(created.to(started))\n\n                started.to.itself(event=Event())  # event id not defined\n\n    def test_derive_from_id(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            created.to(started, event=Event(\"launch_rocket\"))\n\n        assert StartMachine.launch_rocket.name == \"Launch rocket\"\n\n    def test_of_passing_event_as_parameters(self):\n        class TrafficLightMachine(StateChart):\n            \"A traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            red = State()\n\n            cycle = Event(name=\"Loop\")\n            slowdown = Event(name=\"slow down\")\n            stop = Event(name=\"Please stop\")\n            go = Event(name=\"Go! Go! Go!\")\n\n            green.to(yellow, event=[cycle, slowdown])\n            yellow.to(red, event=[cycle, stop])\n            red.to(green, event=[cycle, go])\n\n            def on_cycle(self, event_data, event: str):\n                assert event_data.event == event\n                return (\n                    f\"Running {event} from {event_data.transition.source.id} to \"\n                    f\"{event_data.transition.target.id}\"\n                )\n\n        sm = TrafficLightMachine()\n\n        assert sm.send(\"cycle\") == \"Running cycle from green to yellow\"\n        assert sm.send(\"cycle\") == \"Running cycle from yellow to red\"\n        assert sm.send(\"cycle\") == \"Running cycle from red to green\"\n        assert sm.cycle.name == \"Loop\"\n        assert sm.slowdown.name == \"slow down\"\n        assert sm.stop.name == \"Please stop\"\n        assert sm.go.name == \"Go! Go! Go!\"\n\n    def test_mixing_event_and_parameters(self):\n        class TrafficLightMachine(StateChart):\n            \"A traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            red = State()\n\n            cycle = Event(\n                green.to(yellow, event=Event(\"slowdown\", name=\"Slow down\"))\n                | yellow.to(red, event=Event(\"stop\", name=\"Please stop!\"))\n                | red.to(green, event=Event(\"go\", name=\"Go! Go! Go!\")),\n                name=\"Loop\",\n            )\n\n            def on_cycle(self, event_data, event: str):\n                assert event_data.event == event\n                return (\n                    f\"Running {event} from {event_data.transition.source.id} to \"\n                    f\"{event_data.transition.target.id}\"\n                )\n\n        sm = TrafficLightMachine()\n\n        assert sm.send(\"cycle\") == \"Running cycle from green to yellow\"\n        assert sm.send(\"cycle\") == \"Running cycle from yellow to red\"\n        assert sm.send(\"cycle\") == \"Running cycle from red to green\"\n        assert sm.cycle.name == \"Loop\"\n        assert sm.slowdown.name == \"Slow down\"\n        assert sm.stop.name == \"Please stop!\"\n        assert sm.go.name == \"Go! Go! Go!\"\n\n    def test_name_derived_from_identifier(self):\n        class TrafficLightMachine(StateChart):\n            \"A traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            red = State()\n\n            cycle = Event(name=\"Loop\")\n            slow_down = Event()\n            green.to(yellow, event=[cycle, slow_down])\n            yellow.to(red, event=[cycle, \"stop\"])\n            red.to(green, event=[cycle, \"go\"])\n\n            def on_cycle(self, event_data, event: str):\n                assert event_data.event == event\n                return (\n                    f\"Running {event} from {event_data.transition.source.id} to \"\n                    f\"{event_data.transition.target.id}\"\n                )\n\n        sm = TrafficLightMachine()\n\n        assert sm.send(\"cycle\") == \"Running cycle from green to yellow\"\n        assert sm.send(\"cycle\") == \"Running cycle from yellow to red\"\n        assert sm.send(\"cycle\") == \"Running cycle from red to green\"\n        assert sm.cycle.name == \"Loop\"\n        assert sm.slow_down.name == \"Slow down\"\n        assert sm.stop.name == \"Stop\"\n        assert sm.go.name == \"Go\"\n\n    def test_multiple_ids_from_the_same_event_will_be_converted_to_multiple_events(self):\n        class TrafficLightMachine(StateChart):\n            \"A traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            red = State()\n\n            green.to(yellow, event=Event(\"cycle slowdown\", name=\"Will be ignored\"))\n            yellow.to(red, event=Event(\"cycle stop\", name=\"Will be ignored\"))\n            red.to(green, event=Event(\"cycle go\", name=\"Will be ignored\"))\n\n            def on_cycle(self, event_data, event: str):\n                assert event_data.event == event\n                return (\n                    f\"Running {event} from {event_data.transition.source.id} to \"\n                    f\"{event_data.transition.target.id}\"\n                )\n\n        sm = TrafficLightMachine()\n\n        assert sm.slowdown.name == \"Slowdown\"\n        assert sm.stop.name == \"Stop\"\n        assert sm.go.name == \"Go\"\n\n        assert sm.send(\"cycle\") == \"Running cycle from green to yellow\"\n        assert sm.send(\"cycle\") == \"Running cycle from yellow to red\"\n        assert sm.send(\"cycle\") == \"Running cycle from red to green\"\n\n    def test_allow_registering_callbacks_using_decorator(self):\n        class TrafficLightMachine(StateChart):\n            \"A traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            red = State()\n\n            cycle = Event(\n                green.to(yellow, event=\"slow_down\")\n                | yellow.to(red, event=[\"stop\"])\n                | red.to(green, event=[\"go\"]),\n                name=\"Loop\",\n            )\n\n            @cycle.on\n            def do_cycle(self, event_data, event: str):\n                assert event_data.event == event\n                return (\n                    f\"Running {event} from {event_data.transition.source.id} to \"\n                    f\"{event_data.transition.target.id}\"\n                )\n\n        sm = TrafficLightMachine()\n\n        assert sm.send(\"cycle\") == \"Running cycle from green to yellow\"\n\n    def test_raise_registering_callbacks_using_decorator_if_no_transitions(self):\n        with pytest.raises(InvalidDefinition, match=\"event with no transitions\"):\n\n            class TrafficLightMachine(StateChart):\n                \"A traffic light machine\"\n\n                green = State(initial=True)\n                yellow = State()\n                red = State()\n\n                cycle = Event(name=\"Loop\")\n                slow_down = Event()\n                green.to(yellow, event=[cycle, slow_down])\n                yellow.to(red, event=[cycle, \"stop\"])\n                red.to(green, event=[cycle, \"go\"])\n\n                @cycle.on\n                def do_cycle(self, event_data, event: str):\n                    assert event_data.event == event\n                    return (\n                        f\"Running {event} from {event_data.transition.source.id} to \"\n                        f\"{event_data.transition.target.id}\"\n                    )\n\n    def test_allow_using_events_as_commands(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            created.to(started, event=Event(\"launch_rocket\"))\n\n        sm = StartMachine()\n        event = next(iter(sm.events))\n\n        event()  # events on an instance machine are \"bounded events\"\n\n        assert sm.started.is_active\n\n    def test_event_commands_fail_when_unbound_to_instance(self):\n        class StartMachine(StateChart):\n            created = State(initial=True)\n            started = State(final=True)\n\n            created.to(started, event=Event(\"launch_rocket\"))\n\n        event = next(iter(StartMachine.events))\n        with pytest.raises(AssertionError):\n            event()\n\n\ndef test_event_match_trailing_dot():\n    \"\"\"Event descriptor ending with '.' matches the prefix.\"\"\"\n    event = Event(\"error.\")\n    assert event.match(\"error\") is True\n    assert event.match(\"error.execution\") is True\n\n\ndef test_event_build_trigger_with_none_machine():\n    \"\"\"build_trigger raises when machine is None.\"\"\"\n    event = Event(\"go\")\n    with pytest.raises(RuntimeError, match=\"cannot be called without\"):\n        event.build_trigger(machine=None)\n\n\ndef test_events_match_none_with_empty():\n    \"\"\"Empty Events collection matches None event.\"\"\"\n    from statemachine.events import Events\n\n    events = Events()\n    assert events.match(None) is True\n\n\ndef test_event_raises_on_non_string_id():\n    \"\"\"Event() should raise InvalidDefinition when id is not a string.\n\n    This catches a common mistake where users pass multiple transitions as\n    positional args (e.g. Event(t1, t2)) instead of combining them with |.\n    \"\"\"\n    s1 = State(initial=True)\n    s2 = State(final=True)\n    t1 = s1.to(s2)\n    t2 = s2.to(s1)\n\n    with pytest.raises(InvalidDefinition, match=\"non-string 'id'.*use the \\\\| operator\"):\n        Event(t1, t2)\n"
  },
  {
    "path": "tests/test_examples.py",
    "content": "from inspect import iscoroutinefunction\nfrom pathlib import Path\n\nimport pytest\n\nfrom .helpers import import_module_by_path\n\n\ndef pytest_generate_tests(metafunc):\n    if \"example_file_wrapper\" not in metafunc.fixturenames:\n        return\n\n    file_names = [\n        pytest.param(example_path, id=f\"{example_path}\")\n        for example_path in Path(\"tests/examples\").glob(\"**/*_machine.py\")\n    ]\n    metafunc.parametrize(\"file_name\", file_names)\n\n\n@pytest.fixture()\ndef example_file_wrapper(file_name):\n    def execute_file_wrapper():\n        module = import_module_by_path(file_name.with_suffix(\"\"))\n        return getattr(module, \"main\", None)\n\n    return execute_file_wrapper\n\n\nasync def test_example(example_file_wrapper):\n    \"\"\"Import the example file so the module is executed\"\"\"\n    main = example_file_wrapper()\n    if main is None:\n        return\n\n    if iscoroutinefunction(main):\n        await main()\n    else:\n        main()\n"
  },
  {
    "path": "tests/test_fellowship_quest.py",
    "content": "\"\"\"Fellowship Quest: error.execution with conditions, listeners, and flow control.\n\nDemonstrates how a single StateChart definition can produce different outcomes\ndepending on the character (listener) capabilities and the type of peril (exception).\n\nPer SCXML spec:\n- error.execution transitions follow the same rules as any other transition\n- conditions are evaluated in document order; the first match wins\n- the error object is available to conditions and handlers via the ``error`` kwarg\n- executable content (``on`` callbacks) on error transitions is executed normally\n- errors during error.execution processing are ignored to prevent infinite loops\n\"\"\"\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n# ---------------------------------------------------------------------------\n# Peril types (exception hierarchy)\n# ---------------------------------------------------------------------------\n\n\nclass Peril(Exception):\n    \"\"\"Base class for all Middle-earth perils.\"\"\"\n\n\nclass RingTemptation(Peril):\n    \"\"\"The One Ring tries to corrupt its bearer.\"\"\"\n\n\nclass OrcAmbush(Peril):\n    \"\"\"An orc war party attacks the fellowship.\"\"\"\n\n\nclass DarkSorcery(Peril):\n    \"\"\"Sauron's dark magic or a Nazgûl's sorcery.\"\"\"\n\n\nclass TreacherousTerrain(Peril):\n    \"\"\"Natural hazards: avalanches, marshes, crumbling paths.\"\"\"\n\n\nclass BalrogFury(Peril):\n    \"\"\"An ancient Balrog of Morgoth. Even wizards may fall.\"\"\"\n\n\n# ---------------------------------------------------------------------------\n# Characters (listeners)\n# ---------------------------------------------------------------------------\n\n\nclass Character:\n    \"\"\"Base class for fellowship members. Subclasses override capability flags.\n\n    Condition methods are discovered by the StateChart via the listener mechanism,\n    so the method names must match the ``cond`` strings on the error_execution\n    transitions.\n    \"\"\"\n\n    name: str = \"Unknown\"\n    has_magic: bool = False\n    has_ring_resistance: bool = False\n    has_combat_prowess: bool = False\n    has_endurance: bool = False\n\n    def can_counter_with_magic(self, error=None, **kwargs):\n        \"\"\"Wizards can deflect dark sorcery — but not a Balrog.\"\"\"\n        return self.has_magic and isinstance(error, DarkSorcery)\n\n    def can_resist_temptation(self, error=None, **kwargs):\n        \"\"\"Ring-bearers and the wise can resist the Ring's call.\"\"\"\n        return self.has_ring_resistance and isinstance(error, RingTemptation)\n\n    def can_endure(self, error=None, **kwargs):\n        \"\"\"Warriors and the resilient survive physical perils.\"\"\"\n        return (self.has_combat_prowess and isinstance(error, OrcAmbush)) or (\n            self.has_endurance and isinstance(error, TreacherousTerrain)\n        )\n\n    def __repr__(self):\n        return self.name\n\n\nclass Gandalf(Character):\n    name = \"Gandalf\"\n    has_magic = True\n    has_ring_resistance = True\n    has_combat_prowess = True\n    has_endurance = True\n\n\nclass Aragorn(Character):\n    name = \"Aragorn\"\n    has_combat_prowess = True\n    has_endurance = True\n\n\nclass Frodo(Character):\n    name = \"Frodo\"\n    has_ring_resistance = True\n    has_endurance = True  # mithril coat\n\n\nclass Legolas(Character):\n    name = \"Legolas\"\n    has_combat_prowess = True  # elven agility\n    has_endurance = True\n\n\nclass Boromir(Character):\n    name = \"Boromir\"\n    has_combat_prowess = True\n    has_endurance = True\n\n\nclass Pippin(Character):\n    name = \"Pippin\"\n\n\nclass Samwise(Character):\n    name = \"Samwise\"\n    has_ring_resistance = True  # briefly bore the Ring without corruption\n    has_endurance = True  # \"I can't carry it for you, but I can carry you!\"\n\n\n# ---------------------------------------------------------------------------\n# The StateChart\n# ---------------------------------------------------------------------------\n\n\nclass FellowshipQuest(StateChart):\n    \"\"\"A quest through Middle-earth where perils are handled differently\n    depending on the character's capabilities.\n\n    Conditions on error_execution transitions (evaluated in document order):\n    1. can_counter_with_magic — wizard deflects sorcery, stays adventuring\n    2. can_resist_temptation  — ring resistance deflects corruption, stays adventuring\n    3. can_endure             — physical resilience survives the blow, but wounded\n    4. is_ring_corruption     — if the peril is ring corruption, route to corrupted\n    5. (no condition)         — fallback: the character falls\n\n    From wounded state, any further peril is fatal (no conditions).\n    \"\"\"\n\n    adventuring = State(\"adventuring\", initial=True)\n    wounded = State(\"wounded\")\n    corrupted = State(\"corrupted\", final=True)\n    fallen = State(\"fallen\", final=True)\n    healed = State(\"healed\", final=True)\n\n    face_peril = adventuring.to(adventuring, on=\"encounter_danger\")\n    face_peril_wounded = wounded.to(wounded, on=\"encounter_danger\")\n\n    # error_execution transitions — document order determines priority.\n    # Character capability conditions are resolved from the listener.\n    error_execution = (\n        adventuring.to(adventuring, cond=\"can_counter_with_magic\")\n        | adventuring.to(adventuring, cond=\"can_resist_temptation\")\n        | adventuring.to(wounded, cond=\"can_endure\", on=\"take_hit\")\n        | adventuring.to(corrupted, cond=\"is_ring_corruption\")\n        | adventuring.to(fallen)\n        | wounded.to(fallen)\n    )\n\n    recover = wounded.to(healed)\n\n    wound_description: \"str | None\" = None\n\n    def encounter_danger(self, peril, **kwargs):\n        raise peril\n\n    def is_ring_corruption(self, error=None, **kwargs):\n        \"\"\"Universal condition (on the SM itself, not character-dependent).\"\"\"\n        return isinstance(error, RingTemptation)\n\n    def take_hit(self, error=None, **kwargs):\n        self.wound_description = str(error)\n\n\n# ---------------------------------------------------------------------------\n# Helpers\n# ---------------------------------------------------------------------------\n\n# State name aliases for readable parametrize IDs\nADVENTURING = \"adventuring\"\nWOUNDED = \"wounded\"\nCORRUPTED = \"corrupted\"\nFALLEN = \"fallen\"\n\n\ndef _state_by_name(sm, name):\n    return getattr(sm, name)\n\n\n# ---------------------------------------------------------------------------\n# Tests — single-peril outcome matrix\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.timeout(5)\n@pytest.mark.parametrize(\n    (\"character\", \"peril\", \"expected\"),\n    [\n        # --- Gandalf: magic + ring resistance + combat + endurance ---\n        pytest.param(\n            Gandalf(), DarkSorcery(\"Nazgûl screams\"), ADVENTURING, id=\"gandalf-deflects-sorcery\"\n        ),\n        pytest.param(\n            Gandalf(),\n            RingTemptation(\"The Ring calls to power\"),\n            ADVENTURING,\n            id=\"gandalf-resists-ring\",\n        ),\n        pytest.param(Gandalf(), OrcAmbush(\"Goblins in Moria\"), WOUNDED, id=\"gandalf-endures-orcs\"),\n        pytest.param(\n            Gandalf(),\n            TreacherousTerrain(\"Caradhras blizzard\"),\n            WOUNDED,\n            id=\"gandalf-endures-terrain\",\n        ),\n        pytest.param(Gandalf(), BalrogFury(\"Flame of Udûn\"), FALLEN, id=\"gandalf-falls-to-balrog\"),\n        # --- Aragorn: combat + endurance ---\n        pytest.param(\n            Aragorn(),\n            DarkSorcery(\"Mouth of Sauron's curse\"),\n            FALLEN,\n            id=\"aragorn-falls-to-sorcery\",\n        ),\n        pytest.param(\n            Aragorn(),\n            RingTemptation(\"The Ring offers kingship\"),\n            CORRUPTED,\n            id=\"aragorn-corrupted-by-ring\",\n        ),\n        pytest.param(Aragorn(), OrcAmbush(\"Uruk-hai charge\"), WOUNDED, id=\"aragorn-endures-orcs\"),\n        pytest.param(\n            Aragorn(),\n            TreacherousTerrain(\"Caradhras avalanche\"),\n            WOUNDED,\n            id=\"aragorn-endures-terrain\",\n        ),\n        # --- Frodo: ring resistance + endurance (mithril) ---\n        pytest.param(\n            Frodo(), DarkSorcery(\"Witch-king's blade\"), FALLEN, id=\"frodo-falls-to-sorcery\"\n        ),\n        pytest.param(\n            Frodo(), RingTemptation(\"The Ring whispers\"), ADVENTURING, id=\"frodo-resists-ring\"\n        ),\n        pytest.param(Frodo(), OrcAmbush(\"Cirith Ungol orcs\"), FALLEN, id=\"frodo-falls-to-orcs\"),\n        pytest.param(\n            Frodo(),\n            TreacherousTerrain(\"Cave troll stab (mithril saves)\"),\n            WOUNDED,\n            id=\"frodo-endures-terrain-mithril\",\n        ),\n        # --- Legolas: combat + endurance ---\n        pytest.param(Legolas(), DarkSorcery(\"Dark spell\"), FALLEN, id=\"legolas-falls-to-sorcery\"),\n        pytest.param(\n            Legolas(),\n            RingTemptation(\"The Ring promises immortal forest\"),\n            CORRUPTED,\n            id=\"legolas-corrupted-by-ring\",\n        ),\n        pytest.param(Legolas(), OrcAmbush(\"Orc arrows rain\"), WOUNDED, id=\"legolas-endures-orcs\"),\n        # --- Boromir: combat + endurance, no ring resistance ---\n        pytest.param(\n            Boromir(),\n            RingTemptation(\"Give me the Ring!\"),\n            CORRUPTED,\n            id=\"boromir-corrupted-by-ring\",\n        ),\n        pytest.param(Boromir(), OrcAmbush(\"Lurtz attacks\"), WOUNDED, id=\"boromir-endures-orcs\"),\n        # --- Samwise: ring resistance + endurance ---\n        pytest.param(\n            Samwise(),\n            RingTemptation(\"Ring tempts with gardens\"),\n            ADVENTURING,\n            id=\"samwise-resists-ring\",\n        ),\n        pytest.param(\n            Samwise(),\n            TreacherousTerrain(\"Stairs of Cirith Ungol\"),\n            WOUNDED,\n            id=\"samwise-endures-terrain\",\n        ),\n        pytest.param(\n            Samwise(), DarkSorcery(\"Shelob's darkness\"), FALLEN, id=\"samwise-falls-to-sorcery\"\n        ),\n        pytest.param(Samwise(), OrcAmbush(\"Orc patrol\"), FALLEN, id=\"samwise-falls-to-orcs\"),\n        # --- Pippin: no special capabilities ---\n        pytest.param(\n            Pippin(),\n            RingTemptation(\"The Ring shows second breakfast\"),\n            CORRUPTED,\n            id=\"pippin-corrupted-by-ring\",\n        ),\n        pytest.param(\n            Pippin(), DarkSorcery(\"Palantír vision\"), FALLEN, id=\"pippin-falls-to-sorcery\"\n        ),\n        pytest.param(Pippin(), OrcAmbush(\"Troll swings\"), FALLEN, id=\"pippin-falls-to-orcs\"),\n        pytest.param(\n            Pippin(), TreacherousTerrain(\"Dead Marshes\"), FALLEN, id=\"pippin-falls-to-terrain\"\n        ),\n    ],\n)\ndef test_single_peril_outcome(character, peril, expected):\n    \"\"\"Each character × peril combination produces the expected outcome.\"\"\"\n    sm = FellowshipQuest(listeners=[character])\n    sm.send(\"face_peril\", peril=peril)\n    assert sm.configuration == {_state_by_name(sm, expected)}\n\n\n# ---------------------------------------------------------------------------\n# Tests — on callback receives error context\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.timeout(5)\n@pytest.mark.parametrize(\n    (\"character\", \"peril\", \"expect_wound\"),\n    [\n        pytest.param(\n            Aragorn(), OrcAmbush(\"Poisoned orc blade\"), \"Poisoned orc blade\", id=\"wound-from-orcs\"\n        ),\n        pytest.param(\n            Legolas(),\n            TreacherousTerrain(\"Caradhras ice\"),\n            \"Caradhras ice\",\n            id=\"wound-from-terrain\",\n        ),\n        pytest.param(Gandalf(), DarkSorcery(\"Nazgûl\"), None, id=\"no-wound-when-deflected\"),\n        pytest.param(\n            Boromir(), RingTemptation(\"The Ring calls\"), None, id=\"no-wound-when-corrupted\"\n        ),\n    ],\n)\ndef test_wound_description(character, peril, expect_wound):\n    \"\"\"The take_hit callback stores the wound description only when can_endure matches.\"\"\"\n    sm = FellowshipQuest(listeners=[character])\n    sm.send(\"face_peril\", peril=peril)\n    assert sm.wound_description == expect_wound\n\n\n# ---------------------------------------------------------------------------\n# Tests — multi-peril sagas\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.timeout(5)\n@pytest.mark.parametrize(\n    (\"character\", \"perils_and_states\"),\n    [\n        pytest.param(\n            Gandalf(),\n            [\n                (DarkSorcery(\"Saruman's blast\"), ADVENTURING),\n                (RingTemptation(\"The Ring calls\"), ADVENTURING),\n                (DarkSorcery(\"Witch-king's curse\"), ADVENTURING),\n                (OrcAmbush(\"Moria goblins\"), WOUNDED),\n            ],\n            id=\"gandalf-saga-deflects-three-then-wounded\",\n        ),\n        pytest.param(\n            Frodo(),\n            [\n                (RingTemptation(\"Ring at Weathertop\"), ADVENTURING),\n                (RingTemptation(\"Ring at Amon Hen\"), ADVENTURING),\n                (TreacherousTerrain(\"Emyn Muil rocks\"), WOUNDED),\n            ],\n            id=\"frodo-saga-resists-ring-twice-then-wounded\",\n        ),\n        pytest.param(\n            Samwise(),\n            [\n                (RingTemptation(\"Ring offers a garden\"), ADVENTURING),\n                (TreacherousTerrain(\"Stairs of Cirith Ungol\"), WOUNDED),\n            ],\n            id=\"samwise-saga-resists-ring-then-wounded\",\n        ),\n    ],\n)\ndef test_multi_peril_saga(character, perils_and_states):\n    \"\"\"Characters face a sequence of perils — each step checked.\"\"\"\n    sm = FellowshipQuest(listeners=[character])\n    for peril, expected in perils_and_states:\n        sm.send(\"face_peril\", peril=peril)\n        assert sm.configuration == {_state_by_name(sm, expected)}\n\n\n# ---------------------------------------------------------------------------\n# Tests — wounded then second peril (always fatal)\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.timeout(5)\n@pytest.mark.parametrize(\n    (\"character\", \"first_peril\", \"second_peril\"),\n    [\n        pytest.param(\n            Aragorn(),\n            OrcAmbush(\"First wave\"),\n            OrcAmbush(\"Second wave\"),\n            id=\"aragorn-wounded-then-falls\",\n        ),\n        pytest.param(\n            Boromir(),\n            OrcAmbush(\"Lurtz's arrows\"),\n            RingTemptation(\"The Ring in his final moments\"),\n            id=\"boromir-wounded-then-corrupted-by-ring-but-falls\",\n        ),\n        pytest.param(\n            Legolas(),\n            TreacherousTerrain(\"Ice bridge cracks\"),\n            DarkSorcery(\"Shadow spell\"),\n            id=\"legolas-wounded-then-falls\",\n        ),\n    ],\n)\ndef test_wounded_then_second_peril_is_fatal(character, first_peril, second_peril):\n    \"\"\"A wounded character facing any second peril always falls —\n    no conditions on the wounded→fallen transition.\"\"\"\n    sm = FellowshipQuest(listeners=[character])\n    sm.send(\"face_peril\", peril=first_peril)\n    assert sm.configuration == {sm.wounded}\n\n    sm.send(\"face_peril_wounded\", peril=second_peril)\n    assert sm.configuration == {sm.fallen}\n\n\n# ---------------------------------------------------------------------------\n# Tests — recovery after wound\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.timeout(5)\n@pytest.mark.parametrize(\n    (\"character\", \"peril\"),\n    [\n        pytest.param(Aragorn(), TreacherousTerrain(\"Cliff fall\"), id=\"aragorn-recovers\"),\n        pytest.param(Gandalf(), OrcAmbush(\"Goblin arrow\"), id=\"gandalf-recovers\"),\n        pytest.param(Frodo(), TreacherousTerrain(\"Shelob's lair\"), id=\"frodo-recovers\"),\n    ],\n)\ndef test_recovery_after_wound(character, peril):\n    \"\"\"A wounded character can recover and reach a positive ending.\"\"\"\n    sm = FellowshipQuest(listeners=[character])\n    sm.send(\"face_peril\", peril=peril)\n    assert sm.configuration == {sm.wounded}\n\n    sm.send(\"recover\")\n    assert sm.configuration == {sm.healed}\n"
  },
  {
    "path": "tests/test_invoke.py",
    "content": "\"\"\"Tests for the invoke callback group.\"\"\"\n\nimport threading\nimport time\n\nfrom statemachine.invoke import IInvoke\nfrom statemachine.invoke import InvokeContext\nfrom statemachine.invoke import invoke_group\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass TestInvokeSimpleCallable:\n    \"\"\"Simple callable invoke — function runs in background, done.invoke fires.\"\"\"\n\n    async def test_simple_callable_invoke(self, sm_runner):\n        results = []\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: 42)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                results.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert results == [42]\n\n    async def test_invoke_return_value_in_done_event(self, sm_runner):\n        results = []\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: {\"key\": \"value\"})\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                results.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert results == [{\"key\": \"value\"}]\n\n\nclass TestInvokeNamingConvention:\n    \"\"\"Naming convention — on_invoke_<state>() method is discovered and invoked.\"\"\"\n\n    async def test_naming_convention(self, sm_runner):\n        invoked = []\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_invoke_loading(self, **kwargs):\n                invoked.append(True)\n                return \"done\"\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert invoked == [True]\n        assert \"ready\" in sm.configuration_values\n\n\nclass TestInvokeDecorator:\n    \"\"\"Decorator — @state.invoke handler.\"\"\"\n\n    async def test_decorator_invoke(self, sm_runner):\n        invoked = []\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            @loading.invoke\n            def do_work(self, **kwargs):\n                invoked.append(True)\n                return \"result\"\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert invoked == [True]\n        assert \"ready\" in sm.configuration_values\n\n\nclass TestInvokeIInvokeProtocol:\n    \"\"\"IInvoke protocol — class with run(ctx) method.\"\"\"\n\n    async def test_iinvoke_class(self, sm_runner):\n        \"\"\"Pass an IInvoke class — engine instantiates per SM instance.\"\"\"\n        results = []\n\n        class MyInvoker:\n            def run(self, ctx: InvokeContext):\n                results.append(ctx.state_id)\n                return \"invoker_result\"\n\n            def on_cancel(self):\n                pass  # no-op: only verifying the protocol is satisfied\n\n        assert isinstance(MyInvoker(), IInvoke)\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=MyInvoker)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                results.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"loading\" in results\n        assert \"invoker_result\" in results\n        assert \"ready\" in sm.configuration_values\n\n    async def test_each_sm_instance_gets_own_handler(self, sm_runner):\n        \"\"\"Each StateChart instance must get a fresh IInvoke instance.\"\"\"\n        handler_ids = []\n\n        class TrackingInvoker:\n            def run(self, ctx: InvokeContext):\n                handler_ids.append(id(self))\n                return None\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=TrackingInvoker)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n        sm1 = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm1)\n\n        sm2 = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm2)\n\n        assert len(handler_ids) == 2\n        assert handler_ids[0] != handler_ids[1], \"Each SM must get its own handler instance\"\n\n\nclass TestInvokeCancelOnExit:\n    \"\"\"Cancel on exit — ctx.cancelled is set when state is exited.\"\"\"\n\n    async def test_cancel_on_exit_sync(self):\n        \"\"\"Test cancel in sync mode only — uses threading.Event.wait().\"\"\"\n        from tests.conftest import SMRunner\n\n        sm_runner = SMRunner(is_async=False)\n        cancel_observed = []\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            cancelled_state = State(final=True)\n            cancel = loading.to(cancelled_state)\n\n            def on_invoke_loading(self, ctx=None, **kwargs):\n                if ctx is None:\n                    return\n                ctx.cancelled.wait(timeout=5.0)\n                cancel_observed.append(ctx.cancelled.is_set())\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.05)\n        await sm_runner.send(sm, \"cancel\")\n        await sm_runner.sleep(0.1)\n\n        assert cancel_observed == [True]\n        assert \"cancelled_state\" in sm.configuration_values\n\n    async def test_cancel_on_exit_with_on_cancel(self, sm_runner):\n        \"\"\"Test that on_cancel() is called when state is exited.\"\"\"\n        cancel_called = []\n\n        class CancelTracker:\n            def run(self, ctx):\n                while not ctx.cancelled.is_set():\n                    ctx.cancelled.wait(0.01)\n\n            def on_cancel(self):\n                cancel_called.append(True)\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=CancelTracker)\n            cancelled_state = State(final=True)\n            cancel = loading.to(cancelled_state)\n\n        sm = await sm_runner.start(SM)\n        # Give the invoke handler time to start in its background thread\n        await sm_runner.sleep(0.15)\n        await sm_runner.send(sm, \"cancel\")\n        await sm_runner.sleep(0.15)\n\n        assert cancel_called == [True]\n        assert \"cancelled_state\" in sm.configuration_values\n\n\nclass TestInvokeErrorHandling:\n    \"\"\"Error in invoker → error.execution event.\"\"\"\n\n    async def test_error_in_invoke(self, sm_runner):\n        errors = []\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: 1 / 0)\n            error_state = State(final=True)\n            error_execution = loading.to(error_state)\n\n            def on_enter_error_state(self, **kwargs):\n                errors.append(True)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert errors == [True]\n        assert \"error_state\" in sm.configuration_values\n\n\nclass TestInvokeMultiple:\n    \"\"\"Multiple invokes per state — all run concurrently.\"\"\"\n\n    async def test_multiple_invokes(self, sm_runner):\n        results = []\n        lock = threading.Lock()\n\n        def task_a():\n            with lock:\n                results.append(\"a\")\n            return \"a\"\n\n        def task_b():\n            with lock:\n                results.append(\"b\")\n            return \"b\"\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=invoke_group(task_a, task_b))\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        assert sorted(results) == [\"a\", \"b\"]\n\n\nclass TestInvokeStateChartChild:\n    \"\"\"StateChart as invoker — child machine runs, completion fires done event.\"\"\"\n\n    async def test_statechart_invoker(self, sm_runner):\n        class ChildMachine(StateChart):\n            start = State(initial=True)\n            end = State(final=True)\n            go = start.to(end)\n\n            def on_enter_start(self, **kwargs):\n                self.send(\"go\")\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=ChildMachine)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n\n\nclass TestDoneInvokeTransition:\n    \"\"\"done_invoke_<state> transition — naming convention works.\"\"\"\n\n    async def test_done_invoke_transition(self, sm_runner):\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: \"hello\")\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n\n\nclass TestDoneInvokeEventFormat:\n    \"\"\"done.invoke event name must be done.invoke.<state_id>.<platform_id> (no duplication).\"\"\"\n\n    async def test_done_invoke_event_has_no_duplicate_state_id(self, sm_runner):\n        received_events = []\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: \"ok\")\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, event=None, **kwargs):\n                if event is not None:\n                    received_events.append(str(event))\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert len(received_events) == 1\n        event_name = received_events[0]\n        # Must be \"done.invoke.loading.<hex>\" — NOT \"done.invoke.loading.loading.<hex>\"\n        assert event_name.startswith(\"done.invoke.loading.\")\n        parts = event_name.split(\".\")\n        # [\"done\", \"invoke\", \"loading\", \"<hex>\"] — exactly 4 parts\n        assert len(parts) == 4, f\"Expected 4 parts, got {parts}\"\n\n\nclass TestInvokeGroup:\n    \"\"\"invoke_group() — runs multiple callables concurrently, returns list of results.\"\"\"\n\n    async def test_group_returns_ordered_results(self, sm_runner):\n        \"\"\"Results are returned in the same order as the input callables.\"\"\"\n        results = []\n\n        def slow():\n            time.sleep(0.05)\n            return \"slow\"\n\n        def fast():\n            return \"fast\"\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=invoke_group(slow, fast))\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                results.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert results == [[\"slow\", \"fast\"]]\n\n    async def test_group_with_file_io(self, sm_runner, tmp_path):\n        \"\"\"Real I/O: read two files concurrently and get both results.\"\"\"\n        file_a = tmp_path / \"a.txt\"\n        file_b = tmp_path / \"b.txt\"\n        file_a.write_text(\"hello\")\n        file_b.write_text(\"world\")\n\n        results = []\n\n        class SM(StateChart):\n            loading = State(\n                initial=True,\n                invoke=invoke_group(\n                    lambda: file_a.read_text(),\n                    lambda: file_b.read_text(),\n                ),\n            )\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                results.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert results == [[\"hello\", \"world\"]]\n\n    async def test_group_error_cancels_remaining(self, sm_runner):\n        \"\"\"If one callable raises, error.execution is sent.\"\"\"\n        errors = []\n\n        def ok():\n            time.sleep(0.1)\n            return \"ok\"\n\n        def fail():\n            raise ValueError(\"boom\")\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=invoke_group(ok, fail))\n            error_state = State(final=True)\n            error_execution = loading.to(error_state)\n\n            def on_enter_error_state(self, **kwargs):\n                errors.append(True)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.3)\n        await sm_runner.processing_loop(sm)\n\n        assert \"error_state\" in sm.configuration_values\n        assert errors == [True]\n\n    async def test_group_cancel_on_exit(self, sm_runner):\n        \"\"\"Cancellation propagates: exiting state stops the group.\"\"\"\n        cancel_flag = threading.Event()\n\n        def slow_task():\n            # Use interruptible wait so thread can exit promptly on cancellation.\n            cancel_flag.wait(timeout=5.0)\n            return \"should not complete\"\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=invoke_group(slow_task))\n            stopped = State(final=True)\n            cancel = loading.to(stopped)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.05)\n        await sm_runner.send(sm, \"cancel\")\n        cancel_flag.set()  # Unblock the slow_task thread\n        await sm_runner.sleep(0.1)\n\n        assert \"stopped\" in sm.configuration_values\n\n    async def test_group_single_callable(self, sm_runner):\n        \"\"\"Edge case: group with a single callable still returns a list.\"\"\"\n        results = []\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=invoke_group(lambda: 42))\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                results.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert results == [[42]]\n\n    async def test_each_sm_instance_gets_own_group(self, sm_runner):\n        \"\"\"Each SM instance must get its own InvokeGroup — no shared state.\"\"\"\n        all_results = []\n\n        counter = {\"value\": 0}\n        lock = threading.Lock()\n\n        def counting_task():\n            with lock:\n                counter[\"value\"] += 1\n                return counter[\"value\"]\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=invoke_group(counting_task))\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                all_results.append(data)\n\n        sm1 = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm1)\n\n        sm2 = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm2)\n\n        assert len(all_results) == 2\n        assert all_results[0] == [1]\n        assert all_results[1] == [2]\n\n\nclass TestInvokeEventKwargs:\n    \"\"\"Event kwargs from send() are forwarded to invoke handlers.\"\"\"\n\n    async def test_plain_callable_receives_event_kwargs(self, sm_runner):\n        \"\"\"Plain callable invoke handler receives event kwargs via SignatureAdapter.\"\"\"\n        received = []\n\n        class SM(StateChart):\n            idle = State(initial=True)\n            loading = State()\n            ready = State(final=True)\n            start = idle.to(loading)\n            done_invoke_loading = loading.to(ready)\n\n            def on_invoke_loading(self, file_name=None, **kwargs):\n                received.append(file_name)\n                return f\"loaded:{file_name}\"\n\n            def on_enter_ready(self, data=None, **kwargs):\n                received.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.send(sm, \"start\", file_name=\"config.json\")\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert received == [\"config.json\", \"loaded:config.json\"]\n\n    async def test_iinvoke_handler_receives_event_kwargs_via_ctx(self, sm_runner):\n        \"\"\"IInvoke handler receives event kwargs via ctx.kwargs.\"\"\"\n        received = []\n\n        class FileLoader:\n            def run(self, ctx: InvokeContext):\n                received.append(ctx.kwargs.get(\"file_name\"))\n                return f\"loaded:{ctx.kwargs['file_name']}\"\n\n        class SM(StateChart):\n            idle = State(initial=True)\n            loading = State(invoke=FileLoader)\n            ready = State(final=True)\n            start = idle.to(loading)\n            done_invoke_loading = loading.to(ready)\n\n            def on_enter_ready(self, data=None, **kwargs):\n                received.append(data)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.send(sm, \"start\", file_name=\"data.csv\")\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        assert received == [\"data.csv\", \"loaded:data.csv\"]\n\n    async def test_initial_state_invoke_has_empty_kwargs(self, sm_runner):\n        \"\"\"Invoke on initial state gets empty kwargs (no triggering event).\"\"\"\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: 42)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n        assert \"ready\" in sm.configuration_values\n\n\nclass TestInvokeNotTriggeredOnNonInvokeState:\n    \"\"\"States without invoke handlers should not be affected.\"\"\"\n\n    async def test_no_invoke_on_plain_state(self, sm_runner):\n        class SM(StateChart):\n            idle = State(initial=True)\n            active = State()\n            done = State(final=True)\n\n            go = idle.to(active)\n            finish = active.to(done)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.send(sm, \"go\")\n        assert \"active\" in sm.configuration_values\n        await sm_runner.send(sm, \"finish\")\n        assert \"done\" in sm.configuration_values\n\n\nclass TestInvokeManagerCancelAll:\n    \"\"\"InvokeManager.cancel_all() cancels every active invocation.\"\"\"\n\n    async def test_cancel_all(self, sm_runner):\n        class SlowHandler:\n            def run(self, ctx):\n                ctx.cancelled.wait(timeout=5.0)\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=SlowHandler)\n            stopped = State(final=True)\n            cancel = loading.to(stopped)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        sm._engine._invoke_manager.cancel_all()\n        await sm_runner.sleep(0.15)\n\n        # All invocations should be terminated\n        for inv in sm._engine._invoke_manager._active.values():\n            assert inv.terminated\n\n\nclass TestInvokeCancelAlreadyTerminated:\n    \"\"\"Cancelling an already-terminated invocation is a no-op.\"\"\"\n\n    async def test_cancel_terminated_invocation(self, sm_runner):\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: 42)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n        # All invocations should be terminated by now\n        manager = sm._engine._invoke_manager\n        for inv in manager._active.values():\n            assert inv.terminated\n        # Calling cancel on terminated invocations should be a safe no-op\n        for inv_id in list(manager._active.keys()):\n            manager._cancel(inv_id)\n\n\nclass TestInvokeOnCancelException:\n    \"\"\"Exception in on_cancel() is caught and logged, not propagated.\"\"\"\n\n    async def test_on_cancel_exception_is_suppressed(self, sm_runner):\n        class BadCancelHandler:\n            def run(self, ctx):\n                ctx.cancelled.wait(timeout=5.0)\n\n            def on_cancel(self):\n                raise RuntimeError(\"on_cancel exploded\")\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=BadCancelHandler)\n            stopped = State(final=True)\n            cancel = loading.to(stopped)\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        # This should NOT raise even though on_cancel() raises\n        await sm_runner.send(sm, \"cancel\")\n        await sm_runner.sleep(0.15)\n\n        assert \"stopped\" in sm.configuration_values\n\n\nclass TestStateChartInvokerOnCancel:\n    \"\"\"StateChartInvoker.on_cancel() cleans up the child reference.\"\"\"\n\n    def test_on_cancel_clears_child(self):\n        from statemachine.invoke import StateChartInvoker\n\n        class ChildMachine(StateChart):\n            start = State(initial=True, final=True)\n\n        invoker = StateChartInvoker(ChildMachine)\n        ctx = InvokeContext(\n            invokeid=\"test.123\",\n            state_id=\"test\",\n            send=lambda *a, **kw: None,\n            machine=None,\n        )\n        invoker.run(ctx)\n        assert invoker._child is not None\n        invoker.on_cancel()\n        assert invoker._child is None\n\n\nclass TestNormalizeInvokeCallbacks:\n    \"\"\"normalize_invoke_callbacks handles edge cases.\"\"\"\n\n    def test_string_passes_through(self):\n        from statemachine.invoke import normalize_invoke_callbacks\n\n        result = normalize_invoke_callbacks(\"some_method_name\")\n        assert result == [\"some_method_name\"]\n\n    def test_already_wrapped_passes_through(self):\n        from statemachine.invoke import _InvokeCallableWrapper\n        from statemachine.invoke import normalize_invoke_callbacks\n\n        class MyHandler:\n            def run(self, ctx):\n                pass\n\n        wrapper = _InvokeCallableWrapper(MyHandler)\n        result = normalize_invoke_callbacks(wrapper)\n        assert len(result) == 1\n        assert result[0] is wrapper\n\n    def test_iinvoke_class_with_run_method(self):\n        \"\"\"IInvoke-compatible class gets wrapped.\"\"\"\n        from statemachine.invoke import _InvokeCallableWrapper\n        from statemachine.invoke import normalize_invoke_callbacks\n\n        class CustomHandler:\n            def run(self, ctx):\n                return \"result\"\n\n        # CustomHandler satisfies IInvoke protocol (has run method)\n        assert isinstance(CustomHandler(), IInvoke)\n        result = normalize_invoke_callbacks(CustomHandler)\n        assert len(result) == 1\n        assert isinstance(result[0], _InvokeCallableWrapper)\n\n    def test_plain_callable_passes_through(self):\n        from statemachine.invoke import _InvokeCallableWrapper\n        from statemachine.invoke import normalize_invoke_callbacks\n\n        def my_func():\n            return 42\n\n        result = normalize_invoke_callbacks(my_func)\n        assert len(result) == 1\n        assert result[0] is my_func\n        assert not isinstance(result[0], _InvokeCallableWrapper)\n\n    def test_non_invoke_class_passes_through(self):\n        \"\"\"A class without run() (not IInvoke, not StateChart) passes through unwrapped.\"\"\"\n        from statemachine.invoke import _InvokeCallableWrapper\n        from statemachine.invoke import normalize_invoke_callbacks\n\n        class PlainClass:\n            pass\n\n        result = normalize_invoke_callbacks(PlainClass)\n        assert len(result) == 1\n        assert result[0] is PlainClass\n        assert not isinstance(result[0], _InvokeCallableWrapper)\n\n\nclass TestResolveHandler:\n    \"\"\"InvokeManager._resolve_handler edge cases.\"\"\"\n\n    def test_bare_iinvoke_instance(self):\n        from statemachine.invoke import InvokeManager\n\n        class MyHandler:\n            def run(self, ctx):\n                return \"result\"\n\n        handler = MyHandler()\n        assert isinstance(handler, IInvoke)\n        resolved = InvokeManager._resolve_handler(handler)\n        assert resolved is handler\n\n    def test_bare_statechart_class(self):\n        from statemachine.invoke import InvokeManager\n        from statemachine.invoke import StateChartInvoker\n\n        class ChildMachine(StateChart):\n            start = State(initial=True, final=True)\n\n        resolved = InvokeManager._resolve_handler(ChildMachine)\n        assert isinstance(resolved, StateChartInvoker)\n\n    def test_plain_callable_returns_none(self):\n        from statemachine.invoke import InvokeManager\n\n        def my_func():\n            return 42\n\n        assert InvokeManager._resolve_handler(my_func) is None\n\n\nclass TestInvokeCallableWrapperOnCancel:\n    \"\"\"_InvokeCallableWrapper.on_cancel() edge cases.\"\"\"\n\n    def test_on_cancel_non_class_instance_with_on_cancel(self):\n        \"\"\"Non-class handler (already instantiated) delegates on_cancel.\"\"\"\n        from statemachine.invoke import _InvokeCallableWrapper\n\n        cancel_called = []\n\n        class MyHandler:\n            def run(self, ctx):\n                return \"result\"\n\n            def on_cancel(self):\n                cancel_called.append(True)\n\n        handler = MyHandler()\n        wrapper = _InvokeCallableWrapper(handler)\n        # _instance is None, _is_class is False → falls through to _invoke_handler\n        wrapper.on_cancel()\n        assert cancel_called == [True]\n\n    def test_on_cancel_class_not_yet_instantiated(self):\n        \"\"\"Class handler not yet instantiated — on_cancel is a no-op.\"\"\"\n        from statemachine.invoke import _InvokeCallableWrapper\n\n        class MyHandler:\n            def run(self, ctx):\n                return \"result\"\n\n            def on_cancel(self):\n                raise RuntimeError(\"should not be called\")\n\n        wrapper = _InvokeCallableWrapper(MyHandler)\n        # _instance is None, _is_class is True → early return\n        wrapper.on_cancel()  # should not raise\n\n    def test_callable_wrapper_call_returns_handler(self):\n        \"\"\"__call__ returns the original handler (used by callback system for resolution).\"\"\"\n        from statemachine.invoke import _InvokeCallableWrapper\n\n        class MyHandler:\n            def run(self, ctx):\n                return \"result\"\n\n        wrapper = _InvokeCallableWrapper(MyHandler)\n        assert wrapper() is MyHandler\n\n\nclass TestInvokeGroupOnCancelBeforeRun:\n    \"\"\"InvokeGroup.on_cancel() before run() is a safe no-op.\"\"\"\n\n    def test_on_cancel_before_run(self):\n        group = invoke_group(lambda: 1)\n        # on_cancel before run — executor is None, no futures\n        group.on_cancel()\n\n\nclass TestDoneInvokeEventFactory:\n    \"\"\"done_invoke_ prefix works with both TransitionList and Event.\"\"\"\n\n    async def test_done_invoke_with_event_object(self, sm_runner):\n        \"\"\"Event() object with done_invoke_ prefix should match done.invoke events.\"\"\"\n\n        class SM(StateChart):\n            loading = State(initial=True, invoke=lambda: \"result\")\n            ready = State(final=True)\n            done_invoke_loading = Event(loading.to(ready))\n\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n\n        assert \"ready\" in sm.configuration_values\n\n\nclass TestVisitNoCallbacks:\n    \"\"\"visit/async_visit with no registered callbacks is a no-op.\"\"\"\n\n    def test_visit_missing_key(self):\n        from statemachine.callbacks import CallbacksRegistry\n\n        registry = CallbacksRegistry()\n        # Should not raise — just returns\n        registry.visit(\"nonexistent_key\", lambda cb, **kw: None)\n\n    async def test_async_visit_missing_key(self):\n        from statemachine.callbacks import CallbacksRegistry\n\n        registry = CallbacksRegistry()\n        await registry.async_visit(\"nonexistent_key\", lambda cb, **kw: None)\n\n\nclass TestAsyncVisitAwaitable:\n    \"\"\"async_visit should await the visitor_fn result when it is awaitable.\"\"\"\n\n    async def test_async_visitor_fn_is_awaited(self):\n        from statemachine.callbacks import CallbackGroup\n        from statemachine.callbacks import CallbacksExecutor\n        from statemachine.callbacks import CallbackSpec\n\n        visited = []\n\n        async def async_visitor(callback, **kwargs):\n            visited.append(str(callback))\n\n        executor = CallbacksExecutor()\n        spec = CallbackSpec(\"dummy\", group=CallbackGroup.INVOKE, is_convention=True)\n        executor.add(\"test_key\", spec, lambda: lambda **kw: True)\n\n        await executor.async_visit(async_visitor)\n        assert visited == [\"dummy\"]\n\n\nclass TestIInvokeProtocolRun:\n    \"\"\"IInvoke.run() protocol method can be called on a concrete implementation.\"\"\"\n\n    def test_protocol_run_is_callable(self):\n        \"\"\"Verify that calling run() on a concrete IInvoke instance works.\"\"\"\n\n        class ConcreteInvoker:\n            def run(self, ctx):\n                return \"concrete_result\"\n\n        invoker: IInvoke = ConcreteInvoker()\n        result = invoker.run(None)\n        assert result == \"concrete_result\"\n\n\nclass TestSpawnPendingAsyncEmpty:\n    \"\"\"spawn_pending_async with nothing pending is a no-op.\"\"\"\n\n    async def test_spawn_pending_async_no_pending(self, sm_runner):\n        class SM(StateChart):\n            idle = State(initial=True)\n            active = State(final=True)\n            go = idle.to(active)\n\n        sm = await sm_runner.start(SM)\n        # Directly call spawn_pending_async with empty pending list\n        await sm._engine._invoke_manager.spawn_pending_async()\n\n\nclass TestInvokeAsyncCancelledDuringExecution:\n    \"\"\"Async handler completes or errors after state was already exited.\"\"\"\n\n    async def test_success_after_cancel(self):\n        \"\"\"Handler returns successfully but ctx.cancelled is already set.\"\"\"\n        from tests.conftest import SMRunner\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            stopped = State(final=True)\n            cancel = loading.to(stopped)\n\n            def on_invoke_loading(self, ctx=None, **kwargs):\n                if ctx is None:\n                    return\n                # Simulate: cancelled is set during execution but we still return\n                ctx.cancelled.set()\n                return \"should_be_ignored\"\n\n        sm_runner = SMRunner(is_async=True)\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        # The done.invoke event should NOT have been sent (cancelled)\n        assert \"loading\" in sm.configuration_values\n\n    async def test_error_after_cancel(self):\n        \"\"\"Handler raises but ctx.cancelled is already set — error is swallowed.\"\"\"\n        from tests.conftest import SMRunner\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            error_state = State(final=True)\n            error_execution = loading.to(error_state)\n\n            def on_invoke_loading(self, ctx=None, **kwargs):\n                if ctx is None:\n                    return\n                # Simulate: cancelled during execution, then error\n                ctx.cancelled.set()\n                raise ValueError(\"should be ignored\")\n\n        sm_runner = SMRunner(is_async=True)\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        # The error.execution event should NOT have been sent (cancelled)\n        assert \"loading\" in sm.configuration_values\n\n\nclass TestSyncInvokeErrorAfterCancel:\n    \"\"\"Sync handler errors after state was already exited.\"\"\"\n\n    async def test_sync_error_after_cancel(self):\n        \"\"\"Sync handler raises but ctx.cancelled is set — error.execution not sent.\"\"\"\n        from tests.conftest import SMRunner\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            error_state = State(final=True)\n            error_execution = loading.to(error_state)\n\n            def on_invoke_loading(self, ctx=None, **kwargs):\n                if ctx is None:\n                    return\n                ctx.cancelled.set()\n                raise ValueError(\"should be ignored\")\n\n        sm_runner = SMRunner(is_async=False)\n        sm = await sm_runner.start(SM)\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n\n        assert \"loading\" in sm.configuration_values\n\n\nclass TestInvokeManagerUnit:\n    \"\"\"Unit tests for InvokeManager methods not exercised by integration tests.\"\"\"\n\n    def test_send_to_child_not_found(self):\n        \"\"\"send_to_child returns False when invokeid is not in _active.\"\"\"\n        from unittest.mock import Mock\n\n        from statemachine.invoke import InvokeManager\n\n        engine = Mock()\n        manager = InvokeManager(engine)\n\n        assert manager.send_to_child(\"nonexistent\", \"event\") is False\n\n    def test_send_to_child_handler_without_on_event(self):\n        \"\"\"send_to_child returns False when handler has no on_event.\"\"\"\n        from unittest.mock import Mock\n\n        from statemachine.invoke import Invocation\n        from statemachine.invoke import InvokeContext\n        from statemachine.invoke import InvokeManager\n\n        engine = Mock()\n        manager = InvokeManager(engine)\n\n        handler = Mock(spec=[])  # no on_event\n        ctx = InvokeContext(invokeid=\"test_id\", state_id=\"s1\", send=Mock(), machine=Mock())\n        inv = Invocation(invokeid=\"test_id\", state_id=\"s1\", ctx=ctx, _handler=handler)\n        manager._active[\"test_id\"] = inv\n\n        assert manager.send_to_child(\"test_id\", \"event\") is False\n\n    def test_handle_external_event_none_event(self):\n        \"\"\"handle_external_event returns early when event is None.\"\"\"\n        from unittest.mock import Mock\n\n        from statemachine.invoke import InvokeManager\n\n        engine = Mock()\n        manager = InvokeManager(engine)\n\n        trigger_data = Mock(event=None)\n        # Should not raise\n        manager.handle_external_event(trigger_data)\n\n\nclass TestStopChildMachine:\n    \"\"\"Tests for _stop_child_machine.\"\"\"\n\n    def test_stop_child_machine_exception_swallowed(self):\n        \"\"\"_stop_child_machine swallows exceptions during stop.\"\"\"\n        from unittest.mock import Mock\n\n        from statemachine.invoke import _stop_child_machine\n\n        child = Mock()\n        child._engine.running = True\n        child._engine._invoke_manager.cancel_all.side_effect = RuntimeError(\"boom\")\n\n        # Should not raise\n        _stop_child_machine(child)\n\n\nclass TestEngineDelCleanup:\n    \"\"\"Test BaseEngine.__del__ cancel_all exception handling.\"\"\"\n\n    def test_del_swallows_cancel_all_exception(self):\n        \"\"\"__del__ swallows exceptions from cancel_all.\"\"\"\n\n        class SM(StateChart):\n            s1 = State(initial=True, final=True)\n\n        sm = SM()\n        engine = sm._engine\n        engine._invoke_manager.cancel_all = lambda: (_ for _ in ()).throw(RuntimeError(\"boom\"))\n\n        # Should not raise\n        engine.__del__()\n"
  },
  {
    "path": "tests/test_io.py",
    "content": "\"\"\"Tests for statemachine.io module (dictionary-based state machine definitions).\"\"\"\n\nfrom statemachine.io import _parse_history\nfrom statemachine.io import create_machine_class_from_definition\n\n\nclass TestParseHistory:\n    def test_history_without_transitions(self):\n        \"\"\"History state with no 'on' or 'transitions' keys.\"\"\"\n        states_instances, events_definitions = _parse_history({\"h1\": {\"type\": \"shallow\"}})\n        assert \"h1\" in states_instances\n        assert states_instances[\"h1\"].type.value == \"shallow\"\n        assert events_definitions == {}\n\n    def test_history_with_on_only(self):\n        \"\"\"History state with 'on' events but no 'transitions' key.\"\"\"\n        states_instances, events_definitions = _parse_history(\n            {\"h1\": {\"type\": \"deep\", \"on\": {\"restore\": [{\"target\": \"s1\"}]}}}\n        )\n        assert \"h1\" in states_instances\n        assert \"h1\" in events_definitions\n        assert \"restore\" in events_definitions[\"h1\"]\n\n\nclass TestCreateMachineWithEventNameConcat:\n    def test_transition_with_both_parent_and_own_event_name(self):\n        \"\"\"Transition inside 'on' dict that also has its own 'event' key concatenates names.\"\"\"\n        sm_cls = create_machine_class_from_definition(\n            \"TestMachine\",\n            states={\n                \"s1\": {\n                    \"initial\": True,\n                    \"on\": {\n                        \"parent_evt\": [\n                            {\"target\": \"s2\", \"event\": \"sub_evt\"},\n                        ],\n                    },\n                },\n                \"s2\": {\"final\": True},\n            },\n        )\n        sm = sm_cls()\n        # The concatenated event name \"parent_evt sub_evt\" gets split into two events\n        event_ids = sorted(e.id for e in sm.events)\n        assert \"parent_evt\" in event_ids\n        assert \"sub_evt\" in event_ids\n"
  },
  {
    "path": "tests/test_listener.py",
    "content": "from statemachine.state import State\nfrom statemachine.statemachine import StateChart\n\nEXPECTED_LOG_ADD = \"\"\"Frodo on: draft--(add_job)-->draft\nFrodo enter: draft from add_job\nFrodo on: draft--(produce)-->producing\nFrodo enter: producing from produce\n\"\"\"\n\nEXPECTED_LOG_CREATION = \"\"\"Frodo enter: draft from __initial__\nFrodo on: draft--(add_job)-->draft\nFrodo enter: draft from add_job\nFrodo on: draft--(produce)-->producing\nFrodo enter: producing from produce\n\"\"\"\n\n\nclass TestObserver:\n    def test_add_log_observer(self, campaign_machine, capsys):\n        class LogObserver:\n            def __init__(self, name):\n                self.name = name\n\n            def on_transition(self, event, state, target):\n                print(f\"{self.name} on: {state.id}--({event})-->{target.id}\")\n\n            def on_enter_state(self, target, event):\n                print(f\"{self.name} enter: {target.id} from {event}\")\n\n        sm = campaign_machine()\n\n        sm.add_listener(LogObserver(\"Frodo\"))\n\n        sm.add_job()\n        sm.produce()\n\n        captured = capsys.readouterr()\n        assert captured.out == EXPECTED_LOG_ADD\n\n    def test_log_observer_on_creation(self, campaign_machine, capsys):\n        class LogObserver:\n            def __init__(self, name):\n                self.name = name\n\n            def on_transition(self, event, state, target):\n                print(f\"{self.name} on: {state.id}--({event})-->{target.id}\")\n\n            def on_enter_state(self, target, event):\n                print(f\"{self.name} enter: {target.id} from {event}\")\n\n        sm = campaign_machine(listeners=[LogObserver(\"Frodo\")])\n\n        sm.add_job()\n        sm.produce()\n\n        captured = capsys.readouterr()\n        assert captured.out == EXPECTED_LOG_CREATION\n\n\ndef test_regression_456():\n    class TestListener:\n        def __init__(self):\n            pass\n\n    class MyMachine(StateChart):\n        first = State(\"FIRST\", initial=True)\n\n        second = State(\"SECOND\")\n\n        first_selected = second.to(first)\n\n        second_selected = first.to(second)\n\n        @first.exit\n        def exit_first(self) -> None:\n            print(\"exit SLEEPING\")\n\n    m = MyMachine()\n    m.add_listener(TestListener())\n\n    m.send(\"second_selected\")\n"
  },
  {
    "path": "tests/test_mermaid_renderer.py",
    "content": "from statemachine.contrib.diagram import MermaidGraphMachine\nfrom statemachine.contrib.diagram.model import ActionType\nfrom statemachine.contrib.diagram.model import DiagramAction\nfrom statemachine.contrib.diagram.model import DiagramGraph\nfrom statemachine.contrib.diagram.model import DiagramState\nfrom statemachine.contrib.diagram.model import DiagramTransition\nfrom statemachine.contrib.diagram.model import StateType\nfrom statemachine.contrib.diagram.renderers.mermaid import MermaidRenderer\nfrom statemachine.contrib.diagram.renderers.mermaid import MermaidRendererConfig\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass TestMermaidRendererSimple:\n    \"\"\"Basic MermaidRenderer tests with simple states.\"\"\"\n\n    def test_simple_states(self):\n        graph = DiagramGraph(\n            name=\"Simple\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"stateDiagram-v2\" in result\n        assert \"direction LR\" in result\n        assert \"[*] --> s1\" in result\n        assert \"s1 --> s2 : go\" in result\n\n    def test_initial_and_final(self):\n        graph = DiagramGraph(\n            name=\"InitFinal\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.FINAL),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"finish\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"[*] --> s1\" in result\n        assert \"s2 --> [*]\" in result\n\n    def test_custom_direction(self):\n        config = MermaidRendererConfig(direction=\"TB\")\n        graph = DiagramGraph(\n            name=\"TB\",\n            states=[DiagramState(id=\"a\", name=\"A\", type=StateType.REGULAR, is_initial=True)],\n        )\n        result = MermaidRenderer(config=config).render(graph)\n        assert \"direction TB\" in result\n\n    def test_state_name_differs_from_id(self):\n        graph = DiagramGraph(\n            name=\"Named\",\n            states=[\n                DiagramState(\n                    id=\"my_state\", name=\"My State\", type=StateType.REGULAR, is_initial=True\n                ),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert 'state \"My State\" as my_state' in result\n\n    def test_state_name_equals_id_no_declaration(self):\n        \"\"\"When name == id, no explicit state declaration is emitted.\"\"\"\n        graph = DiagramGraph(\n            name=\"NoDecl\",\n            states=[\n                DiagramState(id=\"s1\", name=\"s1\", type=StateType.REGULAR, is_initial=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert 'state \"s1\"' not in result\n\n\nclass TestMermaidRendererTransitions:\n    \"\"\"Transition rendering tests.\"\"\"\n\n    def test_transition_with_guards(self):\n        graph = DiagramGraph(\n            name=\"Guards\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\", guards=[\"is_ready\"]),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 --> s2 : go [is_ready]\" in result\n\n    def test_eventless_transition(self):\n        graph = DiagramGraph(\n            name=\"Eventless\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 --> s2\\n\" in result\n\n    def test_self_transition(self):\n        graph = DiagramGraph(\n            name=\"SelfLoop\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s1\"], event=\"tick\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 --> s1 : tick\" in result\n\n    def test_targetless_transition(self):\n        graph = DiagramGraph(\n            name=\"Targetless\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[], event=\"tick\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 --> s1 : tick\" in result\n\n    def test_multi_target_transition(self):\n        graph = DiagramGraph(\n            name=\"Multi\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n                DiagramState(id=\"s3\", name=\"S3\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\", \"s3\"], event=\"split\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 --> s2 : split\" in result\n        assert \"s1 --> s3 : split\" in result\n\n    def test_internal_transitions_skipped(self):\n        graph = DiagramGraph(\n            name=\"Internal\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s1\"], event=\"check\", is_internal=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 --> s1\" not in result\n\n    def test_initial_transitions_skipped(self):\n        graph = DiagramGraph(\n            name=\"InitTrans\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"\", is_initial=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        # Implicit initial transitions are NOT rendered as edges\n        assert \"s1 --> s2\" not in result\n\n\nclass TestMermaidRendererActiveState:\n    \"\"\"Active state highlighting tests.\"\"\"\n\n    def test_active_state_class(self):\n        graph = DiagramGraph(\n            name=\"Active\",\n            states=[\n                DiagramState(\n                    id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True, is_active=True\n                ),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"classDef active\" in result\n        assert \"s1:::active\" in result\n        assert \"s2:::active\" not in result\n\n    def test_no_active_state_no_classdef(self):\n        graph = DiagramGraph(\n            name=\"NoActive\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"classDef\" not in result\n\n    def test_active_fill_config(self):\n        config = MermaidRendererConfig(active_fill=\"#FF0000\", active_stroke=\"#000\")\n        graph = DiagramGraph(\n            name=\"CustomActive\",\n            states=[\n                DiagramState(\n                    id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True, is_active=True\n                ),\n            ],\n        )\n        result = MermaidRenderer(config=config).render(graph)\n        assert \"fill:#FF0000\" in result\n        assert \"stroke:#000\" in result\n\n\nclass TestMermaidRendererCompound:\n    \"\"\"Compound and parallel state tests.\"\"\"\n\n    def test_compound_state(self):\n        class SM(StateChart):\n            class parent(State.Compound, name=\"Parent\"):\n                child1 = State(initial=True)\n                child2 = State(final=True)\n                go = child1.to(child2)\n\n            start = State(initial=True)\n            end = State(final=True)\n\n            enter = start.to(parent)\n            finish = parent.to(end)\n\n        result = MermaidGraphMachine(SM).get_mermaid()\n        assert 'state \"Parent\" as parent {' in result\n        assert \"[*] --> child1\" in result\n        assert \"child1 --> child2 : Go\" in result\n        assert \"child2 --> [*]\" in result\n        assert \"start --> parent : Enter\" in result\n        assert \"parent --> end : Finish\" in result\n\n    def test_compound_no_duplicate_transitions(self):\n        \"\"\"Transitions inside compound states must not also appear at top level.\"\"\"\n\n        class SM(StateChart):\n            class parent(State.Compound, name=\"Parent\"):\n                child1 = State(initial=True)\n                child2 = State(final=True)\n                go = child1.to(child2)\n\n            start = State(initial=True)\n            enter = start.to(parent)\n\n        result = MermaidGraphMachine(SM).get_mermaid()\n        # \"child1 --> child2 : Go\" should appear exactly once (inside compound)\n        assert result.count(\"child1 --> child2 : Go\") == 1\n\n    def test_parallel_state(self):\n        class SM(StateChart):\n            class p(State.Parallel, name=\"Parallel\"):\n                class r1(State.Compound, name=\"Region1\"):\n                    a = State(initial=True)\n                    a_done = State(final=True)\n                    finish_a = a.to(a_done)\n\n                class r2(State.Compound, name=\"Region2\"):\n                    b = State(initial=True)\n                    b_done = State(final=True)\n                    finish_b = b.to(b_done)\n\n            start = State(initial=True)\n            begin = start.to(p)\n\n        result = MermaidGraphMachine(SM).get_mermaid()\n        assert 'state \"Parallel\" as p {' in result\n        assert \"--\" in result  # parallel separator\n\n    def test_parallel_redirects_compound_endpoints(self):\n        \"\"\"Transitions to/from compound states inside parallel regions are redirected\n        to the initial child (Mermaid workaround for mermaid-js/mermaid#4052).\"\"\"\n\n        class SM(StateChart):\n            class p(State.Parallel, name=\"Parallel\"):\n                class region1(State.Compound, name=\"Region1\"):\n                    idle = State(initial=True)\n\n                    class inner(State.Compound, name=\"Inner\"):\n                        working = State(initial=True)\n\n                    start = idle.to(inner)\n\n                class region2(State.Compound, name=\"Region2\"):\n                    x = State(initial=True)\n\n            begin = State(initial=True)\n            enter = begin.to(p)\n\n        result = MermaidGraphMachine(SM).get_mermaid()\n        # Inside parallel: compound endpoint redirected to initial child\n        assert \"idle --> working : Start\" in result\n        assert \"idle --> inner\" not in result\n\n    def test_compound_outside_parallel_not_redirected(self):\n        \"\"\"Compound states outside parallel regions keep direct transitions.\"\"\"\n\n        class SM(StateChart):\n            class parent(State.Compound, name=\"Parent\"):\n                child = State(initial=True)\n\n            start = State(initial=True)\n            end = State(final=True)\n            enter = start.to(parent)\n            leave = parent.to(end)\n\n        result = MermaidGraphMachine(SM).get_mermaid()\n        assert \"start --> parent : Enter\" in result\n        assert \"parent --> end : Leave\" in result\n\n    def test_nested_compound(self):\n        class SM(StateChart):\n            class outer(State.Compound, name=\"Outer\"):\n                class inner(State.Compound, name=\"Inner\"):\n                    deep = State(initial=True)\n                    deep_final = State(final=True)\n                    go_deep = deep.to(deep_final)\n\n                start_inner = State(initial=True)\n                to_inner = start_inner.to(inner)\n\n            begin = State(initial=True)\n            enter = begin.to(outer)\n\n        result = MermaidGraphMachine(SM).get_mermaid()\n        assert 'state \"Outer\" as outer {' in result\n        assert 'state \"Inner\" as inner {' in result\n\n\nclass TestMermaidRendererPseudoStates:\n    \"\"\"Pseudo-state rendering tests.\"\"\"\n\n    def test_history_shallow(self):\n        graph = DiagramGraph(\n            name=\"History\",\n            states=[\n                DiagramState(\n                    id=\"comp\",\n                    name=\"Comp\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    children=[\n                        DiagramState(id=\"h\", name=\"H\", type=StateType.HISTORY_SHALLOW),\n                        DiagramState(id=\"c1\", name=\"C1\", type=StateType.REGULAR, is_initial=True),\n                    ],\n                ),\n            ],\n            compound_state_ids={\"comp\"},\n        )\n        result = MermaidRenderer().render(graph)\n        assert 'state \"H\" as h' in result\n\n    def test_history_deep(self):\n        graph = DiagramGraph(\n            name=\"DeepHistory\",\n            states=[\n                DiagramState(\n                    id=\"comp\",\n                    name=\"Comp\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    children=[\n                        DiagramState(id=\"h\", name=\"H*\", type=StateType.HISTORY_DEEP),\n                        DiagramState(id=\"c1\", name=\"C1\", type=StateType.REGULAR, is_initial=True),\n                    ],\n                ),\n            ],\n            compound_state_ids={\"comp\"},\n        )\n        result = MermaidRenderer().render(graph)\n        assert 'state \"H*\" as h' in result\n\n    def test_choice_state(self):\n        graph = DiagramGraph(\n            name=\"Choice\",\n            states=[\n                DiagramState(id=\"ch\", name=\"ch\", type=StateType.CHOICE, is_initial=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"state ch <<choice>>\" in result\n\n    def test_fork_state(self):\n        graph = DiagramGraph(\n            name=\"Fork\",\n            states=[\n                DiagramState(id=\"fk\", name=\"fk\", type=StateType.FORK, is_initial=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"state fk <<fork>>\" in result\n\n    def test_join_state(self):\n        graph = DiagramGraph(\n            name=\"Join\",\n            states=[\n                DiagramState(id=\"jn\", name=\"jn\", type=StateType.JOIN, is_initial=True),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"state jn <<join>>\" in result\n\n\nclass TestMermaidRendererActions:\n    \"\"\"State action rendering tests.\"\"\"\n\n    def test_entry_exit_actions(self):\n        graph = DiagramGraph(\n            name=\"Actions\",\n            states=[\n                DiagramState(\n                    id=\"s1\",\n                    name=\"S1\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    actions=[\n                        DiagramAction(type=ActionType.ENTRY, body=\"setup\"),\n                        DiagramAction(type=ActionType.EXIT, body=\"cleanup\"),\n                    ],\n                ),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 : entry / setup\" in result\n        assert \"s1 : exit / cleanup\" in result\n\n    def test_internal_action(self):\n        graph = DiagramGraph(\n            name=\"InternalAction\",\n            states=[\n                DiagramState(\n                    id=\"s1\",\n                    name=\"S1\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    actions=[\n                        DiagramAction(type=ActionType.INTERNAL, body=\"tick / handle\"),\n                    ],\n                ),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 : tick / handle\" in result\n\n    def test_empty_internal_action_skipped(self):\n        graph = DiagramGraph(\n            name=\"EmptyInternal\",\n            states=[\n                DiagramState(\n                    id=\"s1\",\n                    name=\"S1\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    actions=[\n                        DiagramAction(type=ActionType.INTERNAL, body=\"\"),\n                    ],\n                ),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"s1 : \" not in result\n\n\nclass TestMermaidGraphMachine:\n    \"\"\"Tests for the MermaidGraphMachine facade.\"\"\"\n\n    def test_facade_returns_string(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = MermaidGraphMachine(TrafficLightMachine).get_mermaid()\n        assert isinstance(result, str)\n        assert \"stateDiagram-v2\" in result\n\n    def test_facade_callable(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        facade = MermaidGraphMachine(TrafficLightMachine)\n        assert facade() == facade.get_mermaid()\n\n    def test_facade_with_instance(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        result = MermaidGraphMachine(sm).get_mermaid()\n        assert \"green:::active\" in result\n\n    def test_facade_custom_config(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        class Custom(MermaidGraphMachine):\n            direction = \"TB\"\n            active_fill = \"#FF0000\"\n\n        sm = TrafficLightMachine()\n        result = Custom(sm).get_mermaid()\n        assert \"direction TB\" in result\n        assert \"fill:#FF0000\" in result\n\n\nclass TestMermaidRendererEdgeCases:\n    \"\"\"Edge case tests for coverage.\"\"\"\n\n    def test_compound_state_name_equals_id(self):\n        \"\"\"Compound state where name == id uses unquoted declaration.\"\"\"\n        graph = DiagramGraph(\n            name=\"NameId\",\n            states=[\n                DiagramState(\n                    id=\"comp\",\n                    name=\"comp\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    children=[\n                        DiagramState(id=\"c1\", name=\"C1\", type=StateType.REGULAR, is_initial=True),\n                    ],\n                ),\n            ],\n            compound_state_ids={\"comp\"},\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"state comp {\" in result\n        assert '\"comp\"' not in result\n\n    def test_active_compound_state(self):\n        \"\"\"Compound state that is active gets classDef.\"\"\"\n        graph = DiagramGraph(\n            name=\"ActiveComp\",\n            states=[\n                DiagramState(\n                    id=\"comp\",\n                    name=\"Comp\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    is_active=True,\n                    children=[\n                        DiagramState(id=\"c1\", name=\"C1\", type=StateType.REGULAR, is_initial=True),\n                    ],\n                ),\n            ],\n            compound_state_ids={\"comp\"},\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"comp:::active\" in result\n\n    def test_cross_scope_transition_rendered_at_parent(self):\n        \"\"\"Transition crossing compound boundaries is rendered at the parent scope.\"\"\"\n        graph = DiagramGraph(\n            name=\"CrossScope\",\n            states=[\n                DiagramState(\n                    id=\"comp\",\n                    name=\"Comp\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    children=[\n                        DiagramState(id=\"c1\", name=\"C1\", type=StateType.REGULAR, is_initial=True),\n                    ],\n                ),\n                DiagramState(id=\"outside\", name=\"Outside\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"c1\", targets=[\"outside\"], event=\"leave\"),\n            ],\n            compound_state_ids={\"comp\"},\n        )\n        result = MermaidRenderer().render(graph)\n        # c1 is inside comp, outside is at top level — the transition\n        # crosses the compound boundary and is rendered at the top scope.\n        assert \"c1 --> outside : leave\" in result\n        # It should NOT appear inside the compound block\n        lines = result.split(\"\\n\")\n        for line in lines:\n            if \"c1 --> outside\" in line:\n                # Should be at indent level 1 (top scope), not deeper\n                assert line.startswith(\"    c1\"), f\"Expected top-level indent, got: {line!r}\"\n\n    def test_cross_scope_to_history_state(self):\n        \"\"\"Transition from outside a compound to a history state inside it is rendered.\"\"\"\n        graph = DiagramGraph(\n            name=\"HistoryCross\",\n            states=[\n                DiagramState(\n                    id=\"process\",\n                    name=\"Process\",\n                    type=StateType.REGULAR,\n                    children=[\n                        DiagramState(\n                            id=\"step1\", name=\"Step1\", type=StateType.REGULAR, is_initial=True\n                        ),\n                        DiagramState(id=\"step2\", name=\"Step2\", type=StateType.REGULAR),\n                        DiagramState(id=\"h\", name=\"H\", type=StateType.HISTORY_SHALLOW),\n                    ],\n                ),\n                DiagramState(id=\"paused\", name=\"Paused\", type=StateType.REGULAR, is_initial=True),\n            ],\n            transitions=[\n                DiagramTransition(source=\"step1\", targets=[\"step2\"], event=\"advance\"),\n                DiagramTransition(source=\"process\", targets=[\"paused\"], event=\"pause\"),\n                DiagramTransition(source=\"paused\", targets=[\"h\"], event=\"resume\"),\n                DiagramTransition(source=\"paused\", targets=[\"process\"], event=\"begin\"),\n            ],\n            compound_state_ids={\"process\"},\n        )\n        result = MermaidRenderer().render(graph)\n        # The resume transition crosses the compound boundary\n        assert \"paused --> h : resume\" in result\n        # advance stays inside the compound\n        assert \"step1 --> step2 : advance\" in result\n        # pause and begin are at top level (both endpoints are top-level)\n        assert \"process --> paused : pause\" in result\n        assert \"paused --> process : begin\" in result\n\n    def test_no_initial_state(self):\n        \"\"\"Graph with no initial state omits [*] arrow.\"\"\"\n        graph = DiagramGraph(\n            name=\"NoInitial\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert \"[*]\" not in result\n\n    def test_duplicate_transition_rendered_once(self):\n        \"\"\"Duplicate transitions in the IR are rendered only once.\"\"\"\n        graph = DiagramGraph(\n            name=\"Dedup\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = MermaidRenderer().render(graph)\n        assert result.count(\"s1 --> s2 : go\") == 1\n\n    def test_compound_no_initial_child(self):\n        \"\"\"Compound state with no initial child omits internal [*] arrow.\"\"\"\n        graph = DiagramGraph(\n            name=\"NoInitChild\",\n            states=[\n                DiagramState(\n                    id=\"comp\",\n                    name=\"Comp\",\n                    type=StateType.REGULAR,\n                    is_initial=True,\n                    children=[\n                        DiagramState(id=\"c1\", name=\"C1\", type=StateType.REGULAR),\n                    ],\n                ),\n            ],\n            compound_state_ids={\"comp\"},\n        )\n        result = MermaidRenderer().render(graph)\n        # No [*] --> c1 inside the compound\n        lines = result.strip().split(\"\\n\")\n        inner_initial = [ln for ln in lines if \"[*] --> c1\" in ln]\n        assert len(inner_initial) == 0\n\n\nclass TestMermaidRendererIntegration:\n    \"\"\"Integration tests with real state machines.\"\"\"\n\n    def test_traffic_light(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        result = MermaidGraphMachine(TrafficLightMachine).get_mermaid()\n        assert \"green --> yellow : Cycle\" in result\n        assert \"yellow --> red : Cycle\" in result\n        assert \"red --> green : Cycle\" in result\n\n    def test_traffic_light_with_events(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        sm = TrafficLightMachine()\n        sm.send(\"cycle\")\n        result = MermaidGraphMachine(sm).get_mermaid()\n        assert \"yellow:::active\" in result\n"
  },
  {
    "path": "tests/test_mixins.py",
    "content": "import pytest\nfrom statemachine.mixins import MachineMixin\n\nfrom tests.models import MyModel\n\n\nclass MyMixedModel(MyModel, MachineMixin):\n    state_machine_name = \"tests.machines.workflow.campaign_machine.CampaignMachine\"\n\n\ndef test_mixin_should_instantiate_a_machine(campaign_machine):\n    model = MyMixedModel(state=\"draft\")\n    assert isinstance(model.statemachine, campaign_machine)\n    assert model.state == \"draft\"\n    assert model.statemachine.draft.is_active\n\n\ndef test_mixin_should_raise_exception_if_machine_class_does_not_exist():\n    class MyModelWithoutMachineName(MachineMixin):\n        pass\n\n    with pytest.raises(ValueError, match=\"None is not a valid state machine name\"):\n        MyModelWithoutMachineName()\n\n\ndef test_mixin_should_skip_init_for_django_historical_models():\n    \"\"\"Regression test for #551: MachineMixin fails in Django data migrations.\n\n    Django's ``apps.get_model()`` returns historical models with ``__module__ = '__fake__'``\n    that don't carry user-defined class attributes like ``state_machine_name``.\n    \"\"\"\n\n    # Simulate a Django historical model: __module__ is '__fake__' and\n    # state_machine_name is not set (falls back to None from MachineMixin).\n    HistoricalModel = type(\"HistoricalModel\", (MachineMixin,), {\"__module__\": \"__fake__\"})\n\n    instance = HistoricalModel()\n    assert not hasattr(instance, \"statemachine\")\n"
  },
  {
    "path": "tests/test_mock_compatibility.py",
    "content": "from statemachine import State\nfrom statemachine import StateChart\n\n\ndef test_minimal(mocker):\n    class Observer:\n        def on_enter_state(self, event, model, source, target, state): ...\n\n    obs = Observer()\n    on_enter_state = mocker.spy(obs, \"on_enter_state\")\n\n    class Machine(StateChart):\n        a = State(\"Init\", initial=True)\n        b = State(\"Fin\")\n\n        cycle = a.to(b) | b.to(a)\n\n    state = Machine().add_listener(obs)\n    assert state.a.is_active\n\n    state.cycle()\n\n    assert state.b.is_active\n    on_enter_state.assert_called_once()\n"
  },
  {
    "path": "tests/test_multiple_destinations.py",
    "content": "import pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\nfrom statemachine import exceptions\n\n\nclass Request:\n    def __init__(self, state=\"requested\"):\n        self.state = None\n        self._is_ok = False\n\n    def is_ok(self):\n        return self._is_ok\n\n\ndef test_transition_should_choose_final_state_on_multiple_possibilities(\n    approval_machine, current_time\n):\n    # given\n    model = Request()\n    machine = approval_machine(model)\n\n    model._is_ok = False\n\n    # when\n    assert machine.validate() == model\n\n    # then\n    assert model.rejected_at == current_time\n    assert machine.rejected.is_active\n\n    # given\n    model._is_ok = True\n\n    # when\n    assert machine.retry() == model\n\n    # then\n    assert model.rejected_at is None\n    assert machine.requested.is_active\n\n    # when\n    assert machine.validate() == model\n\n    # then\n    assert model.accepted_at == current_time\n    assert machine.accepted.is_active\n\n\ndef test_transition_to_first_that_executes_if_multiple_targets():\n    class ApprovalMachine(StateChart):\n        \"A workflow\"\n\n        requested = State(initial=True)\n        accepted = State(final=True)\n        rejected = State(final=True)\n\n        validate = requested.to(accepted, rejected)\n\n    machine = ApprovalMachine()\n\n    machine.validate()\n    assert machine.accepted.is_active\n\n\ndef test_do_not_transition_if_multiple_targets_with_guard():\n    def never_will_pass(event_data):\n        return False\n\n    class ApprovalMachine(StateChart):\n        \"A workflow\"\n\n        allow_event_without_transition = False\n        catch_errors_as_events = False\n\n        requested = State(initial=True)\n        accepted = State(final=True)\n        rejected = State(final=True)\n\n        validate = (\n            requested.to(accepted, cond=never_will_pass)\n            | requested.to(rejected, cond=\"also_never_will_pass\")\n            | requested.to(requested, cond=\"this_also_never_will_pass\")\n        )\n\n        @property\n        def also_never_will_pass(self):\n            return False\n\n        def this_also_never_will_pass(self, event_data):\n            return False\n\n    machine = ApprovalMachine()\n\n    with pytest.raises(exceptions.TransitionNotAllowed):\n        machine.validate()\n    assert machine.requested.is_active\n\n\ndef test_check_invalid_reference_to_conditions():\n    class ApprovalMachine(StateChart):\n        \"A workflow\"\n\n        catch_errors_as_events = False\n\n        requested = State(initial=True)\n        accepted = State(final=True)\n        rejected = State(final=True)\n\n        validate = requested.to(accepted, cond=\"not_found_condition\") | requested.to(rejected)\n\n    with pytest.raises(exceptions.InvalidDefinition):\n        ApprovalMachine()\n\n\ndef test_should_change_to_returned_state_on_multiple_target_with_combined_transitions():\n    class ApprovalMachine(StateChart):\n        \"A workflow\"\n\n        allow_event_without_transition = False\n        catch_errors_as_events = False\n\n        requested = State(initial=True)\n        accepted = State()\n        rejected = State()\n        completed = State(final=True)\n\n        validate = (\n            requested.to(accepted, cond=\"is_ok\") | requested.to(rejected) | accepted.to(completed)\n        )\n        retry = rejected.to(requested)\n\n        def on_validate(self, previous_configuration):\n            if self.accepted in previous_configuration and self.model.is_ok():\n                return \"congrats!\"\n\n    # given\n    model = Request()\n    machine = ApprovalMachine(model)\n\n    model._is_ok = False\n\n    # when\n    assert machine.validate() is None\n    # then\n    assert machine.rejected.is_active\n\n    # given\n    assert machine.retry() is None\n    assert machine.requested.is_active\n    model._is_ok = True\n\n    # when\n    assert machine.validate() is None\n    # then\n    assert machine.accepted.is_active\n\n    # when\n    assert machine.validate() == \"congrats!\"\n    # then\n    assert machine.completed.is_active\n\n    assert machine.is_terminated\n\n    with pytest.raises(exceptions.TransitionNotAllowed, match=\"Can't Validate when in Completed.\"):\n        assert machine.validate()\n\n\ndef test_transition_on_execute_should_be_called_with_run_syntax(approval_machine, current_time):\n    # given\n    model = Request()\n    machine = approval_machine(model)\n\n    model._is_ok = True\n\n    # when\n    assert machine.send(\"validate\") == model\n    # then\n    assert model.accepted_at == current_time\n    assert machine.accepted.is_active\n\n\ndef test_multiple_values_returned_with_multiple_targets():\n    class ApprovalMachine(StateChart):\n        \"A workflow\"\n\n        requested = State(initial=True)\n        accepted = State(final=True)\n        denied = State(final=True)\n\n        @requested.to(accepted, denied)\n        def validate(self):\n            return 1, 2\n\n    machine = ApprovalMachine()\n\n    assert machine.validate() == (\n        1,\n        2,\n    )\n\n\n@pytest.mark.parametrize(\n    (\"payment_failed\", \"expected_state\"),\n    [\n        (False, \"paid\"),\n        (True, \"failed\"),\n    ],\n)\ndef test_multiple_targets_using_or_starting_from_same_origin(payment_failed, expected_state):\n    class InvoiceStateMachine(StateChart):\n        catch_errors_as_events = False\n\n        unpaid = State(initial=True)\n        paid = State(final=True)\n        failed = State()\n\n        pay = unpaid.to(paid, unless=\"payment_success\") | failed.to(paid) | unpaid.to(failed)\n\n        def payment_success(self, event_data):\n            return payment_failed\n\n    invoice_fsm = InvoiceStateMachine()\n    invoice_fsm.pay()\n    assert invoice_fsm.current_state_value == expected_state\n\n\ndef test_order_control(OrderControl):\n    control = OrderControl()\n    assert control.add_to_order(3) == 3\n    assert control.add_to_order(7) == 10\n\n    control.receive_payment(4)\n    with pytest.raises(exceptions.TransitionNotAllowed):\n        control.process_order()\n\n    control.receive_payment(6)\n    control.process_order()\n\n    control.ship_order()\n    assert control.order_total == 10\n    assert control.payments == [4, 6]\n    assert control.completed.is_active\n"
  },
  {
    "path": "tests/test_profiling.py",
    "content": "import weakref\n\nimport pytest\n\nfrom statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\n\n# ---------------------------------------------------------------------------\n# Machines under test\n# ---------------------------------------------------------------------------\n\n\n# 1. Flat machine with model, guards, and listener callbacks (v1-style)\nclass OrderControl(StateChart):\n    allow_event_without_transition = False\n    catch_errors_as_events = False\n\n    waiting_for_payment = State(initial=True)\n    processing = State()\n    shipping = State()\n    completed = State(final=True)\n\n    add_to_order = waiting_for_payment.to(waiting_for_payment)\n    receive_payment = waiting_for_payment.to(\n        processing, cond=\"payments_enough\"\n    ) | waiting_for_payment.to(waiting_for_payment, unless=\"payments_enough\")\n    process_order = processing.to(shipping, cond=\"payment_received\")\n    ship_order = shipping.to(completed)\n\n\nclass Order:\n    def __init__(self):\n        self.order_total = 0\n        self.payments = []\n        self.payment_received = False\n        self.state_machine = OrderControl(model=weakref.proxy(self))\n\n    def payments_enough(self, amount):\n        return sum(self.payments) + amount >= self.order_total\n\n    def before_add_to_order(self, amount):\n        self.order_total += amount\n        return self.order_total\n\n    def on_receive_payment(self, amount):\n        self.payments.append(amount)\n        return self.payments\n\n    def after_receive_payment(self):\n        self.payment_received = True\n\n\n# 2. Compound (nested) states\nclass CompoundSC(StateChart):\n    class active(State.Compound, name=\"Active\"):\n        idle = State(initial=True)\n        working = State()\n        begin = idle.to(working)\n\n    off = State(initial=True)\n    done = State(final=True)\n\n    turn_on = off.to(active)\n    turn_off = active.to(done)\n\n\n# 3. Parallel regions\nclass ParallelSC(StateChart):\n    class both(State.Parallel, name=\"Both\"):\n        class left(State.Compound, name=\"Left\"):\n            l1 = State(initial=True)\n            l2 = State()\n            go_l = l1.to(l2)\n            back_l = l2.to(l1)\n\n        class right(State.Compound, name=\"Right\"):\n            r1 = State(initial=True)\n            r2 = State()\n            go_r = r1.to(r2)\n            back_r = r2.to(r1)\n\n    start = State(initial=True)\n    enter = start.to(both)\n\n\n# 4. Guards with boolean expressions\nclass GuardedSC(StateChart):\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State(final=True)\n\n    def check_a(self):\n        return True\n\n    def check_b(self):\n        return False\n\n    go = s1.to(s2, cond=\"check_a\") | s1.to(s3, cond=\"check_b\")\n    back = s2.to(s1)\n\n\n# 5. History states (shallow)\nclass HistoryShallowSC(StateChart):\n    class process(State.Compound, name=\"Process\"):\n        step1 = State(initial=True)\n        step2 = State()\n        advance = step1.to(step2)\n        h = HistoryState()\n\n    paused = State(initial=True)\n\n    pause = process.to(paused)\n    resume = paused.to(process.h)\n    begin = paused.to(process)\n\n\n# 6. Deep history with nested compound states\nclass DeepHistorySC(StateChart):\n    class outer(State.Compound, name=\"Outer\"):\n        class inner(State.Compound, name=\"Inner\"):\n            a = State(initial=True)\n            b = State()\n            go = a.to(b)\n            back = b.to(a)\n\n        start = State(initial=True)\n        enter_inner = start.to(inner)\n        h = HistoryState(type=\"deep\")\n\n    away = State(initial=True)\n\n    dive = away.to(outer)\n    leave = outer.to(away)\n    restore = away.to(outer.h)\n\n\n# 7. Many-transition stress machine (wide, not deep)\nclass ManyTransitionsSC(StateChart):\n    s1 = State(initial=True)\n    s2 = State()\n    s3 = State()\n    s4 = State()\n    s5 = State()\n\n    go_12 = s1.to(s2)\n    go_23 = s2.to(s3)\n    go_34 = s3.to(s4)\n    go_45 = s4.to(s5)\n    go_51 = s5.to(s1)\n    reset = s2.to(s1) | s3.to(s1) | s4.to(s1) | s5.to(s1)\n\n\n# ---------------------------------------------------------------------------\n# Helpers\n# ---------------------------------------------------------------------------\n\n\ndef create_order():\n    order = Order()\n    assert order.state_machine.waiting_for_payment.is_active\n\n\ndef add_to_order(sm, amount):\n    sm.add_to_order(amount)\n\n\n# ---------------------------------------------------------------------------\n# Benchmark: instance creation\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.slow()\nclass TestSetupPerformance:\n    \"\"\"Benchmark the cost of creating and activating state machine instances.\"\"\"\n\n    def test_flat_machine(self, benchmark):\n        benchmark.pedantic(create_order, rounds=10, iterations=1000)\n\n    def test_compound_machine(self, benchmark):\n        benchmark.pedantic(lambda: CompoundSC(), rounds=10, iterations=1000)\n\n    def test_parallel_machine(self, benchmark):\n        benchmark.pedantic(lambda: ParallelSC(), rounds=10, iterations=1000)\n\n    def test_guarded_machine(self, benchmark):\n        benchmark.pedantic(lambda: GuardedSC(), rounds=10, iterations=1000)\n\n    def test_history_machine(self, benchmark):\n        benchmark.pedantic(lambda: HistoryShallowSC(), rounds=10, iterations=1000)\n\n    def test_deep_history_machine(self, benchmark):\n        benchmark.pedantic(lambda: DeepHistorySC(), rounds=10, iterations=1000)\n\n\n# ---------------------------------------------------------------------------\n# Benchmark: event throughput\n# ---------------------------------------------------------------------------\n\n\n@pytest.mark.slow()\nclass TestEventPerformance:\n    \"\"\"Benchmark event processing (self-transitions and state changes).\"\"\"\n\n    def test_flat_self_transition(self, benchmark):\n        \"\"\"Self-transition on a flat machine with model/listener.\"\"\"\n        order = Order()\n        sm = order.state_machine\n        benchmark.pedantic(add_to_order, args=(sm, 1), rounds=10, iterations=1000)\n\n    def test_compound_enter_exit(self, benchmark):\n        \"\"\"Enter and exit a compound state repeatedly.\"\"\"\n\n        def cycle():\n            sm = CompoundSC()\n            sm.turn_on()\n            sm.begin()\n            sm.turn_off()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=500)\n\n    def test_parallel_region_events(self, benchmark):\n        \"\"\"Send events within parallel regions.\"\"\"\n        sm = ParallelSC()\n        sm.enter()\n\n        def cycle():\n            sm.go_l()\n            sm.go_r()\n            sm.back_l()\n            sm.back_r()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=500)\n\n    def test_guarded_transitions(self, benchmark):\n        \"\"\"Guard evaluation + transition selection.\"\"\"\n        sm = GuardedSC()\n\n        def cycle():\n            sm.go()\n            sm.back()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=1000)\n\n    def test_history_pause_resume(self, benchmark):\n        \"\"\"Shallow history: pause and resume compound state.\"\"\"\n        sm = HistoryShallowSC()\n        sm.begin()\n        sm.advance()\n\n        def cycle():\n            sm.pause()\n            sm.resume()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=500)\n\n    def test_deep_history_cycle(self, benchmark):\n        \"\"\"Deep history: leave and restore nested compound state.\"\"\"\n        sm = DeepHistorySC()\n        sm.dive()\n        sm.enter_inner()\n        sm.go()\n\n        def cycle():\n            sm.leave()\n            sm.restore()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=500)\n\n    def test_many_transitions_full_cycle(self, benchmark):\n        \"\"\"Traverse a 5-state ring (s1→s2→s3→s4→s5→s1).\"\"\"\n        sm = ManyTransitionsSC()\n\n        def cycle():\n            sm.go_12()\n            sm.go_23()\n            sm.go_34()\n            sm.go_45()\n            sm.go_51()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=500)\n\n    def test_many_transitions_reset(self, benchmark):\n        \"\"\"Composite event (|) selecting among multiple source states.\"\"\"\n        sm = ManyTransitionsSC()\n\n        def cycle():\n            sm.go_12()\n            sm.go_23()\n            sm.go_34()\n            sm.reset()\n\n        benchmark.pedantic(cycle, rounds=10, iterations=500)\n"
  },
  {
    "path": "tests/test_registry.py",
    "content": "from unittest import mock\n\nimport pytest\n\n\n@pytest.fixture()\ndef django_autodiscover_modules():\n    auto_discover_modules = mock.MagicMock()\n\n    with mock.patch(\"statemachine.registry.autodiscover_modules\", new=auto_discover_modules):\n        yield auto_discover_modules\n\n\ndef test_should_register_a_state_machine(caplog, django_autodiscover_modules):\n    from statemachine import State\n    from statemachine import StateMachine\n    from statemachine import registry\n\n    class CampaignMachine(StateMachine):\n        \"A workflow machine\"\n\n        draft = State(initial=True)\n        producing = State()\n\n        add_job = draft.to(draft) | producing.to(producing)\n        produce = draft.to(producing)\n\n    assert registry.get_machine_cls(\"tests.test_registry.CampaignMachine\") == CampaignMachine\n    assert \"CampaignMachine\" not in registry._REGISTRY\n\n\ndef test_load_modules_should_call_autodiscover_modules(django_autodiscover_modules):\n    from statemachine.registry import load_modules\n\n    # given\n    modules = [\"a\", \"c\", \"statemachine\", \"statemachines\"]\n\n    # when\n    load_modules(modules)\n\n    # then\n    django_autodiscover_modules.assert_has_calls(mock.call(m) for m in modules)\n"
  },
  {
    "path": "tests/test_rtc.py",
    "content": "import inspect\nfrom unittest import mock\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.fixture()\ndef chained_after_sm_class():  # noqa: C901\n    class ChainedSM(StateChart):\n        a = State(initial=True)\n        b = State()\n        c = State(final=True)\n\n        t1 = a.to(b, after=\"t1\") | b.to(c)\n\n        def __init__(self, *args, **kwargs):\n            self.spy = mock.Mock(side_effect=lambda x, **kwargs: x)\n            super().__init__(*args, **kwargs)\n\n        def before_t1(self, source: State, value: int = 0):\n            return self.spy(\"before_t1\", source=source.id, value=value)\n\n        def on_t1(self, source: State, value: int = 0):\n            return self.spy(\"on_t1\", source=source.id, value=value)\n\n        def after_t1(self, source: State, value: int = 0):\n            return self.spy(\"after_t1\", source=source.id, value=value)\n\n        def on_enter_state(self, state: State, source: State, value: int = 0):\n            return self.spy(\n                \"on_enter_state\",\n                state=state.id,\n                source=getattr(source, \"id\", None),\n                value=value,\n            )\n\n        def on_exit_state(self, state: State, source: State, value: int = 0):\n            return self.spy(\"on_exit_state\", state=state.id, source=source.id, value=value)\n\n    return ChainedSM\n\n\n@pytest.fixture()\ndef chained_on_sm_class():  # noqa: C901\n    class ChainedSM(StateChart):\n        s1 = State(initial=True)\n        s2 = State()\n        s3 = State()\n        s4 = State(final=True)\n\n        t1 = s1.to(s2)\n        t2a = s2.to(s2)\n        t2b = s2.to(s3)\n        t3 = s3.to(s4)\n\n        def __init__(self):\n            self.spy = mock.Mock()\n            super().__init__()\n\n        def on_t1(self):\n            return [self.t2a(), self.t2b(), self.send(\"t3\")]\n\n        def on_enter_state(self, event: str, state: State, source: State):\n            self.spy(\n                \"on_enter_state\",\n                event=event,\n                state=state.id,\n                source=getattr(source, \"id\", \"\"),\n            )\n\n        def on_exit_state(self, event: str, state: State, target: State):\n            self.spy(\"on_exit_state\", event=event, state=state.id, target=target.id)\n\n        def on_transition(self, event: str, source: State, target: State):\n            self.spy(\"on_transition\", event=event, source=source.id, target=target.id)\n            return event\n\n        def after_transition(self, event: str, source: State, target: State):\n            self.spy(\"after_transition\", event=event, source=source.id, target=target.id)\n\n    return ChainedSM\n\n\nclass TestChainedTransition:\n    @pytest.mark.parametrize(\n        (\"expected_calls\"),\n        [\n            [\n                mock.call(\"on_enter_state\", state=\"a\", source=\"\", value=0),\n                mock.call(\"before_t1\", source=\"a\", value=42),\n                mock.call(\"on_exit_state\", state=\"a\", source=\"a\", value=42),\n                mock.call(\"on_t1\", source=\"a\", value=42),\n                mock.call(\"on_enter_state\", state=\"b\", source=\"a\", value=42),\n                mock.call(\"after_t1\", source=\"a\", value=42),\n                mock.call(\"before_t1\", source=\"b\", value=42),\n                mock.call(\"on_exit_state\", state=\"b\", source=\"b\", value=42),\n                mock.call(\"on_t1\", source=\"b\", value=42),\n                mock.call(\"on_enter_state\", state=\"c\", source=\"b\", value=42),\n                mock.call(\"after_t1\", source=\"b\", value=42),\n            ],\n        ],\n    )\n    def test_should_allow_chaining_transitions_using_actions(\n        self, chained_after_sm_class, expected_calls\n    ):\n        sm = chained_after_sm_class()\n        sm.t1(value=42)\n\n        assert sm.c.is_active\n\n        assert sm.spy.call_args_list == expected_calls\n\n    @pytest.mark.parametrize(\n        (\"expected\"),\n        [\n            [\n                mock.call(\"on_enter_state\", event=\"__initial__\", state=\"s1\", source=\"\"),\n                mock.call(\"on_exit_state\", event=\"t1\", state=\"s1\", target=\"s2\"),\n                mock.call(\"on_transition\", event=\"t1\", source=\"s1\", target=\"s2\"),\n                mock.call(\"on_enter_state\", event=\"t1\", state=\"s2\", source=\"s1\"),\n                mock.call(\"after_transition\", event=\"t1\", source=\"s1\", target=\"s2\"),\n                mock.call(\"on_exit_state\", event=\"t2a\", state=\"s2\", target=\"s2\"),\n                mock.call(\"on_transition\", event=\"t2a\", source=\"s2\", target=\"s2\"),\n                mock.call(\"on_enter_state\", event=\"t2a\", state=\"s2\", source=\"s2\"),\n                mock.call(\"after_transition\", event=\"t2a\", source=\"s2\", target=\"s2\"),\n                mock.call(\"on_exit_state\", event=\"t2b\", state=\"s2\", target=\"s3\"),\n                mock.call(\"on_transition\", event=\"t2b\", source=\"s2\", target=\"s3\"),\n                mock.call(\"on_enter_state\", event=\"t2b\", state=\"s3\", source=\"s2\"),\n                mock.call(\"after_transition\", event=\"t2b\", source=\"s2\", target=\"s3\"),\n                mock.call(\"on_exit_state\", event=\"t3\", state=\"s3\", target=\"s4\"),\n                mock.call(\"on_transition\", event=\"t3\", source=\"s3\", target=\"s4\"),\n                mock.call(\"on_enter_state\", event=\"t3\", state=\"s4\", source=\"s3\"),\n                mock.call(\"after_transition\", event=\"t3\", source=\"s3\", target=\"s4\"),\n            ],\n        ],\n    )\n    def test_should_preserve_event_order(self, chained_on_sm_class, expected):\n        sm = chained_on_sm_class()\n\n        if inspect.isclass(expected) and issubclass(expected, Exception):\n            with pytest.raises(expected):\n                sm.send(\"t1\")\n        else:\n            assert sm.send(\"t1\") == [\"t1\", [None, None, None]]\n            assert sm.spy.call_args_list == expected\n\n\nclass TestAsyncEngineRTC:\n    @pytest.mark.parametrize(\n        (\"expected\"),\n        [\n            [\n                mock.call(\"on_enter_state\", event=\"__initial__\", state=\"s1\", source=\"\"),\n                mock.call(\"on_exit_state\", event=\"t1\", state=\"s1\", target=\"s2\"),\n                mock.call(\"on_transition\", event=\"t1\", source=\"s1\", target=\"s2\"),\n                mock.call(\"on_enter_state\", event=\"t1\", state=\"s2\", source=\"s1\"),\n                mock.call(\"after_transition\", event=\"t1\", source=\"s1\", target=\"s2\"),\n                mock.call(\"on_exit_state\", event=\"t2a\", state=\"s2\", target=\"s2\"),\n                mock.call(\"on_transition\", event=\"t2a\", source=\"s2\", target=\"s2\"),\n                mock.call(\"on_enter_state\", event=\"t2a\", state=\"s2\", source=\"s2\"),\n                mock.call(\"after_transition\", event=\"t2a\", source=\"s2\", target=\"s2\"),\n                mock.call(\"on_exit_state\", event=\"t2b\", state=\"s2\", target=\"s3\"),\n                mock.call(\"on_transition\", event=\"t2b\", source=\"s2\", target=\"s3\"),\n                mock.call(\"on_enter_state\", event=\"t2b\", state=\"s3\", source=\"s2\"),\n                mock.call(\"after_transition\", event=\"t2b\", source=\"s2\", target=\"s3\"),\n                mock.call(\"on_exit_state\", event=\"t3\", state=\"s3\", target=\"s4\"),\n                mock.call(\"on_transition\", event=\"t3\", source=\"s3\", target=\"s4\"),\n                mock.call(\"on_enter_state\", event=\"t3\", state=\"s4\", source=\"s3\"),\n                mock.call(\"after_transition\", event=\"t3\", source=\"s3\", target=\"s4\"),\n            ],\n        ],\n    )\n    def test_should_preserve_event_order(self, expected):  # noqa: C901\n        class ChainedSM(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n            s4 = State(final=True)\n\n            t1 = s1.to(s2)\n            t2a = s2.to(s2)\n            t2b = s2.to(s3)\n            t3 = s3.to(s4)\n\n            def __init__(self):\n                self.spy = mock.Mock()\n                super().__init__()\n\n            async def on_t1(self):\n                return [await self.t2a(), await self.t2b(), await self.send(\"t3\")]\n\n            async def on_enter_state(self, event: str, state: State, source: State):\n                self.spy(\n                    \"on_enter_state\",\n                    event=event,\n                    state=state.id,\n                    source=getattr(source, \"id\", \"\"),\n                )\n\n            async def on_exit_state(self, event: str, state: State, target: State):\n                self.spy(\"on_exit_state\", event=event, state=state.id, target=target.id)\n\n            async def on_transition(self, event: str, source: State, target: State):\n                self.spy(\"on_transition\", event=event, source=source.id, target=target.id)\n                return event\n\n            async def after_transition(self, event: str, source: State, target: State):\n                self.spy(\"after_transition\", event=event, source=source.id, target=target.id)\n\n        sm = ChainedSM()\n\n        assert sm.send(\"t1\") == [\"t1\", [None, None, None]]\n        assert sm.spy.call_args_list == expected\n"
  },
  {
    "path": "tests/test_scxml_units.py",
    "content": "\"\"\"Unit tests for SCXML parser, actions, and schema modules.\"\"\"\n\nimport logging\nimport xml.etree.ElementTree as ET\nfrom unittest.mock import Mock\n\nimport pytest\nfrom statemachine.io.scxml.actions import EventDataWrapper\nfrom statemachine.io.scxml.actions import Log\nfrom statemachine.io.scxml.actions import ParseTime\nfrom statemachine.io.scxml.actions import create_action_callable\nfrom statemachine.io.scxml.actions import create_datamodel_action_callable\nfrom statemachine.io.scxml.invoke import SCXMLInvoker\nfrom statemachine.io.scxml.parser import parse_element\nfrom statemachine.io.scxml.parser import parse_scxml\nfrom statemachine.io.scxml.parser import strip_namespaces\nfrom statemachine.io.scxml.schema import CancelAction\nfrom statemachine.io.scxml.schema import DataModel\nfrom statemachine.io.scxml.schema import IfBranch\nfrom statemachine.io.scxml.schema import InvokeDefinition\nfrom statemachine.io.scxml.schema import LogAction\nfrom statemachine.io.scxml.schema import Param\n\n# --- ParseTime ---\n\n\nclass TestParseTimeErrors:\n    def test_invalid_milliseconds_value(self):\n        \"\"\"ParseTime raises ValueError for non-numeric milliseconds.\"\"\"\n        with pytest.raises(ValueError, match=\"Invalid time value\"):\n            ParseTime.time_in_ms(\"abcms\")\n\n    def test_invalid_seconds_value(self):\n        \"\"\"ParseTime raises ValueError for non-numeric seconds.\"\"\"\n        with pytest.raises(ValueError, match=\"Invalid time value\"):\n            ParseTime.time_in_ms(\"abcs\")\n\n    def test_invalid_unit(self):\n        \"\"\"ParseTime raises ValueError for values without recognized unit.\"\"\"\n        with pytest.raises(ValueError, match=\"Invalid time unit\"):\n            ParseTime.time_in_ms(\"abc\")\n\n\n# --- Parser ---\n\n\nclass TestStripNamespaces:\n    def test_removes_namespace_from_attributes(self):\n        \"\"\"strip_namespaces removes namespace prefixes from attribute names.\"\"\"\n        xml = '<root xmlns:ns=\"http://example.com\"><child ns:attr=\"value\"/></root>'\n        tree = ET.fromstring(xml)\n        strip_namespaces(tree)\n        child = tree.find(\"child\")\n        assert \"attr\" in child.attrib\n        assert child.attrib[\"attr\"] == \"value\"\n\n\nclass TestParseScxml:\n    def test_no_scxml_element_raises(self):\n        \"\"\"parse_scxml raises ValueError if no scxml element is found.\"\"\"\n        xml = \"<notscxml><state id='s1'/></notscxml>\"\n        with pytest.raises(ValueError, match=\"No scxml element found\"):\n            parse_scxml(xml)\n\n\nclass TestParseState:\n    def test_state_without_id_gets_auto_generated(self):\n        \"\"\"State element without id attribute gets an auto-generated id.\"\"\"\n        xml = '<scxml xmlns=\"http://www.w3.org/2005/07/scxml\"><state/></scxml>'\n        definition = parse_scxml(xml)\n        state_ids = list(definition.states.keys())\n        assert len(state_ids) == 1\n        assert state_ids[0].startswith(\"__auto_\")\n\n\nclass TestParseHistory:\n    def test_history_without_id_raises(self):\n        \"\"\"History element without id attribute raises ValueError.\"\"\"\n        xml = (\n            '<scxml xmlns=\"http://www.w3.org/2005/07/scxml\">'\n            '<state id=\"s1\"><history/></state>'\n            \"</scxml>\"\n        )\n        with pytest.raises(ValueError, match=\"History must have an 'id' attribute\"):\n            parse_scxml(xml)\n\n\nclass TestParseElement:\n    def test_unknown_tag_raises(self):\n        \"\"\"parse_element raises ValueError for an unrecognized tag.\"\"\"\n        element = ET.fromstring(\"<unknown_tag/>\")\n        with pytest.raises(ValueError, match=\"Unknown tag: unknown_tag\"):\n            parse_element(element)\n\n\nclass TestParseSendParam:\n    def test_param_without_expr_or_location_raises(self):\n        \"\"\"Send param without expr or location raises ValueError.\"\"\"\n        xml = (\n            '<scxml xmlns=\"http://www.w3.org/2005/07/scxml\">'\n            '<state id=\"s1\">'\n            \"<onentry>\"\n            '<send event=\"test\"><param name=\"p1\"/></send>'\n            \"</onentry>\"\n            \"</state>\"\n            \"</scxml>\"\n        )\n        with pytest.raises(ValueError, match=\"Must specify\"):\n            parse_scxml(xml)\n\n\n# --- Actions ---\n\n\nclass TestCreateActionCallable:\n    def test_unknown_action_type_raises(self):\n        \"\"\"create_action_callable raises ValueError for unknown action types.\"\"\"\n        from statemachine.io.scxml.schema import Action\n\n        with pytest.raises(ValueError, match=\"Unknown action type\"):\n            create_action_callable(Action())\n\n\nclass TestLogAction:\n    def test_log_without_label(self, capsys):\n        \"\"\"Log action without label prints just the value.\"\"\"\n        action = LogAction(label=None, expr=\"42\")\n        log = Log(action)\n        log()  # \"42\" is a literal that evaluates without machine context\n        captured = capsys.readouterr()\n        assert \"42\" in captured.out\n\n\nclass TestCancelActionCallable:\n    def test_cancel_without_sendid_raises(self):\n        \"\"\"CancelAction without sendid or sendidexpr raises ValueError.\"\"\"\n        from statemachine.io.scxml.actions import create_cancel_action_callable\n\n        action = CancelAction(sendid=None, sendidexpr=None)\n        cancel = create_cancel_action_callable(action)\n        with pytest.raises(ValueError, match=\"must have either 'sendid' or 'sendidexpr'\"):\n            cancel(machine=None)\n\n\nclass TestCreateDatamodelCallable:\n    def test_empty_datamodel_returns_none(self):\n        \"\"\"create_datamodel_action_callable returns None for empty DataModel.\"\"\"\n        model = DataModel(data=[], scripts=[])\n        result = create_datamodel_action_callable(model)\n        assert result is None\n\n\n# --- Schema ---\n\n\nclass TestIfBranch:\n    def test_str_with_none_cond(self):\n        \"\"\"IfBranch.__str__ returns '<empty cond>' for None condition.\"\"\"\n        branch = IfBranch(cond=None)\n        assert str(branch) == \"<empty cond>\"\n\n    def test_str_with_cond(self):\n        \"\"\"IfBranch.__str__ returns the condition string.\"\"\"\n        branch = IfBranch(cond=\"x > 0\")\n        assert str(branch) == \"x > 0\"\n\n\n# --- SCXML integration tests for action edge cases ---\n\n\nclass TestSCXMLIfConditionError:\n    \"\"\"SCXML <if> with a condition that raises an error.\"\"\"\n\n    def test_if_condition_error_sends_error_execution(self):\n        \"\"\"When an <if> condition evaluation fails, error.execution is sent.\"\"\"\n        from statemachine.io.scxml.processor import SCXMLProcessor\n\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <onentry>\n              <if cond=\"undefined_var\">\n                <log label=\"unreachable\"/>\n              </if>\n            </onentry>\n            <transition event=\"error.execution\" target=\"error\"/>\n          </state>\n          <final id=\"error\"/>\n        </scxml>\n        \"\"\"\n        processor = SCXMLProcessor()\n        processor.parse_scxml(\"test_if_error\", scxml)\n        sm = processor.start()\n        assert sm.configuration == {sm.states_map[\"error\"]}\n\n\nclass TestSCXMLForeachArrayError:\n    \"\"\"SCXML <foreach> with an array expression that fails to evaluate.\"\"\"\n\n    def test_foreach_bad_array_raises(self):\n        \"\"\"<foreach> with invalid array expression raises ValueError.\"\"\"\n        from statemachine.io.scxml.processor import SCXMLProcessor\n\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <datamodel>\n            <data id=\"result\" expr=\"0\"/>\n          </datamodel>\n          <state id=\"s1\">\n            <onentry>\n              <foreach array=\"undefined_array\" item=\"x\">\n                <assign location=\"result\" expr=\"1\"/>\n              </foreach>\n            </onentry>\n            <transition event=\"error.execution\" target=\"error\"/>\n          </state>\n          <final id=\"error\"/>\n        </scxml>\n        \"\"\"\n        processor = SCXMLProcessor()\n        processor.parse_scxml(\"test_foreach_error\", scxml)\n        sm = processor.start()\n        # The foreach array eval raises, which gets caught by catch_errors_as_events\n        assert sm.configuration == {sm.states_map[\"error\"]}\n\n\nclass TestSCXMLParallelFinalState:\n    \"\"\"Test done.state detection when all regions of a parallel state complete.\"\"\"\n\n    def test_parallel_state_done_when_all_regions_final(self):\n        \"\"\"done.state fires when all regions of a parallel state are in final states.\"\"\"\n        from statemachine.io.scxml.processor import SCXMLProcessor\n\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"wrapper\">\n          <state id=\"wrapper\">\n            <parallel id=\"p1\">\n              <state id=\"r1\" initial=\"a\">\n                <state id=\"a\">\n                  <transition target=\"a_done\"/>\n                </state>\n                <final id=\"a_done\"/>\n              </state>\n              <state id=\"r2\" initial=\"b\">\n                <state id=\"b\">\n                  <transition target=\"b_done\"/>\n                </state>\n                <final id=\"b_done\"/>\n              </state>\n            </parallel>\n            <transition event=\"done.state.p1\" target=\"done\"/>\n          </state>\n          <final id=\"done\"/>\n        </scxml>\n        \"\"\"\n        processor = SCXMLProcessor()\n        processor.parse_scxml(\"test_parallel_final\", scxml)\n        sm = processor.start()\n        # Both regions auto-transition to final states, done.state.p1 fires\n        assert sm.states_map[\"done\"] in sm.configuration\n\n\nclass TestEventDataWrapperMultipleArgs:\n    \"\"\"EventDataWrapper.data returns tuple when trigger_data has multiple args.\"\"\"\n\n    def test_data_returns_tuple_for_multiple_args(self):\n        \"\"\"EventDataWrapper.data returns the args tuple when more than one positional arg.\"\"\"\n        from unittest.mock import Mock\n\n        from statemachine.io.scxml.actions import EventDataWrapper\n\n        trigger_data = Mock()\n        trigger_data.kwargs = {}\n        trigger_data.args = (1, 2, 3)\n        trigger_data.event = Mock(internal=True)\n        trigger_data.event.__str__ = lambda self: \"test\"\n        trigger_data.send_id = None\n\n        event_data = Mock()\n        event_data.trigger_data = trigger_data\n\n        wrapper = EventDataWrapper(event_data)\n        assert wrapper.data == (1, 2, 3)\n\n\nclass TestIfActionRaisesWithoutErrorOnExecution:\n    \"\"\"SCXML <if> condition error raises when catch_errors_as_events is False.\"\"\"\n\n    def test_if_condition_error_propagates_without_catch_errors_as_events(self):\n        \"\"\"<if> with failing condition raises when machine.catch_errors_as_events is False.\"\"\"\n        from statemachine.io.scxml.actions import create_if_action_callable\n        from statemachine.io.scxml.schema import IfAction\n        from statemachine.io.scxml.schema import IfBranch\n\n        action = IfAction(branches=[IfBranch(cond=\"undefined_var\")])\n        if_callable = create_if_action_callable(action)\n\n        machine = Mock()\n        machine.catch_errors_as_events = False\n        machine.model.__dict__ = {}\n\n        with pytest.raises(NameError, match=\"undefined_var\"):\n            if_callable(machine=machine)\n\n\nclass TestSCXMLSendWithParamNoExpr:\n    \"\"\"SCXML <send> with a param that has location but no expr.\"\"\"\n\n    def test_send_param_with_location_only(self):\n        \"\"\"<send> param with location only evaluates the location.\"\"\"\n        from statemachine.io.scxml.processor import SCXMLProcessor\n\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <datamodel>\n            <data id=\"myvar\" expr=\"42\"/>\n          </datamodel>\n          <state id=\"s1\">\n            <onentry>\n              <send event=\"done\" target=\"#_internal\">\n                <param name=\"val\" location=\"myvar\"/>\n              </send>\n            </onentry>\n            <transition event=\"done\" target=\"s2\"/>\n          </state>\n          <final id=\"s2\"/>\n        </scxml>\n        \"\"\"\n        processor = SCXMLProcessor()\n        processor.parse_scxml(\"test_send_param\", scxml)\n        sm = processor.start()\n        assert sm.configuration == {sm.states_map[\"s2\"]}\n\n\nclass TestSCXMLHistoryWithoutTransitions:\n    \"\"\"SCXML history state without default transitions.\"\"\"\n\n    def test_history_without_transitions(self):\n        \"\"\"History state without transitions is processed correctly.\"\"\"\n        from statemachine.io.scxml.processor import SCXMLProcessor\n\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\" initial=\"a\">\n            <history id=\"h1\" type=\"shallow\"/>\n            <state id=\"a\">\n              <transition event=\"go\" target=\"b\"/>\n            </state>\n            <state id=\"b\">\n              <transition event=\"back\" target=\"h1\"/>\n            </state>\n            <transition event=\"out\" target=\"s2\"/>\n          </state>\n          <state id=\"s2\">\n            <transition event=\"ret\" target=\"h1\"/>\n          </state>\n        </scxml>\n        \"\"\"\n        processor = SCXMLProcessor()\n        processor.parse_scxml(\"test_history_no_trans\", scxml)\n        sm = processor.start()\n        assert sm.states_map[\"a\"] in sm.configuration\n\n\n# --- SCXMLInvoker ---\n\n\ndef _make_invoker(definition=None, base_dir=None, register_child=None):\n    \"\"\"Helper to create an SCXMLInvoker with sensible defaults.\"\"\"\n    if definition is None:\n        definition = InvokeDefinition()\n    if base_dir is None:\n        base_dir = \"\"\n    if register_child is None:\n        register_child = Mock(return_value=Mock)\n    return SCXMLInvoker(\n        definition=definition,\n        base_dir=base_dir,\n        register_child=register_child,\n    )\n\n\nclass TestSCXMLInvoker:\n    def test_invalid_invoke_type_raises(self):\n        \"\"\"run() raises ValueError for unsupported invoke type.\"\"\"\n        defn = InvokeDefinition(\n            type=\"http://unsupported/type\",\n            content=\"<scxml/>\",\n        )\n        invoker = _make_invoker(definition=defn)\n        ctx = Mock()\n        model = Mock(spec=[])\n        ctx.machine = Mock(model=model)\n\n        with pytest.raises(ValueError, match=\"Unsupported invoke type\"):\n            invoker.run(ctx)\n\n    def test_no_content_resolved_raises(self):\n        \"\"\"run() raises ValueError when no src/content/srcexpr is provided.\"\"\"\n        defn = InvokeDefinition()  # no content, src, or srcexpr\n        invoker = _make_invoker(definition=defn)\n        ctx = Mock()\n        model = Mock(spec=[])\n        ctx.machine = Mock(model=model)\n\n        with pytest.raises(ValueError, match=\"No content resolved\"):\n            invoker.run(ctx)\n\n    def test_resolve_content_inline_xml(self):\n        \"\"\"_resolve_content returns inline XML content directly.\"\"\"\n        xml_content = '<scxml xmlns=\"http://www.w3.org/2005/07/scxml\"><final id=\"f\"/></scxml>'\n        defn = InvokeDefinition(content=xml_content)\n        invoker = _make_invoker(definition=defn)\n\n        result = invoker._resolve_content(Mock())\n        assert result == xml_content\n\n    def test_resolve_content_from_file(self, tmp_path):\n        \"\"\"_resolve_content reads content from src file path.\"\"\"\n        scxml_file = tmp_path / \"child.scxml\"\n        scxml_file.write_text(\"<scxml/>\")\n\n        defn = InvokeDefinition(src=\"child.scxml\")\n        invoker = _make_invoker(definition=defn, base_dir=str(tmp_path))\n\n        result = invoker._resolve_content(Mock())\n        assert result == \"<scxml/>\"\n\n    def test_evaluate_params_namelist_and_params(self):\n        \"\"\"_evaluate_params resolves both namelist variables and param elements.\"\"\"\n        defn = InvokeDefinition(\n            namelist=\"var1 var2\",\n            params=[Param(name=\"p1\", expr=\"42\")],\n        )\n        invoker = _make_invoker(definition=defn)\n\n        model = type(\"Model\", (), {\"var1\": \"a\", \"var2\": \"b\"})()\n        machine = Mock(model=model)\n\n        result = invoker._evaluate_params(machine)\n        assert result == {\"var1\": \"a\", \"var2\": \"b\", \"p1\": 42}\n\n    def test_on_cancel_clears_child(self):\n        \"\"\"on_cancel() sets _child to None.\"\"\"\n        invoker = _make_invoker()\n        invoker._child = Mock()\n\n        invoker.on_cancel()\n        assert invoker._child is None\n\n    def test_on_event_skips_terminated_child(self):\n        \"\"\"on_event() does not error when child is terminated.\"\"\"\n        invoker = _make_invoker()\n        child = Mock()\n        child.is_terminated = True\n        invoker._child = child\n\n        # Should not raise or call send\n        invoker.on_event(\"some.event\")\n        child.send.assert_not_called()\n\n    def test_on_finalize_without_block_is_noop(self):\n        \"\"\"on_finalize() does nothing when no finalize block is defined.\"\"\"\n        invoker = _make_invoker()\n        assert invoker._finalize_block is None\n\n        # Should not raise\n        trigger_data = Mock()\n        invoker.on_finalize(trigger_data)\n\n    def test_send_to_parent_warns_without_session(self, caplog):\n        \"\"\"_send_to_parent logs a warning when machine has no _invoke_session.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_parent\n        from statemachine.io.scxml.parser import SendAction\n\n        action = SendAction(event=\"done\", target=\"#_parent\")\n        machine = Mock(spec=[])  # spec=[] ensures no _invoke_session attribute\n        machine.name = \"test_machine\"\n\n        with caplog.at_level(logging.WARNING, logger=\"statemachine.io.scxml.actions\"):\n            _send_to_parent(action, machine=machine)\n\n        assert \"no _invoke_session\" in caplog.text\n\n\n# --- _send_to_invoke ---\n\n\nclass TestSendToInvoke:\n    \"\"\"Unit tests for _send_to_invoke (routes <send target=\"#_<invokeid>\">).\"\"\"\n\n    def _make_machine_with_invoke_manager(self, send_to_child_return=True):\n        \"\"\"Create a mock machine with an InvokeManager that has send_to_child.\"\"\"\n        machine = Mock()\n        machine.model = Mock()\n        machine.model.__dict__ = {}\n        machine._engine._invoke_manager.send_to_child.return_value = send_to_child_return\n        return machine\n\n    def test_routes_event_to_child(self):\n        \"\"\"_send_to_invoke forwards the event to InvokeManager.send_to_child.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        action = SendAction(event=\"childEvent\", target=\"#_child1\")\n\n        _send_to_invoke(action, \"child1\", machine=machine)\n\n        machine._engine._invoke_manager.send_to_child.assert_called_once_with(\n            \"child1\", \"childEvent\"\n        )\n        machine.send.assert_not_called()\n\n    def test_sends_error_communication_when_child_not_found(self):\n        \"\"\"_send_to_invoke sends error.communication when invokeid is not found.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager(send_to_child_return=False)\n        action = SendAction(event=\"childEvent\", target=\"#_unknown\")\n\n        _send_to_invoke(action, \"unknown\", machine=machine)\n\n        machine._put_nonblocking.assert_called_once()\n        trigger_data = machine._put_nonblocking.call_args[0][0]\n        assert str(trigger_data.event) == \"error.communication\"\n\n    def test_evaluates_eventexpr(self):\n        \"\"\"_send_to_invoke evaluates eventexpr when event is None.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        action = SendAction(event=None, eventexpr=\"'dynamic_event'\", target=\"#_child1\")\n\n        _send_to_invoke(action, \"child1\", machine=machine)\n\n        machine._engine._invoke_manager.send_to_child.assert_called_once_with(\n            \"child1\", \"dynamic_event\"\n        )\n\n    def test_forwards_params(self):\n        \"\"\"_send_to_invoke forwards evaluated params to send_to_child.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        action = SendAction(\n            event=\"childEvent\",\n            target=\"#_child1\",\n            params=[Param(name=\"x\", expr=\"42\"), Param(name=\"y\", expr=\"'hello'\")],\n        )\n\n        _send_to_invoke(action, \"child1\", machine=machine)\n\n        machine._engine._invoke_manager.send_to_child.assert_called_once_with(\n            \"child1\", \"childEvent\", x=42, y=\"hello\"\n        )\n\n    def test_forwards_namelist_variables(self):\n        \"\"\"_send_to_invoke resolves namelist variables from model and forwards them.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        model = type(\"Model\", (), {})()\n        model.var1 = \"alpha\"\n        model.var2 = \"beta\"\n        machine.model = model\n        action = SendAction(event=\"childEvent\", target=\"#_child1\", namelist=\"var1 var2\")\n\n        _send_to_invoke(action, \"child1\", machine=machine)\n\n        machine._engine._invoke_manager.send_to_child.assert_called_once_with(\n            \"child1\", \"childEvent\", var1=\"alpha\", var2=\"beta\"\n        )\n\n    def test_namelist_missing_variable_raises(self):\n        \"\"\"_send_to_invoke raises NameError when namelist variable is not on model.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        machine.model = Mock(spec=[])  # no attributes\n        action = SendAction(event=\"childEvent\", target=\"#_child1\", namelist=\"missing_var\")\n\n        with pytest.raises(NameError, match=\"missing_var\"):\n            _send_to_invoke(action, \"child1\", machine=machine)\n\n    def test_send_action_callable_routes_invoke_target(self):\n        \"\"\"create_send_action_callable routes #_<invokeid> targets to _send_to_invoke.\"\"\"\n        from statemachine.io.scxml.actions import create_send_action_callable\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        action = SendAction(event=\"hello\", target=\"#_myinvoke\")\n        send_callable = create_send_action_callable(action)\n\n        send_callable(machine=machine)\n\n        machine._engine._invoke_manager.send_to_child.assert_called_once_with(\"myinvoke\", \"hello\")\n\n    def test_send_action_callable_scxml_session_target(self):\n        \"\"\"create_send_action_callable sends error.communication for #_scxml_ targets.\"\"\"\n        from statemachine.io.scxml.actions import create_send_action_callable\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = self._make_machine_with_invoke_manager()\n        action = SendAction(event=\"hello\", target=\"#_scxml_session123\")\n        send_callable = create_send_action_callable(action)\n\n        send_callable(machine=machine)\n\n        machine._put_nonblocking.assert_called_once()\n        trigger_data = machine._put_nonblocking.call_args[0][0]\n        assert str(trigger_data.event) == \"error.communication\"\n        machine._engine._invoke_manager.send_to_child.assert_not_called()\n\n\n# --- EventDataWrapper coverage ---\n\n\nclass TestEventDataWrapperEdgeCases:\n    def test_no_event_data_no_trigger_data_raises(self):\n        \"\"\"EventDataWrapper raises ValueError when neither is provided.\"\"\"\n        with pytest.raises(ValueError, match=\"Either event_data or trigger_data\"):\n            EventDataWrapper()\n\n    def test_getattr_with_event_data_delegates(self):\n        \"\"\"__getattr__ delegates to event_data when present.\"\"\"\n        event_data = Mock()\n        event_data.trigger_data = Mock(\n            kwargs={}, send_id=None, event=Mock(internal=True, __str__=lambda s: \"test\")\n        )\n        event_data.some_custom_attr = \"custom_value\"\n        wrapper = EventDataWrapper(event_data)\n        assert wrapper.some_custom_attr == \"custom_value\"\n\n    def test_getattr_without_event_data_raises(self):\n        \"\"\"__getattr__ raises AttributeError when event_data is None.\"\"\"\n        trigger_data = Mock(kwargs={}, send_id=None, event=Mock(internal=True))\n        trigger_data.event.__str__ = lambda s: \"test\"\n        wrapper = EventDataWrapper(trigger_data=trigger_data)\n        with pytest.raises(AttributeError, match=\"no attribute 'missing_attr'\"):\n            wrapper.missing_attr  # noqa: B018\n\n    def test_name_via_trigger_data(self):\n        \"\"\"name property returns event string from trigger_data when no event_data.\"\"\"\n        trigger_data = Mock(kwargs={}, send_id=None, event=Mock(internal=True))\n        trigger_data.event.__str__ = lambda s: \"my.event\"\n        wrapper = EventDataWrapper(trigger_data=trigger_data)\n        assert wrapper.name == \"my.event\"\n\n\n# --- _send_to_parent coverage ---\n\n\nclass TestSendToParentParams:\n    def test_send_to_parent_with_namelist_and_params(self):\n        \"\"\"_send_to_parent resolves namelist and params before sending.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_parent\n        from statemachine.io.scxml.parser import SendAction\n\n        model = type(\"Model\", (), {})()\n        model.myvar = \"hello\"\n        machine = Mock(model=model)\n        machine.model.__dict__ = {\"myvar\": \"hello\"}\n        session = Mock()\n        machine._invoke_session = session\n\n        action = SendAction(\n            event=\"childDone\",\n            target=\"#_parent\",\n            namelist=\"myvar\",\n            params=[Param(name=\"extra\", expr=\"42\")],\n        )\n\n        _send_to_parent(action, machine=machine)\n\n        session.send_to_parent.assert_called_once_with(\"childDone\", myvar=\"hello\", extra=42)\n\n    def test_send_to_parent_namelist_missing_raises(self):\n        \"\"\"_send_to_parent raises NameError when namelist variable is missing.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_parent\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = Mock()\n        machine.model = Mock(spec=[])  # no attributes\n        machine._invoke_session = Mock()\n\n        action = SendAction(event=\"ev\", target=\"#_parent\", namelist=\"missing_var\")\n\n        with pytest.raises(NameError, match=\"missing_var\"):\n            _send_to_parent(action, machine=machine)\n\n    def test_send_to_parent_param_without_expr_skipped(self):\n        \"\"\"_send_to_parent skips params where expr is None.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_parent\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = Mock()\n        machine.model = Mock()\n        machine.model.__dict__ = {}\n        session = Mock()\n        machine._invoke_session = session\n\n        action = SendAction(\n            event=\"ev\",\n            target=\"#_parent\",\n            params=[\n                Param(name=\"has_expr\", expr=\"1\"),\n                Param(name=\"no_expr\", expr=None),\n            ],\n        )\n\n        _send_to_parent(action, machine=machine)\n        session.send_to_parent.assert_called_once_with(\"ev\", has_expr=1)\n\n\n# --- _send_to_invoke param skip coverage ---\n\n\nclass TestSendToInvokeParamSkip:\n    def test_param_without_expr_is_skipped(self):\n        \"\"\"_send_to_invoke skips params where expr is None.\"\"\"\n        from statemachine.io.scxml.actions import _send_to_invoke\n        from statemachine.io.scxml.parser import SendAction\n\n        machine = Mock()\n        machine.model = Mock()\n        machine.model.__dict__ = {}\n        machine._engine._invoke_manager.send_to_child.return_value = True\n\n        action = SendAction(\n            event=\"ev\",\n            target=\"#_child\",\n            params=[\n                Param(name=\"with_expr\", expr=\"1\"),\n                Param(name=\"no_expr\", expr=None),\n            ],\n        )\n\n        _send_to_invoke(action, \"child\", machine=machine)\n\n        machine._engine._invoke_manager.send_to_child.assert_called_once_with(\n            \"child\", \"ev\", with_expr=1\n        )\n\n\n# --- invoke_init coverage ---\n\n\nclass TestInvokeInitCallback:\n    def test_invoke_init_idempotent(self):\n        \"\"\"invoke_init only runs once, even if called multiple times.\"\"\"\n        from statemachine.io.scxml.actions import create_invoke_init_callable\n\n        callback = create_invoke_init_callable()\n        machine = Mock()\n\n        callback(machine=machine)\n        assert machine._invoke_params is not None or True  # first call sets attrs\n\n        # Reset to detect second call\n        machine._invoke_params = \"first\"\n        callback(machine=machine)\n        # Should NOT have been overwritten\n        assert machine._invoke_params == \"first\"\n\n\n# --- SCXMLInvoker edge cases ---\n\n\nclass TestSCXMLInvokerEdgeCases:\n    def test_on_event_exception_in_child_send(self):\n        \"\"\"on_event swallows exceptions from child.send().\"\"\"\n        invoker = _make_invoker()\n        child = Mock()\n        child.is_terminated = False\n        child.send.side_effect = RuntimeError(\"child error\")\n        invoker._child = child\n\n        # Should not raise\n        invoker.on_event(\"some.event\")\n        child.send.assert_called_once_with(\"some.event\")\n\n    def test_resolve_content_expr_non_string(self):\n        \"\"\"_resolve_content converts non-string eval result to string.\"\"\"\n        defn = InvokeDefinition(content=\"42\")  # evaluates to int\n        invoker = _make_invoker(definition=defn)\n        machine = Mock()\n        machine.model.__dict__ = {}\n\n        result = invoker._resolve_content(machine)\n        assert result == \"42\"\n\n    def test_evaluate_params_with_location(self):\n        \"\"\"_evaluate_params resolves param with location instead of expr.\"\"\"\n        defn = InvokeDefinition(\n            params=[Param(name=\"p1\", expr=None, location=\"myvar\")],\n        )\n        invoker = _make_invoker(definition=defn)\n\n        model = type(\"Model\", (), {})()\n        model.myvar = \"resolved\"\n        machine = Mock(model=model)\n        machine.model.__dict__ = {\"myvar\": \"resolved\"}\n\n        result = invoker._evaluate_params(machine)\n        assert result == {\"p1\": \"resolved\"}\n\n\n# --- Parser edge cases ---\n\n\nclass TestParserAssignChildXml:\n    def test_assign_with_child_xml_content(self):\n        \"\"\"<assign> with child XML content is parsed as child_xml.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <datamodel>\n            <data id=\"mydata\" expr=\"None\"/>\n          </datamodel>\n          <state id=\"s1\">\n            <onentry>\n              <assign location=\"mydata\"><node attr=\"val\"/></assign>\n            </onentry>\n            <transition event=\"error.execution\" target=\"err\"/>\n          </state>\n          <final id=\"err\"/>\n        </scxml>\n        \"\"\"\n        # Should parse without error — the child XML is stored in child_xml\n        definition = parse_scxml(scxml)\n        # Verify it parsed states correctly\n        assert \"s1\" in definition.states\n\n    def test_assign_with_text_content(self):\n        \"\"\"<assign> with text content (no expr attr) uses text as expr.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <datamodel>\n            <data id=\"mydata\" expr=\"0\"/>\n          </datamodel>\n          <state id=\"s1\">\n            <onentry>\n              <assign location=\"mydata\">42</assign>\n            </onentry>\n            <transition target=\"s2\"/>\n          </state>\n          <final id=\"s2\"/>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        assert \"s1\" in definition.states\n\n\nclass TestParserInvokeContent:\n    def test_invoke_with_text_content(self):\n        \"\"\"<invoke> <content> with text body is parsed.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <invoke type=\"http://www.w3.org/TR/scxml/\">\n              <content>some text content</content>\n            </invoke>\n          </state>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        assert \"s1\" in definition.states\n        invoke_def = definition.states[\"s1\"].invocations[0]\n        assert \"some text content\" in invoke_def.content\n\n    def test_invoke_with_content_expr(self):\n        \"\"\"<invoke> <content expr=\"...\"> is parsed as dynamic content.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <invoke type=\"http://www.w3.org/TR/scxml/\">\n              <content expr=\"'dynamic'\"/>\n            </invoke>\n          </state>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        invoke_def = definition.states[\"s1\"].invocations[0]\n        assert invoke_def.content == \"'dynamic'\"\n\n    def test_invoke_with_inline_scxml_no_namespace(self):\n        \"\"\"<invoke> <content> with inline <scxml> (no namespace) is parsed.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <invoke type=\"http://www.w3.org/TR/scxml/\">\n              <content><scxml><final id=\"f\"/></scxml></content>\n            </invoke>\n          </state>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        invoke_def = definition.states[\"s1\"].invocations[0]\n        assert \"<final\" in invoke_def.content\n\n    def test_invoke_with_unknown_child_element(self):\n        \"\"\"Unknown child elements inside <invoke> are silently ignored.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <invoke type=\"http://www.w3.org/TR/scxml/\">\n              <param name=\"x\" expr=\"1\"/>\n              <unknownElement/>\n            </invoke>\n          </state>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        invoke_def = definition.states[\"s1\"].invocations[0]\n        assert len(invoke_def.params) == 1\n\n    def test_invoke_with_empty_content(self):\n        \"\"\"<invoke> with empty <content/> results in content=None.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <invoke type=\"http://www.w3.org/TR/scxml/\">\n              <content/>\n            </invoke>\n          </state>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        invoke_def = definition.states[\"s1\"].invocations[0]\n        assert invoke_def.content is None\n\n    def test_invoke_with_finalize_block(self):\n        \"\"\"<invoke> with <finalize> block is parsed.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <state id=\"s1\">\n            <invoke type=\"http://www.w3.org/TR/scxml/\">\n              <content>child content</content>\n              <finalize>\n                <log label=\"finalized\"/>\n              </finalize>\n            </invoke>\n          </state>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        invoke_def = definition.states[\"s1\"].invocations[0]\n        assert invoke_def.finalize is not None\n        assert len(invoke_def.finalize.actions) == 1\n\n\nclass TestParserAssignEdgeCases:\n    def test_assign_without_children_or_text(self):\n        \"\"\"<assign> with neither children nor text results in expr=None.\"\"\"\n        scxml = \"\"\"\n        <scxml xmlns=\"http://www.w3.org/2005/07/scxml\" initial=\"s1\">\n          <datamodel>\n            <data id=\"mydata\" expr=\"0\"/>\n          </datamodel>\n          <state id=\"s1\">\n            <onentry>\n              <assign location=\"mydata\"/>\n            </onentry>\n            <transition event=\"error.execution\" target=\"err\"/>\n          </state>\n          <final id=\"err\"/>\n        </scxml>\n        \"\"\"\n        definition = parse_scxml(scxml)\n        assert \"s1\" in definition.states\n\n\nclass TestSCXMLInvokerResolveContentAbsolutePath:\n    def test_resolve_content_absolute_path(self, tmp_path):\n        \"\"\"_resolve_content with absolute src path doesn't prepend base_dir.\"\"\"\n        scxml_file = tmp_path / \"child.scxml\"\n        scxml_file.write_text(\"<scxml/>\")\n\n        defn = InvokeDefinition(src=str(scxml_file))\n        invoker = _make_invoker(definition=defn, base_dir=\"/some/other/dir\")\n\n        result = invoker._resolve_content(Mock())\n        assert result == \"<scxml/>\"\n\n\nclass TestSCXMLInvokerEvaluateParamsNoExprNoLocation:\n    def test_param_without_expr_or_location_skipped(self):\n        \"\"\"_evaluate_params skips params with neither expr nor location.\"\"\"\n        defn = InvokeDefinition(\n            params=[Param(name=\"p1\", expr=None, location=None)],\n        )\n        invoker = _make_invoker(definition=defn)\n        machine = Mock(model=type(\"M\", (), {})())\n        machine.model.__dict__ = {}\n\n        result = invoker._evaluate_params(machine)\n        assert result == {}\n\n\nclass TestInvokeInitMachineNone:\n    def test_invoke_init_without_machine_is_noop(self):\n        \"\"\"invoke_init does nothing when machine is not in kwargs.\"\"\"\n        from statemachine.io.scxml.actions import create_invoke_init_callable\n\n        callback = create_invoke_init_callable()\n        # Call without machine kwarg — should not raise\n        callback()\n\n\nclass TestInvokeCallableWrapperRunInstance:\n    def test_run_with_instance_not_class(self):\n        \"\"\"_InvokeCallableWrapper.run() works with an instance (not a class).\"\"\"\n        from statemachine.invoke import _InvokeCallableWrapper\n\n        class Handler:\n            def run(self, ctx):\n                return \"result\"\n\n        handler_instance = Handler()\n        wrapper = _InvokeCallableWrapper(handler_instance)\n        assert not wrapper._is_class\n\n        ctx = Mock()\n        result = wrapper.run(ctx)\n        assert result == \"result\"\n        assert wrapper._instance is handler_instance\n\n\nclass TestOrderedSetStr:\n    def test_str_representation(self):\n        \"\"\"OrderedSet.__str__ returns a set-like string.\"\"\"\n        from statemachine.orderedset import OrderedSet\n\n        os = OrderedSet([1, 2, 3])\n        assert str(os) == \"{1, 2, 3}\"\n"
  },
  {
    "path": "tests/test_signature.py",
    "content": "import inspect\nfrom functools import partial\n\nimport pytest\nfrom statemachine.dispatcher import callable_method\nfrom statemachine.signature import SignatureAdapter\n\n\ndef single_positional_param(a):\n    return a\n\n\ndef single_default_keyword_param(a=42):\n    return a\n\n\ndef args_param(*args):\n    return args\n\n\ndef kwargs_param(**kwargs):\n    return kwargs\n\n\ndef args_and_kwargs_param(*args, **kwargs):\n    return args, kwargs\n\n\ndef positional_optional_catchall(a, b=\"ham\", *args):\n    return a, b, args\n\n\ndef ignored_param(a, b, *, c, d=10):\n    return a, b, c, d\n\n\ndef positional_and_kw_arguments(source, target, event):\n    return source, target, event\n\n\ndef default_kw_arguments(source: str = \"A\", target: str = \"B\", event: str = \"go\"):\n    return source, target, event\n\n\nclass MyObject:\n    def __init__(self, value=42):\n        self.value = value\n\n    def method_no_argument(self):\n        return self.value\n\n\nclass TestSignatureAdapter:\n    @pytest.mark.parametrize(\n        (\"func\", \"args\", \"kwargs\", \"expected\"),\n        [\n            (single_positional_param, [10], {}, 10),\n            (single_positional_param, [], {\"a\": 10}, 10),\n            pytest.param(single_positional_param, [], {}, TypeError),\n            (single_default_keyword_param, [10], {}, 10),\n            (single_default_keyword_param, [], {\"a\": 10}, 10),\n            pytest.param(single_default_keyword_param, [], {}, 42),\n            (MyObject().method_no_argument, [], {}, 42),\n            (MyObject().method_no_argument, [\"ignored\"], {\"x\": True}, 42),\n            (MyObject.method_no_argument, [MyObject()], {\"x\": True}, 42),\n            pytest.param(MyObject.method_no_argument, [], {}, TypeError),\n            (args_param, [], {}, ()),\n            (args_param, [42], {}, (42,)),\n            (args_param, [1, 1, 2, 3, 5, 8, 13], {}, (1, 1, 2, 3, 5, 8, 13)),\n            (\n                args_param,\n                [1, 1, 2, 3, 5, 8, 13],\n                {\"x\": True, \"other\": 42},\n                (1, 1, 2, 3, 5, 8, 13),\n            ),\n            (kwargs_param, [], {}, {}),\n            (kwargs_param, [1], {}, {}),\n            (kwargs_param, [1, 3, 5, 8, \"x\", True], {}, {}),\n            (kwargs_param, [], {\"x\": True}, {\"x\": True}),\n            (kwargs_param, [], {\"x\": True, \"n\": 42}, {\"x\": True, \"n\": 42}),\n            (\n                kwargs_param,\n                [10, \"x\", False],\n                {\"x\": True, \"n\": 42},\n                {\"x\": True, \"n\": 42},\n            ),\n            (args_and_kwargs_param, [], {}, ((), {})),\n            (args_and_kwargs_param, [1], {}, ((1,), {})),\n            (\n                args_and_kwargs_param,\n                [1, 3, 5, False, \"n\"],\n                {\"x\": True, \"n\": 42},\n                ((1, 3, 5, False, \"n\"), {\"x\": True, \"n\": 42}),\n            ),\n            (positional_optional_catchall, [], {}, TypeError),\n            (positional_optional_catchall, [42], {}, (42, \"ham\", ())),\n            pytest.param(\n                positional_optional_catchall,\n                [True],\n                {\"b\": \"spam\"},\n                (True, \"spam\", ()),\n            ),\n            pytest.param(\n                positional_optional_catchall,\n                [\"a\", \"b\"],\n                {\"b\": \"spam\"},\n                (\"a\", \"spam\", ()),\n            ),\n            (\n                positional_optional_catchall,\n                [\"a\", \"b\", \"c\"],\n                {\"b\": \"spam\"},\n                (\"a\", \"spam\", (\"c\",)),\n            ),\n            (\n                positional_optional_catchall,\n                [\"a\", \"b\", \"c\", False, 10],\n                {\"other\": 42},\n                (\"a\", \"b\", (\"c\", False, 10)),\n            ),\n            (ignored_param, [], {}, TypeError),\n            (ignored_param, [1, 2, 3], {}, TypeError),\n            pytest.param(\n                ignored_param,\n                [1, 2],\n                {\"c\": 42},\n                (1, 2, 42, 10),\n            ),\n            pytest.param(\n                ignored_param,\n                [1, 2],\n                {\"c\": 42, \"d\": 21},\n                (1, 2, 42, 21),\n            ),\n            pytest.param(positional_and_kw_arguments, [], {}, TypeError),\n            (positional_and_kw_arguments, [1, 2, 3], {}, (1, 2, 3)),\n            (positional_and_kw_arguments, [1, 2], {\"event\": \"foo\"}, (1, 2, \"foo\")),\n            (\n                positional_and_kw_arguments,\n                [],\n                {\"source\": \"A\", \"target\": \"B\", \"event\": \"foo\"},\n                (\"A\", \"B\", \"foo\"),\n            ),\n            pytest.param(default_kw_arguments, [], {}, (\"A\", \"B\", \"go\")),\n            (default_kw_arguments, [1, 2, 3], {}, (1, 2, 3)),\n            (default_kw_arguments, [1, 2], {\"event\": \"wait\"}, (1, 2, \"wait\")),\n        ],\n    )\n    def test_wrap_fn_single_positional_parameter(self, func, args, kwargs, expected):\n        wrapped_func = callable_method(func)\n        assert wrapped_func.__name__ == func.__name__\n\n        if inspect.isclass(expected) and issubclass(expected, Exception):\n            with pytest.raises(expected):\n                wrapped_func(*args, **kwargs)\n        else:\n            assert wrapped_func(*args, **kwargs) == expected\n\n    def test_support_for_partial(self):\n        part = partial(positional_and_kw_arguments, event=\"activated\")\n        wrapped_func = callable_method(part)\n\n        assert wrapped_func(\"A\", \"B\") == (\"A\", \"B\", \"activated\")\n        assert wrapped_func.__name__ == positional_and_kw_arguments.__name__\n\n\ndef named_and_kwargs(source, **kwargs):\n    return source, kwargs\n\n\nclass TestCachedBindExpected:\n    \"\"\"Tests that exercise the cache fast-path by calling the same\n    wrapped function twice with the same argument shape.\"\"\"\n\n    def setup_method(self):\n        SignatureAdapter.from_callable.clear_cache()\n\n    def test_named_param_not_leaked_into_kwargs(self):\n        \"\"\"Named params should not appear in the **kwargs dict on cache hit.\"\"\"\n        wrapped = callable_method(named_and_kwargs)\n\n        # 1st call: cache miss -> _full_bind\n        result1 = wrapped(source=\"A\", target=\"B\", event=\"go\")\n        assert result1 == (\"A\", {\"target\": \"B\", \"event\": \"go\"})\n\n        # 2nd call: cache hit -> _fast_bind\n        result2 = wrapped(source=\"X\", target=\"Y\", event=\"stop\")\n        assert result2 == (\"X\", {\"target\": \"Y\", \"event\": \"stop\"})\n\n    def test_kwargs_only_receives_unmatched_keys_with_positional(self):\n        \"\"\"When mixing positional and keyword args with **kwargs.\"\"\"\n        wrapped = callable_method(named_and_kwargs)\n\n        result1 = wrapped(\"A\", target=\"B\")\n        assert result1 == (\"A\", {\"target\": \"B\"})\n\n        result2 = wrapped(\"X\", target=\"Y\")\n        assert result2 == (\"X\", {\"target\": \"Y\"})\n\n    def test_var_positional_collected_as_tuple(self):\n        \"\"\"VAR_POSITIONAL (*args) must be collected into a tuple on cache hit.\"\"\"\n\n        def fn(*args, **kwargs):\n            return args, kwargs\n\n        wrapped = callable_method(fn)\n\n        result1 = wrapped(1, 2, 3, key=\"val\")\n        assert result1 == ((1, 2, 3), {\"key\": \"val\"})\n\n        result2 = wrapped(4, 5, key=\"other\")\n        assert result2 == ((4, 5), {\"key\": \"other\"})\n\n    def test_keyword_only_after_var_positional(self):\n        \"\"\"KEYWORD_ONLY params after *args must be extracted from kwargs on cache hit.\"\"\"\n\n        def fn(*args, event, **kwargs):\n            return args, event, kwargs\n\n        wrapped = callable_method(fn)\n\n        result1 = wrapped(100, event=\"ev1\", source=\"s0\")\n        assert result1 == ((100,), \"ev1\", {\"source\": \"s0\"})\n\n        result2 = wrapped(200, event=\"ev2\", source=\"s1\")\n        assert result2 == ((200,), \"ev2\", {\"source\": \"s1\"})\n\n    def test_positional_or_keyword_prefers_kwargs_over_positional(self):\n        \"\"\"When a POSITIONAL_OR_KEYWORD param is in both args and kwargs, kwargs wins.\"\"\"\n\n        def fn(event, source, target):\n            return event, source, target\n\n        wrapped = callable_method(fn)\n\n        # 1st call: positional arg provided but 'event' also in kwargs -> kwargs wins\n        result1 = wrapped(\"discarded_content\", event=\"ev1\", source=\"s0\", target=\"t0\")\n        assert result1 == (\"ev1\", \"s0\", \"t0\")\n\n        # 2nd call: cache hit, same behavior expected\n        result2 = wrapped(\"other_content\", event=\"ev2\", source=\"s1\", target=\"t1\")\n        assert result2 == (\"ev2\", \"s1\", \"t1\")\n\n    def test_empty_var_positional(self):\n        \"\"\"Empty *args is handled correctly on cache hit.\"\"\"\n\n        def fn(*args, **kwargs):\n            return args, kwargs\n\n        wrapped = callable_method(fn)\n\n        # 1st call with args\n        result1 = wrapped(1, key=\"val\")\n        assert result1 == ((1,), {\"key\": \"val\"})\n\n        # 2nd call: only kwargs, no positional args — different cache key (len=0)\n        result2 = wrapped(key=\"val2\")\n        assert result2 == ((), {\"key\": \"val2\"})\n\n        # 3rd call: hits cache for len=0\n        result3 = wrapped(key=\"val3\")\n        assert result3 == ((), {\"key\": \"val3\"})\n\n    def test_named_params_before_var_positional(self):\n        \"\"\"Named params before *args are filled correctly on cache hit.\"\"\"\n\n        def fn(a, b, *args, **kwargs):\n            return a, b, args, kwargs\n\n        wrapped = callable_method(fn)\n\n        result1 = wrapped(1, 2, 3, 4, key=\"val\")\n        assert result1 == (1, 2, (3, 4), {\"key\": \"val\"})\n\n        result2 = wrapped(10, 20, 30, key=\"val2\")\n        assert result2 == (10, 20, (30,), {\"key\": \"val2\"})\n\n    def test_kwargs_wins_with_var_positional_present(self):\n        \"\"\"POSITIONAL_OR_KEYWORD before *args prefers kwargs when name matches.\"\"\"\n\n        def fn(event, *args, **kwargs):\n            return event, args, kwargs\n\n        wrapped = callable_method(fn)\n\n        # 1st call: 'event' in both positional and kwargs — kwargs wins\n        result1 = wrapped(\"discarded\", \"extra\", event=\"ev1\", key=\"a\")\n        assert result1 == (\"ev1\", (\"extra\",), {\"key\": \"a\"})\n\n        # 2nd call: cache hit, same behavior\n        result2 = wrapped(\"other\", \"more\", event=\"ev2\", key=\"b\")\n        assert result2 == (\"ev2\", (\"more\",), {\"key\": \"b\"})\n"
  },
  {
    "path": "tests/test_signature_positional_only.py",
    "content": "import inspect\n\nimport pytest\nfrom statemachine.dispatcher import callable_method\n\n\nclass TestSignatureAdapter:\n    @pytest.mark.parametrize(\n        (\"args\", \"kwargs\", \"expected\"),\n        [\n            ([], {}, TypeError),\n            ([1, 2, 3], {}, TypeError),\n            ([1, 2], {\"kw_only_param\": 42}, (1, 2, 42)),\n            ([1], {\"pos_or_kw_param\": 21, \"kw_only_param\": 42}, (1, 21, 42)),\n            (\n                [],\n                {\"pos_only\": 10, \"pos_or_kw_param\": 21, \"kw_only_param\": 42},\n                TypeError,\n            ),\n        ],\n    )\n    def test_positional_only(self, args, kwargs, expected):\n        def func(pos_only, /, pos_or_kw_param, *, kw_only_param):\n            # https://peps.python.org/pep-0570/\n            return pos_only, pos_or_kw_param, kw_only_param\n\n        wrapped_func = callable_method(func)\n\n        if inspect.isclass(expected) and issubclass(expected, Exception):\n            with pytest.raises(expected):\n                wrapped_func(*args, **kwargs)\n        else:\n            assert wrapped_func(*args, **kwargs) == expected\n"
  },
  {
    "path": "tests/test_spec_parser.py",
    "content": "import asyncio\nfrom typing import TYPE_CHECKING\n\nimport pytest\nfrom statemachine.spec_parser import Functions\nfrom statemachine.spec_parser import operator_mapping\nfrom statemachine.spec_parser import parse_boolean_expr\n\nif TYPE_CHECKING:\n    from collections.abc import Callable\n\n\ndef variable_hook(\n    var_name: str,\n    spy: \"Callable[[str], None] | None\" = None,\n) -> \"Callable\":\n    values = {\n        \"frodo_has_ring\": True,\n        \"sauron_alive\": False,\n        \"gandalf_present\": True,\n        \"sam_is_loyal\": True,\n        \"orc_army_ready\": False,\n        \"frodo_age\": 50,\n        \"height\": 1.75,\n        \"name\": \"Frodo\",\n        \"aragorn_age\": 87,\n        \"legolas_age\": 2931,\n        \"gimli_age\": 139,\n        \"ring_power\": 100,\n        \"sword_power\": 80,\n        \"bow_power\": 75,\n        \"axe_power\": 85,\n    }\n\n    def decorated(*args, **kwargs):\n        if spy is not None:\n            spy(var_name)\n        return values.get(var_name, False)\n\n    decorated.__name__ = var_name\n    return decorated\n\n\n@pytest.mark.parametrize(\n    (\"expression\", \"expected\", \"hooks_called\"),\n    [\n        (\"frodo_has_ring\", True, [\"frodo_has_ring\"]),\n        (\"frodo_has_ring or sauron_alive\", True, [\"frodo_has_ring\"]),\n        (\n            \"frodo_has_ring and gandalf_present\",\n            True,\n            [\"frodo_has_ring\", \"gandalf_present\"],\n        ),\n        (\"sauron_alive\", False, [\"sauron_alive\"]),\n        (\"not sauron_alive\", True, [\"sauron_alive\"]),\n        (\n            \"frodo_has_ring and (gandalf_present or sauron_alive)\",\n            True,\n            [\"frodo_has_ring\", \"gandalf_present\"],\n        ),\n        (\n            \"not sauron_alive and orc_army_ready\",\n            False,\n            [\"sauron_alive\", \"orc_army_ready\"],\n        ),\n        (\n            \"not (not sauron_alive and orc_army_ready)\",\n            True,\n            [\"sauron_alive\", \"orc_army_ready\"],\n        ),\n        (\n            \"(frodo_has_ring and sam_is_loyal) or (not sauron_alive and orc_army_ready)\",\n            True,\n            [\"frodo_has_ring\", \"sam_is_loyal\"],\n        ),\n        (\n            \"(frodo_has_ring ^ sam_is_loyal) v (!sauron_alive ^ orc_army_ready)\",\n            True,\n            [\"frodo_has_ring\", \"sam_is_loyal\"],\n        ),\n        (\"not (not frodo_has_ring)\", True, [\"frodo_has_ring\"]),\n        (\"!(!frodo_has_ring)\", True, [\"frodo_has_ring\"]),\n        (\n            \"frodo_has_ring and orc_army_ready\",\n            False,\n            [\"frodo_has_ring\", \"orc_army_ready\"],\n        ),\n        (\n            \"frodo_has_ring ^ orc_army_ready\",\n            False,\n            [\"frodo_has_ring\", \"orc_army_ready\"],\n        ),\n        (\n            \"frodo_has_ring and not orc_army_ready\",\n            True,\n            [\"frodo_has_ring\", \"orc_army_ready\"],\n        ),\n        (\n            \"frodo_has_ring ^ !orc_army_ready\",\n            True,\n            [\"frodo_has_ring\", \"orc_army_ready\"],\n        ),\n        (\n            \"frodo_has_ring and (sam_is_loyal or (gandalf_present and not sauron_alive))\",\n            True,\n            [\"frodo_has_ring\", \"sam_is_loyal\"],\n        ),\n        (\n            \"frodo_has_ring ^ (sam_is_loyal v (gandalf_present ^ !sauron_alive))\",\n            True,\n            [\"frodo_has_ring\", \"sam_is_loyal\"],\n        ),\n        (\"sauron_alive or orc_army_ready\", False, [\"sauron_alive\", \"orc_army_ready\"]),\n        (\"sauron_alive v orc_army_ready\", False, [\"sauron_alive\", \"orc_army_ready\"]),\n        (\n            \"(frodo_has_ring and gandalf_present) or orc_army_ready\",\n            True,\n            [\"frodo_has_ring\", \"gandalf_present\"],\n        ),\n        (\n            \"orc_army_ready or (frodo_has_ring and gandalf_present)\",\n            True,\n            [\"orc_army_ready\", \"frodo_has_ring\", \"gandalf_present\"],\n        ),\n        (\n            \"orc_army_ready and (frodo_has_ring and gandalf_present)\",\n            False,\n            [\"orc_army_ready\"],\n        ),\n        (\n            \"!orc_army_ready and (frodo_has_ring and gandalf_present)\",\n            True,\n            [\"orc_army_ready\", \"frodo_has_ring\", \"gandalf_present\"],\n        ),\n        (\n            \"!orc_army_ready and !(frodo_has_ring and gandalf_present)\",\n            False,\n            [\"orc_army_ready\", \"frodo_has_ring\", \"gandalf_present\"],\n        ),\n        (\"frodo_has_ring or True\", True, [\"frodo_has_ring\"]),\n        (\"sauron_alive or True\", True, [\"sauron_alive\"]),\n        (\"frodo_age >= 50\", True, [\"frodo_age\"]),\n        (\"50 <= frodo_age\", True, [\"frodo_age\"]),\n        (\"frodo_age <= 50\", True, [\"frodo_age\"]),\n        (\"frodo_age == 50\", True, [\"frodo_age\"]),\n        (\"frodo_age > 50\", False, [\"frodo_age\"]),\n        (\"frodo_age < 50\", False, [\"frodo_age\"]),\n        (\"frodo_age != 50\", False, [\"frodo_age\"]),\n        (\"frodo_age != 49\", True, [\"frodo_age\"]),\n        (\"49 < frodo_age < 51\", True, [\"frodo_age\", \"frodo_age\"]),\n        (\"49 < frodo_age > 50\", False, [\"frodo_age\", \"frodo_age\"]),\n        (\n            \"aragorn_age < legolas_age < gimli_age\",\n            False,\n            [\"aragorn_age\", \"legolas_age\", \"legolas_age\", \"gimli_age\"],\n        ),  # 87 < 2931 and 2931 < 139\n        (\n            \"gimli_age > aragorn_age < legolas_age\",\n            True,\n            [\"gimli_age\", \"aragorn_age\", \"aragorn_age\", \"legolas_age\"],\n        ),  # 139 > 87 and 87 < 2931\n        (\n            \"sword_power < ring_power > bow_power\",\n            True,\n            [\"sword_power\", \"ring_power\", \"ring_power\", \"bow_power\"],\n        ),  # 80 < 100 and 100 > 75\n        (\n            \"axe_power > sword_power == bow_power\",\n            False,\n            [\"axe_power\", \"sword_power\", \"sword_power\", \"bow_power\"],\n        ),  # 85 > 80 and 80 == 75\n        (\"name == 'Frodo'\", True, [\"name\"]),\n        (\"name != 'Sam'\", True, [\"name\"]),\n        (\"height == 1.75\", True, [\"height\"]),\n        (\"height > 1 and height < 2\", True, [\"height\", \"height\"]),\n    ],\n)\ndef test_expressions(expression, expected, hooks_called):\n    calls: list[str] = []\n\n    def hook(name):\n        return variable_hook(name, spy=calls.append)\n\n    parsed_expr = parse_boolean_expr(expression, hook, operator_mapping)\n    assert parsed_expr() is expected, expression\n    assert calls == hooks_called\n\n\ndef test_negating_compound_false_expression():\n    expr = \"not (not sauron_alive and orc_army_ready)\"\n    parsed_expr = parse_boolean_expr(expr, variable_hook, operator_mapping)\n    assert parsed_expr() is True\n    assert parsed_expr.__name__ == \"not((not(sauron_alive) and orc_army_ready))\"\n\n\ndef test_expression_name_uniqueness():\n    expr = \"frodo_has_ring or not orc_army_ready\"\n    parsed_expr = parse_boolean_expr(expr, variable_hook, operator_mapping)\n    assert (\n        parsed_expr.__name__ == \"(frodo_has_ring or not(orc_army_ready))\"\n    )  # name reflects expression structure\n\n\ndef test_classical_operators_name():\n    expr = \"frodo_has_ring ^ !orc_army_ready\"\n    parsed_expr = parse_boolean_expr(expr, variable_hook, operator_mapping)\n    assert parsed_expr() is True  # both parts are True\n    assert (\n        parsed_expr.__name__ == \"(frodo_has_ring and not(orc_army_ready))\"\n    )  # name reflects expression structure\n\n\ndef test_empty_expression():\n    expr = \"\"\n    with pytest.raises(SyntaxError):\n        parse_boolean_expr(expr, variable_hook, operator_mapping)\n\n\ndef test_whitespace_expression():\n    expr = \"   \"\n    with pytest.raises(SyntaxError):\n        parse_boolean_expr(expr, variable_hook, operator_mapping)\n\n\ndef test_missing_operator_expression():\n    expr = \"frodo_has_ring orc_army_ready\"\n    with pytest.raises(SyntaxError):\n        parse_boolean_expr(expr, variable_hook, operator_mapping)\n\n\ndef test_dict_usage_expression():\n    expr = \"frodo_has_ring or {}\"\n    with pytest.raises(ValueError, match=\"Unsupported expression structure\"):\n        parse_boolean_expr(expr, variable_hook, operator_mapping)\n\n\ndef test_unsupported_operator():\n    # Define an unsupported operator like MUL\n    expr = \"frodo_has_ring * gandalf_present\"\n    with pytest.raises(ValueError, match=\"Unsupported expression structure\"):\n        parse_boolean_expr(expr, variable_hook, operator_mapping)\n\n\ndef test_simple_variable_returns_the_original_callback():\n    def original_callback(*args, **kwargs):\n        return True\n\n    mapping = {\"original\": original_callback}\n\n    def variable_hook(var_name):\n        return mapping.get(var_name, None)\n\n    expr = \"original\"\n    parsed_expr = parse_boolean_expr(expr, variable_hook, operator_mapping)\n\n    assert parsed_expr is original_callback\n\n\ndef async_variable_hook(var_name):\n    \"\"\"Variable hook that returns async callables, for testing issue #535.\"\"\"\n    values = {\n        \"cond_true\": True,\n        \"cond_false\": False,\n        \"val_10\": 10,\n        \"val_20\": 20,\n    }\n\n    value = values.get(var_name, False)\n\n    async def decorated(*args, **kwargs):\n        await asyncio.sleep(0)\n        return value\n\n    decorated.__name__ = var_name\n    return decorated\n\n\n@pytest.mark.parametrize(\n    (\"expression\", \"expected\"),\n    [\n        (\"not cond_false\", True),\n        (\"not cond_true\", False),\n        (\"cond_true and cond_true\", True),\n        (\"cond_true and cond_false\", False),\n        (\"cond_false and cond_true\", False),\n        (\"cond_false or cond_true\", True),\n        (\"cond_true or cond_false\", True),\n        (\"cond_false or cond_false\", False),\n        (\"not cond_false and cond_true\", True),\n        (\"not (cond_true and cond_false)\", True),\n        (\"not (cond_false or cond_false)\", True),\n        (\"cond_true and not cond_false\", True),\n        (\"val_10 == 10\", True),\n        (\"val_10 != 20\", True),\n        (\"val_10 < val_20\", True),\n        (\"val_20 > val_10\", True),\n        (\"val_10 >= 10\", True),\n        (\"val_10 <= val_20\", True),\n    ],\n)\ndef test_async_expressions(expression, expected):\n    \"\"\"Issue #535: condition expressions with async predicates must await results.\"\"\"\n    parsed_expr = parse_boolean_expr(expression, async_variable_hook, operator_mapping)\n    result = parsed_expr()\n    assert asyncio.iscoroutine(result), f\"Expected coroutine for async expression: {expression}\"\n    assert asyncio.run(result) is expected, expression\n\n\ndef mixed_variable_hook(var_name):\n    \"\"\"Variable hook where some vars are sync and some are async.\"\"\"\n    sync_values = {\"sync_true\": True, \"sync_false\": False, \"sync_10\": 10}\n    async_values = {\"async_true\": True, \"async_false\": False, \"async_20\": 20}\n\n    if var_name in async_values:\n        value = async_values[var_name]\n\n        async def async_decorated(*args, **kwargs):\n            await asyncio.sleep(0)\n            return value\n\n        async_decorated.__name__ = var_name\n        return async_decorated\n\n    def sync_decorated(*args, **kwargs):\n        return sync_values.get(var_name, False)\n\n    sync_decorated.__name__ = var_name\n    return sync_decorated\n\n\n@pytest.mark.parametrize(\n    (\"expression\", \"expected\"),\n    [\n        # async left, sync right\n        (\"async_true and sync_true\", True),\n        (\"async_false or sync_true\", True),\n        # sync left, async right\n        (\"sync_true and async_true\", True),\n        (\"sync_false or async_true\", True),\n        (\"sync_true and async_false\", False),\n        (\"sync_false or async_false\", False),\n    ],\n)\ndef test_mixed_sync_async_expressions(expression, expected):\n    \"\"\"Expressions mixing sync and async predicates must handle both correctly.\"\"\"\n    parsed_expr = parse_boolean_expr(expression, mixed_variable_hook, operator_mapping)\n    result = parsed_expr()\n    if asyncio.iscoroutine(result):\n        assert asyncio.run(result) is expected, expression\n    else:\n        assert result is expected, expression\n\n\ndef test_functions_get_unknown_raises():\n    \"\"\"Functions.get raises ValueError for unknown functions.\"\"\"\n    with pytest.raises(ValueError, match=\"Unsupported function\"):\n        Functions.get(\"nonexistent_function\")\n"
  },
  {
    "path": "tests/test_state.py",
    "content": "import pytest\nfrom statemachine.orderedset import OrderedSet\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.fixture()\ndef sm_class():\n    class SM(StateChart):\n        pending = State(initial=True)\n        waiting_approval = State()\n        approved = State(final=True)\n\n        start = pending.to(waiting_approval)\n        approve = waiting_approval.to(approved)\n\n    return SM\n\n\nclass TestState:\n    def test_name_derived_from_id(self, sm_class):\n        assert sm_class.pending.name == \"Pending\"\n        assert sm_class.waiting_approval.name == \"Waiting approval\"\n        assert sm_class.approved.name == \"Approved\"\n\n    def test_state_from_instance_is_hashable(self, sm_class):\n        sm = sm_class()\n        states_set = {sm.pending, sm.waiting_approval, sm.approved, sm.approved}\n        assert states_set == {sm.pending, sm.waiting_approval, sm.approved}\n\n    def test_state_knows_if_its_initial(self, sm_class):\n        sm = sm_class()\n        assert sm.pending.initial\n        assert not sm.waiting_approval.initial\n        assert not sm.approved.initial\n\n    def test_state_knows_if_its_final(self, sm_class):\n        sm = sm_class()\n        assert not sm.pending.final\n        assert not sm.waiting_approval.final\n        assert sm.approved.final\n\n\ndef test_ordered_set_clear():\n    \"\"\"OrderedSet.clear empties the set.\"\"\"\n    s = OrderedSet([1, 2, 3])\n    s.clear()\n    assert len(s) == 0\n\n\ndef test_ordered_set_getitem():\n    \"\"\"OrderedSet supports index access.\"\"\"\n    s = OrderedSet([10, 20, 30])\n    assert s[0] == 10\n    assert s[2] == 30\n\n\ndef test_ordered_set_getitem_out_of_range():\n    \"\"\"OrderedSet raises IndexError for out-of-range index.\"\"\"\n    s = OrderedSet([10, 20])\n    with pytest.raises(IndexError, match=\"index 5 out of range\"):\n        s[5]\n\n\ndef test_ordered_set_union():\n    \"\"\"OrderedSet.union returns new set with elements from both.\"\"\"\n    s1 = OrderedSet([1, 2])\n    result = s1.union([3, 4], [5, 6])\n    assert list(result) == [1, 2, 3, 4, 5, 6]\n"
  },
  {
    "path": "tests/test_state_callbacks.py",
    "content": "from unittest import mock\n\nimport pytest\n\n\n@pytest.fixture()\ndef event_mock():\n    return mock.MagicMock()\n\n\n@pytest.fixture()\ndef traffic_light_machine(event_mock):  # noqa: C901\n    from statemachine import State\n    from statemachine import StateChart\n\n    class TrafficLightMachineStateEvents(StateChart):\n        \"A traffic light machine\"\n\n        green = State(initial=True)\n        yellow = State()\n        red = State()\n\n        cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n        def on_enter_state(self, event_data):\n            event_mock.on_enter_state(event_data.transition.target)\n\n        def on_exit_state(self, event_data):\n            event_mock.on_exit_state(event_data.state)\n\n        def on_enter_green(self):\n            event_mock.on_enter_green(self)\n\n        def on_exit_green(self):\n            event_mock.on_exit_green(self)\n\n        def on_enter_yellow(self):\n            event_mock.on_enter_yellow(self)\n\n        def on_exit_yellow(self):\n            event_mock.on_exit_yellow(self)\n\n        def on_enter_red(self):\n            event_mock.on_enter_red(self)\n\n        def on_exit_red(self):\n            event_mock.on_exit_red(self)\n\n    return TrafficLightMachineStateEvents\n\n\nclass TestStateCallbacks:\n    def test_should_call_on_enter_generic_state(self, event_mock, traffic_light_machine):\n        machine = traffic_light_machine()\n        machine.cycle()\n        assert event_mock.on_enter_state.call_args_list == [\n            mock.call(machine.green),\n            mock.call(machine.yellow),\n        ]\n\n    def test_should_call_on_exit_generic_state(self, event_mock, traffic_light_machine):\n        machine = traffic_light_machine()\n        machine.cycle()\n        event_mock.on_exit_state.assert_called_once_with(machine.green)\n\n    def test_should_call_on_enter_of_specific_state(self, event_mock, traffic_light_machine):\n        machine = traffic_light_machine()\n        machine.cycle()\n        event_mock.on_enter_yellow.assert_called_once_with(machine)\n\n    def test_should_call_on_exit_of_specific_state(self, event_mock, traffic_light_machine):\n        machine = traffic_light_machine()\n        machine.cycle()\n        event_mock.on_exit_green.assert_called_once_with(machine)\n\n    def test_should_be_on_the_previous_state_when_exiting(self, event_mock, traffic_light_machine):\n        machine = traffic_light_machine()\n\n        def assert_is_green_from_state(s):\n            assert s.value == \"green\"\n\n        def assert_is_green(m):\n            assert m.green.is_active\n\n        event_mock.on_exit_state.side_effect = assert_is_green_from_state\n        event_mock.on_exit_green.side_effect = assert_is_green\n\n        machine.cycle()\n\n    def test_should_be_on_the_next_state_when_entering(self, event_mock, traffic_light_machine):\n        machine = traffic_light_machine()\n\n        def assert_is_yellow_from_state(s):\n            assert s.value == \"yellow\"\n\n        def assert_is_yellow(m):\n            assert m.yellow.is_active\n\n        event_mock.on_enter_state.side_effect = assert_is_yellow_from_state\n        event_mock.on_enter_yellow.side_effect = assert_is_yellow\n\n        machine.cycle()\n"
  },
  {
    "path": "tests/test_statechart_compound.py",
    "content": "\"\"\"Compound state behavior using Python class syntax.\n\nTests exercise entering/exiting compound states, nested compounds, cross-compound\ntransitions, done.state events from final children, callback ordering, and discovery\nof methods defined inside State.Compound class bodies.\n\nTheme: Fellowship journey through Middle-earth.\n\"\"\"\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\nfrom tests.machines.compound.middle_earth_journey import MiddleEarthJourney\nfrom tests.machines.compound.middle_earth_journey_two_compounds import (\n    MiddleEarthJourneyTwoCompounds,\n)\nfrom tests.machines.compound.middle_earth_journey_with_finals import MiddleEarthJourneyWithFinals\nfrom tests.machines.compound.moria_expedition import MoriaExpedition\nfrom tests.machines.compound.moria_expedition_with_escape import MoriaExpeditionWithEscape\nfrom tests.machines.compound.quest_for_erebor import QuestForErebor\nfrom tests.machines.compound.shire_to_rivendell import ShireToRivendell\n\n\n@pytest.mark.timeout(5)\nclass TestCompoundStates:\n    async def test_enter_compound_activates_initial_child(self, sm_runner):\n        \"\"\"Entering a compound activates both parent and the initial child.\"\"\"\n        sm = await sm_runner.start(ShireToRivendell)\n        assert {\"shire\", \"bag_end\"} == set(sm.configuration_values)\n\n    async def test_transition_within_compound(self, sm_runner):\n        \"\"\"Inner state changes while parent stays active.\"\"\"\n        sm = await sm_runner.start(ShireToRivendell)\n        await sm_runner.send(sm, \"visit_pub\")\n        assert \"shire\" in sm.configuration_values\n        assert \"green_dragon\" in sm.configuration_values\n        assert \"bag_end\" not in sm.configuration_values\n\n    async def test_exit_compound_removes_all_descendants(self, sm_runner):\n        \"\"\"Leaving a compound removes the parent and all children.\"\"\"\n        sm = await sm_runner.start(ShireToRivendell)\n        await sm_runner.send(sm, \"depart\")\n        assert {\"road\"} == set(sm.configuration_values)\n\n    async def test_nested_compound_two_levels(self, sm_runner):\n        \"\"\"Three-level nesting: outer > middle > leaf.\"\"\"\n        sm = await sm_runner.start(MoriaExpedition)\n        assert {\"moria\", \"upper_halls\", \"entrance\"} == set(sm.configuration_values)\n\n    async def test_transition_from_inner_to_outer(self, sm_runner):\n        \"\"\"A deep child can transition to an outer state.\"\"\"\n        sm = await sm_runner.start(MoriaExpeditionWithEscape)\n        await sm_runner.send(sm, \"escape\")\n        assert {\"daylight\"} == set(sm.configuration_values)\n\n    async def test_cross_compound_transition(self, sm_runner):\n        \"\"\"Transition from one compound to another removes old children.\"\"\"\n        sm = await sm_runner.start(MiddleEarthJourney)\n        assert \"rivendell\" in sm.configuration_values\n        assert \"council\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"march_to_moria\")\n        assert \"moria\" in sm.configuration_values\n        assert \"gates\" in sm.configuration_values\n        assert \"rivendell\" not in sm.configuration_values\n        assert \"council\" not in sm.configuration_values\n\n    async def test_enter_compound_lands_on_initial(self, sm_runner):\n        \"\"\"Entering a compound from outside lands on the initial child.\"\"\"\n        sm = await sm_runner.start(MiddleEarthJourneyTwoCompounds)\n        await sm_runner.send(sm, \"march_to_moria\")\n        assert \"gates\" in sm.configuration_values\n        assert \"moria\" in sm.configuration_values\n\n    async def test_final_child_fires_done_state(self, sm_runner):\n        \"\"\"Reaching a final child triggers done.state.{parent_id}.\"\"\"\n        sm = await sm_runner.start(QuestForErebor)\n        assert \"approach\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"enter_mountain\")\n        assert {\"victory\"} == set(sm.configuration_values)\n\n    async def test_multiple_compound_sequential_traversal(self, sm_runner):\n        \"\"\"Traverse all three compounds sequentially.\"\"\"\n        sm = await sm_runner.start(MiddleEarthJourneyWithFinals)\n        await sm_runner.send(sm, \"march_to_moria\")\n        assert \"moria\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"march_to_lorien\")\n        assert \"lothlorien\" in sm.configuration_values\n        assert \"mirror\" in sm.configuration_values\n        assert \"moria\" not in sm.configuration_values\n\n    async def test_entry_exit_action_ordering(self, sm_runner):\n        \"\"\"on_exit fires before on_enter (verified via log).\"\"\"\n        log = []\n\n        class ActionOrderTracker(StateChart):\n            class realm(State.Compound):\n                day = State(initial=True)\n                night = State()\n\n                sunset = day.to(night)\n\n            outside = State(final=True)\n            leave = realm.to(outside)\n\n            def on_exit_day(self):\n                log.append(\"exit_day\")\n\n            def on_exit_realm(self):\n                log.append(\"exit_realm\")\n\n            def on_enter_outside(self):\n                log.append(\"enter_outside\")\n\n        sm = await sm_runner.start(ActionOrderTracker)\n        await sm_runner.send(sm, \"leave\")\n        assert log == [\"exit_day\", \"exit_realm\", \"enter_outside\"]\n\n    async def test_callbacks_inside_compound_class(self, sm_runner):\n        \"\"\"Methods defined inside the State.Compound class body are discovered.\"\"\"\n        log = []\n\n        class CallbackDiscovery(StateChart):\n            class realm(State.Compound):\n                peaceful = State(initial=True)\n                troubled = State()\n\n                darken = peaceful.to(troubled)\n\n                def on_enter_troubled(self):\n                    log.append(\"entered troubled times\")\n\n            end = State(final=True)\n            conclude = realm.to(end)\n\n        sm = await sm_runner.start(CallbackDiscovery)\n        await sm_runner.send(sm, \"darken\")\n        assert log == [\"entered troubled times\"]\n\n    async def test_done_state_inside_compound(self, sm_runner):\n        \"\"\"done_state_* bare transition inside a compound body registers done.state.* event.\"\"\"\n\n        class InnerDoneState(StateChart):\n            class outer(State.Compound):\n                class inner(State.Compound):\n                    start = State(initial=True)\n                    end = State(final=True)\n\n                    finish = start.to(end)\n\n                after_inner = State(final=True)\n                done_state_inner = inner.to(after_inner)\n\n            victory = State(final=True)\n            done_state_outer = outer.to(victory)\n\n        sm = await sm_runner.start(InnerDoneState)\n        assert \"start\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"finish\")\n        assert {\"victory\"} == set(sm.configuration_values)\n\n    async def test_done_invoke_inside_compound(self, sm_runner):\n        \"\"\"done_invoke_* bare transition inside a compound registers done.invoke.* event.\"\"\"\n\n        class InvokeInCompound(StateChart):\n            class wrapper(State.Compound):\n                loading = State(initial=True, invoke=lambda: 42)\n                loaded = State(final=True)\n\n                done_invoke_loading = loading.to(loaded)\n\n            done = State(final=True)\n            done_state_wrapper = wrapper.to(done)\n\n        sm = await sm_runner.start(InvokeInCompound)\n        await sm_runner.sleep(0.15)\n        await sm_runner.processing_loop(sm)\n        assert {\"done\"} == set(sm.configuration_values)\n\n    async def test_error_execution_inside_compound(self, sm_runner):\n        \"\"\"error_execution inside a compound body registers error.execution event.\"\"\"\n\n        def raise_error():\n            raise RuntimeError(\"boom\")\n\n        class ErrorInCompound(StateChart):\n            class active(State.Compound):\n                ok = State(initial=True)\n                failing = State()\n\n                trigger = ok.to(failing, on=raise_error)\n\n                errored = State()\n                error_execution = failing.to(errored)\n\n            done = State(final=True)\n            finish = active.to(done)\n\n        sm = await sm_runner.start(ErrorInCompound)\n        await sm_runner.send(sm, \"trigger\")\n        assert \"errored\" in sm.configuration_values\n\n    def test_compound_state_name_attribute(self):\n        \"\"\"The name= kwarg in class syntax sets the state name.\"\"\"\n\n        class NamedCompound(StateChart):\n            class shire(State.Compound, name=\"The Shire\"):\n                home = State(initial=True, final=True)\n\n        sm = NamedCompound()\n        assert sm.shire.name == \"The Shire\"\n"
  },
  {
    "path": "tests/test_statechart_delayed.py",
    "content": "\"\"\"Delayed event sends and cancellations.\n\nTests exercise queuing events with a delay (fires after elapsed time),\ncancelling delayed events before they fire, zero-delay immediate firing,\nand the Event(delay=...) definition syntax.\n\nTheme: Beacons of Gondor — signal fires propagate with timing.\n\"\"\"\n\nimport asyncio\n\nimport pytest\nfrom statemachine.event import BoundEvent\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.mark.timeout(10)\nclass TestDelayedEvents:\n    async def test_delayed_event_fires_after_delay(self, sm_runner):\n        \"\"\"Queuing a delayed event does not fire immediately; processing after delay does.\"\"\"\n\n        class BeaconsOfGondor(StateChart):\n            dark = State(initial=True)\n            first_lit = State()\n            all_lit = State(final=True)\n\n            light_first = dark.to(first_lit)\n            light_all = first_lit.to(all_lit)\n\n        sm = await sm_runner.start(BeaconsOfGondor)\n        await sm_runner.send(sm, \"light_first\")\n        assert \"first_lit\" in sm.configuration_values\n\n        # Queue the event with delay without triggering the processing loop\n        event = BoundEvent(id=\"light_all\", name=\"Light all\", delay=50, _sm=sm)\n        event.put()\n\n        # Not yet processed\n        assert \"first_lit\" in sm.configuration_values\n\n        await asyncio.sleep(0.1)\n        await sm_runner.processing_loop(sm)\n        assert \"all_lit\" in sm.configuration_values\n\n    async def test_cancel_delayed_event(self, sm_runner):\n        \"\"\"Cancelled delayed events do not fire.\"\"\"\n\n        class BeaconsOfGondor(StateChart):\n            dark = State(initial=True)\n            lit = State(final=True)\n\n            light = dark.to(lit)\n\n        sm = await sm_runner.start(BeaconsOfGondor)\n        # Queue delayed event\n        event = BoundEvent(id=\"light\", name=\"Light\", delay=500, _sm=sm)\n        event.put(send_id=\"beacon_signal\")\n\n        sm.cancel_event(\"beacon_signal\")\n\n        await asyncio.sleep(0.1)\n        await sm_runner.processing_loop(sm)\n        assert \"dark\" in sm.configuration_values\n\n    async def test_zero_delay_fires_immediately(self, sm_runner):\n        \"\"\"delay=0 fires immediately.\"\"\"\n\n        class BeaconsOfGondor(StateChart):\n            dark = State(initial=True)\n            lit = State(final=True)\n\n            light = dark.to(lit)\n\n        sm = await sm_runner.start(BeaconsOfGondor)\n        await sm_runner.send(sm, \"light\", delay=0)\n        assert \"lit\" in sm.configuration_values\n\n    async def test_delayed_event_on_event_definition(self, sm_runner):\n        \"\"\"Event(transitions, delay=100) syntax queues with a delay.\"\"\"\n\n        class BeaconsOfGondor(StateChart):\n            dark = State(initial=True)\n            lit = State(final=True)\n\n            light = Event(dark.to(lit), delay=50)\n\n        sm = await sm_runner.start(BeaconsOfGondor)\n        # Queue via BoundEvent.put() to avoid blocking in processing_loop\n        event = BoundEvent(id=\"light\", name=\"Light\", delay=50, _sm=sm)\n        event.put()\n\n        # Not yet processed\n        assert \"dark\" in sm.configuration_values\n\n        await asyncio.sleep(0.1)\n        await sm_runner.processing_loop(sm)\n        assert \"lit\" in sm.configuration_values\n"
  },
  {
    "path": "tests/test_statechart_donedata.py",
    "content": "\"\"\"Donedata on final states passes data to done.state handlers.\n\nTests exercise callable donedata returning dicts, done.state transitions triggered\nwith data, nested compound donedata propagation, InvalidDefinition for donedata on\nnon-final states, and listener capture of done event kwargs.\n\nTheme: Quest completion — returning data about how the quest ended.\n\"\"\"\n\nimport pytest\nfrom statemachine.exceptions import InvalidDefinition\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\nfrom tests.machines.donedata.destroy_the_ring import DestroyTheRing\nfrom tests.machines.donedata.destroy_the_ring_simple import DestroyTheRingSimple\nfrom tests.machines.donedata.nested_quest_donedata import NestedQuestDoneData\nfrom tests.machines.donedata.quest_for_erebor_done_convention import QuestForEreborDoneConvention\nfrom tests.machines.donedata.quest_for_erebor_explicit_id import QuestForEreborExplicitId\nfrom tests.machines.donedata.quest_for_erebor_multi_word import QuestForEreborMultiWord\nfrom tests.machines.donedata.quest_for_erebor_with_event import QuestForEreborWithEvent\n\n\n@pytest.mark.timeout(5)\nclass TestDoneData:\n    async def test_donedata_callable_returns_dict(self, sm_runner):\n        \"\"\"Handler receives donedata as kwargs.\"\"\"\n        sm = await sm_runner.start(DestroyTheRing)\n        await sm_runner.send(sm, \"finish\")\n        assert sm.received[\"ring_destroyed\"] is True\n        assert sm.received[\"hero\"] == \"frodo\"\n\n    async def test_donedata_fires_done_state_with_data(self, sm_runner):\n        \"\"\"done.state event fires and triggers a transition.\"\"\"\n        sm = await sm_runner.start(DestroyTheRingSimple)\n        await sm_runner.send(sm, \"finish\")\n        assert {\"celebration\"} == set(sm.configuration_values)\n\n    async def test_donedata_in_nested_compound(self, sm_runner):\n        \"\"\"Inner done.state propagates up through nesting.\"\"\"\n        sm = await sm_runner.start(NestedQuestDoneData)\n        await sm_runner.send(sm, \"go\")\n        # inner finishes -> done.state.inner -> after_inner (final)\n        # -> done.state.outer -> final\n        assert {\"final\"} == set(sm.configuration_values)\n\n    def test_donedata_only_on_final_state(self):\n        \"\"\"InvalidDefinition if donedata is on a non-final state.\"\"\"\n        with pytest.raises(InvalidDefinition, match=\"donedata.*final\"):\n\n            class BadDoneData(StateChart):\n                s1 = State(initial=True, donedata=\"oops\")\n                s2 = State(final=True)\n\n                go = s1.to(s2)\n\n    async def test_donedata_with_listener(self, sm_runner):\n        \"\"\"Listener captures done event kwargs.\"\"\"\n        captured = {}\n\n        class QuestListener:\n            def on_enter_celebration(self, ring_destroyed=None, **kwargs):\n                captured[\"ring_destroyed\"] = ring_destroyed\n\n        class DestroyTheRingWithListener(StateChart):\n            class quest(State.Compound):\n                traveling = State(initial=True)\n                completed = State(final=True, donedata=\"get_result\")\n\n                finish = traveling.to(completed)\n\n                def get_result(self):\n                    return {\"ring_destroyed\": True}\n\n            celebration = State(final=True)\n            done_state_quest = Event(quest.to(celebration))\n\n        listener = QuestListener()\n        sm = await sm_runner.start(DestroyTheRingWithListener, listeners=[listener])\n        await sm_runner.send(sm, \"finish\")\n        assert {\"celebration\"} == set(sm.configuration_values)\n\n\n@pytest.mark.timeout(5)\nclass TestDoneStateConvention:\n    async def test_done_state_convention_with_transition_list(self, sm_runner):\n        \"\"\"Bare TransitionList with done_state_ name auto-registers done.state.X.\"\"\"\n        sm = await sm_runner.start(QuestForEreborDoneConvention)\n        await sm_runner.send(sm, \"finish\")\n        assert {\"celebration\"} == set(sm.configuration_values)\n\n    async def test_done_state_convention_with_event_no_explicit_id(self, sm_runner):\n        \"\"\"Event() wrapper without explicit id= applies the convention.\"\"\"\n        sm = await sm_runner.start(QuestForEreborWithEvent)\n        await sm_runner.send(sm, \"finish\")\n        assert {\"celebration\"} == set(sm.configuration_values)\n\n    async def test_done_state_convention_preserves_explicit_id(self, sm_runner):\n        \"\"\"Explicit id= takes precedence over the convention.\"\"\"\n        sm = await sm_runner.start(QuestForEreborExplicitId)\n        await sm_runner.send(sm, \"finish\")\n        assert {\"celebration\"} == set(sm.configuration_values)\n\n    async def test_done_state_convention_with_multi_word_state(self, sm_runner):\n        \"\"\"done_state_lonely_mountain maps to done.state.lonely_mountain.\"\"\"\n        sm = await sm_runner.start(QuestForEreborMultiWord)\n        await sm_runner.send(sm, \"enter_mountain\")\n        assert {\"victory\"} == set(sm.configuration_values)\n"
  },
  {
    "path": "tests/test_statechart_error.py",
    "content": "\"\"\"Error handling in compound and parallel contexts.\n\nTests exercise error.execution firing when on_enter raises in a compound child,\nerror handling in parallel regions, and error.execution transitions that leave\na compound state entirely.\n\"\"\"\n\nimport pytest\n\nfrom statemachine import Event\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\n@pytest.mark.timeout(5)\nclass TestErrorExecutionStatechart:\n    async def test_error_in_compound_child_onentry(self, sm_runner):\n        \"\"\"Error in on_enter of compound child fires error.execution.\"\"\"\n\n        class CompoundError(StateChart):\n            class realm(State.Compound):\n                safe = State(initial=True)\n                danger = State()\n\n                enter_danger = safe.to(danger)\n\n                def on_enter_danger(self):\n                    raise RuntimeError(\"Balrog awakens!\")\n\n            error_state = State(final=True)\n            error_execution = Event(realm.to(error_state), id=\"error.execution\")\n\n        sm = await sm_runner.start(CompoundError)\n        await sm_runner.send(sm, \"enter_danger\")\n        assert {\"error_state\"} == set(sm.configuration_values)\n\n    async def test_error_in_parallel_region_isolation(self, sm_runner):\n        \"\"\"Error in one parallel region; error.execution handles the exit.\"\"\"\n\n        class ParallelError(StateChart):\n            class fronts(State.Parallel):\n                class battle_a(State.Compound):\n                    fighting = State(initial=True)\n                    victory = State()\n\n                    win = fighting.to(victory)\n\n                    def on_enter_victory(self):\n                        raise RuntimeError(\"Ambush!\")\n\n                class battle_b(State.Compound):\n                    holding = State(initial=True)\n                    won = State(final=True)\n\n                    triumph = holding.to(won)\n\n            error_state = State(final=True)\n            error_execution = Event(fronts.to(error_state), id=\"error.execution\")\n\n        sm = await sm_runner.start(ParallelError)\n        await sm_runner.send(sm, \"win\")\n        assert {\"error_state\"} == set(sm.configuration_values)\n\n    async def test_error_recovery_exits_compound(self, sm_runner):\n        \"\"\"error.execution transition leaves compound state entirely.\"\"\"\n\n        class CompoundRecovery(StateChart):\n            class dungeon(State.Compound):\n                room_a = State(initial=True)\n                room_b = State()\n\n                explore = room_a.to(room_b)\n\n                def on_enter_room_b(self):\n                    raise RuntimeError(\"Trap!\")\n\n            safe = State(final=True)\n            error_execution = Event(dungeon.to(safe), id=\"error.execution\")\n\n        sm = await sm_runner.start(CompoundRecovery)\n        await sm_runner.send(sm, \"explore\")\n        assert {\"safe\"} == set(sm.configuration_values)\n        assert \"dungeon\" not in sm.configuration_values\n"
  },
  {
    "path": "tests/test_statechart_eventless.py",
    "content": "\"\"\"Eventless (automatic) transitions with guards.\n\nTests exercise eventless transitions that fire when conditions are met,\nstay inactive when conditions are false, cascade through chains in a single\nmacrostep, work with gradual threshold conditions, and combine with In() guards.\n\nTheme: The One Ring's corruption and Beacons of Gondor.\n\"\"\"\n\nimport pytest\n\nfrom tests.machines.eventless.auto_advance import AutoAdvance\nfrom tests.machines.eventless.beacon_chain import BeaconChain\nfrom tests.machines.eventless.beacon_chain_lighting import BeaconChainLighting\nfrom tests.machines.eventless.coordinated_advance import CoordinatedAdvance\nfrom tests.machines.eventless.ring_corruption import RingCorruption\nfrom tests.machines.eventless.ring_corruption_with_bear_ring import RingCorruptionWithBearRing\nfrom tests.machines.eventless.ring_corruption_with_tick import RingCorruptionWithTick\n\n\n@pytest.mark.timeout(5)\nclass TestEventlessTransitions:\n    async def test_eventless_fires_when_condition_met(self, sm_runner):\n        \"\"\"Eventless transition fires when guard is True.\"\"\"\n        sm = await sm_runner.start(RingCorruption)\n        assert \"resisting\" in sm.configuration_values\n\n        sm.ring_power = 6\n        # Need to trigger processing loop — send a no-op event\n        await sm_runner.send(sm, \"tick\")\n        assert \"corrupted\" in sm.configuration_values\n\n    async def test_eventless_does_not_fire_when_condition_false(self, sm_runner):\n        \"\"\"Eventless transition stays when guard is False.\"\"\"\n        sm = await sm_runner.start(RingCorruptionWithTick)\n        sm.ring_power = 2\n        await sm_runner.send(sm, \"tick\")\n        assert \"resisting\" in sm.configuration_values\n\n    async def test_eventless_chain_cascades(self, sm_runner):\n        \"\"\"All beacons light in a single macrostep via unconditional eventless chain.\"\"\"\n        sm = await sm_runner.start(BeaconChainLighting)\n        # The chain should cascade through all states in a single macrostep\n        assert {\"all_lit\"} == set(sm.configuration_values)\n\n    async def test_eventless_gradual_condition(self, sm_runner):\n        \"\"\"Multiple events needed before the condition threshold is met.\"\"\"\n        sm = await sm_runner.start(RingCorruptionWithBearRing)\n        await sm_runner.send(sm, \"bear_ring\")  # power = 2\n        assert \"resisting\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"bear_ring\")  # power = 4\n        assert \"resisting\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"bear_ring\")  # power = 6 -> threshold exceeded\n        assert \"corrupted\" in sm.configuration_values\n\n    async def test_eventless_in_compound_state(self, sm_runner):\n        \"\"\"Eventless transition between compound children.\"\"\"\n        sm = await sm_runner.start(AutoAdvance)\n        # Eventless chain cascades through all children\n        assert {\"done\"} == set(sm.configuration_values)\n\n    async def test_eventless_with_in_condition(self, sm_runner):\n        \"\"\"Eventless transition guarded by In('state_id').\"\"\"\n        sm = await sm_runner.start(CoordinatedAdvance)\n        assert \"waiting\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"move_forward\")\n        # Vanguard advances, then rearguard's eventless fires\n        vals = set(sm.configuration_values)\n        assert \"advanced\" in vals\n        assert \"moved_up\" in vals\n\n    async def test_eventless_chain_with_final_triggers_done(self, sm_runner):\n        \"\"\"Eventless chain reaches final state -> done.state fires.\"\"\"\n        sm = await sm_runner.start(BeaconChain)\n        assert {\"signal_received\"} == set(sm.configuration_values)\n"
  },
  {
    "path": "tests/test_statechart_history.py",
    "content": "\"\"\"History state behavior with shallow and deep history.\n\nTests exercise shallow history (remembers last direct child), deep history\n(remembers exact leaf in nested compounds), default transitions on first visit,\nmultiple exit/reentry cycles, and the history_values dict.\n\nTheme: Gollum's dual personality — remembers which was active.\n\"\"\"\n\nimport pytest\n\nfrom tests.machines.history.deep_memory_of_moria import DeepMemoryOfMoria\nfrom tests.machines.history.gollum_personality import GollumPersonality\nfrom tests.machines.history.gollum_personality_default_gollum import GollumPersonalityDefaultGollum\nfrom tests.machines.history.gollum_personality_with_default import GollumPersonalityWithDefault\nfrom tests.machines.history.shallow_moria import ShallowMoria\n\n\n@pytest.mark.timeout(5)\nclass TestHistoryStates:\n    async def test_shallow_history_remembers_last_child(self, sm_runner):\n        \"\"\"Exit compound, re-enter via history -> restores last active child.\"\"\"\n        sm = await sm_runner.start(GollumPersonality)\n        await sm_runner.send(sm, \"dark_side\")\n        assert \"gollum\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"leave\")\n        assert {\"outside\"} == set(sm.configuration_values)\n\n        await sm_runner.send(sm, \"return_via_history\")\n        assert \"gollum\" in sm.configuration_values\n        assert \"personality\" in sm.configuration_values\n\n    async def test_shallow_history_default_on_first_visit(self, sm_runner):\n        \"\"\"No prior visit -> history uses default transition target.\"\"\"\n        sm = await sm_runner.start(GollumPersonalityWithDefault)\n        assert {\"outside\"} == set(sm.configuration_values)\n\n        await sm_runner.send(sm, \"enter_via_history\")\n        assert \"smeagol\" in sm.configuration_values\n\n    async def test_deep_history_remembers_full_descendant(self, sm_runner):\n        \"\"\"Deep history restores the exact leaf in a nested compound.\"\"\"\n        sm = await sm_runner.start(DeepMemoryOfMoria)\n        await sm_runner.send(sm, \"explore\")\n        assert \"chamber\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"escape\")\n        assert {\"outside\"} == set(sm.configuration_values)\n\n        await sm_runner.send(sm, \"return_deep\")\n        assert \"chamber\" in sm.configuration_values\n        assert \"halls\" in sm.configuration_values\n        assert \"moria\" in sm.configuration_values\n\n    async def test_multiple_exits_and_reentries(self, sm_runner):\n        \"\"\"History updates each time we exit the compound.\"\"\"\n        sm = await sm_runner.start(GollumPersonality)\n        await sm_runner.send(sm, \"leave\")\n        await sm_runner.send(sm, \"return_via_history\")\n        assert \"smeagol\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"dark_side\")\n        await sm_runner.send(sm, \"leave\")\n        await sm_runner.send(sm, \"return_via_history\")\n        assert \"gollum\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"light_side\")\n        await sm_runner.send(sm, \"leave\")\n        await sm_runner.send(sm, \"return_via_history\")\n        assert \"smeagol\" in sm.configuration_values\n\n    async def test_history_after_state_change(self, sm_runner):\n        \"\"\"Change state within compound, exit, re-enter -> new state restored.\"\"\"\n        sm = await sm_runner.start(GollumPersonality)\n        await sm_runner.send(sm, \"dark_side\")\n        await sm_runner.send(sm, \"leave\")\n        await sm_runner.send(sm, \"return_via_history\")\n        assert \"gollum\" in sm.configuration_values\n\n    async def test_shallow_only_remembers_immediate_child(self, sm_runner):\n        \"\"\"Shallow history in nested compound restores direct child, not grandchild.\"\"\"\n        sm = await sm_runner.start(ShallowMoria)\n        await sm_runner.send(sm, \"explore\")\n        assert \"chamber\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"escape\")\n        await sm_runner.send(sm, \"return_shallow\")\n        # Shallow history restores 'halls' as the direct child,\n        # but re-enters halls at its initial state (entrance), not chamber\n        assert \"halls\" in sm.configuration_values\n        assert \"entrance\" in sm.configuration_values\n\n    async def test_history_values_dict_populated(self, sm_runner):\n        \"\"\"sm.history_values[history_id] has saved states after exit.\"\"\"\n        sm = await sm_runner.start(GollumPersonality)\n        await sm_runner.send(sm, \"dark_side\")\n        await sm_runner.send(sm, \"leave\")\n        assert \"h\" in sm.history_values\n        saved = sm.history_values[\"h\"]\n        assert len(saved) == 1\n        assert saved[0].id == \"gollum\"\n\n    async def test_history_with_default_transition(self, sm_runner):\n        \"\"\"HistoryState with explicit default .to() transition.\"\"\"\n        sm = await sm_runner.start(GollumPersonalityDefaultGollum)\n        await sm_runner.send(sm, \"enter_via_history\")\n        assert \"gollum\" in sm.configuration_values\n"
  },
  {
    "path": "tests/test_statechart_in_condition.py",
    "content": "\"\"\"In('state_id') condition for cross-state checks.\n\nTests exercise In() conditions that enable/block transitions based on whether\na given state is active, cross-region In() in parallel states, In() with\ncompound descendants, combined event + In() guards, and eventless + In() guards.\n\nTheme: Fellowship coordination — actions depend on where members are.\n\"\"\"\n\nimport pytest\n\nfrom tests.machines.in_condition.combined_guard import CombinedGuard\nfrom tests.machines.in_condition.descendant_check import DescendantCheck\nfrom tests.machines.in_condition.eventless_in import EventlessIn\nfrom tests.machines.in_condition.fellowship import Fellowship\nfrom tests.machines.in_condition.fellowship_coordination import FellowshipCoordination\nfrom tests.machines.in_condition.gate_of_moria import GateOfMoria\n\n\n@pytest.mark.timeout(5)\nclass TestInCondition:\n    async def test_in_condition_true_enables_transition(self, sm_runner):\n        \"\"\"In('state_id') when state is active -> transition fires.\"\"\"\n        sm = await sm_runner.start(Fellowship)\n        await sm_runner.send(sm, \"journey\")\n        vals = set(sm.configuration_values)\n        assert \"mordor_f\" in vals\n        assert \"mordor_s\" in vals\n\n    async def test_in_condition_false_blocks_transition(self, sm_runner):\n        \"\"\"In('state_id') when state is not active -> transition blocked.\"\"\"\n        sm = await sm_runner.start(GateOfMoria)\n        await sm_runner.send(sm, \"enter_gate\")\n        assert \"outside\" in sm.configuration_values\n\n    async def test_in_with_parallel_regions(self, sm_runner):\n        \"\"\"Cross-region In() evaluation in parallel states.\"\"\"\n        sm = await sm_runner.start(FellowshipCoordination)\n        vals = set(sm.configuration_values)\n        assert \"waiting\" in vals\n        assert \"scouting\" in vals\n\n        await sm_runner.send(sm, \"report\")\n        vals = set(sm.configuration_values)\n        assert \"reported\" in vals\n        assert \"marching\" in vals\n\n    async def test_in_with_compound_descendant(self, sm_runner):\n        \"\"\"In('child') when child is an active descendant.\"\"\"\n        sm = await sm_runner.start(DescendantCheck)\n        await sm_runner.send(sm, \"conquer\")\n        assert \"realm\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"ascend\")\n        assert \"castle\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"conquer\")\n        assert {\"conquered\"} == set(sm.configuration_values)\n\n    async def test_in_combined_with_event(self, sm_runner):\n        \"\"\"Event + In() guard together.\"\"\"\n        sm = await sm_runner.start(CombinedGuard)\n        await sm_runner.send(sm, \"charge\")\n        assert \"idle\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"return_scout\")\n        await sm_runner.send(sm, \"charge\")\n        assert \"attacking\" in sm.configuration_values\n\n    async def test_in_with_eventless_transition(self, sm_runner):\n        \"\"\"Eventless + In() guard.\"\"\"\n        sm = await sm_runner.start(EventlessIn)\n        assert \"waiting\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"get_ready\")\n        vals = set(sm.configuration_values)\n        assert \"ready\" in vals\n        assert \"moving\" in vals\n"
  },
  {
    "path": "tests/test_statechart_parallel.py",
    "content": "\"\"\"Parallel state behavior with independent regions.\n\nTests exercise entering parallel states (all regions activate), region isolation\n(events in one region don't affect others), exiting parallel states, done.state\nwhen all regions reach final, and mixed compound/parallel hierarchies.\n\nTheme: War of the Ring — multiple simultaneous fronts.\n\"\"\"\n\nimport pytest\n\nfrom tests.machines.parallel.session import Session\nfrom tests.machines.parallel.session_with_done_state import SessionWithDoneState\nfrom tests.machines.parallel.two_towers import TwoTowers\nfrom tests.machines.parallel.war_of_the_ring import WarOfTheRing\nfrom tests.machines.parallel.war_with_exit import WarWithExit\n\n\n@pytest.mark.timeout(5)\nclass TestParallelStates:\n    async def test_parallel_activates_all_regions(self, sm_runner):\n        \"\"\"Entering a parallel state activates the initial child of every region.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        vals = set(sm.configuration_values)\n        assert \"war\" in vals\n        assert \"frodos_quest\" in vals\n        assert \"shire\" in vals\n        assert \"aragorns_path\" in vals\n        assert \"ranger\" in vals\n        assert \"gandalfs_defense\" in vals\n        assert \"rohan\" in vals\n\n    async def test_independent_transitions_in_regions(self, sm_runner):\n        \"\"\"An event in one region does not affect others.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        await sm_runner.send(sm, \"journey\")\n        vals = set(sm.configuration_values)\n        assert \"mordor\" in vals\n        assert \"ranger\" in vals  # unchanged\n        assert \"rohan\" in vals  # unchanged\n\n    async def test_configuration_includes_all_active_states(self, sm_runner):\n        \"\"\"Configuration set includes all active states across regions.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        config_ids = {s.id for s in sm.configuration}\n        assert config_ids == {\n            \"war\",\n            \"frodos_quest\",\n            \"shire\",\n            \"aragorns_path\",\n            \"ranger\",\n            \"gandalfs_defense\",\n            \"rohan\",\n        }\n\n    async def test_exit_parallel_exits_all_regions(self, sm_runner):\n        \"\"\"Transition out of a parallel clears everything.\"\"\"\n        sm = await sm_runner.start(WarWithExit)\n        assert \"war\" in sm.configuration_values\n        await sm_runner.send(sm, \"truce\")\n        assert {\"peace\"} == set(sm.configuration_values)\n\n    async def test_event_in_one_region_no_effect_on_others(self, sm_runner):\n        \"\"\"Region isolation: events affect only the targeted region.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        await sm_runner.send(sm, \"coronation\")\n        vals = set(sm.configuration_values)\n        assert \"king\" in vals\n        assert \"shire\" in vals  # Frodo's region unchanged\n        assert \"rohan\" in vals  # Gandalf's region unchanged\n\n    async def test_parallel_with_compound_children(self, sm_runner):\n        \"\"\"Mixed hierarchy: parallel with compound regions verified.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        assert \"shire\" in sm.configuration_values\n        assert \"ranger\" in sm.configuration_values\n        assert \"rohan\" in sm.configuration_values\n\n    async def test_current_state_value_set_comparison(self, sm_runner):\n        \"\"\"configuration_values supports set comparison for parallel states.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        vals = set(sm.configuration_values)\n        expected = {\n            \"war\",\n            \"frodos_quest\",\n            \"shire\",\n            \"aragorns_path\",\n            \"ranger\",\n            \"gandalfs_defense\",\n            \"rohan\",\n        }\n        assert vals == expected\n\n    async def test_parallel_done_when_all_regions_final(self, sm_runner):\n        \"\"\"done.state fires when ALL regions reach a final state.\"\"\"\n        sm = await sm_runner.start(TwoTowers)\n        await sm_runner.send(sm, \"win\")\n        # Only one region is final, battle continues\n        assert \"battle\" in sm.configuration_values\n\n        await sm_runner.send(sm, \"flood\")\n        # Both regions are final -> done.state.battle fires\n        assert {\"aftermath\"} == set(sm.configuration_values)\n\n    async def test_parallel_not_done_when_one_region_final(self, sm_runner):\n        \"\"\"Parallel not done when only one region reaches final.\"\"\"\n        sm = await sm_runner.start(TwoTowers)\n        await sm_runner.send(sm, \"win\")\n        assert \"battle\" in sm.configuration_values\n        assert \"victory\" in sm.configuration_values\n        assert \"besieging\" in sm.configuration_values\n\n    async def test_transition_within_compound_inside_parallel(self, sm_runner):\n        \"\"\"Deep transition within a compound region of a parallel state.\"\"\"\n        sm = await sm_runner.start(WarOfTheRing)\n        await sm_runner.send(sm, \"journey\")\n        await sm_runner.send(sm, \"destroy_ring\")\n        vals = set(sm.configuration_values)\n        assert \"mount_doom\" in vals\n        assert \"ranger\" in vals  # other regions unchanged\n\n    async def test_top_level_parallel_terminates_when_all_children_final(self, sm_runner):\n        \"\"\"A root parallel terminates when all regions reach final states.\"\"\"\n        sm = await sm_runner.start(Session)\n        assert sm.is_terminated is False\n\n        await sm_runner.send(sm, \"close_ui\")\n        assert sm.is_terminated is False  # one region still active\n\n        await sm_runner.send(sm, \"stop_backend\")\n        assert sm.is_terminated is True\n\n    async def test_top_level_parallel_done_state_fires_before_termination(self, sm_runner):\n        \"\"\"done.state fires and transitions before root-final check terminates.\"\"\"\n        sm = await sm_runner.start(SessionWithDoneState)\n        await sm_runner.send(sm, \"close_ui\")\n        await sm_runner.send(sm, \"stop_backend\")\n        # done.state.session fires, transitions to finished, then terminates\n        assert {\"finished\"} == set(sm.configuration_values)\n        assert sm.is_terminated is True\n\n    async def test_top_level_parallel_not_terminated_when_one_region_pending(self, sm_runner):\n        \"\"\"Machine keeps running when only one region reaches final.\"\"\"\n        sm = await sm_runner.start(Session)\n        await sm_runner.send(sm, \"close_ui\")\n        assert sm.is_terminated is False\n        assert \"closed\" in sm.configuration_values\n        assert \"running\" in sm.configuration_values\n"
  },
  {
    "path": "tests/test_statemachine.py",
    "content": "import pytest\nfrom statemachine.orderedset import OrderedSet\n\nfrom statemachine import HistoryState\nfrom statemachine import State\nfrom statemachine import StateChart\nfrom statemachine import exceptions\nfrom tests.models import MyModel\n\n\ndef test_machine_repr(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n    assert (\n        repr(machine) == \"CampaignMachine(model=MyModel({'state': 'draft'}), \"\n        \"state_field='state', configuration=['draft'])\"\n    )\n\n\ndef test_machine_should_be_at_start_state(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n\n    assert [s.value for s in campaign_machine.states] == [\n        \"draft\",\n        \"producing\",\n        \"closed\",\n    ]\n    assert [t.name for t in campaign_machine.events] == [\n        \"Add job\",\n        \"Produce\",\n        \"Deliver\",\n    ]\n\n    assert model.state == \"draft\"\n    assert machine.draft.is_active\n\n\ndef test_machine_should_only_allow_only_one_initial_state():\n    with pytest.raises(exceptions.InvalidDefinition):\n\n        class CampaignMachine(StateChart):\n            \"A workflow machine\"\n\n            draft = State(initial=True)\n            producing = State()\n            closed = State(\n                \"Closed\", initial=True\n            )  # Should raise an Exception right after the class is defined\n\n            add_job = draft.to(draft) | producing.to(producing)\n            produce = draft.to(producing)\n            deliver = producing.to(closed)\n\n\ndef test_machine_should_activate_initial_state(mocker):\n    spy = mocker.Mock()\n\n    class CampaignMachine(StateChart):\n        \"A workflow machine\"\n\n        draft = State(initial=True)\n        producing = State()\n        closed = State(final=True)\n\n        add_job = draft.to(draft) | producing.to(producing)\n        produce = draft.to(producing)\n        deliver = producing.to(closed)\n\n        def on_enter_draft(self):\n            spy(\"draft\")\n            return \"draft\"\n\n    sm = CampaignMachine()\n\n    spy.assert_called_once_with(\"draft\")\n    assert sm.draft.is_active\n    assert sm.draft.is_active\n\n    spy.reset_mock()\n    # trying to activate the initial state again should does nothing\n    assert sm.activate_initial_state() is None\n\n    spy.assert_not_called()\n    assert sm.draft.is_active\n    assert sm.draft.is_active\n\n\ndef test_machine_should_not_allow_transitions_from_final_state():\n    with pytest.raises(exceptions.InvalidDefinition):\n\n        class CampaignMachine(StateChart):\n            \"A workflow machine\"\n\n            draft = State(initial=True)\n            producing = State()\n            closed = State(final=True)\n\n            add_job = draft.to(draft) | producing.to(producing) | closed.to(draft)\n            produce = draft.to(producing)\n            deliver = producing.to(closed)\n\n\ndef test_should_change_state(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n\n    assert model.state == \"draft\"\n    assert machine.draft.is_active\n\n    machine.produce()\n\n    assert model.state == \"producing\"\n    assert machine.producing.is_active\n\n\ndef test_should_run_a_transition_that_keeps_the_state(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n\n    assert model.state == \"draft\"\n    assert machine.draft.is_active\n\n    machine.add_job()\n    assert model.state == \"draft\"\n    assert machine.draft.is_active\n\n    machine.produce()\n    assert model.state == \"producing\"\n    assert machine.producing.is_active\n\n    machine.add_job()\n    assert model.state == \"producing\"\n    assert machine.producing.is_active\n\n\ndef test_should_change_state_with_multiple_machine_instances(campaign_machine):\n    model1 = MyModel()\n    model2 = MyModel()\n    machine1 = campaign_machine(model1)\n    machine2 = campaign_machine(model2)\n\n    assert machine1.draft.is_active\n    assert machine2.draft.is_active\n\n    p1 = machine1.produce\n    p2 = machine2.produce\n\n    p2()\n    assert machine1.draft.is_active\n    assert machine2.producing.is_active\n\n    p1()\n    assert machine1.producing.is_active\n    assert machine2.producing.is_active\n\n\ndef test_machine_should_list_allowed_events_in_the_current_state(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n\n    assert model.state == \"draft\"\n    assert [t.name for t in machine.allowed_events] == [\"Add job\", \"Produce\"]\n\n    machine.produce()\n    assert model.state == \"producing\"\n    assert [t.name for t in machine.allowed_events] == [\"Add job\", \"Deliver\"]\n\n    deliver = machine.allowed_events[1]\n\n    deliver()\n    assert model.state == \"closed\"\n    assert machine.allowed_events == []\n\n\ndef test_machine_should_run_a_transition_by_his_key(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n\n    assert model.state == \"draft\"\n\n    machine.send(\"add_job\")\n    assert model.state == \"draft\"\n    assert machine.draft.is_active\n\n    machine.send(\"produce\")\n    assert model.state == \"producing\"\n    assert machine.producing.is_active\n\n\ndef test_machine_should_use_and_model_attr_other_than_state(campaign_machine):\n    model = MyModel(status=\"producing\")\n    machine = campaign_machine(model, state_field=\"status\")\n\n    assert getattr(model, \"state\", None) is None\n    assert model.status == \"producing\"\n    assert machine.producing.is_active\n\n    machine.deliver()\n\n    assert model.status == \"closed\"\n    assert machine.closed.is_active\n\n\ndef test_cant_assign_an_invalid_state_directly(campaign_machine):\n    machine = campaign_machine()\n    with pytest.raises(exceptions.InvalidStateValue):\n        machine.current_state_value = \"non existing state\"\n\n\ndef test_should_allow_validate_data_for_transition(campaign_machine_with_validator):\n    model = MyModel()\n    machine = campaign_machine_with_validator(model)\n\n    with pytest.raises(LookupError):\n        machine.produce()\n\n    machine.produce(goods=\"something\")\n\n    assert model.state == \"producing\"\n\n\ndef test_should_check_if_is_in_status(campaign_machine):\n    model = MyModel()\n    machine = campaign_machine(model)\n\n    assert machine.draft.is_active\n    assert not machine.producing.is_active\n    assert not machine.closed.is_active\n\n    machine.produce()\n\n    assert not machine.draft.is_active\n    assert machine.producing.is_active\n    assert not machine.closed.is_active\n\n    machine.deliver()\n\n    assert not machine.draft.is_active\n    assert not machine.producing.is_active\n    assert machine.closed.is_active\n\n\ndef test_defined_value_must_be_assigned_to_models(campaign_machine_with_values):\n    model = MyModel()\n    machine = campaign_machine_with_values(model)\n\n    assert model.state == 1\n    machine.produce()\n    assert model.state == 2\n    machine.deliver()\n    assert model.state == 3\n\n\ndef test_state_machine_without_model(campaign_machine):\n    machine = campaign_machine()\n    assert machine.draft.is_active\n    assert not machine.producing.is_active\n    assert not machine.closed.is_active\n\n    machine.produce()\n\n    assert not machine.draft.is_active\n    assert machine.producing.is_active\n    assert not machine.closed.is_active\n\n\n@pytest.mark.parametrize(\n    (\"model\", \"machine_name\", \"start_value\"),\n    [\n        (None, \"campaign_machine\", \"producing\"),\n        (None, \"campaign_machine_with_values\", 2),\n        (MyModel(), \"campaign_machine\", \"producing\"),\n        (MyModel(), \"campaign_machine_with_values\", 2),\n    ],\n)\ndef test_state_machine_with_a_start_value(request, model, machine_name, start_value):\n    machine_cls = request.getfixturevalue(machine_name)\n    machine = machine_cls(model, start_value=start_value)\n    assert not machine.draft.is_active\n    assert machine.producing.is_active\n    assert not model or model.state == start_value\n\n\n@pytest.mark.parametrize(\n    (\"model\", \"machine_name\", \"start_value\"),\n    [\n        (None, \"campaign_machine\", \"tapioca\"),\n        (None, \"campaign_machine_with_values\", 99),\n        (MyModel(), \"campaign_machine\", \"tapioca\"),\n        (MyModel(), \"campaign_machine_with_values\", 99),\n    ],\n)\ndef test_state_machine_with_a_invalid_start_value(request, model, machine_name, start_value):\n    machine_cls = request.getfixturevalue(machine_name)\n    with pytest.raises(exceptions.InvalidStateValue):\n        machine_cls(model, start_value=start_value)\n\n\ndef test_state_machine_with_a_invalid_model_state_value(request, campaign_machine):\n    machine_cls = campaign_machine\n    model = MyModel(state=\"tapioca\")\n    sm = machine_cls(model)\n\n    with pytest.raises(KeyError):\n        sm.configuration  # noqa: B018\n\n\ndef test_should_not_create_instance_of_abstract_machine():\n    class EmptyMachine(StateChart):\n        \"An empty machine\"\n\n        pass\n\n    with pytest.raises(exceptions.InvalidDefinition):\n        EmptyMachine()\n\n\ndef test_should_not_create_instance_of_machine_without_states():\n    s1 = State()\n\n    class OnlyTransitionMachine(StateChart):\n        t1 = s1.to.itself()\n\n    with pytest.raises(exceptions.InvalidDefinition):\n        OnlyTransitionMachine()\n\n\ndef test_should_not_create_instance_of_machine_without_transitions():\n    with pytest.raises(exceptions.InvalidDefinition):\n\n        class NoTransitionsMachine(StateChart):\n            \"A machine without transitions\"\n\n            initial = State(initial=True)\n\n\ndef test_should_not_create_disconnected_machine():\n    expected = (\n        r\"There are unreachable states. The statemachine graph should have a single component. \"\n        r\"Disconnected states: \\['blue'\\]\"\n    )\n    with pytest.raises(exceptions.InvalidDefinition, match=expected):\n\n        class BrokenTrafficLightMachine(StateChart):\n            \"A broken traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            blue = State()  # This state is unreachable\n\n            cycle = green.to(yellow) | yellow.to(green)\n\n\ndef test_should_not_create_big_disconnected_machine():\n    expected = (\n        r\"There are unreachable states. The statemachine graph should have a single component. \"\n        r\"Disconnected states: \\[.*\\]$\"\n    )\n    with pytest.raises(exceptions.InvalidDefinition, match=expected):\n\n        class BrokenTrafficLightMachine(StateChart):\n            \"A broken traffic light machine\"\n\n            green = State(initial=True)\n            yellow = State()\n            magenta = State()  # This state is unreachable\n            red = State()\n            cyan = State()\n            blue = State()  # This state is also unreachable\n\n            cycle = green.to(yellow)\n            diverge = green.to(cyan) | cyan.to(red)\n            validate = yellow.to(green)\n\n\ndef test_disconnected_validation_bypassed_by_flag():\n    \"\"\"Setting validate_disconnected_states=False allows unreachable states.\"\"\"\n\n    class DisconnectedButAllowed(StateChart):\n        validate_disconnected_states = False\n        green = State(initial=True)\n        yellow = State()\n        blue = State()  # unreachable, but flag disables the check\n\n        cycle = green.to(yellow) | yellow.to(green)\n        blink = blue.to.itself()\n\n    assert \"green\" in DisconnectedButAllowed.states_map\n\n\ndef test_parallel_states_reachable_without_disabling_flag():\n    \"\"\"Substates of parallel regions are reachable via hierarchy.\"\"\"\n\n    class ParallelMachine(StateChart):\n        class top(State.Parallel):\n            class region1(State.Compound):\n                a = State(initial=True)\n                b = State(final=True)\n                go = a.to(b)\n\n            class region2(State.Compound):\n                c = State(initial=True)\n                d = State(final=True)\n                go2 = c.to(d)\n\n    assert \"a\" in ParallelMachine.states_map\n    assert \"c\" in ParallelMachine.states_map\n\n\ndef test_compound_substates_reachable_without_disabling_flag():\n    \"\"\"Substates of a compound state are reachable via hierarchy.\"\"\"\n\n    class CompoundMachine(StateChart):\n        start = State(initial=True)\n\n        class parent(State.Compound):\n            child1 = State(initial=True)\n            child2 = State(final=True)\n            inner = child1.to(child2)\n\n        enter = start.to(parent)\n\n    assert \"child1\" in CompoundMachine.states_map\n    assert \"child2\" in CompoundMachine.states_map\n\n\ndef test_history_state_reachable_without_disabling_flag():\n    \"\"\"History states and their parent compound are reachable via hierarchy.\"\"\"\n\n    class HistoryMachine(StateChart):\n        outside = State(initial=True)\n\n        class compound(State.Compound):\n            a = State(initial=True)\n            b = State()\n            h = HistoryState()\n            go = a.to(b)\n\n        enter_via_history = outside.to(compound.h)\n        leave = compound.to(outside)\n\n    assert \"compound\" in HistoryMachine.states_map\n    assert \"a\" in HistoryMachine.states_map\n\n\ndef test_state_value_is_correct():\n    STATE_NEW = 0\n    STATE_DRAFT = 1\n\n    class ValueTestModel(StateChart):\n        new = State(STATE_NEW, value=STATE_NEW, initial=True)\n        draft = State(STATE_DRAFT, value=STATE_DRAFT, final=True)\n\n        write = new.to(draft)\n\n    model = ValueTestModel()\n    assert model.new.value == STATE_NEW\n    assert model.draft.value == STATE_DRAFT\n\n\ndef test_final_states(campaign_machine_with_final_state):\n    model = MyModel()\n    machine = campaign_machine_with_final_state(model)\n    final_states = machine.final_states\n    assert len(final_states) == 1\n    assert final_states[0].name == \"Closed\"\n\n\ndef test_should_not_override_states_properties(campaign_machine):\n    machine = campaign_machine()\n    with pytest.raises(exceptions.StateMachineError) as e:\n        machine.draft = \"something else\"\n\n    assert \"State overriding is not allowed. Trying to add 'something else' to draft\" in str(e)\n\n\nclass TestWarnings:\n    def test_should_warn_if_model_already_has_attribute_and_binding_is_enabled(\n        self, campaign_machine_with_final_state, capsys\n    ):\n        class Model:\n            state = \"draft\"\n\n            def produce(self):\n                return f\"producing from {self.__class__.__name__!r}\"\n\n        model = Model()\n\n        sm = campaign_machine_with_final_state(model)\n        with pytest.warns(\n            UserWarning, match=\"Attribute 'produce' already exists on <tests.test.*\"\n        ):\n            sm.bind_events_to(model)\n\n        assert model.produce() == \"producing from 'Model'\"\n        assert sm.current_state_value == \"draft\"\n\n        assert sm.produce() is None\n        assert sm.current_state_value == \"producing\"\n\n        # event trigger bound to the model\n        model.deliver()\n        assert sm.current_state_value == \"closed\"\n\n    def test_should_raise_if_thereis_a_trap_state(self):\n        with pytest.raises(\n            exceptions.InvalidDefinition,\n            match=r\"have no outgoing transition: \\['state_without_outgoing_transition'\\]\",\n        ):\n\n            class TrapStateMachine(StateChart):\n                initial = State(initial=True)\n                state_without_outgoing_transition = State()\n\n                t = initial.to(state_without_outgoing_transition)\n\n    def test_should_raise_if_no_path_to_a_final_state(self):\n        with pytest.raises(\n            exceptions.InvalidDefinition,\n            match=r\"have no path to a final state: \\['producing'\\]\",\n        ):\n\n            class TrapStateMachine(StateChart):\n                started = State(initial=True)\n                closed = State(final=True)\n                producing = State()\n\n                start = started.to(producing)\n                close = started.to(closed)\n                add_job = producing.to.itself(internal=True)\n\n\ndef test_model_with_custom_bool_is_not_replaced(campaign_machine):\n    class FalseyModel(MyModel):\n        def __bool__(self):\n            return False\n\n    model = FalseyModel()\n    machine = campaign_machine(model)\n\n    assert machine.model is model\n    assert model.state == \"draft\"\n\n    machine.produce()\n    assert model.state == \"producing\"\n\n\ndef test_abstract_sm_no_states():\n    \"\"\"A state machine class with no states is abstract.\"\"\"\n\n    class AbstractSM(StateChart):\n        pass\n\n    assert AbstractSM._abstract is True\n\n\ndef test_raise_sends_internal_event():\n    \"\"\"raise_ sends an internal event.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        internal_event = s1.to(s2)\n\n    sm = SM()\n    sm.raise_(\"internal_event\")\n    assert sm.s2.is_active\n\n\ndef test_configuration_values_returns_ordered_set():\n    \"\"\"configuration_values returns OrderedSet.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n    sm = SM()\n    vals = sm.configuration_values\n    assert isinstance(vals, OrderedSet)\n\n\ndef test_states_getitem():\n    \"\"\"States supports index access.\"\"\"\n\n    class SM(StateChart):\n        s1 = State(initial=True)\n        s2 = State(final=True)\n\n        go = s1.to(s2)\n\n    assert SM.states[0].id == \"s1\"\n    assert SM.states[1].id == \"s2\"\n\n\ndef test_multiple_initial_states_raises():\n    \"\"\"Multiple initial states raise InvalidDefinition.\"\"\"\n    with pytest.raises(exceptions.InvalidDefinition, match=\"one and only one initial state\"):\n\n        class BadSM(StateChart):\n            s1 = State(initial=True)\n            s2 = State(initial=True)\n\n            go = s1.to(s2)\n\n\ndef test_configuration_values_returns_orderedset_when_compound_state():\n    \"\"\"configuration_values returns the OrderedSet directly when it is already one.\"\"\"\n    from statemachine import StateChart\n\n    class SM(StateChart):\n        class parent(State.Compound, name=\"parent\"):\n            child1 = State(initial=True)\n            child2 = State(final=True)\n\n            go = child1.to(child2)\n\n        start = State(initial=True)\n        end = State(final=True)\n\n        enter = start.to(parent)\n        finish = parent.to(end)\n\n    sm = SM()\n    sm.send(\"enter\")\n    vals = sm.configuration_values\n    assert isinstance(vals, OrderedSet)\n\n\nclass TestEnabledEvents:\n    def test_no_conditions_same_as_allowed_events(self, campaign_machine):\n        \"\"\"Without conditions, enabled_events should match allowed_events.\"\"\"\n        sm = campaign_machine()\n        assert [e.id for e in sm.enabled_events()] == [e.id for e in sm.allowed_events]\n\n    def test_passing_condition_returns_event(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"is_ready\")\n\n            def is_ready(self):\n                return True\n\n        sm = MyMachine()\n        assert [e.id for e in sm.enabled_events()] == [\"go\"]\n\n    def test_failing_condition_excludes_event(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"is_ready\")\n\n            def is_ready(self):\n                return False\n\n        sm = MyMachine()\n        assert sm.enabled_events() == []\n\n    def test_multiple_transitions_one_passes(self):\n        \"\"\"Same event with multiple transitions: included if at least one passes.\"\"\"\n\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n            s2 = State(final=True)\n\n            go = s0.to(s1, cond=\"cond_false\") | s0.to(s2, cond=\"cond_true\")\n\n            def cond_false(self):\n                return False\n\n            def cond_true(self):\n                return True\n\n        sm = MyMachine()\n        assert [e.id for e in sm.enabled_events()] == [\"go\"]\n\n    def test_duplicate_event_across_transitions_deduplicated(self):\n        \"\"\"Same event on multiple passing transitions appears only once.\"\"\"\n\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n            s2 = State(final=True)\n\n            go = s0.to(s1, cond=\"cond_a\") | s0.to(s2, cond=\"cond_b\")\n\n            def cond_a(self):\n                return True\n\n            def cond_b(self):\n                return True\n\n        sm = MyMachine()\n        ids = [e.id for e in sm.enabled_events()]\n        assert ids == [\"go\"]\n        assert len(ids) == 1\n\n    def test_final_state_returns_empty(self, campaign_machine):\n        sm = campaign_machine()\n        sm.produce()\n        sm.deliver()\n        assert sm.enabled_events() == []\n\n    def test_kwargs_forwarded_to_conditions(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"check_value\")\n\n            def check_value(self, value=0):\n                return value > 10\n\n        sm = MyMachine()\n        assert sm.enabled_events() == []\n        assert [e.id for e in sm.enabled_events(value=20)] == [\"go\"]\n\n    def test_condition_exception_treated_as_enabled(self):\n        \"\"\"If a condition raises, the event is treated as enabled (permissive).\"\"\"\n\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, cond=\"bad_cond\")\n\n            def bad_cond(self):\n                raise RuntimeError(\"boom\")\n\n        sm = MyMachine()\n        assert [e.id for e in sm.enabled_events()] == [\"go\"]\n\n    def test_mixed_enabled_and_disabled(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n            s2 = State(final=True)\n\n            go = s0.to(s1, cond=\"cond_true\")\n            stop = s0.to(s2, cond=\"cond_false\")\n\n            def cond_true(self):\n                return True\n\n            def cond_false(self):\n                return False\n\n        sm = MyMachine()\n        assert [e.id for e in sm.enabled_events()] == [\"go\"]\n\n    def test_unless_condition(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, unless=\"is_blocked\")\n\n            def is_blocked(self):\n                return True\n\n        sm = MyMachine()\n        assert sm.enabled_events() == []\n\n    def test_unless_condition_passes(self):\n        class MyMachine(StateChart):\n            s0 = State(initial=True)\n            s1 = State(final=True)\n\n            go = s0.to(s1, unless=\"is_blocked\")\n\n            def is_blocked(self):\n                return False\n\n        sm = MyMachine()\n        assert [e.id for e in sm.enabled_events()] == [\"go\"]\n\n\nclass TestInvalidStateValueNonNone:\n    \"\"\"current_state raises InvalidStateValue when state value is non-None but invalid.\"\"\"\n\n    def test_invalid_non_none_state_value(self):\n        import warnings\n\n        class SM(StateChart):\n            idle = State(initial=True)\n            active = State(final=True)\n            go = idle.to(active)\n\n        sm = SM()\n        # Bypass setter validation by writing directly to the model attribute\n        setattr(sm.model, sm.state_field, \"nonexistent_state\")\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", DeprecationWarning)\n            with pytest.raises(exceptions.InvalidStateValue):\n                _ = sm.current_state\n\n\nclass TestInitKwargsPropagation:\n    \"\"\"Constructor kwargs are forwarded to initial state entry callbacks.\"\"\"\n\n    async def test_kwargs_available_in_on_enter_initial(self, sm_runner):\n        class SM(StateChart):\n            idle = State(initial=True)\n            done = State(final=True)\n            go = idle.to(done)\n\n            def on_enter_idle(self, greeting=None, **kwargs):\n                self.greeting = greeting\n\n        sm = await sm_runner.start(SM, greeting=\"hello\")\n        assert sm.greeting == \"hello\"\n\n    async def test_kwargs_flow_through_eventless_transitions(self, sm_runner):\n        class Pipeline(StateChart):\n            start = State(initial=True)\n            processing = State()\n            done = State(final=True)\n\n            start.to(processing)\n            processing.to(done)\n\n            def on_enter_start(self, task_id=None, **kwargs):\n                self.task_id = task_id\n\n        sm = await sm_runner.start(Pipeline, task_id=\"abc-123\")\n        assert sm.task_id == \"abc-123\"\n        assert \"done\" in sm.configuration_values\n\n    async def test_no_kwargs_still_works(self, sm_runner):\n        class SM(StateChart):\n            idle = State(initial=True)\n            done = State(final=True)\n            go = idle.to(done)\n\n            def on_enter_idle(self, **kwargs):\n                self.entered = True\n\n        sm = await sm_runner.start(SM)\n        assert sm.entered is True\n\n    async def test_multiple_kwargs(self, sm_runner):\n        class SM(StateChart):\n            idle = State(initial=True)\n            done = State(final=True)\n            go = idle.to(done)\n\n            def on_enter_idle(self, host=None, port=None, **kwargs):\n                self.host = host\n                self.port = port\n\n        sm = await sm_runner.start(SM, host=\"localhost\", port=5432)\n        assert sm.host == \"localhost\"\n        assert sm.port == 5432\n\n    async def test_kwargs_in_invoke_handler(self, sm_runner):\n        \"\"\"Init kwargs flow to invoke handlers via dependency injection.\"\"\"\n\n        class SM(StateChart):\n            loading = State(initial=True)\n            ready = State(final=True)\n            done_invoke_loading = loading.to(ready)\n\n            def on_invoke_loading(self, url=None, **kwargs):\n                return f\"fetched:{url}\"\n\n            def on_enter_ready(self, data=None, **kwargs):\n                self.result = data\n\n        sm = await sm_runner.start(SM, url=\"https://example.com\")\n        await sm_runner.sleep(0.2)\n        await sm_runner.processing_loop(sm)\n        assert \"ready\" in sm.configuration_values\n        assert sm.result == \"fetched:https://example.com\"\n"
  },
  {
    "path": "tests/test_statemachine_bounded_transitions.py",
    "content": "from unittest import mock\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\nfrom .models import MyModel\n\n\n@pytest.fixture()\ndef event_mock():\n    return mock.MagicMock()\n\n\n@pytest.fixture()\ndef state_machine(event_mock):\n    class CampaignMachine(StateChart):\n        draft = State(initial=True)\n        producing = State()\n        closed = State(final=True)\n\n        add_job = draft.to(draft) | producing.to(producing)\n        produce = draft.to(producing)\n        deliver = producing.to(closed)\n\n        def on_enter_producing(self, param1=None, param2=None):\n            event_mock.on_enter_producing(param1=param1, param2=param2)\n\n        def on_exit_draft(self, param1=None, param2=None):\n            event_mock.on_exit_draft(param1=param1, param2=param2)\n\n        def on_enter_closed(self):\n            event_mock.on_enter_closed()\n\n        def on_exit_producing(self):\n            event_mock.on_exit_producing()\n\n    return CampaignMachine\n\n\ndef test_run_transition_pass_arguments_to_sub_transitions(\n    state_machine,\n    event_mock,\n):\n    model = MyModel(state=\"draft\")\n    machine = state_machine(model)\n\n    machine.send(\"produce\", param1=\"value1\", param2=\"value2\")\n\n    assert model.state == \"producing\"\n    event_mock.on_enter_producing.assert_called_with(param1=\"value1\", param2=\"value2\")\n    event_mock.on_exit_draft.assert_called_with(param1=\"value1\", param2=\"value2\")\n\n    machine.send(\"deliver\", param3=\"value3\")\n\n    event_mock.on_enter_closed.assert_called_with()\n    event_mock.on_exit_producing.assert_called_with()\n"
  },
  {
    "path": "tests/test_statemachine_compat.py",
    "content": "\"\"\"Backward-compatibility tests for the StateMachine (v2) API.\n\nThese tests verify that ``StateMachine`` (which inherits from ``StateChart``\nwith different defaults) continues to work as expected.  Tests here exercise\nbehaviour that differs from ``StateChart`` defaults:\n\n- ``allow_event_without_transition = False``  → ``TransitionNotAllowed``\n- ``enable_self_transition_entries = False``\n- ``atomic_configuration_update = True``\n- ``catch_errors_as_events = False``  → exceptions propagate directly\n- ``current_state`` deprecated property\n\"\"\"\n\nimport warnings\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateMachine\nfrom statemachine import exceptions\n\n# ---------------------------------------------------------------------------\n# Flag defaults\n# ---------------------------------------------------------------------------\n\n\nclass TestStateMachineDefaults:\n    \"\"\"Verify the four class-level flag defaults on StateMachine.\"\"\"\n\n    def test_allow_event_without_transition(self):\n        assert StateMachine.allow_event_without_transition is False\n\n    def test_enable_self_transition_entries(self):\n        assert StateMachine.enable_self_transition_entries is False\n\n    def test_atomic_configuration_update(self):\n        assert StateMachine.atomic_configuration_update is True\n\n    def test_catch_errors_as_events(self):\n        assert StateMachine.catch_errors_as_events is False\n\n\n# ---------------------------------------------------------------------------\n# Smoke test\n# ---------------------------------------------------------------------------\n\n\nclass TestStateMachineSmoke:\n    \"\"\"StateMachine as a subclass works for basic operations.\"\"\"\n\n    def test_create_send_and_check_state(self):\n        class TrafficLight(StateMachine):\n            green = State(initial=True)\n            yellow = State()\n            red = State()\n\n            cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n        sm = TrafficLight()\n        assert sm.green.is_active\n\n        sm.send(\"cycle\")\n        assert sm.yellow.is_active\n\n        sm.send(\"cycle\")\n        assert sm.red.is_active\n\n    def test_final_state_terminates(self):\n        class Simple(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        sm = Simple()\n        sm.send(\"go\")\n        assert sm.is_terminated\n\n\n# ---------------------------------------------------------------------------\n# TransitionNotAllowed (allow_event_without_transition = False)\n# ---------------------------------------------------------------------------\n\n\nclass TestTransitionNotAllowed:\n    \"\"\"StateMachine raises TransitionNotAllowed for invalid events.\"\"\"\n\n    @pytest.fixture()\n    def sm(self):\n        class Workflow(StateMachine):\n            draft = State(initial=True)\n            published = State(final=True)\n\n            publish = draft.to(published)\n\n        return Workflow()\n\n    def test_invalid_event_raises(self, sm):\n        with pytest.raises(exceptions.TransitionNotAllowed):\n            sm.send(\"nonexistent\")\n\n    def test_event_not_available_in_current_state(self, sm):\n        sm.send(\"publish\")\n        with pytest.raises(exceptions.TransitionNotAllowed):\n            sm.send(\"publish\")\n\n    def test_condition_blocks_transition(self):\n        class Gated(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2, cond=\"allowed\")\n\n            allowed: bool = False\n\n        sm = Gated()\n        with pytest.raises(sm.TransitionNotAllowed):\n            sm.go()\n\n    def test_multiple_destinations_all_blocked(self):\n        def never(event_data):\n            return False\n\n        class Multi(StateMachine):\n            requested = State(initial=True)\n            accepted = State(final=True)\n            rejected = State(final=True)\n\n            validate = requested.to(accepted, cond=never) | requested.to(\n                rejected, cond=\"also_never\"\n            )\n\n            @property\n            def also_never(self):\n                return False\n\n        sm = Multi()\n        with pytest.raises(exceptions.TransitionNotAllowed):\n            sm.validate()\n        assert sm.requested.is_active\n\n    def test_from_any_with_cond_blocked(self):\n        class Account(StateMachine):\n            active = State(initial=True)\n            closed = State(final=True)\n\n            close = closed.from_.any(cond=\"can_close\")\n\n            can_close: bool = False\n\n        sm = Account()\n        with pytest.raises(sm.TransitionNotAllowed):\n            sm.close()\n\n    def test_condition_algebra_any_false(self):\n        class CondAlgebra(StateMachine):\n            start = State(initial=True)\n            end = State(final=True)\n\n            submit = start.to(end, cond=\"used_money or used_credit\")\n\n            used_money: bool = False\n            used_credit: bool = False\n\n        sm = CondAlgebra()\n        with pytest.raises(sm.TransitionNotAllowed):\n            sm.submit()\n\n\n# ---------------------------------------------------------------------------\n# TransitionNotAllowed — async\n# ---------------------------------------------------------------------------\n\n\nclass TestTransitionNotAllowedAsync:\n    \"\"\"TransitionNotAllowed in async machines.\"\"\"\n\n    @pytest.fixture()\n    def async_sm_cls(self):\n        class AsyncWorkflow(StateMachine):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State(final=True)\n\n            go = s1.to(s2, cond=\"is_ready\")\n            finish = s2.to(s3)\n\n            is_ready: bool = False\n\n            async def on_go(self): ...\n\n        return AsyncWorkflow\n\n    async def test_async_transition_not_allowed(self, async_sm_cls):\n        sm = async_sm_cls()\n        await sm.activate_initial_state()\n        with pytest.raises(sm.TransitionNotAllowed):\n            await sm.send(\"go\")\n\n    def test_sync_context_transition_not_allowed(self, async_sm_cls):\n        sm = async_sm_cls()\n        with pytest.raises(sm.TransitionNotAllowed):\n            sm.send(\"go\")\n\n    async def test_async_condition_blocks(self):\n        class AsyncCond(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2, cond=\"check\")\n\n            async def check(self):\n                return False\n\n        sm = AsyncCond()\n        await sm.activate_initial_state()\n        with pytest.raises(sm.TransitionNotAllowed):\n            await sm.go()\n\n\n# ---------------------------------------------------------------------------\n# catch_errors_as_events = False (exceptions propagate directly)\n# ---------------------------------------------------------------------------\n\n\nclass TestErrorOnExecutionFalse:\n    \"\"\"With catch_errors_as_events=False, exceptions propagate without being caught.\"\"\"\n\n    def test_runtime_error_in_action_propagates(self):\n        class SM(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            def on_go(self):\n                raise RuntimeError(\"boom\")\n\n        sm = SM()\n        with pytest.raises(RuntimeError, match=\"boom\"):\n            sm.send(\"go\")\n\n    def test_runtime_error_in_after_propagates(self):\n        class SM(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            def after_go(self):\n                raise RuntimeError(\"after boom\")\n\n        sm = SM()\n        with pytest.raises(RuntimeError, match=\"after boom\"):\n            sm.send(\"go\")\n\n    @pytest.mark.timeout(5)\n    async def test_async_runtime_error_in_after_propagates(self):\n        class SM(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n            async def after_go(self, **kwargs):\n                raise RuntimeError(\"async after boom\")\n\n        sm = SM()\n        await sm.activate_initial_state()\n        with pytest.raises(RuntimeError, match=\"async after boom\"):\n            await sm.send(\"go\")\n\n\n# ---------------------------------------------------------------------------\n# enable_self_transition_entries = False\n# ---------------------------------------------------------------------------\n\n\nclass TestSelfTransitionNoEntries:\n    \"\"\"With enable_self_transition_entries=False, internal self-transitions do NOT fire entry/exit.\n\n    Note: ``enable_self_transition_entries`` only applies to *internal* self-transitions\n    (``internal=True``). External self-transitions always fire entry/exit regardless.\n    \"\"\"\n\n    def test_internal_self_transition_does_not_fire_enter_exit(self):\n        log = []\n\n        class SM(StateMachine):\n            s1 = State(initial=True)\n\n            loop = s1.to.itself(internal=True)\n\n            def on_enter_s1(self):\n                log.append(\"enter_s1\")\n\n            def on_exit_s1(self):\n                log.append(\"exit_s1\")\n\n        sm = SM()\n        log.clear()  # clear initial enter\n        sm.send(\"loop\")\n        assert \"enter_s1\" not in log\n        assert \"exit_s1\" not in log\n\n    def test_external_self_transition_fires_enter_exit(self):\n        \"\"\"External self-transitions always fire, regardless of the flag.\"\"\"\n        log = []\n\n        class SM(StateMachine):\n            s1 = State(initial=True)\n\n            loop = s1.to.itself()\n\n            def on_enter_s1(self):\n                log.append(\"enter_s1\")\n\n            def on_exit_s1(self):\n                log.append(\"exit_s1\")\n\n        sm = SM()\n        log.clear()\n        sm.send(\"loop\")\n        assert \"enter_s1\" in log\n        assert \"exit_s1\" in log\n\n\n# ---------------------------------------------------------------------------\n# current_state deprecated property\n# ---------------------------------------------------------------------------\n\n\nclass TestCurrentStateDeprecated:\n    \"\"\"The current_state property emits DeprecationWarning but still works.\"\"\"\n\n    def test_current_state_returns_state(self):\n        class SM(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        sm = SM()\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", DeprecationWarning)\n            cs = sm.current_state\n        assert cs == sm.s1\n\n    def test_current_state_emits_warning(self):\n        class SM(StateMachine):\n            s1 = State(initial=True)\n            s2 = State(final=True)\n\n            go = s1.to(s2)\n\n        sm = SM()\n        with pytest.warns(DeprecationWarning, match=\"current_state\"):\n            _ = sm.current_state  # noqa: F841\n"
  },
  {
    "path": "tests/test_statemachine_inheritance.py",
    "content": "import pytest\n\nfrom statemachine import exceptions\n\n\n@pytest.fixture()\ndef BaseMachine():\n    from statemachine import State\n    from statemachine import StateChart\n\n    class BaseMachine(StateChart):\n        state_1 = State(initial=True)\n        state_2 = State()\n        trans_1_2 = state_1.to(state_2)\n        trans_2_2 = state_2.to.itself(internal=True)\n\n    return BaseMachine\n\n\n@pytest.fixture()\ndef InheritedClass(BaseMachine):\n    class InheritedClass(BaseMachine):\n        pass\n\n    return InheritedClass\n\n\n@pytest.fixture()\ndef ExtendedClass(BaseMachine):\n    from statemachine import State\n\n    class ExtendedClass(BaseMachine):\n        state_3 = State()\n        trans_2_3 = BaseMachine.state_2.to(state_3)\n        trans_3_3 = state_3.to.itself(internal=True)\n\n    return ExtendedClass\n\n\n@pytest.fixture()\ndef OverridedClass(BaseMachine):\n    from statemachine import State\n\n    class OverridedClass(BaseMachine):\n        state_2 = State()\n\n        trans_1_2 = BaseMachine.state_1.to(state_2)\n        trans_2_2 = state_2.to.itself(internal=True)\n\n    return OverridedClass\n\n\n@pytest.fixture()\ndef OverridedTransitionClass(BaseMachine):\n    from statemachine import State\n\n    class OverridedTransitionClass(BaseMachine):\n        state_3 = State()\n\n        trans_1_2 = BaseMachine.state_1.to(state_3)\n        trans_3_3 = state_3.to.itself(internal=True)\n\n    return OverridedTransitionClass\n\n\ndef test_should_inherit_states_and_transitions(BaseMachine, InheritedClass):\n    assert InheritedClass.states == [\n        BaseMachine.state_1,\n        BaseMachine.state_2,\n    ]\n\n    expected = [e.name for e in BaseMachine.events]\n    actual = [e.name for e in InheritedClass.events]\n    assert actual == expected\n\n\ndef test_should_extend_states_and_transitions(BaseMachine, ExtendedClass):\n    assert ExtendedClass.states == [\n        BaseMachine.state_1,\n        BaseMachine.state_2,\n        ExtendedClass.state_3,\n    ]\n\n    base_events = [e.name for e in BaseMachine.events]\n    expected = base_events + [ExtendedClass.trans_2_3.name, ExtendedClass.trans_3_3.name]\n    actual = [e.name for e in ExtendedClass.events]\n    assert actual == expected\n\n\ndef test_should_execute_transitions(ExtendedClass):\n    instance = ExtendedClass()\n    instance.trans_1_2()\n    instance.trans_2_3()\n\n    assert instance.state_3.is_active\n\n\n@pytest.mark.xfail(reason=\"State overriding is not supported\")\ndef test_dont_support_overriden_states(OverridedClass):\n    # There's no support for overriding states\n    with pytest.raises(exceptions.InvalidDefinition):\n        OverridedClass()\n\n\n@pytest.mark.xfail(reason=\"Transition overriding is not supported\")\ndef test_support_override_transitions(OverridedTransitionClass):\n    instance = OverridedTransitionClass()\n\n    instance.trans_1_2()\n    assert instance.state_3.is_active\n"
  },
  {
    "path": "tests/test_threading.py",
    "content": "import threading\nimport time\nfrom collections import Counter\n\nimport pytest\nfrom statemachine.state import State\nfrom statemachine.statemachine import StateChart\n\n\ndef test_machine_should_allow_multi_thread_event_changes():\n    \"\"\"\n    Test for https://github.com/fgmacedo/python-statemachine/issues/443\n    \"\"\"\n\n    class CampaignMachine(StateChart):\n        \"A workflow machine\"\n\n        draft = State(initial=True)\n        producing = State()\n        closed = State(final=True)\n        add_job = draft.to(producing) | producing.to(closed)\n\n    machine = CampaignMachine()\n\n    def off_thread_change_state():\n        time.sleep(0.01)\n        machine.add_job()\n\n    thread = threading.Thread(target=off_thread_change_state)\n    thread.start()\n    thread.join()\n    assert machine.current_state_value == \"producing\"\n\n\ndef test_regression_443():\n    \"\"\"\n    Test for https://github.com/fgmacedo/python-statemachine/issues/443\n    \"\"\"\n    total_iterations = 4\n    send_at_iteration = 3  # 0-indexed: send before the 4th sample\n\n    class TrafficLightMachine(StateChart):\n        \"A traffic light machine\"\n\n        green = State(initial=True)\n        yellow = State()\n        red = State()\n\n        cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n    class Controller:\n        def __init__(self):\n            self.statuses_history = []\n            self.fsm = TrafficLightMachine()\n            # set up thread\n            self.thread = threading.Thread(target=self.recv_cmds)\n            self.thread.start()\n\n        def recv_cmds(self):\n            \"\"\"Pretend we receive a command triggering a state change.\"\"\"\n            for i in range(total_iterations):\n                if i == send_at_iteration:\n                    self.fsm.cycle()\n                self.statuses_history.append(self.fsm.current_state_value)\n\n    c1 = Controller()\n    c2 = Controller()\n    c1.thread.join()\n    c2.thread.join()\n    assert c1.statuses_history == [\"green\", \"green\", \"green\", \"yellow\"]\n    assert c2.statuses_history == [\"green\", \"green\", \"green\", \"yellow\"]\n\n\ndef test_regression_443_with_modifications():\n    \"\"\"\n    Test for https://github.com/fgmacedo/python-statemachine/issues/443\n    \"\"\"\n    total_iterations = 4\n    send_at_iteration = 3  # 0-indexed: send before the 4th sample\n\n    class TrafficLightMachine(StateChart):\n        \"A traffic light machine\"\n\n        green = State(initial=True)\n        yellow = State()\n        red = State()\n\n        cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n        def __init__(self, name):\n            self.name = name\n            self.statuses_history = []\n            super().__init__()\n\n        def beat(self):\n            for i in range(total_iterations):\n                if i == send_at_iteration:\n                    self.cycle()\n                self.statuses_history.append(f\"{self.name}.{self.current_state_value}\")\n\n    class Controller:\n        def __init__(self, name):\n            self.fsm = TrafficLightMachine(name)\n            # set up thread\n            self.thread = threading.Thread(target=self.fsm.beat)\n            self.thread.start()\n\n    c1 = Controller(\"c1\")\n    c2 = Controller(\"c2\")\n    c3 = Controller(\"c3\")\n    c1.thread.join()\n    c2.thread.join()\n    c3.thread.join()\n\n    assert c1.fsm.statuses_history == [\"c1.green\", \"c1.green\", \"c1.green\", \"c1.yellow\"]\n    assert c2.fsm.statuses_history == [\"c2.green\", \"c2.green\", \"c2.green\", \"c2.yellow\"]\n    assert c3.fsm.statuses_history == [\"c3.green\", \"c3.green\", \"c3.green\", \"c3.yellow\"]\n\n\nclass TestThreadSafety:\n    \"\"\"Stress tests for concurrent access to a single state machine instance.\n\n    These tests exercise real contention: multiple threads sending events to the\n    same SM simultaneously, synchronized via barriers to maximize overlap.\n    \"\"\"\n\n    @pytest.fixture()\n    def cycling_machine(self):\n        class CyclingMachine(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n            cycle = s1.to(s2) | s2.to(s3) | s3.to(s1)\n\n        return CyclingMachine()\n\n    @pytest.mark.parametrize(\"num_threads\", [4, 8])\n    def test_concurrent_sends_no_lost_events(self, cycling_machine, num_threads):\n        \"\"\"All events sent concurrently must be processed — none lost.\"\"\"\n        events_per_thread = 300\n        total_events = num_threads * events_per_thread\n        barrier = threading.Barrier(num_threads)\n        errors = []\n\n        def sender():\n            try:\n                barrier.wait(timeout=5)\n                for _ in range(events_per_thread):\n                    cycling_machine.send(\"cycle\")\n            except Exception as e:\n                errors.append(e)\n\n        threads = [threading.Thread(target=sender) for _ in range(num_threads)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join(timeout=30)\n\n        assert not errors, f\"Thread errors: {errors}\"\n\n        # The machine cycles s1→s2→s3→s1. After N total cycle events starting\n        # from s1, the state is determined by (N % 3).\n        expected_states = {0: \"s1\", 1: \"s2\", 2: \"s3\"}\n        expected = expected_states[total_events % 3]\n        assert cycling_machine.current_state_value == expected\n\n    def test_concurrent_sends_state_consistency(self, cycling_machine):\n        \"\"\"State must always be one of the valid states, never corrupted.\"\"\"\n        valid_values = {\"s1\", \"s2\", \"s3\"}\n        num_threads = 6\n        events_per_thread = 500\n        barrier = threading.Barrier(num_threads + 1)  # +1 for observer\n        stop_event = threading.Event()\n        observed_values = []\n        errors = []\n\n        def sender():\n            try:\n                barrier.wait(timeout=5)\n                for _ in range(events_per_thread):\n                    cycling_machine.send(\"cycle\")\n            except Exception as e:\n                errors.append(e)\n\n        def observer():\n            barrier.wait(timeout=5)\n            while not stop_event.is_set():\n                val = cycling_machine.current_state_value\n                observed_values.append(val)\n\n        threads = [threading.Thread(target=sender) for _ in range(num_threads)]\n        obs_thread = threading.Thread(target=observer)\n\n        for t in threads:\n            t.start()\n        obs_thread.start()\n\n        for t in threads:\n            t.join(timeout=30)\n\n        stop_event.set()\n        obs_thread.join(timeout=5)\n\n        assert not errors, f\"Thread errors: {errors}\"\n        # None may appear transiently during configuration updates — that's expected.\n        invalid = [v for v in observed_values if v not in valid_values and v is not None]\n        assert not invalid, f\"Observed invalid state values: {set(invalid)}\"\n        assert len(observed_values) > 100, \"Observer didn't collect enough samples\"\n\n    def test_concurrent_sends_with_callbacks(self):\n        \"\"\"Callbacks must execute exactly once per transition under contention.\"\"\"\n        call_log = []\n        lock = threading.Lock()\n\n        class CallbackMachine(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            go = s1.to(s2) | s2.to(s1)\n\n            def on_enter_s2(self):\n                with lock:\n                    call_log.append(\"enter_s2\")\n\n            def on_enter_s1(self):\n                with lock:\n                    call_log.append(\"enter_s1\")\n\n        sm = CallbackMachine()\n        num_threads = 4\n        events_per_thread = 200\n        total_events = num_threads * events_per_thread\n        barrier = threading.Barrier(num_threads)\n        errors = []\n\n        def sender():\n            try:\n                barrier.wait(timeout=5)\n                for _ in range(events_per_thread):\n                    sm.send(\"go\")\n            except Exception as e:\n                errors.append(e)\n\n        threads = [threading.Thread(target=sender) for _ in range(num_threads)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join(timeout=30)\n\n        assert not errors, f\"Thread errors: {errors}\"\n\n        # Each transition fires exactly one on_enter callback.\n        # +1 because initial activation also fires on_enter_s1.\n        counts = Counter(call_log)\n        total_callbacks = counts[\"enter_s1\"] + counts[\"enter_s2\"]\n        assert total_callbacks == total_events + 1\n\n    def test_concurrent_send_and_read_configuration(self, cycling_machine):\n        \"\"\"Reading configuration while events are being processed must not raise.\"\"\"\n        num_senders = 4\n        events_per_sender = 300\n        barrier = threading.Barrier(num_senders + 1)\n        stop_event = threading.Event()\n        errors = []\n\n        def sender():\n            try:\n                barrier.wait(timeout=5)\n                for _ in range(events_per_sender):\n                    cycling_machine.send(\"cycle\")\n            except Exception as e:\n                errors.append(e)\n\n        def reader():\n            barrier.wait(timeout=5)\n            while not stop_event.is_set():\n                try:\n                    _ = cycling_machine.configuration\n                    _ = cycling_machine.current_state_value\n                    _ = list(cycling_machine.configuration)\n                except Exception as e:\n                    errors.append(e)\n\n        threads = [threading.Thread(target=sender) for _ in range(num_senders)]\n        reader_thread = threading.Thread(target=reader)\n\n        for t in threads:\n            t.start()\n        reader_thread.start()\n\n        for t in threads:\n            t.join(timeout=30)\n        stop_event.set()\n        reader_thread.join(timeout=5)\n\n        assert not errors, f\"Thread errors: {errors}\"\n\n\nasync def test_regression_443_with_modifications_for_async_engine():\n    \"\"\"\n    Test for https://github.com/fgmacedo/python-statemachine/issues/443\n    \"\"\"\n    total_iterations = 4\n    send_at_iteration = 3  # 0-indexed: send before the 4th sample\n\n    class TrafficLightMachine(StateChart):\n        \"A traffic light machine\"\n\n        green = State(initial=True)\n        yellow = State()\n        red = State()\n\n        cycle = green.to(yellow) | yellow.to(red) | red.to(green)\n\n        async def on_cycle(self):\n            return \"caution\"\n\n        def __init__(self, name):\n            self.name = name\n            self.statuses_history = []\n            super().__init__()\n\n        def beat(self):\n            for i in range(total_iterations):\n                if i == send_at_iteration:\n                    self.cycle()\n                self.statuses_history.append(f\"{self.name}.{self.current_state_value}\")\n\n    class Controller:\n        def __init__(self, name):\n            self.fsm = TrafficLightMachine(name)\n\n        async def start(self):\n            # set up thread\n            await self.fsm.activate_initial_state()\n            self.thread = threading.Thread(target=self.fsm.beat)\n            self.thread.start()\n\n    c1 = Controller(\"c1\")\n    c2 = Controller(\"c2\")\n    c3 = Controller(\"c3\")\n    await c1.start()\n    await c2.start()\n    await c3.start()\n    c1.thread.join()\n    c2.thread.join()\n    c3.thread.join()\n\n    assert c1.fsm.statuses_history == [\"c1.green\", \"c1.green\", \"c1.green\", \"c1.yellow\"]\n    assert c2.fsm.statuses_history == [\"c2.green\", \"c2.green\", \"c2.green\", \"c2.yellow\"]\n    assert c3.fsm.statuses_history == [\"c3.green\", \"c3.green\", \"c3.green\", \"c3.yellow\"]\n"
  },
  {
    "path": "tests/test_transition_list.py",
    "content": "import pytest\nfrom statemachine.callbacks import CallbacksRegistry\nfrom statemachine.dispatcher import resolver_factory_from_objects\nfrom statemachine.transition import Transition\nfrom statemachine.transition_list import TransitionList\n\nfrom statemachine import State\n\n\ndef test_transition_list_or_operator():\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    s3 = State(\"s3\")\n    s4 = State(\"s4\", final=True)\n\n    t12 = s1.to(s2)\n    t23 = s2.to(s3)\n    t34 = s3.to(s4)\n\n    cycle = t12 | t23 | t34\n\n    assert [(t.source.name, t.target.name) for t in t12] == [(\"s1\", \"s2\")]\n    assert [(t.source.name, t.target.name) for t in t23] == [(\"s2\", \"s3\")]\n    assert [(t.source.name, t.target.name) for t in t34] == [(\"s3\", \"s4\")]\n    assert [(t.source.name, t.target.name) for t in cycle] == [\n        (\"s1\", \"s2\"),\n        (\"s2\", \"s3\"),\n        (\"s3\", \"s4\"),\n    ]\n\n\nclass TestDecorators:\n    @pytest.mark.parametrize(\n        (\"callback_name\", \"list_attr_name\", \"expected_value\"),\n        [\n            (\"before\", None, 42),\n            (\"after\", None, 42),\n            (\"on\", None, 42),\n            (\"validators\", None, 42),\n            (\"cond\", None, True),\n            (\"unless\", \"cond\", False),\n        ],\n    )\n    def test_should_assign_callback_to_transitions(\n        self, callback_name, list_attr_name, expected_value\n    ):\n        registry = CallbacksRegistry()\n\n        if list_attr_name is None:\n            list_attr_name = callback_name\n\n        s1 = State(\"s1\", initial=True)\n        transition_list = s1.to.itself()\n        decorator = getattr(transition_list, callback_name)\n\n        @decorator\n        def my_callback():\n            return 42\n\n        transition = s1.transitions[0]\n        specs_grouper = getattr(transition, list_attr_name)\n\n        resolver_factory_from_objects(object()).resolve(transition._specs, registry=registry)\n\n        assert registry[specs_grouper.key].call() == [expected_value]\n\n\ndef test_has_eventless_transition():\n    \"\"\"TransitionList.has_eventless_transition returns True for eventless transitions.\"\"\"\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    t = Transition(s1, s2)\n    tl = TransitionList([t])\n    assert tl.has_eventless_transition is True\n\n\ndef test_has_no_eventless_transition():\n    \"\"\"TransitionList.has_eventless_transition returns False when all have events.\"\"\"\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    t = Transition(s1, s2, event=\"go\")\n    tl = TransitionList([t])\n    assert tl.has_eventless_transition is False\n\n\ndef test_transition_list_call_with_callable():\n    \"\"\"Calling a TransitionList with a single callable registers it as an on callback.\"\"\"\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\", final=True)\n    tl = s1.to(s2)\n\n    def my_callback(): ...  # No-op: used only to test callback registration\n\n    result = tl(my_callback)\n    assert result is my_callback\n\n\ndef test_transition_list_call_with_non_callable_raises():\n    \"\"\"Calling a TransitionList with a non-callable raises TypeError.\"\"\"\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\", final=True)\n    tl = s1.to(s2)\n\n    with pytest.raises(TypeError, match=\"only supports the decorator syntax\"):\n        tl(\"not_a_callable\", \"extra_arg\")\n"
  },
  {
    "path": "tests/test_transition_table.py",
    "content": "from statemachine.contrib.diagram.extract import extract\nfrom statemachine.contrib.diagram.model import DiagramGraph\nfrom statemachine.contrib.diagram.model import DiagramState\nfrom statemachine.contrib.diagram.model import DiagramTransition\nfrom statemachine.contrib.diagram.model import StateType\nfrom statemachine.contrib.diagram.renderers.table import TransitionTableRenderer\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass TestTransitionTableMarkdown:\n    \"\"\"Markdown transition table tests.\"\"\"\n\n    def test_simple_table(self):\n        graph = DiagramGraph(\n            name=\"Simple\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"md\")\n        assert \"| State\" in result\n        assert \"| Event\" in result\n        assert \"| Guard\" in result\n        assert \"| Target\" in result\n        assert \"| S1\" in result\n        assert \"go\" in result\n        assert \"| S2\" in result\n\n    def test_with_guards(self):\n        graph = DiagramGraph(\n            name=\"Guards\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\", guards=[\"is_ready\"]),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"md\")\n        assert \"is_ready\" in result\n\n    def test_multiple_targets(self):\n        graph = DiagramGraph(\n            name=\"Multi\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n                DiagramState(id=\"s3\", name=\"S3\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\", \"s3\"], event=\"split\"),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"md\")\n        lines = result.strip().split(\"\\n\")\n        # Header + separator + 2 data rows\n        assert len(lines) == 4\n\n    def test_skips_initial_transitions(self):\n        graph = DiagramGraph(\n            name=\"SkipInit\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"\", is_initial=True),\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"md\")\n        lines = result.strip().split(\"\\n\")\n        # Header + separator + 1 data row (initial skipped)\n        assert len(lines) == 3\n\n    def test_skips_internal_transitions(self):\n        graph = DiagramGraph(\n            name=\"SkipInternal\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s1\"], event=\"check\", is_internal=True),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"md\")\n        lines = result.strip().split(\"\\n\")\n        # Header + separator only (no data rows)\n        assert len(lines) == 2\n\n    def test_targetless_transition(self):\n        graph = DiagramGraph(\n            name=\"Targetless\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[], event=\"tick\"),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"md\")\n        assert \"tick\" in result\n        # Target falls back to source name\n        assert \"S1\" in result\n\n\nclass TestTransitionTableRST:\n    \"\"\"RST grid table tests.\"\"\"\n\n    def test_rst_format(self):\n        graph = DiagramGraph(\n            name=\"RST\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"rst\")\n        assert \"+---\" in result\n        assert \"|\" in result\n        assert \"====\" in result  # header separator\n        assert \"go\" in result\n\n    def test_rst_with_guards(self):\n        graph = DiagramGraph(\n            name=\"RSTGuards\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\", guards=[\"is_ready\"]),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph, fmt=\"rst\")\n        assert \"is_ready\" in result\n\n\nclass TestTransitionTableIntegration:\n    \"\"\"Integration tests with real state machines.\"\"\"\n\n    def test_traffic_light_md(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        ir = extract(TrafficLightMachine)\n        result = TransitionTableRenderer().render(ir, fmt=\"md\")\n        assert \"Green\" in result\n        assert \"Yellow\" in result\n        assert \"Red\" in result\n        assert \"Cycle\" in result\n\n    def test_traffic_light_rst(self):\n        from tests.examples.traffic_light_machine import TrafficLightMachine\n\n        ir = extract(TrafficLightMachine)\n        result = TransitionTableRenderer().render(ir, fmt=\"rst\")\n        assert \"Green\" in result\n        assert \"Cycle\" in result\n        assert \"+---\" in result\n\n    def test_compound_state_names(self):\n        \"\"\"Child state names are properly resolved.\"\"\"\n\n        class SM(StateChart):\n            class parent(State.Compound, name=\"Parent\"):\n                child1 = State(initial=True)\n                child2 = State(final=True)\n                go = child1.to(child2)\n\n            start = State(initial=True)\n            enter = start.to(parent)\n\n        ir = extract(SM)\n        result = TransitionTableRenderer().render(ir, fmt=\"md\")\n        assert \"Child1\" in result\n        assert \"Child2\" in result\n\n    def test_default_format_is_md(self):\n        \"\"\"render() without fmt defaults to markdown.\"\"\"\n        graph = DiagramGraph(\n            name=\"Default\",\n            states=[\n                DiagramState(id=\"s1\", name=\"S1\", type=StateType.REGULAR, is_initial=True),\n                DiagramState(id=\"s2\", name=\"S2\", type=StateType.REGULAR),\n            ],\n            transitions=[\n                DiagramTransition(source=\"s1\", targets=[\"s2\"], event=\"go\"),\n            ],\n        )\n        result = TransitionTableRenderer().render(graph)\n        assert \"| State\" in result  # markdown uses pipes\n"
  },
  {
    "path": "tests/test_transitions.py",
    "content": "import pytest\nfrom statemachine.exceptions import InvalidDefinition\nfrom statemachine.transition import Transition\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\nfrom .models import MyModel\n\n\ndef test_transition_representation(campaign_machine):\n    s = repr([t for t in campaign_machine.draft.transitions if t.event == \"produce\"][0])\n    assert s == (\n        \"Transition('Draft', 'Being produced', event=[\"\n        \"Event('produce', delay=0, internal=False)], internal=False, initial=False)\"\n    )\n\n\ndef test_list_machine_events(classic_traffic_light_machine):\n    machine = classic_traffic_light_machine()\n    transitions = [t.name for t in machine.events]\n    assert transitions == [\"Slowdown\", \"Stop\", \"Go\"]\n\n\ndef test_list_state_transitions(classic_traffic_light_machine):\n    machine = classic_traffic_light_machine()\n    events = [t.event for t in machine.green.transitions]\n    assert events == [\"slowdown\"]\n\n\ndef test_transition_should_accept_decorator_syntax(traffic_light_machine):\n    machine = traffic_light_machine()\n    assert machine.green.is_active\n\n\ndef test_transition_as_decorator_should_call_method_before_activating_state(\n    traffic_light_machine, capsys\n):\n    machine = traffic_light_machine()\n    assert machine.green.is_active\n    machine.cycle(1, 2, number=3, text=\"x\")\n    assert machine.yellow.is_active\n\n    captured = capsys.readouterr()\n    assert captured.out == \"Running cycle from green to yellow\\n\"\n\n\n@pytest.mark.parametrize(\n    \"machine_name\",\n    [\n        \"traffic_light_machine\",\n        \"reverse_traffic_light_machine\",\n    ],\n)\ndef test_cycle_transitions(request, machine_name):\n    machine_class = request.getfixturevalue(machine_name)\n    machine = machine_class()\n    expected_states = [\"green\", \"yellow\", \"red\"] * 2\n    for expected_state in expected_states:\n        assert machine.current_state_value == expected_state\n        machine.cycle()\n\n\ndef test_transition_call_can_only_be_used_as_decorator():\n    source, dest = State(\"Source\"), State(\"Destination\")\n    transition = Transition(source, dest)\n\n    with pytest.raises(TypeError):\n        transition(\"not a callable\")\n\n\ndef test_transition_list_call_can_only_be_used_as_decorator():\n    source, dest = State(\"Source\"), State(\"Destination\")\n    transition_list = source.to(dest)\n\n    with pytest.raises(TypeError, match=\"TransitionList\"):\n        transition_list(\"not a callable\")\n\n    with pytest.raises(TypeError, match=\"TransitionList\"):\n        transition_list()\n\n    with pytest.raises(TypeError, match=\"TransitionList\"):\n        transition_list(42, extra=\"kwarg\")\n\n\n@pytest.fixture(params=[\"bounded\", \"unbounded\"])\ndef transition_callback_machine(request):\n    if request.param == \"bounded\":\n\n        class ApprovalMachine(StateChart):\n            \"A workflow\"\n\n            requested = State(initial=True)\n            accepted = State(final=True)\n\n            validate = requested.to(accepted)\n\n            def on_validate(self):\n                self.model.calls.append(\"on_validate\")\n                return \"accepted\"\n\n    elif request.param == \"unbounded\":\n\n        class ApprovalMachine(StateChart):\n            \"A workflow\"\n\n            requested = State(initial=True)\n            accepted = State(final=True)\n\n            @requested.to(accepted)\n            def validate(self):\n                self.model.calls.append(\"on_validate\")\n                return \"accepted\"\n\n    else:\n        raise ValueError(\"machine not defined\")\n\n    return ApprovalMachine\n\n\ndef test_statemachine_transition_callback(transition_callback_machine):\n    model = MyModel(state=\"requested\", calls=[])\n    machine = transition_callback_machine(model)\n    assert machine.validate() == \"accepted\"\n    assert model.calls == [\"on_validate\"]\n\n\ndef test_can_run_combined_transitions():\n    class CampaignMachine(StateChart):\n        \"A workflow machine\"\n\n        draft = State(initial=True)\n        producing = State()\n        closed = State()\n\n        abort = draft.to(closed) | producing.to(closed) | closed.to(closed)\n        produce = draft.to(producing)\n\n    machine = CampaignMachine()\n\n    machine.abort()\n\n    assert machine.closed.is_active\n\n\ndef test_can_detect_stuck_states():\n    with pytest.raises(\n        InvalidDefinition,\n        match=\"All non-final states should have at least one outgoing transition.\",\n    ):\n\n        class CampaignMachine(StateChart):\n            \"A workflow machine\"\n\n            draft = State(initial=True)\n            producing = State()\n            paused = State()\n            closed = State()\n\n            abort = draft.to(closed) | producing.to(closed) | closed.to(closed)\n            produce = draft.to(producing)\n            pause = producing.to(paused)\n\n\ndef test_can_opt_out_of_stuck_states_check():\n    class CampaignMachine(StateChart):\n        \"A workflow machine\"\n\n        validate_trap_states = False\n\n        draft = State(initial=True)\n        producing = State()\n        paused = State()\n        closed = State()\n\n        abort = draft.to(closed) | producing.to(closed) | closed.to(closed)\n        produce = draft.to(producing)\n        pause = producing.to(paused)\n\n\ndef test_can_detect_unreachable_final_states():\n    with pytest.raises(\n        InvalidDefinition,\n        match=\"All non-final states should have at least one path to a final state.\",\n    ):\n\n        class CampaignMachine(StateChart):\n            \"A workflow machine\"\n\n            draft = State(initial=True)\n            producing = State()\n            paused = State()\n            closed = State(final=True)\n\n            abort = closed.from_(draft, producing)\n            produce = draft.to(producing)\n            pause = producing.to(paused) | paused.to.itself()\n\n\ndef test_can_opt_out_of_unreachable_final_states_check():\n    class CampaignMachine(StateChart):\n        \"A workflow machine\"\n\n        validate_final_reachability = False\n\n        draft = State(initial=True)\n        producing = State()\n        paused = State()\n        closed = State(final=True)\n\n        abort = closed.from_(draft, producing)\n        produce = draft.to(producing)\n        pause = producing.to(paused) | paused.to.itself()\n\n\ndef test_transitions_to_the_same_estate_as_itself():\n    class CampaignMachine(StateChart):\n        \"A workflow machine\"\n\n        draft = State(initial=True)\n        producing = State()\n        closed = State()\n\n        update = draft.to.itself()\n        abort = draft.to(closed) | producing.to(closed) | closed.to.itself()\n        produce = draft.to(producing)\n\n    machine = CampaignMachine()\n\n    machine.update()\n\n    assert machine.draft.is_active\n\n\nclass TestReverseTransition:\n    @pytest.mark.parametrize(\n        \"initial_state\",\n        [\n            \"green\",\n            \"yellow\",\n            \"red\",\n        ],\n    )\n    def test_reverse_transition(self, reverse_traffic_light_machine, initial_state):\n        machine = reverse_traffic_light_machine(start_value=initial_state)\n        assert machine.current_state_value == initial_state\n\n        machine.stop()\n\n        assert machine.red.is_active\n\n\ndef test_should_transition_with_a_dict_as_return():\n    \"regression test that verifies if a dict can be used as return\"\n\n    expected_result = {\n        \"a\": 1,\n        \"b\": 2,\n        \"c\": 3,\n    }\n\n    class ApprovalMachine(StateChart):\n        \"A workflow\"\n\n        requested = State(initial=True)\n        accepted = State(final=True)\n        rejected = State(final=True)\n\n        accept = requested.to(accepted)\n        reject = requested.to(rejected)\n\n        def on_accept(self):\n            return expected_result\n\n    machine = ApprovalMachine()\n\n    result = machine.send(\"accept\")\n    assert result == expected_result\n\n\nclass TestInternalTransition:\n    def test_external_self_transition_executes_state_actions(self, engine):\n        calls = []\n\n        class TestStateMachine(StateChart):\n            initial = State(initial=True)\n\n            loop = initial.to.itself(internal=False)\n\n            def _get_engine(self):\n                return engine(self)\n\n            def on_exit_initial(self):\n                calls.append(\"on_exit_initial\")\n\n            def on_enter_initial(self):\n                calls.append(\"on_enter_initial\")\n\n        sm = TestStateMachine()\n        sm.activate_initial_state()\n\n        calls.clear()\n        sm.loop()\n        assert calls == [\"on_exit_initial\", \"on_enter_initial\"]\n\n    def test_internal_self_transition_skips_state_actions(self, engine):\n        calls = []\n\n        class TestStateMachine(StateChart):\n            enable_self_transition_entries = False\n\n            initial = State(initial=True)\n\n            loop = initial.to.itself(internal=True)\n\n            def _get_engine(self):\n                return engine(self)\n\n            def on_exit_initial(self):\n                calls.append(\"on_exit_initial\")\n\n            def on_enter_initial(self):\n                calls.append(\"on_enter_initial\")\n\n        sm = TestStateMachine()\n        sm.activate_initial_state()\n\n        calls.clear()\n        sm.loop()\n        assert calls == []\n\n    def test_should_not_allow_internal_transitions_from_distinct_states(self):\n        with pytest.raises(\n            InvalidDefinition, match=\"Not a valid internal transition from source.\"\n        ):\n\n            class TestStateMachine(StateChart):\n                initial = State(initial=True)\n                final = State(final=True)\n\n                execute = initial.to(initial, final, internal=True)\n\n\nclass TestAllowEventWithoutTransition:\n    def test_send_unknown_event(self, classic_traffic_light_machine_allow_event):\n        sm = classic_traffic_light_machine_allow_event()\n        sm.activate_initial_state()  # no-op on sync engine\n\n        assert sm.green.is_active\n        sm.send(\"unknow_event\")\n        assert sm.green.is_active\n\n    def test_send_not_valid_for_the_current_state_event(\n        self, classic_traffic_light_machine_allow_event\n    ):\n        sm = classic_traffic_light_machine_allow_event()\n        sm.activate_initial_state()  # no-op on sync engine\n\n        assert sm.green.is_active\n        sm.stop()\n        assert sm.green.is_active\n\n\nclass TestTransitionFromAny:\n    @pytest.fixture()\n    def account_sm(self):\n        class AccountStateMachine(StateChart):\n            allow_event_without_transition = False\n            catch_errors_as_events = False\n\n            active = State(\"Active\", initial=True)\n            suspended = State(\"Suspended\")\n            overdrawn = State(\"Overdrawn\")\n            closed = State(\"Closed\", final=True)\n\n            # Define transitions between states\n            suspend = active.to(suspended)\n            activate = suspended.to(active)\n            overdraft = active.to(overdrawn)\n            resolve_overdraft = overdrawn.to(active)\n\n            close_account = closed.from_.any(cond=\"can_close_account\")\n\n            can_close_account: bool = True\n\n            # Actions performed during transitions\n            def on_close_account(self):\n                print(\"Account has been closed.\")\n\n        return AccountStateMachine\n\n    def test_transition_from_any(self, account_sm):\n        sm = account_sm()\n        sm.close_account()\n        assert sm.closed.is_active\n\n    def test_can_close_from_every_state(self, account_sm):\n        sm = account_sm()\n        states_can_close = {}\n        for state in sm.states:\n            for transition in state.transitions:\n                print(f\"{state.id} -({transition.event})-> {transition.target.id}\")\n                if transition.target == sm.closed:\n                    states_can_close[state.id] = state\n\n        assert list(states_can_close) == [\"active\", \"suspended\", \"overdrawn\"]\n\n    def test_transition_from_any_with_cond(self, account_sm):\n        sm = account_sm()\n        sm.can_close_account = False\n        with pytest.raises(sm.TransitionNotAllowed):\n            sm.close_account()\n        assert sm.active.is_active\n\n    def test_any_can_be_used_as_decorator(self):\n        class AccountStateMachine(StateChart):\n            catch_errors_as_events = False\n\n            active = State(\"Active\", initial=True)\n            suspended = State(\"Suspended\")\n            overdrawn = State(\"Overdrawn\")\n            closed = State(\"Closed\", final=True)\n\n            # Define transitions between states\n            suspend = active.to(suspended)\n            activate = suspended.to(active)\n            overdraft = active.to(overdrawn)\n            resolve_overdraft = overdrawn.to(active)\n\n            close_account = closed.from_.any()\n\n            flag_for_debug: bool = False\n\n            @close_account.on\n            def do_close_account(self):\n                self.flag_for_debug = True\n\n        sm = AccountStateMachine()\n        sm.close_account()\n        assert sm.closed.is_active\n        assert sm.flag_for_debug is True\n\n\ndef test_initial_transition_with_cond_raises():\n    \"\"\"Initial transitions cannot have conditions.\"\"\"\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    with pytest.raises(InvalidDefinition, match=\"Initial transitions\"):\n        Transition(s1, s2, initial=True, cond=\"some_cond\")\n\n\ndef test_initial_transition_with_event_raises():\n    \"\"\"Initial transitions cannot have events.\"\"\"\n    s1 = State(\"s1\", initial=True)\n    s2 = State(\"s2\")\n    with pytest.raises(InvalidDefinition, match=\"Initial transitions\"):\n        Transition(s1, s2, initial=True, event=\"some_event\")\n"
  },
  {
    "path": "tests/test_validators.py",
    "content": "\"\"\"Tests for the validators feature.\n\nValidators are imperative guards that raise exceptions to reject transitions.\nUnlike conditions (cond/unless), which return booleans and silently skip\ntransitions, validators communicate *why* a transition was rejected.\n\nKey behavior (since v3): validator exceptions always propagate to the caller,\nregardless of the ``catch_errors_as_events`` flag. They are NOT converted to\n``error.execution`` events — they operate in the transition-selection phase,\nnot the execution phase.\n\"\"\"\n\nimport pytest\n\nfrom tests.machines.validators.multi_validator import MultiValidator\nfrom tests.machines.validators.order_validation import OrderValidation\nfrom tests.machines.validators.order_validation_no_error_events import OrderValidationNoErrorEvents\nfrom tests.machines.validators.validator_fallthrough import ValidatorFallthrough\nfrom tests.machines.validators.validator_with_cond import ValidatorWithCond\nfrom tests.machines.validators.validator_with_error_transition import ValidatorWithErrorTransition\n\n\nclass TestValidatorPropagation:\n    \"\"\"Validator exceptions always propagate to the caller.\"\"\"\n\n    async def test_validator_rejects_with_catch_errors_as_events_true(self, sm_runner):\n        \"\"\"With catch_errors_as_events=True (default), validator exceptions still\n        propagate — they are NOT converted to error.execution events.\"\"\"\n        sm = await sm_runner.start(OrderValidation)\n\n        with pytest.raises(ValueError, match=\"Quantity must be positive\"):\n            await sm_runner.send(sm, \"confirm\", quantity=0)\n\n        assert \"pending\" in sm.configuration_values\n\n    async def test_validator_rejects_with_catch_errors_as_events_false(self, sm_runner):\n        \"\"\"With catch_errors_as_events=False, validator exceptions propagate\n        (same behavior as True — validators always propagate).\"\"\"\n        sm = await sm_runner.start(OrderValidationNoErrorEvents)\n\n        with pytest.raises(ValueError, match=\"Quantity must be positive\"):\n            await sm_runner.send(sm, \"confirm\", quantity=0)\n\n        assert \"pending\" in sm.configuration_values\n\n    async def test_validator_accepts(self, sm_runner):\n        \"\"\"When the validator passes, the transition proceeds normally.\"\"\"\n        sm = await sm_runner.start(OrderValidation)\n        await sm_runner.send(sm, \"confirm\", quantity=5)\n        assert \"confirmed\" in sm.configuration_values\n\n    async def test_state_unchanged_after_rejection(self, sm_runner):\n        \"\"\"After a validator rejects, the machine stays in the source state\n        and can still process events normally.\"\"\"\n        sm = await sm_runner.start(OrderValidation)\n\n        with pytest.raises(ValueError, match=\"Quantity must be positive\"):\n            await sm_runner.send(sm, \"confirm\", quantity=0)\n\n        # Machine is still in pending — retry with valid data\n        await sm_runner.send(sm, \"confirm\", quantity=10)\n        assert \"confirmed\" in sm.configuration_values\n\n\nclass TestMultipleValidators:\n    \"\"\"When multiple validators are declared, they run in order.\"\"\"\n\n    async def test_first_validator_fails(self, sm_runner):\n        \"\"\"First validator failure stops the chain — second is not called.\"\"\"\n        sm = await sm_runner.start(MultiValidator)\n\n        with pytest.raises(ValueError, match=\"A failed\"):\n            await sm_runner.send(sm, \"start\", a_ok=False, b_ok=True)\n\n        assert \"idle\" in sm.configuration_values\n\n    async def test_second_validator_fails(self, sm_runner):\n        \"\"\"First passes, second fails.\"\"\"\n        sm = await sm_runner.start(MultiValidator)\n\n        with pytest.raises(ValueError, match=\"B failed\"):\n            await sm_runner.send(sm, \"start\", a_ok=True, b_ok=False)\n\n        assert \"idle\" in sm.configuration_values\n\n    async def test_all_validators_pass(self, sm_runner):\n        sm = await sm_runner.start(MultiValidator)\n        await sm_runner.send(sm, \"start\", a_ok=True, b_ok=True)\n        assert \"active\" in sm.configuration_values\n\n\nclass TestValidatorWithConditions:\n    \"\"\"Validators and conditions can be combined on the same transition.\n    Validators run first (see execution order in actions.md).\"\"\"\n\n    async def test_validator_rejects_before_cond_is_evaluated(self, sm_runner):\n        \"\"\"Validator runs before cond — if it rejects, cond is never checked.\"\"\"\n        sm = await sm_runner.start(ValidatorWithCond)\n        sm.has_permission = True  # cond would pass\n\n        with pytest.raises(PermissionError, match=\"Invalid token\"):\n            await sm_runner.send(sm, \"start\", token=\"bad\")\n\n        assert \"idle\" in sm.configuration_values\n\n    async def test_validator_passes_but_cond_rejects(self, sm_runner):\n        \"\"\"Validator passes, but cond returns False — no transition, no exception.\"\"\"\n        sm = await sm_runner.start(ValidatorWithCond)\n        sm.has_permission = False\n\n        await sm_runner.send(sm, \"start\", token=\"valid\")\n        assert \"idle\" in sm.configuration_values\n\n    async def test_both_validator_and_cond_pass(self, sm_runner):\n        sm = await sm_runner.start(ValidatorWithCond)\n        sm.has_permission = True\n        await sm_runner.send(sm, \"start\", token=\"valid\")\n        assert \"active\" in sm.configuration_values\n\n\nclass TestValidatorDoesNotTriggerErrorExecution:\n    \"\"\"The key semantic: validator rejection is NOT an execution error.\n\n    Even when catch_errors_as_events=True and an error.execution transition\n    exists, a validator raising should propagate to the caller — not\n    be routed through the error.execution mechanism.\n    \"\"\"\n\n    async def test_validator_does_not_trigger_error_transition(self, sm_runner):\n        sm = await sm_runner.start(ValidatorWithErrorTransition)\n\n        with pytest.raises(ValueError, match=\"Input required\"):\n            await sm_runner.send(sm, \"start\")\n\n        # Machine stays in idle — NOT in error_state\n        assert \"idle\" in sm.configuration_values\n\n    async def test_action_error_does_trigger_error_transition(self, sm_runner):\n        \"\"\"Contrast: actual action errors DO trigger error.execution.\"\"\"\n        sm = await sm_runner.start(ValidatorWithErrorTransition)\n\n        # First, get to active state (validator passes)\n        await sm_runner.send(sm, \"start\", value=\"something\")\n        assert \"active\" in sm.configuration_values\n\n        # Now trigger an action error — this SHOULD go to error_state\n        await sm_runner.send(sm, \"do_work\")\n        assert \"error_state\" in sm.configuration_values\n\n\nclass TestValidatorFallthrough:\n    \"\"\"When a validator rejects, the exception propagates immediately.\n    The engine does NOT try the next transition in the chain.\"\"\"\n\n    async def test_validator_rejection_does_not_fallthrough(self, sm_runner):\n        sm = await sm_runner.start(ValidatorFallthrough)\n\n        with pytest.raises(PermissionError, match=\"Premium required\"):\n            await sm_runner.send(sm, \"go\", premium=False)\n\n        # Machine stays in idle — did NOT fall through to path_b\n        assert \"idle\" in sm.configuration_values\n\n    async def test_validator_passes_takes_first_transition(self, sm_runner):\n        sm = await sm_runner.start(ValidatorFallthrough)\n        await sm_runner.send(sm, \"go\", premium=True)\n        assert \"path_a\" in sm.configuration_values\n"
  },
  {
    "path": "tests/test_weighted_transitions.py",
    "content": "from collections import Counter\n\nimport pytest\nfrom statemachine.contrib.weighted import _make_weighted_cond\nfrom statemachine.contrib.weighted import _WeightedGroup\nfrom statemachine.contrib.weighted import to\nfrom statemachine.contrib.weighted import weighted_transitions\n\nfrom statemachine import State\nfrom statemachine import StateChart\nfrom statemachine import StateMachine\n\n\n@pytest.fixture()\ndef WeightedIdleSC():\n    from tests.examples.weighted_idle_machine import WeightedIdleMachine\n\n    return WeightedIdleMachine\n\n\nclass TestWeightedTransitionsBasic:\n    def test_deterministic_with_seed(self, WeightedIdleSC):\n        sm = WeightedIdleSC()\n        sm.send(\"idle\")\n        first_config = sm.configuration\n\n        sm.send(\"finish\")\n        sm.send(\"idle\")\n        second_config = sm.configuration\n\n        # With seed=42, results are deterministic\n        # Create a fresh instance to verify same seed produces same sequence\n        sm2 = WeightedIdleSC()\n        sm2.send(\"idle\")\n        assert sm2.configuration == first_config\n        sm2.send(\"finish\")\n        sm2.send(\"idle\")\n        assert sm2.configuration == second_config\n\n    def test_statistical_distribution(self, WeightedIdleSC):\n        \"\"\"Over many iterations, the distribution should approximate the weights.\"\"\"\n        sm = WeightedIdleSC()\n        counts = Counter()\n        iterations = 10000\n\n        for _ in range(iterations):\n            sm.send(\"idle\")\n            counts[next(iter(sm.configuration)).id] += 1\n            sm.send(\"finish\")\n\n        # With 70/20/10 weights, check roughly correct distribution (within 5%)\n        assert abs(counts[\"shift_weight\"] / iterations - 0.70) < 0.05\n        assert abs(counts[\"adjust_hair\"] / iterations - 0.20) < 0.05\n        assert abs(counts[\"bang_shield\"] / iterations - 0.10) < 0.05\n\n    def test_single_weighted_transition(self):\n        class SingleWeighted(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n\n            go = weighted_transitions(s1, (s2, 100), seed=0)\n            back = s2.to(s1)\n\n        sm = SingleWeighted()\n        sm.send(\"go\")\n        assert sm.configuration == {SingleWeighted.s2}\n\n    def test_equal_weights(self):\n        class EqualWeights(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(s1, (s2, 50), (s3, 50))\n            back = s2.to(s1) | s3.to(s1)\n\n        sm = EqualWeights()\n        counts = Counter()\n        iterations = 5000\n\n        for _ in range(iterations):\n            sm.send(\"go\")\n            counts[next(iter(sm.configuration)).id] += 1\n            sm.send(\"back\")\n\n        # Should be roughly 50/50 within 5%\n        assert abs(counts[\"s2\"] / iterations - 0.50) < 0.05\n        assert abs(counts[\"s3\"] / iterations - 0.50) < 0.05\n\n    def test_float_weights(self):\n        class FloatWeights(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(s1, (s2, 0.7), (s3, 0.3))\n            back = s2.to(s1) | s3.to(s1)\n\n        sm = FloatWeights()\n        counts = Counter()\n        iterations = 5000\n\n        for _ in range(iterations):\n            sm.send(\"go\")\n            counts[next(iter(sm.configuration)).id] += 1\n            sm.send(\"back\")\n\n        assert abs(counts[\"s2\"] / iterations - 0.70) < 0.05\n        assert abs(counts[\"s3\"] / iterations - 0.30) < 0.05\n\n    def test_mixed_int_and_float_weights(self):\n        class MixedWeights(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(s1, (s2, 10), (s3, 5.5), seed=42)\n            back = s2.to(s1) | s3.to(s1)\n\n        sm = MixedWeights()\n        sm.send(\"go\")\n        assert sm.configuration & {MixedWeights.s2, MixedWeights.s3}\n\n\nclass TestWeightedTransitionsWithGuards:\n    def test_with_user_cond_guard(self):\n        class GuardedWeighted(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(\n                s1,\n                to(s2, 50, cond=\"is_allowed\"),\n                (s3, 50),\n                seed=0,\n            )\n            back = s2.to(s1) | s3.to(s1)\n\n            def is_allowed(self):\n                return self.allow_s2\n\n        sm = GuardedWeighted()\n        sm.allow_s2 = True\n\n        # When is_allowed=True, both transitions can fire\n        counts = Counter()\n        for _ in range(1000):\n            sm.send(\"go\")\n            counts[next(iter(sm.configuration)).id] += 1\n            sm.send(\"back\")\n\n        assert counts[\"s2\"] > 0\n        assert counts[\"s3\"] > 0\n\n    def test_with_unless_guard(self):\n        class UnlessWeighted(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(\n                s1,\n                to(s2, 90, unless=\"is_blocked\"),\n                (s3, 10),\n                seed=0,\n            )\n            back = s2.to(s1) | s3.to(s1)\n\n            def is_blocked(self):\n                return self.blocked\n\n        sm = UnlessWeighted()\n        sm.blocked = False\n\n        # When not blocked, s2 can fire\n        sm.send(\"go\")\n        first_state = next(iter(sm.configuration))\n        sm.send(\"back\")\n\n        # When blocked, s2 cond fails even if weight selects it\n        sm.blocked = True\n        results = Counter()\n        for _ in range(100):\n            try:\n                sm.send(\"go\")\n                results[next(iter(sm.configuration)).id] += 1\n                sm.send(\"back\")\n            except Exception:\n                results[\"failed\"] += 1\n\n        # s3 should still work when weight selects it\n        assert results[\"s3\"] > 0\n        assert first_state is not None\n\n    def test_guard_failure_no_fallback(self):\n        \"\"\"When the selected transition's guard fails, no fallback occurs.\"\"\"\n\n        class NoFallback(StateMachine):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(\n                s1,\n                to(s2, 90, cond=\"allow_s2\"),\n                (s3, 10),\n                seed=1,\n            )\n            back = s2.to(s1) | s3.to(s1)\n\n            allow_s2 = True\n\n        sm = NoFallback()\n        sm.allow_s2 = False\n\n        from statemachine.exceptions import TransitionNotAllowed\n\n        got_failure = False\n        for _ in range(50):\n            try:\n                sm.send(\"go\")\n                sm.send(\"back\")\n            except TransitionNotAllowed:\n                got_failure = True\n                break\n\n        assert got_failure, \"Expected TransitionNotAllowed when guard blocks selection\"\n\n\nclass TestWeightedTransitionsValidation:\n    def test_empty_destinations(self):\n        s1 = State(initial=True)\n        with pytest.raises(ValueError, match=\"requires at least one\"):\n            weighted_transitions(s1)\n\n    def test_source_not_a_state(self):\n        with pytest.raises(TypeError, match=\"First argument must be a source State\"):\n            weighted_transitions(\"not_a_state\", (\"target\", 10))  # type: ignore[arg-type]\n\n    def test_not_a_tuple(self):\n        s1 = State(initial=True)\n        with pytest.raises(TypeError, match=\"must be a .* tuple\"):\n            weighted_transitions(s1, \"not a tuple\")  # type: ignore[arg-type]\n\n    def test_wrong_tuple_length(self):\n        s1 = State(initial=True)\n        with pytest.raises(TypeError, match=\"must be a .* tuple\"):\n            weighted_transitions(s1, (1, 2, 3, 4))  # type: ignore[arg-type]\n\n    def test_target_not_a_state(self):\n        s1 = State(initial=True)\n        with pytest.raises(TypeError, match=\"first element must be a State\"):\n            weighted_transitions(s1, (\"not_a_state\", 10))  # type: ignore[arg-type]\n\n    def test_weight_not_a_number(self):\n        s1 = State(initial=True)\n        s2 = State()\n        with pytest.raises(TypeError, match=\"weight must be a positive number\"):\n            weighted_transitions(s1, (s2, \"ten\"))  # type: ignore[arg-type]\n\n    def test_weight_zero(self):\n        s1 = State(initial=True)\n        s2 = State()\n        with pytest.raises(ValueError, match=\"weight must be positive\"):\n            weighted_transitions(s1, (s2, 0))\n\n    def test_weight_negative(self):\n        s1 = State(initial=True)\n        s2 = State()\n        with pytest.raises(ValueError, match=\"weight must be positive\"):\n            weighted_transitions(s1, (s2, -5))\n\n    def test_kwargs_not_a_dict(self):\n        s1 = State(initial=True)\n        s2 = State()\n        with pytest.raises(TypeError, match=\"third element must be a dict\"):\n            weighted_transitions(s1, (s2, 10, \"bad\"))  # type: ignore[arg-type]\n\n    def test_kwargs_forwarded_to_transition(self):\n        \"\"\"Verify that kwargs dict is forwarded to source.to().\"\"\"\n\n        class WithKwargs(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(\n                s1,\n                (s2, 50, {\"on\": \"do_something\"}),\n                (s3, 50),\n                seed=42,\n            )\n            back = s2.to(s1) | s3.to(s1)\n\n            def __init__(self):\n                self.log = []\n                super().__init__()\n\n            def do_something(self):\n                self.log.append(\"did_it\")\n\n        sm = WithKwargs()\n        # Run enough iterations that s2 is selected at least once\n        for _ in range(50):\n            sm.send(\"go\")\n            sm.send(\"back\")\n\n        assert \"did_it\" in sm.log\n\n    def test_to_helper_forwards_kwargs(self):\n        \"\"\"Verify that to() helper passes kwargs to source.to().\"\"\"\n\n        class WithTo(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(\n                s1,\n                to(s2, 50, on=\"do_something\"),\n                to(s3, 50),\n                seed=42,\n            )\n            back = s2.to(s1) | s3.to(s1)\n\n            def __init__(self):\n                self.log = []\n                super().__init__()\n\n            def do_something(self):\n                self.log.append(\"did_it\")\n\n        sm = WithTo()\n        for _ in range(50):\n            sm.send(\"go\")\n            sm.send(\"back\")\n\n        assert \"did_it\" in sm.log\n\n    def test_to_returns_tuple(self):\n        \"\"\"to() returns a plain tuple compatible with weighted_transitions.\"\"\"\n        s2 = State()\n\n        result = to(s2, 70)\n        assert isinstance(result, tuple)\n        assert result == (s2, 70, {})\n\n        result_with_kwargs = to(s2, 30, cond=\"is_ready\", on=\"go\")\n        assert isinstance(result_with_kwargs, tuple)\n        assert result_with_kwargs == (s2, 30, {\"cond\": \"is_ready\", \"on\": \"go\"})\n\n\nclass TestWeightedTransitionsWithCallbacks:\n    def test_action_decorators_on_weighted_event(self):\n        \"\"\"Transition callbacks (before/on/after) work with weighted transitions.\"\"\"\n\n        class WithCallbacks(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(s1, (s2, 50), (s3, 50), seed=42)\n            back = s2.to(s1) | s3.to(s1)\n\n            def __init__(self):\n                self.log = []\n                super().__init__()\n\n            def on_go(self):\n                self.log.append(\"on_go\")\n\n            def after_go(self):\n                self.log.append(\"after_go\")\n\n        sm = WithCallbacks()\n        sm.send(\"go\")\n        assert \"on_go\" in sm.log\n        assert \"after_go\" in sm.log\n\n\nclass TestWeightedTransitionsEngines:\n    async def test_sync_and_async_engines(self, sm_runner):\n        class WeightedSC(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(s1, (s2, 70), (s3, 30), seed=42)\n            back = s2.to(s1) | s3.to(s1)\n\n        sm = await sm_runner.start(WeightedSC)\n        await sm_runner.send(sm, \"go\")\n        assert \"s2\" in sm.configuration_values or \"s3\" in sm.configuration_values\n        await sm_runner.send(sm, \"back\")\n        assert \"s1\" in sm.configuration_values\n\n    async def test_works_with_state_machine(self, sm_runner):\n        class WeightedSM(StateMachine):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n\n            go = weighted_transitions(s1, (s2, 70), (s3, 30), seed=42)\n            back = s2.to(s1) | s3.to(s1)\n\n        sm = await sm_runner.start(WeightedSM)\n        await sm_runner.send(sm, \"go\")\n        assert \"s2\" in sm.configuration_values or \"s3\" in sm.configuration_values\n        await sm_runner.send(sm, \"back\")\n        assert \"s1\" in sm.configuration_values\n\n\nclass TestMultipleWeightedGroups:\n    def test_independent_groups(self):\n        class MultiGroup(StateChart):\n            s1 = State(initial=True)\n            s2 = State()\n            s3 = State()\n            s4 = State()\n            s5 = State()\n\n            go_a = weighted_transitions(s1, (s2, 80), (s3, 20), seed=42)\n            go_b = weighted_transitions(s1, (s4, 30), (s5, 70), seed=99)\n            back = s2.to(s1) | s3.to(s1) | s4.to(s1) | s5.to(s1)\n\n        sm = MultiGroup()\n\n        sm.send(\"go_a\")\n        state_a = next(iter(sm.configuration))\n        assert state_a in (MultiGroup.s2, MultiGroup.s3)\n        sm.send(\"back\")\n\n        sm.send(\"go_b\")\n        state_b = next(iter(sm.configuration))\n        assert state_b in (MultiGroup.s4, MultiGroup.s5)\n\n\nclass TestWeightedCondRepr:\n    def test_cond_name_includes_weight_and_percentage(self):\n        group = _WeightedGroup([70, 20, 10])\n        cond = _make_weighted_cond(0, group, 70.0, 100.0)\n        assert cond.__name__ == \"weight=70.0 (70%)\"\n\n    def test_cond_name_with_fractional_percentage(self):\n        group = _WeightedGroup([1, 2])\n        cond = _make_weighted_cond(0, group, 1.0, 3.0)\n        assert cond.__name__ == \"weight=1.0 (33%)\"\n\n    def test_non_zero_index_cond_rolls_dice_if_not_yet_selected(self):\n        \"\"\"When a non-zero index cond is evaluated before index 0, it rolls the dice.\"\"\"\n        group = _WeightedGroup([50, 50], seed=42)\n        cond_1 = _make_weighted_cond(1, group, 50.0, 100.0)\n\n        assert group.selected is None\n        result = cond_1()\n        assert group.selected is not None  # dice was rolled\n        assert result == (group.selected == 1)\n"
  },
  {
    "path": "tests/testcases/__init__.py",
    "content": ""
  },
  {
    "path": "tests/testcases/issue308.md",
    "content": "### Issue 308\n\nA StateMachine that exercises the example given on issue\n#[308](https://github.com/fgmacedo/python-statemachine/issues/308).\n\nIn this example, we share the transition list between events.\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class TestSM(StateChart):\n...     state1 = State('s1', initial=True)\n...     state2 = State('s2')\n...     state3 = State('s3')\n...     state4 = State('s4', final=True)\n...\n...     event1 = state1.to(state2)\n...     event2 = state2.to(state3)\n...     event3 = state3.to(state4)\n...\n...     # cycle = state1.to(state2) | state2.to(state3) | state3.to(state4)\n...     cycle = event1 | event2 | event3\n...\n...     def before_cycle(self):\n...         print(\"before cycle\")\n...\n...     def on_cycle(self):\n...         print(\"on cycle\")\n...\n...     def after_cycle(self):\n...         print(\"after cycle\")\n...\n...     def on_enter_state1(self):\n...         print('enter state1')\n...\n...     def on_exit_state1(self):\n...         print('exit state1')\n...\n...     def on_enter_state2(self):\n...         print('enter state2')\n...\n...     def on_exit_state2(self):\n...         print('exit state2')\n...\n...     def on_enter_state3(self):\n...         print('enter state3')\n...\n...     def on_exit_state3(self):\n...         print('exit state3')\n...\n...     def on_enter_state4(self):\n...         print('enter state4')\n...\n...     def on_exit_state4(self):\n...         print('exit state4')\n...\n...     def before_trans12(self):\n...         print('before event1')\n...\n...     def on_trans12(self):\n...         print('on event1')\n...\n...     def after_trans12(self):\n...         print('after event1')\n...\n...     def before_trans23(self):\n...         print('before event2')\n...\n...     def on_trans23(self):\n...         print('on event2')\n...\n...     def after_trans23(self):\n...         print('after event2')\n...\n...     def before_trans34(self):\n...         print('before event3')\n...\n...     def on_trans34(self):\n...         print('on event3')\n...\n...     def after_trans34(self):\n...         print('after event3')\n...\n\n```\n\nExample given:\n\n```py\n\n>>> m = TestSM()\nenter state1\n\n>>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values)\n(True, False, False, False, ['state1'])\n\n>>> _ = m.cycle()\nbefore cycle\nexit state1\non cycle\nenter state2\nafter cycle\n\n>>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values)\n(False, True, False, False, ['state2'])\n\n>>> _ = m.cycle()\nbefore cycle\nexit state2\non cycle\nenter state3\nafter cycle\n\n>>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values)\n(False, False, True, False, ['state3'])\n\n>>> _ = m.cycle()\nbefore cycle\nexit state3\non cycle\nenter state4\nafter cycle\n\n>>> m.state1.is_active, m.state2.is_active, m.state3.is_active, m.state4.is_active, list(m.configuration_values)\n(False, False, False, True, ['state4'])\n\n```\n"
  },
  {
    "path": "tests/testcases/issue384_multiple_observers.md",
    "content": "### Issue 384\n\nA StateMachine that exercises the example given on issue\n#[384](https://github.com/fgmacedo/python-statemachine/issues/384).\n\nIn this example, we register multiple observers to the same named callback.\n\nThis works also as a regression test.\n\n```py\n>>> from statemachine import State\n>>> from statemachine import StateChart\n\n>>> class MyObs:\n...     def on_move_car(self):\n...         print(\"I observed moving from 1\")\n\n>>> class MyObs2:\n...     def on_move_car(self):\n...         print(\"I observed moving from 2\")\n...\n\n\n>>> class Car(StateChart):\n...     stopped = State(initial=True)\n...     moving = State()\n...\n...     move_car = stopped.to(moving)\n...     stop_car = moving.to(stopped)\n...\n...     def on_move_car(self):\n...         print(\"I'm moving\")\n\n```\n\nRunning:\n\n```py\n>>> car = Car()\n>>> obs = MyObs()\n>>> obs2 = MyObs2()\n>>> car.add_listener(obs)\nCar(model=Model(state=stopped), state_field='state', configuration=['stopped'])\n\n>>> car.add_listener(obs2)\nCar(model=Model(state=stopped), state_field='state', configuration=['stopped'])\n\n>>> car.add_listener(obs2)  # test to not register duplicated observer callbacks\nCar(model=Model(state=stopped), state_field='state', configuration=['stopped'])\n\n>>> car.move_car()\nI'm moving\nI observed moving from 1\nI observed moving from 2\n[None, None, None]\n\n```\n"
  },
  {
    "path": "tests/testcases/issue449.md",
    "content": "\n### Issue 449\n\nA StateMachine that exercises the example given on issue\n#[449](https://github.com/fgmacedo/python-statemachine/issues/449).\n\n\n\n```py\n>>> from statemachine import StateChart, State\n\n>>> class ExampleStateMachine(StateChart):\n...     initial = State(initial=True)\n...     second = State()\n...     third = State()\n...     fourth = State()\n...     final = State(final=True)\n...\n...     initial_to_second = initial.to(second)\n...     second_to_third = second.to(third)\n...     third_to_fourth = third.to(fourth)\n...     completion = fourth.to(final)\n...\n...     def on_enter_state(self, target: State, event: str):\n...         print(f\"Entering state {target.id}. Event: {event}\")\n...         if event == \"initial_to_second\":\n...             self.send(\"second_to_third\")\n...         if event == \"second_to_third\":\n...             self.send(\"third_to_fourth\")\n...         if event == \"third_to_fourth\":\n...             print(\"third_to_fourth on on_enter_state worked\")\n\n\n```\n\nExercise:\n\n\n```py\n>>> example = ExampleStateMachine()\nEntering state initial. Event: __initial__\n\n>>> print(list(example.configuration_values))\n['initial']\n\n>>> example.send(\"initial_to_second\") # this will call second_to_third and third_to_fourth\nEntering state second. Event: initial_to_second\nEntering state third. Event: second_to_third\nEntering state fourth. Event: third_to_fourth\nthird_to_fourth on on_enter_state worked\n\n>>> print(\"My current state is\", list(example.configuration_values))\nMy current state is ['fourth']\n\n```\n"
  },
  {
    "path": "tests/testcases/test_issue434.py",
    "content": "from time import sleep\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass Model:\n    def __init__(self, data: dict):\n        self.data = data\n\n\nclass DataCheckerMachine(StateChart):\n    catch_errors_as_events = False\n\n    check_data = State(initial=True)\n    data_good = State(final=True)\n    data_bad = State(final=True)\n\n    MAX_CYCLE_COUNT = 10\n    cycle_count = 0\n\n    cycle = (\n        check_data.to(data_good, cond=\"data_looks_good\")\n        | check_data.to(data_bad, cond=\"max_cycle_reached\")\n        | check_data.to.itself(internal=True)\n    )\n\n    def data_looks_good(self):\n        return self.model.data.get(\"value\") > 10.0\n\n    def max_cycle_reached(self):\n        return self.cycle_count > self.MAX_CYCLE_COUNT\n\n    def after_cycle(self, event: str, source: State, target: State):\n        print(f\"Running {event} {self.cycle_count} from {source!s} to {target!s}.\")\n        self.cycle_count += 1\n\n\n@pytest.fixture()\ndef initial_data():\n    return {\"value\": 1}\n\n\n@pytest.fixture()\ndef data_checker_machine(initial_data):\n    return DataCheckerMachine(Model(initial_data))\n\n\ndef test_max_cycle_without_success(data_checker_machine):\n    sm = data_checker_machine\n    cycle_rate = 0.1\n\n    while not sm.is_terminated:\n        sm.cycle()\n        sleep(cycle_rate)\n\n    assert sm.data_bad.is_active\n    assert sm.cycle_count == 12\n\n\ndef test_data_turns_good_mid_cycle(initial_data):\n    sm = DataCheckerMachine(Model(initial_data))\n    cycle_rate = 0.1\n\n    while not sm.is_terminated:\n        sm.cycle()\n        if sm.cycle_count == 5:\n            print(\"Now data looks good!\")\n            sm.model.data[\"value\"] = 20\n        sleep(cycle_rate)\n\n    assert sm.data_good.is_active\n    assert sm.cycle_count == 6  # Transition occurs at the 6th cycle\n"
  },
  {
    "path": "tests/testcases/test_issue480.py",
    "content": "\"\"\"\n\n### Issue 480\n\nA StateMachine that exercises the example given on issue\n#[480](https://github.com/fgmacedo/python-statemachine/issues/480).\n\nShould be possible to trigger an event on the initial state activation handler.\n\"\"\"\n\nfrom unittest.mock import MagicMock\nfrom unittest.mock import call\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass MyStateMachine(StateChart):\n    state_1 = State(initial=True)\n    state_2 = State(final=True)\n\n    trans_1 = state_1.to(state_2)\n\n    def __init__(self):\n        self.mock = MagicMock()\n        super().__init__()\n\n    def on_enter_state_1(self):\n        self.mock(\"on_enter_state_1\")\n        self.long_running_task()\n\n    def on_exit_state_1(self):\n        self.mock(\"on_exit_state_1\")\n\n    def on_enter_state_2(self):\n        self.mock(\"on_enter_state_2\")\n\n    def long_running_task(self):\n        self.mock(\"long_running_task_started\")\n        self.trans_1()\n        self.mock(\"long_running_task_ended\")\n\n\ndef test_initial_state_activation_handler():\n    sm = MyStateMachine()\n\n    expected_calls = [\n        call(\"on_enter_state_1\"),\n        call(\"long_running_task_started\"),\n        call(\"long_running_task_ended\"),\n        call(\"on_exit_state_1\"),\n        call(\"on_enter_state_2\"),\n    ]\n\n    assert sm.mock.mock_calls == expected_calls\n    assert sm.state_2.is_active\n"
  },
  {
    "path": "tests/testcases/test_issue509.py",
    "content": "\"\"\"\n\n### Issue 509\n\nA StateChart that exercises the example given on issue\n#[509](https://github.com/fgmacedo/python-statemachine/issues/509).\n\nWhen multiple async coroutines send events concurrently, each caller should\nreceive its own event's result or exception — not another caller's.\n\nOriginal problem: fn2 triggers a validator exception, but fn1 receives it instead.\n\"\"\"\n\nimport asyncio\n\nimport pytest\n\nfrom statemachine import State\nfrom statemachine import StateChart\n\n\nclass Issue509SC(StateChart):\n    INITIAL = State(initial=True)\n    FINAL = State()\n\n    noop = INITIAL.to(FINAL, on=\"do_nothing\")\n    noop2 = INITIAL.to(FINAL, on=\"do_nothing\", validators=\"raise_exception\") | FINAL.to.itself(\n        on=\"do_nothing\", validators=\"raise_exception\"\n    )\n\n    async def do_nothing(self, name):\n        await asyncio.sleep(0.01)\n        return f\"Did nothing via {name}\"\n\n    def raise_exception(self):\n        raise ValueError(\"noop2 is not allowed\")\n\n\n@pytest.mark.asyncio()\nasync def test_issue509_exception_routed_to_correct_caller():\n    test = Issue509SC()\n    await test.activate_initial_state()\n\n    results = {}\n\n    async def fn1():\n        results[\"fn1\"] = await test.send(\"noop\", \"fn1\")\n\n    async def fn2():\n        try:\n            await test.send(\"noop2\", \"fn2\")\n            results[\"fn2\"] = \"no error\"\n        except ValueError as e:\n            results[\"fn2\"] = f\"caught: {e}\"\n\n    task1 = asyncio.create_task(fn1())\n    task2 = asyncio.create_task(fn2())\n    await asyncio.gather(task1, task2)\n\n    # fn1 should get its own result, not fn2's exception\n    assert results[\"fn1\"] == \"Did nothing via fn1\"\n    # fn2 should catch the ValueError from its own validator\n    assert results[\"fn2\"] == \"caught: noop2 is not allowed\"\n"
  }
]