python-statemachine 2.4.0__tar.gz → 2.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_statemachine-2.6.0/.git-blame-ignore-revs +4 -0
- python_statemachine-2.6.0/.github/FUNDING.yml +1 -0
- python_statemachine-2.6.0/.github/ISSUE_TEMPLATE.md +15 -0
- python_statemachine-2.6.0/.github/workflows/python-package.yml +64 -0
- python_statemachine-2.6.0/.github/workflows/release.yml +68 -0
- python_statemachine-2.6.0/.gitignore +81 -0
- python_statemachine-2.6.0/.pre-commit-config.yaml +33 -0
- python_statemachine-2.6.0/.readthedocs.yaml +23 -0
- python_statemachine-2.6.0/AGENTS.md +114 -0
- python_statemachine-2.6.0/CLAUDE.md +1 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/PKG-INFO +19 -16
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/README.md +6 -4
- python_statemachine-2.6.0/conftest.py +46 -0
- python_statemachine-2.6.0/contributing.md +1 -0
- python_statemachine-2.6.0/docs/_static/custom_machine.css +59 -0
- python_statemachine-2.6.0/docs/actions.md +463 -0
- python_statemachine-2.6.0/docs/api.md +81 -0
- python_statemachine-2.6.0/docs/async.md +186 -0
- python_statemachine-2.6.0/docs/authors.md +19 -0
- python_statemachine-2.6.0/docs/conf.py +299 -0
- python_statemachine-2.6.0/docs/contributing.md +161 -0
- python_statemachine-2.6.0/docs/diagram.md +154 -0
- python_statemachine-2.6.0/docs/guards.md +277 -0
- python_statemachine-2.6.0/docs/images/_oc_machine_processing.svg +90 -0
- python_statemachine-2.6.0/docs/images/lab_approval_machine_accepted.png +0 -0
- python_statemachine-2.6.0/docs/images/oc_machine_processing.svg +90 -0
- python_statemachine-2.6.0/docs/images/order_control_machine_initial.png +0 -0
- python_statemachine-2.6.0/docs/images/order_control_machine_initial_300dpi.png +0 -0
- python_statemachine-2.6.0/docs/images/order_control_machine_processing.png +0 -0
- python_statemachine-2.6.0/docs/images/python-statemachine.png +0 -0
- python_statemachine-2.6.0/docs/images/readme_trafficlightmachine.png +0 -0
- python_statemachine-2.6.0/docs/images/test_state_machine_internal.png +0 -0
- python_statemachine-2.6.0/docs/images/traffic_light_machine.png +0 -0
- python_statemachine-2.6.0/docs/index.md +33 -0
- python_statemachine-2.6.0/docs/installation.md +54 -0
- python_statemachine-2.6.0/docs/integrations.md +90 -0
- python_statemachine-2.6.0/docs/listeners.md +105 -0
- python_statemachine-2.6.0/docs/mixins.md +93 -0
- python_statemachine-2.6.0/docs/models.md +27 -0
- python_statemachine-2.6.0/docs/processing_model.md +138 -0
- python_statemachine-2.6.0/docs/readme.md +2 -0
- python_statemachine-2.6.0/docs/releases/0.1.0.md +5 -0
- python_statemachine-2.6.0/docs/releases/0.2.0.md +8 -0
- python_statemachine-2.6.0/docs/releases/0.3.0.md +7 -0
- python_statemachine-2.6.0/docs/releases/0.4.2.md +12 -0
- python_statemachine-2.6.0/docs/releases/0.5.0.md +7 -0
- python_statemachine-2.6.0/docs/releases/0.5.1.md +7 -0
- python_statemachine-2.6.0/docs/releases/0.6.0.md +7 -0
- python_statemachine-2.6.0/docs/releases/0.6.1.md +6 -0
- python_statemachine-2.6.0/docs/releases/0.6.2.md +6 -0
- python_statemachine-2.6.0/docs/releases/0.7.0.md +6 -0
- python_statemachine-2.6.0/docs/releases/0.7.1.md +6 -0
- python_statemachine-2.6.0/docs/releases/0.8.0.md +21 -0
- python_statemachine-2.6.0/docs/releases/0.9.0.md +65 -0
- python_statemachine-2.6.0/docs/releases/1.0.0.md +6 -0
- python_statemachine-2.6.0/docs/releases/1.0.1.md +229 -0
- python_statemachine-2.6.0/docs/releases/1.0.2.md +14 -0
- python_statemachine-2.6.0/docs/releases/1.0.3.md +14 -0
- python_statemachine-2.6.0/docs/releases/2.0.0.md +366 -0
- python_statemachine-2.6.0/docs/releases/2.1.0.md +43 -0
- python_statemachine-2.6.0/docs/releases/2.1.1.md +10 -0
- python_statemachine-2.6.0/docs/releases/2.1.2.md +20 -0
- python_statemachine-2.6.0/docs/releases/2.2.0.md +71 -0
- python_statemachine-2.6.0/docs/releases/2.3.0.md +49 -0
- python_statemachine-2.6.0/docs/releases/2.3.1.md +8 -0
- python_statemachine-2.6.0/docs/releases/2.3.2.md +95 -0
- python_statemachine-2.6.0/docs/releases/2.3.3.md +20 -0
- python_statemachine-2.6.0/docs/releases/2.3.4.md +8 -0
- python_statemachine-2.6.0/docs/releases/2.3.5.md +12 -0
- python_statemachine-2.6.0/docs/releases/2.3.6.md +9 -0
- python_statemachine-2.6.0/docs/releases/2.4.0.md +89 -0
- python_statemachine-2.6.0/docs/releases/2.5.0.md +188 -0
- python_statemachine-2.6.0/docs/releases/2.6.0.md +128 -0
- python_statemachine-2.6.0/docs/releases/index.md +70 -0
- python_statemachine-2.6.0/docs/states.md +166 -0
- python_statemachine-2.6.0/docs/transitions.md +378 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/pyproject.toml +102 -84
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/__init__.py +1 -1
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/callbacks.py +74 -111
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/contrib/diagram.py +4 -4
- python_statemachine-2.6.0/statemachine/dispatcher.py +232 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/engines/async_.py +55 -34
- python_statemachine-2.6.0/statemachine/engines/base.py +40 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/engines/sync.py +55 -37
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/event.py +23 -6
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/events.py +1 -2
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/factory.py +7 -11
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/locale/en/LC_MESSAGES/statemachine.po +35 -20
- python_statemachine-2.6.0/statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +119 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +53 -26
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +34 -20
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/mixins.py +11 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/signature.py +85 -32
- python_statemachine-2.6.0/statemachine/spec_parser.py +204 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/state.py +59 -27
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/statemachine.py +65 -58
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/transition.py +22 -2
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/transition_list.py +12 -78
- python_statemachine-2.6.0/statemachine/transition_mixin.py +89 -0
- python_statemachine-2.6.0/tests/__init__.py +0 -0
- python_statemachine-2.6.0/tests/conftest.py +213 -0
- python_statemachine-2.6.0/tests/django_project/app.py +11 -0
- python_statemachine-2.6.0/tests/django_project/core/__init__,.py +0 -0
- python_statemachine-2.6.0/tests/django_project/core/settings.py +28 -0
- python_statemachine-2.6.0/tests/django_project/core/wsgi.py +16 -0
- python_statemachine-2.6.0/tests/django_project/manage.py +23 -0
- python_statemachine-2.6.0/tests/django_project/workflow/__init__.py +0 -0
- python_statemachine-2.6.0/tests/django_project/workflow/apps.py +6 -0
- python_statemachine-2.6.0/tests/django_project/workflow/models.py +22 -0
- python_statemachine-2.6.0/tests/django_project/workflow/statemachines.py +15 -0
- python_statemachine-2.6.0/tests/django_project/workflow/tests.py +66 -0
- python_statemachine-2.6.0/tests/examples/README.rst +9 -0
- python_statemachine-2.6.0/tests/examples/__init__.py +0 -0
- python_statemachine-2.6.0/tests/examples/air_conditioner_machine.py +69 -0
- python_statemachine-2.6.0/tests/examples/all_actions_machine.py +201 -0
- python_statemachine-2.6.0/tests/examples/async_guess_the_number_machine.py +173 -0
- python_statemachine-2.6.0/tests/examples/async_without_loop_machine.py +45 -0
- python_statemachine-2.6.0/tests/examples/enum_campaign_machine.py +61 -0
- python_statemachine-2.6.0/tests/examples/guess_the_number_machine.py +123 -0
- python_statemachine-2.6.0/tests/examples/lor_machine.py +104 -0
- python_statemachine-2.6.0/tests/examples/order_control_machine.py +47 -0
- python_statemachine-2.6.0/tests/examples/order_control_rich_model_machine.py +143 -0
- python_statemachine-2.6.0/tests/examples/persistent_model_machine.py +142 -0
- python_statemachine-2.6.0/tests/examples/recursive_event_machine.py +39 -0
- python_statemachine-2.6.0/tests/examples/reusing_transitions_machine.py +97 -0
- python_statemachine-2.6.0/tests/examples/traffic_light_machine.py +64 -0
- python_statemachine-2.6.0/tests/examples/user_machine.py +131 -0
- python_statemachine-2.6.0/tests/helpers.py +10 -0
- python_statemachine-2.6.0/tests/models.py +10 -0
- python_statemachine-2.6.0/tests/scrape_images.py +64 -0
- python_statemachine-2.6.0/tests/test_actions.py +22 -0
- python_statemachine-2.6.0/tests/test_async.py +279 -0
- python_statemachine-2.6.0/tests/test_callbacks.py +348 -0
- python_statemachine-2.6.0/tests/test_callbacks_isolation.py +73 -0
- python_statemachine-2.6.0/tests/test_conditions_algebra.py +65 -0
- python_statemachine-2.6.0/tests/test_contrib_diagram.py +90 -0
- python_statemachine-2.6.0/tests/test_copy.py +232 -0
- python_statemachine-2.6.0/tests/test_dispatcher.py +163 -0
- python_statemachine-2.6.0/tests/test_events.py +310 -0
- python_statemachine-2.6.0/tests/test_examples.py +38 -0
- python_statemachine-2.6.0/tests/test_listener.py +106 -0
- python_statemachine-2.6.0/tests/test_mixins.py +38 -0
- python_statemachine-2.6.0/tests/test_mock_compatibility.py +24 -0
- python_statemachine-2.6.0/tests/test_multiple_destinations.py +232 -0
- python_statemachine-2.6.0/tests/test_profiling.py +62 -0
- python_statemachine-2.6.0/tests/test_registry.py +45 -0
- python_statemachine-2.6.0/tests/test_rtc.py +262 -0
- python_statemachine-2.6.0/tests/test_signature.py +292 -0
- python_statemachine-2.6.0/tests/test_signature_positional_only.py +33 -0
- python_statemachine-2.6.0/tests/test_spec_parser.py +356 -0
- python_statemachine-2.6.0/tests/test_state.py +41 -0
- python_statemachine-2.6.0/tests/test_state_callbacks.py +102 -0
- python_statemachine-2.6.0/tests/test_statemachine.py +636 -0
- python_statemachine-2.6.0/tests/test_statemachine_bounded_transitions.py +58 -0
- python_statemachine-2.6.0/tests/test_statemachine_inheritance.py +110 -0
- python_statemachine-2.6.0/tests/test_threading.py +188 -0
- python_statemachine-2.6.0/tests/test_transition_list.py +63 -0
- python_statemachine-2.6.0/tests/test_transitions.py +387 -0
- python_statemachine-2.6.0/tests/testcases/issue308.md +121 -0
- python_statemachine-2.6.0/tests/testcases/issue384_multiple_observers.md +57 -0
- python_statemachine-2.6.0/tests/testcases/issue434.md +87 -0
- python_statemachine-2.6.0/tests/testcases/issue449.md +55 -0
- python_statemachine-2.6.0/tests/testcases/issue480.md +43 -0
- python_statemachine-2.6.0/uv.lock +2081 -0
- python_statemachine-2.4.0/statemachine/dispatcher.py +0 -161
- python_statemachine-2.4.0/statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +0 -93
- python_statemachine-2.4.0/statemachine/spec_parser.py +0 -79
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/LICENSE +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/contrib/__init__.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/engines/__init__.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/event_data.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/exceptions.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/graph.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/i18n.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/model.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/py.typed +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/registry.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/states.py +0 -0
- {python_statemachine-2.4.0 → python_statemachine-2.6.0}/statemachine/utils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github: fgmacedo
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
* Python State Machine version:
|
|
2
|
+
* Python version:
|
|
3
|
+
* Operating System:
|
|
4
|
+
|
|
5
|
+
### Description
|
|
6
|
+
|
|
7
|
+
Describe what you were trying to get done.
|
|
8
|
+
Tell us what happened, what went wrong, and what you expected to happen.
|
|
9
|
+
|
|
10
|
+
### What I Did
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Paste the command(s) you ran and the output.
|
|
14
|
+
If there was a crash, please include the traceback here.
|
|
15
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
+
|
|
4
|
+
name: Python checks
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "develop" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "develop" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- run: git fetch origin develop
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
- name: Setup Graphviz
|
|
28
|
+
uses: ts-graphviz/setup-graphviz@v2
|
|
29
|
+
- name: Install uv
|
|
30
|
+
uses: astral-sh/setup-uv@v3
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
cache-suffix: "python${{ matrix.python-version }}"
|
|
34
|
+
- name: Install the project
|
|
35
|
+
run: uv sync --all-extras --dev
|
|
36
|
+
#----------------------------------------------
|
|
37
|
+
# run ruff
|
|
38
|
+
#----------------------------------------------
|
|
39
|
+
- name: Linter with ruff
|
|
40
|
+
if: matrix.python-version == 3.14
|
|
41
|
+
run: |
|
|
42
|
+
uv run ruff check .
|
|
43
|
+
uv run ruff format --check .
|
|
44
|
+
#----------------------------------------------
|
|
45
|
+
# run pytest
|
|
46
|
+
#----------------------------------------------
|
|
47
|
+
- name: Test with pytest
|
|
48
|
+
run: |
|
|
49
|
+
uv run pytest --cov-report=xml:coverage.xml
|
|
50
|
+
uv run coverage xml
|
|
51
|
+
#----------------------------------------------
|
|
52
|
+
# upload coverage
|
|
53
|
+
#----------------------------------------------
|
|
54
|
+
- name: Upload coverage to Codecov
|
|
55
|
+
uses: codecov/codecov-action@v4
|
|
56
|
+
if: matrix.python-version == 3.14
|
|
57
|
+
with:
|
|
58
|
+
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
|
59
|
+
directory: .
|
|
60
|
+
env_vars: OS,PYTHON
|
|
61
|
+
fail_ci_if_error: true
|
|
62
|
+
flags: unittests
|
|
63
|
+
name: codecov-umbrella
|
|
64
|
+
verbose: true
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
on:
|
|
2
|
+
push:
|
|
3
|
+
tags: [ 'v?*.*.*' ]
|
|
4
|
+
name: release
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
release-build:
|
|
8
|
+
name: Build release artifacts
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- run: git fetch origin develop
|
|
17
|
+
|
|
18
|
+
- name: Setup Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.14'
|
|
22
|
+
|
|
23
|
+
- name: Setup Graphviz
|
|
24
|
+
uses: ts-graphviz/setup-graphviz@v2
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v3
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Install the project
|
|
32
|
+
run: uv sync --all-extras --dev
|
|
33
|
+
|
|
34
|
+
- name: Test
|
|
35
|
+
run: |
|
|
36
|
+
uv run pytest
|
|
37
|
+
|
|
38
|
+
- name: Build
|
|
39
|
+
run: |
|
|
40
|
+
uv build
|
|
41
|
+
|
|
42
|
+
- name: Upload dists
|
|
43
|
+
uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: release-dists
|
|
46
|
+
path: dist/
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
pypi-publish:
|
|
50
|
+
# by a dedicated job to publish we avoid the risk of
|
|
51
|
+
# running code with access to PyPI credentials
|
|
52
|
+
name: Upload release to PyPI
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
needs:
|
|
55
|
+
- release-build
|
|
56
|
+
environment: release
|
|
57
|
+
permissions:
|
|
58
|
+
id-token: write
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Retrieve release distributions
|
|
62
|
+
uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: release-dists
|
|
65
|
+
path: dist/
|
|
66
|
+
|
|
67
|
+
- name: Publish package distributions to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
env/
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
.mypy_cache
|
|
27
|
+
|
|
28
|
+
# jupyter
|
|
29
|
+
.ipynb_checkpoints/
|
|
30
|
+
.jupyterlite.doit.db
|
|
31
|
+
|
|
32
|
+
# PyInstaller
|
|
33
|
+
# Usually these files are written by a python script from a template
|
|
34
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
35
|
+
*.manifest
|
|
36
|
+
*.spec
|
|
37
|
+
|
|
38
|
+
# Installer logs
|
|
39
|
+
pip-log.txt
|
|
40
|
+
pip-delete-this-directory.txt
|
|
41
|
+
|
|
42
|
+
# Unit test / coverage reports
|
|
43
|
+
prof/
|
|
44
|
+
.benchmarks/
|
|
45
|
+
htmlcov/
|
|
46
|
+
.tox/
|
|
47
|
+
.coverage
|
|
48
|
+
.coverage.*
|
|
49
|
+
.cache
|
|
50
|
+
.pytest_cache
|
|
51
|
+
nosetests.xml
|
|
52
|
+
coverage.xml
|
|
53
|
+
*,cover
|
|
54
|
+
.hypothesis/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
|
|
63
|
+
# Sphinx documentation
|
|
64
|
+
docs/_build/
|
|
65
|
+
docs/auto_examples/
|
|
66
|
+
|
|
67
|
+
# PyBuilder
|
|
68
|
+
target/
|
|
69
|
+
|
|
70
|
+
# pyenv python configuration file
|
|
71
|
+
.python-version
|
|
72
|
+
|
|
73
|
+
# IDEs and editors
|
|
74
|
+
*.sublime*
|
|
75
|
+
.idea/
|
|
76
|
+
.vscode/
|
|
77
|
+
|
|
78
|
+
# Sphinx-galery
|
|
79
|
+
docs/auto_examples/sg_execution_times.*
|
|
80
|
+
docs/auto_examples/*.pickle
|
|
81
|
+
docs/sg_execution_times.rst
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.6.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-yaml
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
exclude: docs/auto_examples
|
|
8
|
+
- id: trailing-whitespace
|
|
9
|
+
exclude: docs/auto_examples
|
|
10
|
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
11
|
+
# Ruff version.
|
|
12
|
+
rev: v0.15.0
|
|
13
|
+
hooks:
|
|
14
|
+
# Run the linter.
|
|
15
|
+
- id: ruff
|
|
16
|
+
args: [ --fix ]
|
|
17
|
+
# Run the formatter.
|
|
18
|
+
- id: ruff-format
|
|
19
|
+
|
|
20
|
+
- repo: local
|
|
21
|
+
hooks:
|
|
22
|
+
- id: mypy
|
|
23
|
+
name: Mypy
|
|
24
|
+
entry: uv run mypy --namespace-packages --explicit-package-bases statemachine/ tests/
|
|
25
|
+
types: [python]
|
|
26
|
+
language: system
|
|
27
|
+
pass_filenames: false
|
|
28
|
+
- id: pytest
|
|
29
|
+
name: Pytest
|
|
30
|
+
entry: uv run pytest
|
|
31
|
+
types: [python]
|
|
32
|
+
language: system
|
|
33
|
+
pass_filenames: false
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# .readthedocs.yaml
|
|
2
|
+
# Read the Docs configuration file
|
|
3
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
4
|
+
|
|
5
|
+
# Required
|
|
6
|
+
version: 2
|
|
7
|
+
|
|
8
|
+
build:
|
|
9
|
+
os: "ubuntu-22.04"
|
|
10
|
+
tools:
|
|
11
|
+
python: "3.14"
|
|
12
|
+
apt_packages:
|
|
13
|
+
- graphviz
|
|
14
|
+
jobs:
|
|
15
|
+
post_create_environment:
|
|
16
|
+
- asdf plugin add uv
|
|
17
|
+
- asdf install uv latest
|
|
18
|
+
- asdf global uv latest
|
|
19
|
+
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --frozen
|
|
20
|
+
|
|
21
|
+
# Build documentation in the docs/ directory with Sphinx
|
|
22
|
+
sphinx:
|
|
23
|
+
configuration: docs/conf.py
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# python-statemachine
|
|
2
|
+
|
|
3
|
+
Python Finite State Machines made easy.
|
|
4
|
+
|
|
5
|
+
## Project overview
|
|
6
|
+
|
|
7
|
+
A library for building finite state machines in Python, with support for sync and async engines,
|
|
8
|
+
Django integration, diagram generation, and a flexible callback/listener system.
|
|
9
|
+
|
|
10
|
+
- **Source code:** `statemachine/`
|
|
11
|
+
- **Tests:** `tests/`
|
|
12
|
+
- **Documentation:** `docs/` (Sphinx + MyST Markdown, hosted on ReadTheDocs)
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
- `statemachine.py` — Core `StateMachine` class
|
|
17
|
+
- `factory.py` — `StateMachineMetaclass` handles class construction, state/transition validation
|
|
18
|
+
- `state.py` / `event.py` — Descriptor-based `State` and `Event` definitions
|
|
19
|
+
- `transition.py` / `transition_list.py` — Transition logic and composition (`|` operator)
|
|
20
|
+
- `callbacks.py` — Priority-based callback registry (`CallbackPriority`, `CallbackGroup`)
|
|
21
|
+
- `dispatcher.py` — Listener/observer pattern, `callable_method` wraps callables with signature adaptation
|
|
22
|
+
- `signature.py` — `SignatureAdapter` for dependency injection into callbacks
|
|
23
|
+
- `engines/sync.py`, `engines/async_.py` — Sync and async run-to-completion engines
|
|
24
|
+
- `registry.py` — Global state machine registry (used by `MachineMixin`)
|
|
25
|
+
- `mixins.py` — `MachineMixin` for domain model integration (e.g., Django models)
|
|
26
|
+
- `spec_parser.py` — Boolean expression parser for condition guards
|
|
27
|
+
- `contrib/diagram.py` — Diagram generation via pydot/Graphviz
|
|
28
|
+
|
|
29
|
+
## Environment setup
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv sync --all-extras --dev
|
|
33
|
+
pre-commit install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Running tests
|
|
37
|
+
|
|
38
|
+
Always use `uv` to run commands:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Run all tests (parallel)
|
|
42
|
+
uv run pytest -n auto
|
|
43
|
+
|
|
44
|
+
# Run a specific test file
|
|
45
|
+
uv run pytest tests/test_signature.py
|
|
46
|
+
|
|
47
|
+
# Run a specific test
|
|
48
|
+
uv run pytest tests/test_signature.py::TestSignatureAdapter::test_wrap_fn_single_positional_parameter
|
|
49
|
+
|
|
50
|
+
# Skip slow tests
|
|
51
|
+
uv run pytest -m "not slow"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tests include doctests from both source modules (`--doctest-modules`) and markdown docs
|
|
55
|
+
(`--doctest-glob=*.md`). Coverage is enabled by default.
|
|
56
|
+
|
|
57
|
+
## Linting and formatting
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Lint
|
|
61
|
+
uv run ruff check .
|
|
62
|
+
|
|
63
|
+
# Auto-fix lint issues
|
|
64
|
+
uv run ruff check --fix .
|
|
65
|
+
|
|
66
|
+
# Format
|
|
67
|
+
uv run ruff format .
|
|
68
|
+
|
|
69
|
+
# Type check
|
|
70
|
+
uv run mypy statemachine/ tests/
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Code style
|
|
74
|
+
|
|
75
|
+
- **Formatter/Linter:** ruff (line length 99, target Python 3.9)
|
|
76
|
+
- **Rules:** pycodestyle, pyflakes, isort, pyupgrade, flake8-comprehensions, flake8-bugbear, flake8-pytest-style
|
|
77
|
+
- **Imports:** single-line, sorted by isort
|
|
78
|
+
- **Docstrings:** Google convention
|
|
79
|
+
- **Naming:** PascalCase for classes, snake_case for functions/methods, UPPER_SNAKE_CASE for constants
|
|
80
|
+
- **Type hints:** used throughout; `TYPE_CHECKING` for circular imports
|
|
81
|
+
- Pre-commit hooks enforce ruff + mypy + pytest
|
|
82
|
+
|
|
83
|
+
## Design principles
|
|
84
|
+
|
|
85
|
+
- **Decouple infrastructure from domain:** Modules like `signature.py` and `dispatcher.py` are
|
|
86
|
+
general-purpose (signature adaptation, listener/observer pattern) and intentionally not coupled
|
|
87
|
+
to the state machine domain. Prefer this separation even for modules that are only used
|
|
88
|
+
internally — it keeps responsibilities clear and the code easier to reason about.
|
|
89
|
+
- **Favor small, focused modules:** When adding new functionality, consider whether it can live in
|
|
90
|
+
its own module with a well-defined boundary, rather than growing an existing one.
|
|
91
|
+
|
|
92
|
+
## Building documentation
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Build HTML docs
|
|
96
|
+
uv run sphinx-build docs docs/_build/html
|
|
97
|
+
|
|
98
|
+
# Live reload for development
|
|
99
|
+
uv run sphinx-autobuild docs docs/_build/html --re-ignore "auto_examples/.*"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Git workflow
|
|
103
|
+
|
|
104
|
+
- Main branch: `develop`
|
|
105
|
+
- PRs target `develop`
|
|
106
|
+
- Releases are tagged as `v*.*.*`
|
|
107
|
+
- Signed commits preferred (`git commit -s`)
|
|
108
|
+
- Use [Conventional Commits](https://www.conventionalcommits.org/) messages
|
|
109
|
+
(e.g., `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `perf:`)
|
|
110
|
+
|
|
111
|
+
## Security
|
|
112
|
+
|
|
113
|
+
- Do not commit secrets, credentials, or `.env` files
|
|
114
|
+
- Validate at system boundaries; trust internal code
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-statemachine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: Python Finite State Machines made easy.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Requires-Python: >=3.7
|
|
5
|
+
Project-URL: homepage, https://github.com/fgmacedo/python-statemachine
|
|
6
|
+
Author-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
7
|
+
Maintainer-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
11
|
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Natural Language :: English
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.7
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -21,9 +20,12 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Home Automation
|
|
24
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Requires-Python: >=3.7
|
|
25
27
|
Provides-Extra: diagrams
|
|
26
|
-
Requires-Dist: pydot
|
|
28
|
+
Requires-Dist: pydot>=2.0.0; extra == 'diagrams'
|
|
27
29
|
Description-Content-Type: text/markdown
|
|
28
30
|
|
|
29
31
|
# Python StateMachine
|
|
@@ -127,6 +129,8 @@ You can now create an instance:
|
|
|
127
129
|
This state machine can be represented graphically as follows:
|
|
128
130
|
|
|
129
131
|
```py
|
|
132
|
+
>>> # This example will only run on automated tests if dot is present
|
|
133
|
+
>>> getfixture("requires_dot_installed")
|
|
130
134
|
>>> img_path = "docs/images/readme_trafficlightmachine.png"
|
|
131
135
|
>>> sm._graph().write_png(img_path)
|
|
132
136
|
|
|
@@ -196,19 +200,19 @@ Easily iterate over all states:
|
|
|
196
200
|
|
|
197
201
|
```py
|
|
198
202
|
>>> [s.id for s in sm.states]
|
|
199
|
-
['green', '
|
|
203
|
+
['green', 'yellow', 'red']
|
|
200
204
|
|
|
201
205
|
```
|
|
202
206
|
|
|
203
207
|
Or over events:
|
|
204
208
|
|
|
205
209
|
```py
|
|
206
|
-
>>> [t.
|
|
210
|
+
>>> [t.id for t in sm.events]
|
|
207
211
|
['cycle']
|
|
208
212
|
|
|
209
213
|
```
|
|
210
214
|
|
|
211
|
-
Call an event by its
|
|
215
|
+
Call an event by its id:
|
|
212
216
|
|
|
213
217
|
```py
|
|
214
218
|
>>> sm.cycle()
|
|
@@ -216,7 +220,7 @@ Don't move.
|
|
|
216
220
|
'Running cycle from yellow to red'
|
|
217
221
|
|
|
218
222
|
```
|
|
219
|
-
Or send an event with the event
|
|
223
|
+
Or send an event with the event id:
|
|
220
224
|
|
|
221
225
|
```py
|
|
222
226
|
>>> sm.send('cycle')
|
|
@@ -427,4 +431,3 @@ request. For more information on how to contribute, please see our [contributing
|
|
|
427
431
|
- **Promote the project**: Help spread the word by sharing on social media,
|
|
428
432
|
writing a blog post, or giving a talk about it. Tag me on Twitter
|
|
429
433
|
[@fgmacedo](https://twitter.com/fgmacedo) so I can share it too!
|
|
430
|
-
|
|
@@ -99,6 +99,8 @@ You can now create an instance:
|
|
|
99
99
|
This state machine can be represented graphically as follows:
|
|
100
100
|
|
|
101
101
|
```py
|
|
102
|
+
>>> # This example will only run on automated tests if dot is present
|
|
103
|
+
>>> getfixture("requires_dot_installed")
|
|
102
104
|
>>> img_path = "docs/images/readme_trafficlightmachine.png"
|
|
103
105
|
>>> sm._graph().write_png(img_path)
|
|
104
106
|
|
|
@@ -168,19 +170,19 @@ Easily iterate over all states:
|
|
|
168
170
|
|
|
169
171
|
```py
|
|
170
172
|
>>> [s.id for s in sm.states]
|
|
171
|
-
['green', '
|
|
173
|
+
['green', 'yellow', 'red']
|
|
172
174
|
|
|
173
175
|
```
|
|
174
176
|
|
|
175
177
|
Or over events:
|
|
176
178
|
|
|
177
179
|
```py
|
|
178
|
-
>>> [t.
|
|
180
|
+
>>> [t.id for t in sm.events]
|
|
179
181
|
['cycle']
|
|
180
182
|
|
|
181
183
|
```
|
|
182
184
|
|
|
183
|
-
Call an event by its
|
|
185
|
+
Call an event by its id:
|
|
184
186
|
|
|
185
187
|
```py
|
|
186
188
|
>>> sm.cycle()
|
|
@@ -188,7 +190,7 @@ Don't move.
|
|
|
188
190
|
'Running cycle from yellow to red'
|
|
189
191
|
|
|
190
192
|
```
|
|
191
|
-
Or send an event with the event
|
|
193
|
+
Or send an event with the event id:
|
|
192
194
|
|
|
193
195
|
```py
|
|
194
196
|
>>> sm.send('cycle')
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture(autouse=True, scope="session")
|
|
8
|
+
def add_doctest_context(doctest_namespace): # noqa: PT004
|
|
9
|
+
from statemachine.utils import run_async_from_sync
|
|
10
|
+
|
|
11
|
+
from statemachine import State
|
|
12
|
+
from statemachine import StateMachine
|
|
13
|
+
|
|
14
|
+
class ContribAsyncio:
|
|
15
|
+
"""
|
|
16
|
+
Using `run_async_from_sync` to be injected in the doctests to better integration with an
|
|
17
|
+
already running loop, as all of our examples are also automated executed as doctests.
|
|
18
|
+
|
|
19
|
+
On real life code you should use standard `import asyncio; asyncio.run(main())`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.run = run_async_from_sync
|
|
24
|
+
|
|
25
|
+
doctest_namespace["State"] = State
|
|
26
|
+
doctest_namespace["StateMachine"] = StateMachine
|
|
27
|
+
doctest_namespace["asyncio"] = ContribAsyncio()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def pytest_ignore_collect(collection_path, path, config):
|
|
31
|
+
if sys.version_info >= (3, 10): # noqa: UP036
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
if "django_project" in str(path):
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture(scope="session")
|
|
39
|
+
def has_dot_installed():
|
|
40
|
+
return bool(shutil.which("dot"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture()
|
|
44
|
+
def requires_dot_installed(request, has_dot_installed):
|
|
45
|
+
if not has_dot_installed:
|
|
46
|
+
pytest.skip(f"Test {request.node.nodeid} requires 'dot' that is not installed.")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Please see [docs/contributing.md](docs/contributing).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* div.sphx-glr-download {
|
|
2
|
+
height: 0px;
|
|
3
|
+
visibility: hidden;
|
|
4
|
+
} */
|
|
5
|
+
|
|
6
|
+
@media only screen and (min-width: 650px) {
|
|
7
|
+
|
|
8
|
+
.sphx-glr-thumbnails {
|
|
9
|
+
grid-template-columns: repeat(auto-fill, minmax(600px, 1fr)) !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sphx-glr-thumbcontainer {
|
|
13
|
+
min-height: 320px !important;
|
|
14
|
+
margin: 20px !important;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
}
|
|
17
|
+
.sphx-glr-thumbcontainer .figure {
|
|
18
|
+
width: 600px !important;
|
|
19
|
+
}
|
|
20
|
+
.sphx-glr-thumbcontainer img {
|
|
21
|
+
max-height: 250px !important;
|
|
22
|
+
max-width: 600px !important;
|
|
23
|
+
width: 100% !important;
|
|
24
|
+
}
|
|
25
|
+
.sphx-glr-thumbcontainer a.internal {
|
|
26
|
+
padding: 20px 10px 0 !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Gallery Donwload buttons */
|
|
32
|
+
div.sphx-glr-download a {
|
|
33
|
+
color: #404040 !important;
|
|
34
|
+
background-color: #f3f6f6 !important;
|
|
35
|
+
background-image: none;
|
|
36
|
+
border-radius: 4px;
|
|
37
|
+
border: none;
|
|
38
|
+
display: inline-block;
|
|
39
|
+
font-weight: bold;
|
|
40
|
+
padding: 1ex;
|
|
41
|
+
text-align: center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
div.sphx-glr-download code.download {
|
|
45
|
+
display: inline-block;
|
|
46
|
+
white-space: normal;
|
|
47
|
+
word-break: normal;
|
|
48
|
+
overflow-wrap: break-word;
|
|
49
|
+
/* border and background are given by the enclosing 'a' */
|
|
50
|
+
border: none;
|
|
51
|
+
background: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
div.sphx-glr-download a:hover {
|
|
55
|
+
box-shadow: none;
|
|
56
|
+
text-decoration: none;
|
|
57
|
+
background-image: none;
|
|
58
|
+
background-color: #e5ebeb !important;
|
|
59
|
+
}
|