Skip to content

Commit

Permalink
Merge tag '1.0.0' into develop
Browse files Browse the repository at this point in the history
*January 11, 2022*

Welcome to StateMachine 1.0!

This version is a huge refactoring adding a lot of new and exiting
features. We hope that
you enjoy.

These release notes cover the [](#whats-new-in-10), as well as
some [backwards incompatible
changes](#backwards-incompatible-changes-in-10) you'll
want to be aware of when upgrading from StateMachine 0.9.0 or earlier.
We've
[begun the deprecation process for some
features](#deprecated-features-in-10).

StateMachine 1.0 supports Python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, and
3.11.

This is the last release to support Python 2.7, 3.5 and 3.6.

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")

        validate = requested.to(accepted, cond="is_ok") |
requested.to(rejected)
```

You can generate diagrams from your statemachine.

Example:

![OrderControl](../images/order_control_machine_initial.png)

Every single callback, being `actions` or `guards`, is now handled
equally by the library.

Also, we've improved the internals in a way that you can implement your
callbacks with any
number of arbritrary positional or keyword arguments (`*args,
**kwargs`), and the dispatch will
match the available arguments with your method signature.

This means that if on your `on_enter_<state>()` or
`on_execute_<event>()` method, you also
need to know the `source` (`state`), or the `event` (`event`), or access
a keyword
argument passed with the trigger, you're covered. Just add this
parameter to the method and It
 will be passed by the dispatch mechanics.

Example of what's available:

```py
def action_or_guard_method_name(self, *args, event_data, event, source,
state, model, **kwargs):
    pass
```

Observers are a way do generically add behaviour to a StateMachine
without
changing it's internal implementation.

The `StateMachine` itself is registered as an observer, so by using
`StateMachine.add_observer()`
an external object can have the same level of functionalities provided
to the built-in class.

- Fixed mypy complaining about incorrect type for ``StateMachine``
class.
- The initial `state` is now entered when the machine starts. The
`actions`, if defined,
  `on_enter_state` and `on_enter_<state>` are now called.

Prior to this release, as we didn't have `validators-and-guards`, there
wasn't an elegant way
to declare multiples target states starting from the same pair (event,
state). But the library
allowed a near-hackish way, by declaring a target state as the result of
the `on_<event>` callback.

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')

    validate = requested.to(accepted, rejected)

    def on_validate(self, current_time):
        if self.model.is_ok():
            self.model.accepted_at = current_time
            return self.accepted
        else:
            return self.rejected
```

Should be rewriten to use `guards`, like this:

``` py
class ApprovalMachine(StateMachine):
    "A workflow machine"
    requested = State("Requested", initial=True)
    accepted = State("Accepted")
    rejected = State("Rejected")

    validate = requested.to(accepted, conditions="is_ok") |
requested.to(rejected)

    def on_validate(self, current_time):
        self.model.accepted_at = current_time
```

This issue was reported at
[#265](#265).

Now StateMachine will execute the actions associated with the
`on_enter_state` and
`on_enter_<state>` when initialized, if they exists.

Statemachine integrity checks are now performed at class declaration
(import time) instead of on
instance creation. This allows early feedback of invalid definitions.

This was the previous behaviour, you only got an error when trying to
instantiate a StateMachine:

```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

    add_job = draft.to(draft) | producing.to(producing)
    produce = draft.to(producing)
    deliver = producing.to(closed)

with pytest.raises(exceptions.InvalidDefinition):
    CampaignMachine()
```

Not this is performed as the class definition is performed:

```py
with pytest.raises(exceptions.InvalidDefinition):

    class CampaignMachine(StateMachine):
        "A workflow machine"
        draft = State("Draft", initial=True)
        producing = State("Being produced")
        closed = State(
            "Closed", initial=True
        )  # Should raise an Exception right after the class is defined

        add_job = draft.to(draft) | producing.to(producing)
        produce = draft.to(producing)
        deliver = producing.to(closed)
```

- Due to the check validations and setup performed at the machine
initialization, it's now harder
  to perform monkey-patching to add callbacks at runtime (not a bad
thing after all).
- `TransitionNotAllowed` changed internal attr from `transition` to
`event`.
- `CombinedTransition` does not exist anymore. `State` now holds a flat
`Transition` list
  called `TransitionList` that implements de `OR` operator. This turns a
valid StateMachine
  traversal much easier: `[transition for state in machine.states for
transition in state.transitions]`.
- `StateMachine.get_transition` is removed. See `event`.
- The previous excetions `MultipleStatesFound` and
`MultipleTransitionCallbacksFound` are removed.
  Since now you can have more than one callback defined to the same
transition.
- `on_enter_state` and `on_exit_state` now accepts any combination of
parameters following the
  `dynamic-dispatch` rules. Previously it only accepted the `state`
param.
- `Transition.__init__` param `on_execute` renamed to simply `on`, and
now follows the
`dynamic-dispatch`.
- `Transition.destinations` removed in favor of `Transition.target`
(following SCXML convention).
Now each transition only points to a unique target. Each
`source->target` pair is holded by a
single `Transition`.

- `StateMachine.run` is deprecated in favor of `StateMachine.send`.
- `StateMachine.allowed_transitions` is deprecated in favor of
`StateMachine.allowed_events`.
- `Statemachine.is_<state>` is deprecated in favor of
`StateMachine.<state>.is_active`.

- `State.identification` is deprecated in favor of `State.id`.
  • Loading branch information
fgmacedo committed Jan 12, 2023
2 parents 33b3ee7 + 90f4629 commit 03a7baf
Show file tree
Hide file tree
Showing 15 changed files with 20 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ repos:
hooks:
- id: check-yaml
- id: end-of-file-fixer
exclude: docs/auto_examples
- id: trailing-whitespace
exclude: docs/auto_examples
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
Expand Down
2 changes: 1 addition & 1 deletion docs/auto_examples/all_actions_machine.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@
},
"nbformat": 4,
"nbformat_minor": 0
}
}
2 changes: 1 addition & 1 deletion docs/auto_examples/all_actions_machine.py.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ac78776e5717c75cacc8861a2f267168
ac78776e5717c75cacc8861a2f267168
2 changes: 1 addition & 1 deletion docs/auto_examples/guess_the_number_machine.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,4 @@
},
"nbformat": 4,
"nbformat_minor": 0
}
}
2 changes: 1 addition & 1 deletion docs/auto_examples/guess_the_number_machine.py.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
410f2a4ba51943452eddefeb5dd03d41
410f2a4ba51943452eddefeb5dd03d41
2 changes: 1 addition & 1 deletion docs/auto_examples/order_control_machine.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@
},
"nbformat": 4,
"nbformat_minor": 0
}
}
2 changes: 1 addition & 1 deletion docs/auto_examples/order_control_machine.py.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
d23c7b284996484ff0c9d8f60c6952a5
d23c7b284996484ff0c9d8f60c6952a5
2 changes: 1 addition & 1 deletion docs/auto_examples/order_control_rich_model_machine.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,4 @@
},
"nbformat": 4,
"nbformat_minor": 0
}
}
2 changes: 1 addition & 1 deletion docs/auto_examples/order_control_rich_model_machine.py.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8400d8e21a12b8d53b600c5df6c73084
8400d8e21a12b8d53b600c5df6c73084
2 changes: 1 addition & 1 deletion docs/auto_examples/traffic_light_machine.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@
},
"nbformat": 4,
"nbformat_minor": 0
}
}
2 changes: 1 addition & 1 deletion docs/auto_examples/traffic_light_machine.py.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
50d4c801c9feb929855800b6ea32ffcb
50d4c801c9feb929855800b6ea32ffcb
2 changes: 1 addition & 1 deletion docs/releases/1.0.0.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# StateMachine 1.0.0

*January 6, 2022*
*January 11, 2022*

Welcome to StateMachine 1.0!

Expand Down
10 changes: 5 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[bumpversion]
current_version = 0.9.0
current_version = 1.0.0
commit = True
tag = True

[bumpversion:file:setup.py]
search = version='{current_version}'
replace = version='{new_version}'
search = version="{current_version}"
replace = version="{new_version}"

[bumpversion:file:statemachine/__init__.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

[bdist_wheel]
universal = 1
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

setup(
name="python-statemachine",
version="0.9.0",
version="1.0.0",
description="Python Finite State Machines made easy.",
long_description=long_description,
author="Fernando Macedo",
Expand Down
2 changes: 1 addition & 1 deletion statemachine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@

__author__ = """Fernando Macedo"""
__email__ = "[email protected]"
__version__ = "0.9.0"
__version__ = "1.0.0"

__all__ = ["StateMachine", "State"]

0 comments on commit 03a7baf

Please sign in to comment.