From bc6e4bf07139ccd4fd109d6ce8fb2be7464f1a7d Mon Sep 17 00:00:00 2001 From: Fernando Macedo Date: Sun, 13 Sep 2026 18:32:02 -0300 Subject: [PATCH 1/2] chore(deps): upgrade all direct dependencies Consolidates the open dependabot PRs (sqlparse 0.6.0, pillow 12.3.0, soupsieve 2.9.2) into a single refresh of the whole lock file, and raises the dev dependency floors that had fallen behind their locked versions. Notable upgrades: mypy 1.20 -> 2.3.1, ruff 0.15.17 -> 0.16.7, Django 6.0.3 -> 6.1.1 / 5.2.14 -> 5.2.17, pytest-django 4.12 -> 4.14, pytest-benchmark 5.2.3 -> 5.3.0, myst-parser 5.0 -> 5.1. Two fallouts required code changes: - mypy 2.x no longer infers the type of an empty dict literal at module scope, so `registry._REGISTRY` now carries an explicit annotation. - ruff 0.16 formats Python code blocks inside Markdown, which reformats fourteen files under `docs/`. Doctest blocks are untouched. Runtime dependency floors (pydot, PyYAML, jsonschema) are unchanged: they are the minimum versions supported for library consumers, not the versions under test. Sphinx, myst-parser and the remaining docs tooling stay unconstrained because their latest releases require Python 3.12+, while the project supports 3.10+; the resolver picks the right version per interpreter. Signed-off-by: Fernando Macedo --- .pre-commit-config.yaml | 2 +- docs/diagram.md | 2 + docs/guards.md | 2 +- docs/how-to/coming_from_state_pattern.md | 9 +- docs/how-to/coming_from_transitions.md | 29 +- docs/integrations.md | 15 +- docs/releases/1.0.1.md | 32 +- docs/releases/2.0.0.md | 4 - docs/releases/2.2.0.md | 11 +- docs/releases/2.3.0.md | 5 +- docs/releases/2.4.0.md | 2 + docs/releases/2.5.0.md | 5 +- docs/releases/3.1.0.md | 6 +- docs/releases/3.2.0.md | 4 +- docs/releases/upgrade_2x_to_3.md | 42 +- pyproject.toml | 22 +- statemachine/registry.py | 7 +- uv.lock | 2186 +++++++++++++--------- 18 files changed, 1406 insertions(+), 979 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b2119bf..9f96d446 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: exclude: docs/auto_examples - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.17 + rev: v0.16.7 hooks: # Run the linter. - id: ruff diff --git a/docs/diagram.md b/docs/diagram.md index d48443d3..a6512a75 100644 --- a/docs/diagram.md +++ b/docs/diagram.md @@ -214,6 +214,7 @@ Sphinx directive): ```python from statemachine.contrib.diagram import formatter + @formatter.register_format("plantuml", "puml") def _render_plantuml(machine_or_class): # your PlantUML renderer here @@ -525,6 +526,7 @@ class CustomDiagram(DotGraphMachine): graph_rankdir = "TB" state_active_fillcolor = "lightyellow" + sm = OrderControl() sm.receive_payment(10) diff --git a/docs/guards.md b/docs/guards.md index 8ce5a0ec..35455604 100644 --- a/docs/guards.md +++ b/docs/guards.md @@ -254,7 +254,7 @@ listeners. They can point to properties, attributes, or methods: **Parentheses** control evaluation order: ```python -cond="(is_admin or is_moderator) and not is_banned" +cond = "(is_admin or is_moderator) and not is_banned" ``` #### Expression examples diff --git a/docs/how-to/coming_from_state_pattern.md b/docs/how-to/coming_from_state_pattern.md index eccc6688..e04296a7 100644 --- a/docs/how-to/coming_from_state_pattern.md +++ b/docs/how-to/coming_from_state_pattern.md @@ -28,16 +28,13 @@ class OrderState(ABC): """Abstract base for all order states.""" @abstractmethod - def confirm(self, order): - ... + def confirm(self, order): ... @abstractmethod - def ship(self, order): - ... + def ship(self, order): ... @abstractmethod - def deliver(self, order): - ... + def deliver(self, order): ... class DraftState(OrderState): diff --git a/docs/how-to/coming_from_transitions.md b/docs/how-to/coming_from_transitions.md index 30e8f1e2..791c4765 100644 --- a/docs/how-to/coming_from_transitions.md +++ b/docs/how-to/coming_from_transitions.md @@ -233,7 +233,7 @@ that reads SCXML, JSON and YAML *documents* straight into a `StateChart`: ```python from statemachine.io import load -Machine = load("traffic_light.scxml") # or .json / .yaml; format detected from the extension +Machine = load("traffic_light.scxml") # or .json / .yaml; format detected from the extension ``` Expressions in the document (guards, datamodel) are evaluated by a restricted allowlist — @@ -306,8 +306,8 @@ True In *transitions*, events are called as methods on the model: ```python -machine.produce() # triggers the "produce" event -machine.deliver() # triggers the "deliver" event +machine.produce() # triggers the "produce" event +machine.deliver() # triggers the "deliver" event ``` python-statemachine supports both styles: @@ -342,13 +342,15 @@ Callbacks are specified as strings (method names) or callables: ```python machine = Machine( states=states, - transitions=[{ - "trigger": "produce", - "source": "draft", - "dest": "producing", - "before": "validate_job", - "after": "notify_team", - }], + transitions=[ + { + "trigger": "produce", + "source": "draft", + "dest": "producing", + "before": "validate_job", + "after": "notify_team", + } + ], initial="draft", ) ``` @@ -454,7 +456,9 @@ In *transitions*: ```python machine.add_transition( - "produce", "draft", "producing", + "produce", + "draft", + "producing", conditions=["is_valid", "has_resources"], unless=["is_locked"], ) @@ -588,10 +592,12 @@ See {ref}`invoke` for full documentation. ```python from transitions.extensions import AsyncMachine + class AsyncModel: async def on_enter_producing(self): await some_async_operation() + machine = AsyncMachine(model=AsyncModel(), states=states, initial="draft") await machine.produce() ``` @@ -752,6 +758,7 @@ No class swapping, no feature matrices to consult — just `StateChart`. class MyModel: pass + model = MyModel() machine = Machine(model=model, states=states, transitions=transitions, initial="draft") model.produce() # events are added to the model diff --git a/docs/integrations.md b/docs/integrations.md index fd362cee..ed1ffa38 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -120,10 +120,11 @@ from statemachine import State class CampaignMachine(StateChart): "A workflow machine" - draft = State('Draft', initial=True, value=1) - producing = State('Being produced', value=2) - closed = State('Closed', value=3) - cancelled = State('Cancelled', value=4) + + draft = State("Draft", initial=True, value=1) + producing = State("Being produced", value=2) + closed = State("Closed", value=3) + cancelled = State("Cancelled", value=4) add_job = draft.to.itself() | producing.to.itself() produce = draft.to(producing) @@ -142,9 +143,9 @@ from statemachine.mixins import MachineMixin class Campaign(models.Model, MachineMixin): - state_machine_name = 'campaign.statemachines.CampaignMachine' - state_machine_attr = 'sm' - state_field_name = 'step' + state_machine_name = "campaign.statemachines.CampaignMachine" + state_machine_attr = "sm" + state_field_name = "step" name = models.CharField(max_length=30) step = models.IntegerField() diff --git a/docs/releases/1.0.1.md b/docs/releases/1.0.1.md index d32b6fcb..e497bf64 100644 --- a/docs/releases/1.0.1.md +++ b/docs/releases/1.0.1.md @@ -27,14 +27,15 @@ Transitions now support `cond` and `unless` parameters, to restrict the execution. ```python - class ApprovalMachine(StateMachine): - "A workflow machine" - requested = State("Requested", initial=True) - accepted = State("Accepted") - rejected = State("Rejected") - completed = State("Completed") +class ApprovalMachine(StateMachine): + "A workflow machine" + + requested = State("Requested", initial=True) + accepted = State("Accepted") + rejected = State("Rejected") + completed = State("Completed") - validate = requested.to(accepted, cond="is_ok") | requested.to(rejected) + validate = requested.to(accepted, cond="is_ok") | requested.to(rejected) ``` ```{seealso} @@ -114,9 +115,10 @@ So, the previous code (not valid anymore): ```py class ApprovalMachine(StateMachine): "A workflow machine" - requested = State('Requested', initial=True) - accepted = State('Accepted') - rejected = State('Rejected') + + requested = State("Requested", initial=True) + accepted = State("Accepted") + rejected = State("Rejected") validate = requested.to(accepted, rejected) @@ -133,6 +135,7 @@ Should be rewritten to use {ref}`guards`, like this: ``` py class ApprovalMachine(StateMachine): "A workflow machine" + requested = State("Requested", initial=True) accepted = State("Accepted") rejected = State("Rejected") @@ -168,14 +171,16 @@ This was the previous behavior, you only got an error when trying to instantiate ```py class CampaignMachine(StateMachine): "A workflow machine" - draft = State('Draft', initial=True) - producing = State('Being produced') - closed = State('Closed', initial=True) # Should raise an Exception when instantiated + + draft = State("Draft", initial=True) + producing = State("Being produced") + closed = State("Closed", initial=True) # Should raise an Exception when instantiated add_job = draft.to(draft) | producing.to(producing) produce = draft.to(producing) deliver = producing.to(closed) + with pytest.raises(exceptions.InvalidDefinition): CampaignMachine() ``` @@ -187,6 +192,7 @@ with pytest.raises(exceptions.InvalidDefinition): class CampaignMachine(StateMachine): "A workflow machine" + draft = State("Draft", initial=True) producing = State("Being produced") closed = State( diff --git a/docs/releases/2.0.0.md b/docs/releases/2.0.0.md index fbc433f1..0d2c48e3 100644 --- a/docs/releases/2.0.0.md +++ b/docs/releases/2.0.0.md @@ -291,7 +291,6 @@ from tests.examples.traffic_light_machine import TrafficLightMachine sm = TrafficLightMachine() sm.run("cycle") - ``` Should become: @@ -313,7 +312,6 @@ from tests.examples.traffic_light_machine import TrafficLightMachine sm = TrafficLightMachine() assert [t.name for t in sm.allowed_transitions] == ["cycle"] - ``` Should become: @@ -333,7 +331,6 @@ from tests.examples.traffic_light_machine import TrafficLightMachine sm = TrafficLightMachine() assert sm.is_green - ``` Should become: @@ -355,7 +352,6 @@ from tests.examples.traffic_light_machine import TrafficLightMachine sm = TrafficLightMachine() assert sm.current_state.identification == "green" - ``` Should become: diff --git a/docs/releases/2.2.0.md b/docs/releases/2.2.0.md index 9ac3e83a..84f22105 100644 --- a/docs/releases/2.2.0.md +++ b/docs/releases/2.2.0.md @@ -42,16 +42,19 @@ This will currently issue a warning, but can be turned into an exception by sett ```python from statemachine import StateMachine, State + class TrafficLightMachine(StateMachine, strict_states=True): "A workflow machine" - red = State('Red', initial=True, value=1) - green = State('Green', value=2) - orange = State('Orange', value=3) - hazard = State('Hazard', value=4) + + red = State("Red", initial=True, value=1) + green = State("Green", value=2) + orange = State("Orange", value=3) + hazard = State("Hazard", value=4) cycle = red.to(green) | green.to(orange) | orange.to(red) fault = red.to(hazard) | green.to(hazard) | orange.to(hazard) + # InvalidDefinition: All non-final states should have at least one outgoing transition. # These states have no outgoing transition: ['hazard'] ``` diff --git a/docs/releases/2.3.0.md b/docs/releases/2.3.0.md index 83bb0901..08ff10c9 100644 --- a/docs/releases/2.3.0.md +++ b/docs/releases/2.3.0.md @@ -28,8 +28,8 @@ async code with a state machine. ```python class AsyncStateMachine(StateMachine): - initial = State('Initial', initial=True) - final = State('Final', final=True) + initial = State("Initial", initial=True) + final = State("Final", final=True) advance = initial.to(final) @@ -42,6 +42,7 @@ async def run_sm(): res = await sm.advance() return (42, sm.current_state.name) + asyncio.run(run_sm()) # (42, 'Final') ``` diff --git a/docs/releases/2.4.0.md b/docs/releases/2.4.0.md index 8054af36..2979cc1d 100644 --- a/docs/releases/2.4.0.md +++ b/docs/releases/2.4.0.md @@ -19,6 +19,7 @@ Example (with a spoiler of the next highlight): ```python from statemachine import StateMachine, State, Event + class AnyConditionSM(StateMachine): start = State(initial=True) end = State(final=True) @@ -31,6 +32,7 @@ class AnyConditionSM(StateMachine): used_money: bool = False used_credit: bool = False + sm = AnyConditionSM() sm.submit() # TransitionNotAllowed: Can't finish order when in Start. diff --git a/docs/releases/2.5.0.md b/docs/releases/2.5.0.md index 8cfe5840..babede62 100644 --- a/docs/releases/2.5.0.md +++ b/docs/releases/2.5.0.md @@ -66,6 +66,7 @@ listing the current state allowed events and executing the simulated user choice ```python import random + random.seed("15") sm = AccountStateMachine() @@ -76,7 +77,7 @@ while not sm.current_state.final: for idx, event in enumerate(allowed_events): print(f"{idx} - {event.name}") - user_input = random.randint(0, len(allowed_events)-1) + user_input = random.randint(0, len(allowed_events) - 1) print(f"User input: {user_input}") event = allowed_events[user_input] @@ -104,6 +105,7 @@ Example: ```python from statemachine import StateMachine, State, Event + class AnyConditionSM(StateMachine): start = State(initial=True) end = State(final=True) @@ -115,6 +117,7 @@ class AnyConditionSM(StateMachine): order_value: float = 0 + sm = AnyConditionSM() sm.submit() # TransitionNotAllowed: Can't finish order when in Start. diff --git a/docs/releases/3.1.0.md b/docs/releases/3.1.0.md index 7e518612..8557268c 100644 --- a/docs/releases/3.1.0.md +++ b/docs/releases/3.1.0.md @@ -41,9 +41,9 @@ from statemachine.contrib.diagram import formatter formatter.render(sm, "mermaid") formatter.supported_formats() + @formatter.register_format("custom") -def _render_custom(machine_or_class): - ... +def _render_custom(machine_or_class): ... ``` See {ref}`formatter-api` for details. @@ -77,6 +77,7 @@ class TrafficLight(StateChart): {statechart:md} """ + green = State(initial=True) yellow = State() red = State() @@ -163,6 +164,7 @@ async def fetch_data(): resp = await session.get("https://api.example.com/data") return await resp.json() + class Loader(StateChart): loading = State(initial=True, invoke=fetch_data) ready = State(final=True) diff --git a/docs/releases/3.2.0.md b/docs/releases/3.2.0.md index 85f72e33..9b3cd135 100644 --- a/docs/releases/3.2.0.md +++ b/docs/releases/3.2.0.md @@ -68,7 +68,7 @@ conformance suite), opt back into full Python with `trusted=True`: ```python from statemachine.io.scxml.processor import SCXMLProcessor -SCXMLProcessor() # safe default: restricted evaluator,