{"id":50512941,"url":"https://github.com/avidrucker/statecharts-py","last_synced_at":"2026-06-02T21:32:55.675Z","repository":{"id":361039608,"uuid":"1252836821","full_name":"avidrucker/statecharts-py","owner":"avidrucker","description":"A faithful Python 3 port of fulcrologic/statecharts: W3C SCXML semantics without the XML (98% conformance), plus async, durable SQLite sessions, a Fulcro-style normalized store, and visualization.","archived":false,"fork":false,"pushed_at":"2026-05-29T00:17:07.000Z","size":222,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T01:21:38.192Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avidrucker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-28T23:24:10.000Z","updated_at":"2026-05-29T00:17:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/avidrucker/statecharts-py","commit_stats":null,"previous_names":["avidrucker/statecharts-py"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/avidrucker/statecharts-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avidrucker%2Fstatecharts-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avidrucker%2Fstatecharts-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avidrucker%2Fstatecharts-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avidrucker%2Fstatecharts-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avidrucker","download_url":"https://codeload.github.com/avidrucker/statecharts-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avidrucker%2Fstatecharts-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33838216,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-02T21:32:55.551Z","updated_at":"2026-06-02T21:32:55.669Z","avatar_url":"https://github.com/avidrucker.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# statecharts (Python)\n\n[![CI](https://github.com/avidrucker/statecharts-py/actions/workflows/ci.yml/badge.svg)](https://github.com/avidrucker/statecharts-py/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n\nA faithful Python 3 port of [`fulcrologic/statecharts`](https://github.com/fulcrologic/statecharts):\n**W3C SCXML** structure and semantics without the XML, expressed as plain Python data,\nwith swappable `DataModel` / `ExecutionModel` / `EventQueue` seams.\n\n👉 **New here? Start with the runnable demos in [`examples/`](./examples/)** — they're\nnumbered in a recommended order, from the core engine to the advanced layers.\n\nSee [`init_plan.md`](./init_plan.md) for the full feasibility analysis and roadmap that motivated this port.\n\n## Status\n\nA working, faithful core engine. Implemented:\n\n- Compound, parallel, atomic, and final states; the W3C processing algorithm\n  (microstep/macrostep, LCCA, exit/entry sets, run-to-completion).\n- Shallow and deep **history** with default transitions.\n- **Eventless** (automatic) transitions and `choice` decision states.\n- Guards (`cond`) and executable content: `Script`, `Assign`, `Raise`, `Log`, `Send`, `Cancel`.\n- Internal vs external transitions; dotted **event-name prefix matching** (`error.network` ⊇ `error.network.timeout`).\n- `done.state.*` events for compound/parallel completion; machine halts on top-level `final`.\n- **Delayed events** via an injectable clock (`send_after`, `ManualClock`).\n- Immutable, serializable **working memory** — the value that *is* a session (persist it between events).\n\nAlso implemented: an **SCXML XML loader** + an **ECMAScript-subset expression\nevaluator**, driving the **W3C conformance suite** (see below). Plus `\u003cif\u003e`/`\u003celseif\u003e`/\n`\u003celse\u003e`, `\u003cforeach\u003e`, `\u003csend\u003e`/`\u003ccancel\u003e` with delays, `\u003cdonedata\u003e`, early/late\nbinding, `error.execution`/`error.communication` semantics, system variables\n(`_event`, `_sessionid`, `_name`, `_ioprocessors`), and **`\u003cinvoke\u003e`** — synchronous\nin-process child statecharts with `#_parent`/`#_\u003cinvokeid\u003e` event routing,\n`done.invoke.*`, `param`/`namelist`, `autoforward`, and `finalize`.\n\nHigher-level layers:\n\n- **`AsyncSession`** (`aio.py`) — an asyncio runtime that drives a session over\n  real time, waking exactly when the next delayed `\u003csend\u003e` is due.\n- **Normalized store** (`store.py`) — the Fulcro-style app-state pattern: entities\n  by ident, **actors** (named idents), and **aliases** (named attribute paths), with\n  `assoc_alias`/`set_actor` ops and `resolve_actors`/`resolve_aliases` helpers. The\n  path toward porting Fulcro-style statechart-driven apps to Python.\n- **Visualization** (`viz.py`) — `to_mermaid` and `to_dot` renderers.\n- **Durable sessions** (`durable.py`) — a SQLite-backed event queue + session store,\n  so a workflow can wait for hours/days **across process restarts**. Charts are\n  registered by name (`ChartRegistry`); only JSON-able working memory and pending\n  timers are persisted. `DurableRuntime.start`/`enqueue`/`tick` drive it. SQLite gives\n  durability + safe multi-process access on one machine; the same schema ports to\n  Postgres (`SELECT ... FOR UPDATE SKIP LOCKED`) for true multi-node distribution.\n  See `examples/05_durable_workflow.py` for a restart-survival demo.\n\nThe scope from the original plan is now complete, including the durable event queue.\n\n## W3C conformance\n\nThe engine runs against the real **W3C SCXML IRP** mandatory automated ecmascript\ntests (vendored under `tests/w3c/cases/`, from the\n[alexzhornyak/SCXML-tutorial](https://github.com/alexzhornyak/SCXML-tutorial) mirror).\nA test passes by reaching `\u003cfinal id=\"pass\"\u003e`.\n\n```\npython3 tests/w3c/runner.py        # full report (add -v for INCOMPLETE/ERROR detail)\n```\n\nCurrent result: **153 / 156 runnable tests pass (98%)**, 0 errors. Only 3 tests are\nskipped (`\u003cscript\u003e` ecmascript bodies). The 3 remaining failures are narrow gaps:\ninline `function(){...}` IIFEs in the expression evaluator, using inline SCXML as a\ndata *value*, and cancelling a delayed event in another live session (which the spec\nitself notes is undefined).\n\nThe missing 2% is conformance of the *embedded scripting language*, not the\nstate-machine engine — a deliberate, reversible scope choice. See\n[`why-98-percent-passing.md`](./why-98-percent-passing.md) for the full rationale.\n\nDriving the suite surfaced (and fixed) several real bugs in the port — most\nnotably **document order was numbered post-order instead of pre-order**, which had\nsilently reversed state entry/exit ordering; `\u003csend\u003e` with no target was routed to\nthe *internal* queue instead of the *external* one; and the same transition selected\nfrom two parallel regions executed twice (the \"optimally enabled set\" must be a set).\n\n## Quick start\n\n```python\nfrom statecharts import Session, statechart, state, on, handle, ops\n\nchart = statechart({\"initial\": \"idle\"},\n    state({\"id\": \"idle\"}, on(\"start\", \"working\")),\n    state({\"id\": \"working\"},\n        handle(\"inc\", lambda env, data: [ops.assign(\"n\", data.get(\"n\", 0) + 1)]),\n        on(\"done\", \"idle\")),\n)\n\ns = Session(chart)          # initialize() runs automatically\ns.send(\"start\")\ns.send(\"inc\").send(\"inc\")\nprint(s.configuration)      # frozenset({'working'})\nprint(s.data[\"n\"])          # 2\n```\n\nThe core is functional — `Session` just wraps it:\n\n```python\nfrom statecharts import make_chart, make_env, initialize, process_event\nenv = make_env(make_chart(chart_root))\nwm = initialize(env)                       # WorkingMemory (serializable)\nwm = process_event(env, wm, \"start\")       # -\u003e new WorkingMemory\n```\n\n## Architecture (the four seams)\n\n| Seam | Protocol | Default impl | Swap it for |\n|---|---|---|---|\n| Data storage | `DataModel` | `LocalDataModel` (dict) | external/normalized store |\n| Expression eval | `ExecutionModel` | `CallableExecutionModel` | sandboxed / symbolic exprs |\n| Event delivery | `EventQueue` | `MemoryEventQueue` + `Clock` | durable / distributed queue |\n| Step interface | `algorithm.process_event` | — | (the engine) |\n\n## Run it\n\n```bash\npython3 run_tests.py               # 48 tests, zero dependencies\npython3 tests/w3c/runner.py        # W3C conformance report\n```\n\n### Examples\n\nThe [`examples/`](./examples/) folder has a runnable demo for every feature — each is\nself-contained and needs no install. They're **numbered in a recommended reading\norder** (core → structure → advanced); see [`examples/README.md`](./examples/README.md).\n\n```bash\npython3 examples/01_payment_flow.py        # core: retries, timeout, guards\npython3 examples/02_visualize.py           # Mermaid / Graphviz output\npython3 examples/03_load_scxml.py          # load + run SCXML XML\npython3 examples/04_async_traffic_light.py # AsyncSession, real-time timers\npython3 examples/05_durable_workflow.py    # SQLite durability across a restart\npython3 examples/06_invoke_demo.py         # \u003cinvoke\u003e child statechart\npython3 examples/07_fulcro_store.py        # normalized store + actors/aliases\n```\n\n(When `pytest` is available, `pytest` works too — tests are standard `test_*` functions.)\n\n## Layout\n\n```\nsrc/statecharts/\n  elements.py        # frozen-dataclass elements (states, transitions, executable content)\n  chart.py           # builder DSL + indexed Chart (id-\u003enode, parent map, document order)\n  events.py          # Event + dotted-prefix matching\n  algorithm.py       # the W3C SCXML algorithm (the heart)\n  working_memory.py  # serializable session value\n  environment.py     # bundles chart + the four protocol impls\n  protocols.py       # DataModel / ExecutionModel / EventQueue (typing.Protocol)\n  data_model.py / execution_model.py / event_queue.py   # default impls\n  ops.py             # data-model operations (assign/delete)\n  convenience.py     # on / handle / choice / send_after\n  simple.py          # Session facade\n  ecma.py            # ECMAScript-subset execution model (for the W3C suite)\n  invocations.py     # synchronous \u003cinvoke\u003e child-statechart sessions\n  aio.py             # AsyncSession: asyncio runtime (real-time delayed sends)\n  store.py           # normalized store + actors/aliases (Fulcro-style app state)\n  durable.py         # SQLite durable event queue + session store + DurableRuntime\n  viz.py             # to_mermaid / to_dot chart renderers\n  scxml/loader.py    # SCXML XML -\u003e element tree\ntests/\n  test_*.py          # native engine tests + W3C smoke guard\n  w3c/runner.py      # full W3C conformance runner\n  w3c/cases/         # vendored W3C mandatory ecmascript tests\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favidrucker%2Fstatecharts-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favidrucker%2Fstatecharts-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favidrucker%2Fstatecharts-py/lists"}