python-statemachine 2.5.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.5.0 → python_statemachine-2.6.0}/.github/workflows/python-package.yml +3 -7
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.github/workflows/release.yml +1 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.pre-commit-config.yaml +1 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.readthedocs.yaml +1 -1
- python_statemachine-2.6.0/AGENTS.md +114 -0
- python_statemachine-2.6.0/CLAUDE.md +1 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/PKG-INFO +6 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/README.md +2 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/conftest.py +14 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/actions.md +2 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/authors.md +1 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/diagram.md +14 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/guards.md +104 -1
- 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/readme_trafficlightmachine.png +0 -0
- python_statemachine-2.6.0/docs/images/test_state_machine_internal.png +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/integrations.md +19 -0
- python_statemachine-2.6.0/docs/releases/2.6.0.md +128 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/index.md +1 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/transitions.md +6 -3
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/pyproject.toml +10 -6
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/__init__.py +1 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/engines/async_.py +28 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/engines/sync.py +26 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/event.py +2 -2
- {python_statemachine-2.5.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.5.0 → python_statemachine-2.6.0}/statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +53 -26
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +34 -20
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/mixins.py +11 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/signature.py +79 -7
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/spec_parser.py +62 -8
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/statemachine.py +22 -3
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/transition_mixin.py +9 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/workflow/models.py +0 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/workflow/statemachines.py +2 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/air_conditioner_machine.py +2 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/enum_campaign_machine.py +2 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/lor_machine.py +2 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/order_control_rich_model_machine.py +2 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/user_machine.py +3 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/scrape_images.py +3 -1
- python_statemachine-2.6.0/tests/test_async.py +279 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_contrib_diagram.py +2 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_copy.py +51 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_dispatcher.py +0 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_listener.py +0 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_mixins.py +16 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_registry.py +1 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_signature.py +129 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_signature_positional_only.py +0 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_spec_parser.py +96 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_statemachine.py +146 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_transitions.py +16 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/uv.lock +693 -232
- python_statemachine-2.5.0/docs/images/order_control_machine_initial.png +0 -0
- python_statemachine-2.5.0/docs/images/order_control_machine_processing.png +0 -0
- python_statemachine-2.5.0/docs/images/readme_trafficlightmachine.png +0 -0
- python_statemachine-2.5.0/docs/images/test_state_machine_internal.png +0 -0
- python_statemachine-2.5.0/statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +0 -93
- python_statemachine-2.5.0/tests/test_async.py +0 -121
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.git-blame-ignore-revs +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.github/FUNDING.yml +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/.gitignore +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/LICENSE +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/contributing.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/_static/custom_machine.css +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/api.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/async.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/conf.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/contributing.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/images/_oc_machine_processing.svg +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/images/lab_approval_machine_accepted.png +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/images/oc_machine_processing.svg +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/images/python-statemachine.png +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/images/traffic_light_machine.png +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/index.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/installation.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/listeners.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/mixins.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/models.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/processing_model.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/readme.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.1.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.2.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.3.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.4.2.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.5.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.5.1.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.6.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.6.1.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.6.2.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.7.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.7.1.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.8.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/0.9.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/1.0.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/1.0.1.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/1.0.2.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/1.0.3.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.0.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.1.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.1.1.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.1.2.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.2.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.1.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.2.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.3.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.4.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.5.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.3.6.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.4.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/releases/2.5.0.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/docs/states.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/callbacks.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/contrib/__init__.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/contrib/diagram.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/dispatcher.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/engines/__init__.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/engines/base.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/event_data.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/events.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/exceptions.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/factory.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/graph.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/i18n.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/model.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/py.typed +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/registry.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/state.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/states.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/transition.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/transition_list.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/statemachine/utils.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/__init__.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/conftest.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/app.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/core/__init__,.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/core/settings.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/core/wsgi.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/manage.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/workflow/__init__.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/workflow/apps.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/django_project/workflow/tests.py +1 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/README.rst +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/__init__.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/all_actions_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/async_guess_the_number_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/async_without_loop_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/guess_the_number_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/order_control_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/persistent_model_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/recursive_event_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/reusing_transitions_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/examples/traffic_light_machine.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/helpers.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/models.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_actions.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_callbacks.py +3 -3
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_callbacks_isolation.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_conditions_algebra.py +1 -1
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_events.py +2 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_examples.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_mock_compatibility.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_multiple_destinations.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_profiling.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_rtc.py +2 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_state.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_state_callbacks.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_statemachine_bounded_transitions.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_statemachine_inheritance.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_threading.py +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/test_transition_list.py +2 -2
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/testcases/issue308.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/testcases/issue384_multiple_observers.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/testcases/issue434.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/testcases/issue449.md +0 -0
- {python_statemachine-2.5.0 → python_statemachine-2.6.0}/tests/testcases/issue480.md +0 -0
{python_statemachine-2.5.0 → python_statemachine-2.6.0}/.github/workflows/python-package.yml
RENAMED
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
strategy:
|
|
16
16
|
fail-fast: false
|
|
17
17
|
matrix:
|
|
18
|
-
python-version: ["3.
|
|
18
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
19
19
|
|
|
20
20
|
steps:
|
|
21
21
|
- uses: actions/checkout@v4
|
|
@@ -33,15 +33,11 @@ jobs:
|
|
|
33
33
|
cache-suffix: "python${{ matrix.python-version }}"
|
|
34
34
|
- name: Install the project
|
|
35
35
|
run: uv sync --all-extras --dev
|
|
36
|
-
- name: Install old pydot for 3.7 only
|
|
37
|
-
if: matrix.python-version == 3.7
|
|
38
|
-
run: |
|
|
39
|
-
uv pip install pydot==2.0.0
|
|
40
36
|
#----------------------------------------------
|
|
41
37
|
# run ruff
|
|
42
38
|
#----------------------------------------------
|
|
43
39
|
- name: Linter with ruff
|
|
44
|
-
if: matrix.python-version == 3.
|
|
40
|
+
if: matrix.python-version == 3.14
|
|
45
41
|
run: |
|
|
46
42
|
uv run ruff check .
|
|
47
43
|
uv run ruff format --check .
|
|
@@ -57,7 +53,7 @@ jobs:
|
|
|
57
53
|
#----------------------------------------------
|
|
58
54
|
- name: Upload coverage to Codecov
|
|
59
55
|
uses: codecov/codecov-action@v4
|
|
60
|
-
if: matrix.python-version == 3.
|
|
56
|
+
if: matrix.python-version == 3.14
|
|
61
57
|
with:
|
|
62
58
|
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
|
63
59
|
directory: .
|
|
@@ -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,11 +1,12 @@
|
|
|
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
5
|
Project-URL: homepage, https://github.com/fgmacedo/python-statemachine
|
|
6
6
|
Author-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
7
7
|
Maintainer-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
8
8
|
License: MIT License
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Framework :: AsyncIO
|
|
11
12
|
Classifier: Framework :: Django
|
|
@@ -19,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
24
|
Classifier: Topic :: Home Automation
|
|
23
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
24
26
|
Requires-Python: >=3.7
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import shutil
|
|
1
2
|
import sys
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -5,9 +6,10 @@ import pytest
|
|
|
5
6
|
|
|
6
7
|
@pytest.fixture(autouse=True, scope="session")
|
|
7
8
|
def add_doctest_context(doctest_namespace): # noqa: PT004
|
|
9
|
+
from statemachine.utils import run_async_from_sync
|
|
10
|
+
|
|
8
11
|
from statemachine import State
|
|
9
12
|
from statemachine import StateMachine
|
|
10
|
-
from statemachine.utils import run_async_from_sync
|
|
11
13
|
|
|
12
14
|
class ContribAsyncio:
|
|
13
15
|
"""
|
|
@@ -31,3 +33,14 @@ def pytest_ignore_collect(collection_path, path, config):
|
|
|
31
33
|
|
|
32
34
|
if "django_project" in str(path):
|
|
33
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.")
|
|
@@ -14,7 +14,7 @@ There are several action callbacks that you can define to interact with a
|
|
|
14
14
|
StateMachine in execution.
|
|
15
15
|
|
|
16
16
|
There are callbacks that you can specify that are generic and will be called
|
|
17
|
-
when something changes and are not
|
|
17
|
+
when something changes, and are not bound to a specific state or event:
|
|
18
18
|
|
|
19
19
|
- `before_transition()`
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ when something changes and are not bounded to a specific state or event:
|
|
|
26
26
|
|
|
27
27
|
- `after_transition()`
|
|
28
28
|
|
|
29
|
-
The following example
|
|
29
|
+
The following example offers an overview of the "generic" callbacks available:
|
|
30
30
|
|
|
31
31
|
```py
|
|
32
32
|
>>> from statemachine import StateMachine, State
|
|
@@ -59,9 +59,23 @@ As this one:
|
|
|
59
59
|

|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
If you find the resolution of the image lacking, you can
|
|
63
|
+
|
|
64
|
+
```py
|
|
65
|
+
>>> dot.set_dpi(300)
|
|
66
|
+
|
|
67
|
+
>>> dot.write_png("docs/images/order_control_machine_initial_300dpi.png")
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
|
|
62
74
|
The current {ref}`state` is also highlighted:
|
|
63
75
|
|
|
64
76
|
``` py
|
|
77
|
+
>>> # This example will only run on automated tests if dot is present
|
|
78
|
+
>>> getfixture("requires_dot_installed")
|
|
65
79
|
|
|
66
80
|
>>> from statemachine.contrib.diagram import DotGraphMachine
|
|
67
81
|
|
|
@@ -22,7 +22,37 @@ A conditional transition occurs only if specific conditions or criteria are met.
|
|
|
22
22
|
|
|
23
23
|
When a transition is conditional, it includes a condition (also known as a _guard_) that must be satisfied for the transition to take place. If the condition is not met, the transition does not occur, and the state machine remains in its current state or follows an alternative path.
|
|
24
24
|
|
|
25
|
-
This feature allows for multiple transitions on the same {ref}`event`, with each {ref}`transition` checked in the order
|
|
25
|
+
This feature allows for multiple transitions on the same {ref}`event`, with each {ref}`transition` checked in **declaration order** — that is, the order in which the transitions themselves were created using `state.to()`. A condition acts like a predicate (a function that evaluates to true/false) and is checked when a {ref}`statemachine` handles an {ref}`event` with a transition from the current state bound to this event. The first transition that meets the conditions (if any) is executed. If none of the transitions meet the conditions, the state machine either raises an exception or does nothing (see the `allow_event_without_transition` parameter of {ref}`StateMachine`).
|
|
26
|
+
|
|
27
|
+
````{important}
|
|
28
|
+
**Evaluation order is based on declaration order, not composition order.**
|
|
29
|
+
|
|
30
|
+
When using conditional transitions, the order of evaluation is determined by **when each transition was created** (the order of `state.to()` calls), **not** by the order they appear when combined with the `|` operator.
|
|
31
|
+
|
|
32
|
+
For example:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# These are evaluated in DECLARATION ORDER (when state.to() was called):
|
|
36
|
+
created_first = state_a.to(state_x) # Created FIRST → Checked FIRST
|
|
37
|
+
created_second = state_a.to(state_y) # Created SECOND → Checked SECOND
|
|
38
|
+
created_third = state_a.to(state_z) # Created THIRD → Checked THIRD
|
|
39
|
+
|
|
40
|
+
# The | operator does NOT change evaluation order:
|
|
41
|
+
my_event = created_third | created_second | created_first
|
|
42
|
+
# Evaluation order is still: created_first → created_second → created_third
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To control the evaluation order, declare transitions in the desired order:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# Declare in the order you want them checked:
|
|
49
|
+
first = state_a.to(state_b, cond="check1") # Checked FIRST
|
|
50
|
+
second = state_a.to(state_c, cond="check2") # Checked SECOND
|
|
51
|
+
third = state_a.to(state_d, cond="check3") # Checked THIRD
|
|
52
|
+
|
|
53
|
+
my_event = first | second | third # Order matches declaration
|
|
54
|
+
```
|
|
55
|
+
````
|
|
26
56
|
|
|
27
57
|
When {ref}`transitions` have guards, it is possible to define two or more transitions for the same {ref}`event` from the same {ref}`state`. When the {ref}`event` occurs, the guarded transitions are checked one by one, and the first transition whose guard is true will be executed, while the others will be ignored.
|
|
28
58
|
|
|
@@ -129,6 +159,79 @@ So, a condition `s1.to(s2, cond=lambda: [])` will evaluate as `False`, as an emp
|
|
|
129
159
|
**falsy** value.
|
|
130
160
|
```
|
|
131
161
|
|
|
162
|
+
### Checking enabled events
|
|
163
|
+
|
|
164
|
+
The {ref}`StateMachine.allowed_events` property returns events reachable from the current state,
|
|
165
|
+
but it does **not** evaluate `cond`/`unless` guards. To check which events actually have their
|
|
166
|
+
conditions satisfied, use {ref}`StateMachine.enabled_events`.
|
|
167
|
+
|
|
168
|
+
```{testsetup}
|
|
169
|
+
|
|
170
|
+
>>> from statemachine import StateMachine, State
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```py
|
|
175
|
+
>>> class ApprovalMachine(StateMachine):
|
|
176
|
+
... pending = State(initial=True)
|
|
177
|
+
... approved = State(final=True)
|
|
178
|
+
... rejected = State(final=True)
|
|
179
|
+
...
|
|
180
|
+
... approve = pending.to(approved, cond="is_manager")
|
|
181
|
+
... reject = pending.to(rejected)
|
|
182
|
+
...
|
|
183
|
+
... is_manager = False
|
|
184
|
+
|
|
185
|
+
>>> sm = ApprovalMachine()
|
|
186
|
+
|
|
187
|
+
>>> [e.id for e in sm.allowed_events]
|
|
188
|
+
['approve', 'reject']
|
|
189
|
+
|
|
190
|
+
>>> [e.id for e in sm.enabled_events()]
|
|
191
|
+
['reject']
|
|
192
|
+
|
|
193
|
+
>>> sm.is_manager = True
|
|
194
|
+
|
|
195
|
+
>>> [e.id for e in sm.enabled_events()]
|
|
196
|
+
['approve', 'reject']
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`enabled_events` is a method (not a property) because conditions may depend on runtime
|
|
201
|
+
arguments. Any `*args`/`**kwargs` passed to `enabled_events()` are forwarded to the
|
|
202
|
+
condition callbacks, just like when triggering an event:
|
|
203
|
+
|
|
204
|
+
```py
|
|
205
|
+
>>> class TaskMachine(StateMachine):
|
|
206
|
+
... idle = State(initial=True)
|
|
207
|
+
... running = State(final=True)
|
|
208
|
+
...
|
|
209
|
+
... start = idle.to(running, cond="has_enough_resources")
|
|
210
|
+
...
|
|
211
|
+
... def has_enough_resources(self, cpu=0):
|
|
212
|
+
... return cpu >= 4
|
|
213
|
+
|
|
214
|
+
>>> sm = TaskMachine()
|
|
215
|
+
|
|
216
|
+
>>> sm.enabled_events()
|
|
217
|
+
[]
|
|
218
|
+
|
|
219
|
+
>>> [e.id for e in sm.enabled_events(cpu=8)]
|
|
220
|
+
['start']
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```{tip}
|
|
225
|
+
This is useful for UI scenarios where you want to show or hide buttons based on whether
|
|
226
|
+
an event's conditions are currently satisfied.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```{note}
|
|
230
|
+
An event is considered **enabled** if at least one of its transitions from the current state
|
|
231
|
+
has all conditions satisfied. If a condition raises an exception, the event is treated as
|
|
232
|
+
enabled (permissive behavior).
|
|
233
|
+
```
|
|
234
|
+
|
|
132
235
|
## Validators
|
|
133
236
|
|
|
134
237
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -69,3 +69,22 @@ class Campaign(models.Model, MachineMixin):
|
|
|
69
69
|
```{seealso}
|
|
70
70
|
Learn more about using the [](mixins.md#machinemixin).
|
|
71
71
|
```
|
|
72
|
+
|
|
73
|
+
### Data migrations
|
|
74
|
+
|
|
75
|
+
Django's `apps.get_model()` returns **historical model** classes that are dynamically created
|
|
76
|
+
and don't carry user-defined class attributes like `state_machine_name`. Since version 2.6.0,
|
|
77
|
+
`MachineMixin` detects these historical models and gracefully skips state machine
|
|
78
|
+
initialization, so data migrations that use `apps.get_model()` work without errors.
|
|
79
|
+
|
|
80
|
+
```{note}
|
|
81
|
+
The state machine instance will **not** be available on historical model objects.
|
|
82
|
+
If your data migration needs to interact with the state machine, set the attributes
|
|
83
|
+
manually on the historical model class:
|
|
84
|
+
|
|
85
|
+
def backfill_data(apps, schema_editor):
|
|
86
|
+
MyModel = apps.get_model("myapp", "MyModel")
|
|
87
|
+
MyModel.state_machine_name = "myapp.statemachines.MyStateMachine"
|
|
88
|
+
for obj in MyModel.objects.all():
|
|
89
|
+
obj.statemachine # now available
|
|
90
|
+
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# StateMachine 2.6.0
|
|
2
|
+
|
|
3
|
+
*February 2026*
|
|
4
|
+
|
|
5
|
+
## What's new in 2.6.0
|
|
6
|
+
|
|
7
|
+
This release adds the {ref}`StateMachine.enabled_events` method, Python 3.14 support,
|
|
8
|
+
a significant performance improvement for callback dispatch, and several bugfixes
|
|
9
|
+
for async condition expressions, type checker compatibility, and Django integration.
|
|
10
|
+
|
|
11
|
+
### Python compatibility in 2.6.0
|
|
12
|
+
|
|
13
|
+
StateMachine 2.6.0 supports Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
14
|
+
|
|
15
|
+
### Checking enabled events
|
|
16
|
+
|
|
17
|
+
A new {ref}`StateMachine.enabled_events` method lets you query which events have their
|
|
18
|
+
`cond`/`unless` guards currently satisfied, going beyond {ref}`StateMachine.allowed_events`
|
|
19
|
+
which only checks reachability from the current state.
|
|
20
|
+
|
|
21
|
+
This is particularly useful for **UI scenarios** where you want to enable or disable buttons
|
|
22
|
+
based on whether an event's conditions are met at runtime.
|
|
23
|
+
|
|
24
|
+
```{testsetup}
|
|
25
|
+
|
|
26
|
+
>>> from statemachine import StateMachine, State
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
>>> class ApprovalMachine(StateMachine):
|
|
32
|
+
... pending = State(initial=True)
|
|
33
|
+
... approved = State(final=True)
|
|
34
|
+
... rejected = State(final=True)
|
|
35
|
+
...
|
|
36
|
+
... approve = pending.to(approved, cond="is_manager")
|
|
37
|
+
... reject = pending.to(rejected)
|
|
38
|
+
...
|
|
39
|
+
... is_manager = False
|
|
40
|
+
|
|
41
|
+
>>> sm = ApprovalMachine()
|
|
42
|
+
|
|
43
|
+
>>> [e.id for e in sm.allowed_events]
|
|
44
|
+
['approve', 'reject']
|
|
45
|
+
|
|
46
|
+
>>> [e.id for e in sm.enabled_events()]
|
|
47
|
+
['reject']
|
|
48
|
+
|
|
49
|
+
>>> sm.is_manager = True
|
|
50
|
+
|
|
51
|
+
>>> [e.id for e in sm.enabled_events()]
|
|
52
|
+
['approve', 'reject']
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Since conditions may depend on runtime arguments, any `*args`/`**kwargs` passed to
|
|
57
|
+
`enabled_events()` are forwarded to the condition callbacks:
|
|
58
|
+
|
|
59
|
+
```py
|
|
60
|
+
>>> class TaskMachine(StateMachine):
|
|
61
|
+
... idle = State(initial=True)
|
|
62
|
+
... running = State(final=True)
|
|
63
|
+
...
|
|
64
|
+
... start = idle.to(running, cond="has_enough_resources")
|
|
65
|
+
...
|
|
66
|
+
... def has_enough_resources(self, cpu=0):
|
|
67
|
+
... return cpu >= 4
|
|
68
|
+
|
|
69
|
+
>>> sm = TaskMachine()
|
|
70
|
+
|
|
71
|
+
>>> sm.enabled_events()
|
|
72
|
+
[]
|
|
73
|
+
|
|
74
|
+
>>> [e.id for e in sm.enabled_events(cpu=8)]
|
|
75
|
+
['start']
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```{seealso}
|
|
80
|
+
See {ref}`Checking enabled events` in the Guards documentation for more details.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Performance: cached signature binding
|
|
84
|
+
|
|
85
|
+
Callback dispatch is now significantly faster thanks to cached signature binding in
|
|
86
|
+
`SignatureAdapter`. The first call to a callback computes the argument binding and
|
|
87
|
+
caches a fast-path template; subsequent calls with the same argument shape skip the
|
|
88
|
+
full binding logic.
|
|
89
|
+
|
|
90
|
+
This results in approximately **60% faster** `bind_expected()` calls and
|
|
91
|
+
around **30% end-to-end improvement** on hot transition paths.
|
|
92
|
+
|
|
93
|
+
See [#548](https://github.com/fgmacedo/python-statemachine/issues/548) for benchmarks.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## Bugfixes in 2.6.0
|
|
97
|
+
|
|
98
|
+
- Fixes [#531](https://github.com/fgmacedo/python-statemachine/issues/531) domain model
|
|
99
|
+
with falsy `__bool__` was being replaced by the default `Model()`.
|
|
100
|
+
- Fixes [#535](https://github.com/fgmacedo/python-statemachine/issues/535) async predicates
|
|
101
|
+
in condition expressions (`not`, `and`, `or`) were not being awaited, causing guards to
|
|
102
|
+
silently return incorrect results.
|
|
103
|
+
- Fixes [#548](https://github.com/fgmacedo/python-statemachine/issues/548)
|
|
104
|
+
`VAR_POSITIONAL` and kwargs precedence bugs in the signature binding cache introduced
|
|
105
|
+
by the performance optimization.
|
|
106
|
+
- Fixes [#511](https://github.com/fgmacedo/python-statemachine/issues/511) Pyright/Pylance
|
|
107
|
+
false positive "Argument missing for parameter f" when calling events. Static analyzers
|
|
108
|
+
could not follow the metaclass transformation from `TransitionList` to `Event`.
|
|
109
|
+
- Fixes [#551](https://github.com/fgmacedo/python-statemachine/issues/551) `MachineMixin`
|
|
110
|
+
now gracefully skips state machine initialization for Django historical models in data
|
|
111
|
+
migrations, instead of raising `ValueError`.
|
|
112
|
+
- Fixes [#526](https://github.com/fgmacedo/python-statemachine/issues/526) sanitize project
|
|
113
|
+
path on Windows for documentation builds.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## Misc in 2.6.0
|
|
117
|
+
|
|
118
|
+
- Added Python 3.14 support [#552](https://github.com/fgmacedo/python-statemachine/pull/552).
|
|
119
|
+
- Upgraded dev dependencies: ruff to 0.15.0, mypy to 1.14.1
|
|
120
|
+
[#552](https://github.com/fgmacedo/python-statemachine/pull/552).
|
|
121
|
+
- Clarified conditional transition evaluation order in documentation
|
|
122
|
+
[#546](https://github.com/fgmacedo/python-statemachine/pull/546).
|
|
123
|
+
- Added pydot DPI resolution settings to diagram documentation
|
|
124
|
+
[#514](https://github.com/fgmacedo/python-statemachine/pull/514).
|
|
125
|
+
- Fixed miscellaneous typos in documentation
|
|
126
|
+
[#522](https://github.com/fgmacedo/python-statemachine/pull/522).
|
|
127
|
+
- Removed Python 3.7 from CI build matrix
|
|
128
|
+
[ef351d5](https://github.com/fgmacedo/python-statemachine/commit/ef351d5).
|
|
@@ -131,6 +131,9 @@ Example:
|
|
|
131
131
|
Usage:
|
|
132
132
|
|
|
133
133
|
```py
|
|
134
|
+
>>> # This example will only run on automated tests if dot is present
|
|
135
|
+
>>> getfixture("requires_dot_installed")
|
|
136
|
+
|
|
134
137
|
>>> sm = TestStateMachine()
|
|
135
138
|
|
|
136
139
|
>>> sm._graph().write_png("docs/images/test_state_machine_internal.png")
|
|
@@ -163,7 +166,7 @@ the event name is used to describe the transition.
|
|
|
163
166
|
## Events
|
|
164
167
|
|
|
165
168
|
An event is an external signal that something has happened.
|
|
166
|
-
They are
|
|
169
|
+
They are sent to a state machine and allow the state machine to react.
|
|
167
170
|
|
|
168
171
|
An event starts a {ref}`transition`, which can be thought of as a "cause" that
|
|
169
172
|
initiates a change in the state of the system.
|
|
@@ -173,7 +176,7 @@ In `python-statemachine`, an event is specified as an attribute of the state mac
|
|
|
173
176
|
|
|
174
177
|
### Declaring events
|
|
175
178
|
|
|
176
|
-
The simplest way to declare an {ref}`event` is by
|
|
179
|
+
The simplest way to declare an {ref}`event` is by assigning a transitions list to a name at the
|
|
177
180
|
State machine class level. The name will be converted to an {ref}`Event`:
|
|
178
181
|
|
|
179
182
|
```py
|
|
@@ -193,7 +196,7 @@ True
|
|
|
193
196
|
```
|
|
194
197
|
|
|
195
198
|
```{versionadded} 2.4.0
|
|
196
|
-
You can also
|
|
199
|
+
You can also explictly declare an {ref}`Event` instance, this helps IDEs to know that the event is callable, and also with translation strings.
|
|
197
200
|
```
|
|
198
201
|
|
|
199
202
|
To declare an explicit event you must also import the {ref}`Event`:
|