Skip to content

Commit 4fa6b28

Browse files
committed
Merge branch 'release/3.2.0'
2 parents 7adc760 + 8baa5a0 commit 4fa6b28

114 files changed

Lines changed: 7907 additions & 2726 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
fail-fast: false
2020
matrix:
21-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
21+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
2222

2323
steps:
2424
- uses: actions/checkout@v5

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,8 @@ tmp/
8585

8686
# Local specs
8787
specs/
88+
89+
# Environment files (secrets)
90+
.env
91+
.env.*
92+
!.env.example

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
exclude: docs/auto_examples
1010
- repo: https://github.com/astral-sh/ruff-pre-commit
1111
# Ruff version.
12-
rev: v0.15.13
12+
rev: v0.15.17
1313
hooks:
1414
# Run the linter.
1515
- id: ruff

AGENTS.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,19 @@ uv run mypy statemachine/ tests/
218218

219219
## Code style
220220

221-
- **Formatter/Linter:** ruff (line length 99, target Python 3.9)
221+
- **Formatter/Linter:** ruff (line length 99, target Python 3.10)
222222
- **Rules:** pycodestyle, pyflakes, isort, pyupgrade, flake8-comprehensions, flake8-bugbear, flake8-pytest-style
223223
- **Imports:** single-line, sorted by isort. **Always prefer top-level imports** — only use
224224
lazy (in-function) imports when strictly necessary to break circular dependencies
225-
- **Docstrings:** Google convention
225+
- **Docstrings:** Google convention. Document only what isn't obvious from the
226+
name/type (hidden conventions, non-obvious semantics), not trivial fields.
227+
- **Attribute docs:** use an attribute *docstring* (a string literal on the line
228+
**below** the attribute), not an inline `#` comment, so Sphinx autodoc captures it.
229+
Reserve `#` for explaining logic. Example:
230+
```python
231+
unless: "str | None" = None
232+
"""Negated guard expression; the transition is allowed only if it is falsy."""
233+
```
226234
- **Naming:** PascalCase for classes, snake_case for functions/methods, UPPER_SNAKE_CASE for constants
227235
- **Type hints:** used throughout; `TYPE_CHECKING` for circular imports
228236
- Pre-commit hooks enforce ruff + mypy + pytest
@@ -268,6 +276,24 @@ All code examples in `docs/*.md` **must** be testable doctests (using ```` ```py
268276
`--doctest-glob=*.md`. If an example cannot be expressed as a doctest (e.g., it requires
269277
real concurrency), write it as a unit test in `tests/` and reference it from the docs instead.
270278

279+
### Declarative IO: keep the JSON Schema and the parser in sync
280+
281+
The native JSON/YAML format has three artifacts that must agree: the parser
282+
(`statemachine/io/native.py`), the runtime, and the published JSON Schema
283+
(`statemachine/io/schemas/statechart.schema.json`). They drift apart silently unless
284+
enforced, so when you add or change native vocabulary:
285+
286+
- Update the **schema** alongside the parser/runtime change, never after.
287+
- The parser is the source of truth for accepted keys: `_ACTION_KEYS` and the
288+
`_DOCUMENT_KEYS`/`_STATE_KEYS`/`_TRANSITION_KEYS`/`_INVOKE_KEYS` frozensets. The parser
289+
**rejects unknown keys** using them, and `tests/io/test_validation.py` asserts each set
290+
equals the matching schema node's properties, so a change on one side without the other
291+
fails CI.
292+
- **Every native example is schema-validated.** Test fixtures load with `validate=True` (and
293+
a corpus test validates every `.yaml`/`.json` under `tests/io/`); docs examples pass
294+
`validate=True` too. A feature exercised by an example but missing from the schema breaks a
295+
test.
296+
271297
## Git workflow
272298

273299
- Main branch: `develop`

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,13 @@ To generate diagrams, install with the `diagrams` extra (requires
372372
pip install python-statemachine[diagrams]
373373
```
374374

375+
To load statecharts from declarative documents, install the IO extras
376+
(`yaml` for YAML, `validation` for `validate=True`, or `io` for both):
377+
378+
```
379+
pip install python-statemachine[io]
380+
```
381+
375382

376383
## Contributing
377384

docs/api.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,19 @@ class MyMachine(StateChart):
147147
.. autofunction:: statemachine.io.create_machine_class_from_definition
148148
```
149149

150+
## io.load
151+
152+
```{versionadded} 3.2.0
153+
```
154+
155+
See [](io/index.md) for the guide.
156+
157+
```{eval-rst}
158+
.. autofunction:: statemachine.io.load
159+
160+
.. autofunction:: statemachine.io.build_processor
161+
```
162+
150163
## timeout
151164

152165
```{versionadded} 3.0.0

docs/behaviour.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ Existing projects should consider migrating when possible, as the
2525
SCXML-compliant behavior provides more predictable semantics.
2626
```
2727

28+
```{warning}
29+
This page describes runtime *semantics*. If you **load SCXML documents** via
30+
`SCXMLProcessor`, note that SCXML is executable content: by default the
31+
datamodel is evaluated with a restricted AST whitelist and `<script>` is
32+
rejected, so untrusted documents cannot execute arbitrary code. Pass
33+
`trusted=True` only for SCXML you control. See the 3.2.0 release notes and
34+
GHSA-v4jc-pm6r-3vj8.
35+
```
36+
2837

2938
## Comparison table
3039

docs/contributing.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,32 @@ which will:
289289

290290
Monitor the workflow run at `https://github.com/fgmacedo/python-statemachine/actions` to
291291
confirm the release was published successfully.
292+
293+
#### 10. Publish the GitHub release
294+
295+
Create the GitHub release from the freshly pushed tag using the release notes file as the
296+
body. Two adjustments are needed when going from MyST/Sphinx markdown to GitHub-flavored
297+
markdown:
298+
299+
- Convert MyST directives (`` ```{note} ``, `` ```{warning} ``, etc.) to GitHub's
300+
[alert syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
301+
(`> [!NOTE]`, `> [!WARNING]`) or to plain blockquotes.
302+
- Rewrite intra-docs links. Relative links like `[3.1.0 release notes](3.1.0.md)` resolve
303+
on ReadTheDocs but not on github.com. Replace them with absolute URLs against the
304+
matching tag, e.g.
305+
`https://github.com/fgmacedo/python-statemachine/blob/vX.Y.Z/docs/releases/X.Y.Z.md`.
306+
Avoid linking to `https://python-statemachine.readthedocs.io/en/vX.Y.Z/` immediately
307+
after publishing because the ReadTheDocs build for the new tag may still be in progress.
308+
309+
Then create the release. Use the converted notes as the body:
310+
311+
```shell
312+
gh release create vX.Y.Z \
313+
--title "vX.Y.Z" \
314+
--notes-file /tmp/release-X.Y.Z-notes.md \
315+
--target main \
316+
--verify-tag
317+
```
318+
319+
Confirm the rendered release at
320+
`https://github.com/fgmacedo/python-statemachine/releases/tag/vX.Y.Z`.

docs/index.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ weighted_transitions
5858
timeout
5959
```
6060

61+
```{toctree}
62+
:caption: IO and formats
63+
:maxdepth: 2
64+
:hidden:
65+
66+
io/index
67+
io/formats
68+
io/json_schema
69+
io/security
70+
```
71+
6172
```{toctree}
6273
:caption: How to
6374
:maxdepth: 2

docs/installation.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,21 @@ Alternatively, if you prefer using [pip](https://pip.pypa.io):
2121
python3 -m pip install python-statemachine
2222
```
2323

24-
For those looking to generate diagrams from your state machines, [pydot](https://github.com/pydot/pydot) and [Graphviz](https://graphviz.org/) are required.
25-
Conveniently, you can install python-statemachine along with the `pydot` dependency using the extras option.
26-
For more information, please refer to our documentation.
24+
## Optional extras
25+
26+
Some features require extra dependencies, available as pip extras:
2727

2828
```shell
29-
python3 -m pip install "python-statemachine[diagrams]"
29+
python3 -m pip install "python-statemachine[diagrams]" # pydot, to generate diagrams from your machines
30+
python3 -m pip install "python-statemachine[yaml]" # PyYAML, to load YAML statechart documents
31+
python3 -m pip install "python-statemachine[validation]" # jsonschema, to validate documents (validate=True)
32+
python3 -m pip install "python-statemachine[io]" # PyYAML + jsonschema, the full JSON/YAML IO stack
3033
```
3134

35+
Diagram generation also requires the [Graphviz](https://graphviz.org/) system package (see
36+
[](diagram.md)). The `[yaml]`, `[validation]` and `[io]` extras back the declarative loaders
37+
documented in [](io/index.md); loading JSON needs no extra, as it uses only the standard library.
38+
3239

3340

3441
## From sources

0 commit comments

Comments
 (0)