Skip to content

Commit

Permalink
docs: Moving from rtd_theme to furo (#460)
Browse files Browse the repository at this point in the history
* docs: Moving from rtd_theme to furo
* docs: Improving docs and examples
* docs: Upd docs deps to python3.8
  • Loading branch information
fgmacedo authored Jul 6, 2024
1 parent 490b86b commit 7de1489
Show file tree
Hide file tree
Showing 22 changed files with 570 additions and 311 deletions.
67 changes: 44 additions & 23 deletions docs/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,33 +304,54 @@ See {ref}`conditions` and {ref}`validators`.

## Ordering

Actions and Guards will be executed in the following order:
There are major groups of callbacks, these groups run sequentially.

- `validators()` (attached to the transition)

- `conditions()` (attached to the transition)

- `unless()` (attached to the transition)

- `before_transition()`

- `before_<event>()`

- `on_exit_state()`

- `on_exit_<state.id>()`

- `on_transition()`

- `on_<event>()`

- `on_enter_state()`
```{warning}
Actions registered on the same group don't have order guaranties and are executed in parallel when using the {ref}`AsyncEngine`, and may be executed in parallel in future versions of {ref}`SyncEngine`.
```

- `on_enter_<state.id>()`

- `after_<event>()`
```{list-table}
:header-rows: 1
* - Group
- Action
- Current state
- Description
* - Validators
- `validators()`
- `source`
- Validators raise exceptions.
* - Conditions
- `cond()`, `unless()`
- `source`
- Conditions are predicates that prevent transitions to occur.
* - Before
- `before_transition()`, `before_<event>()`
- `source`
- Callbacks declared in the transition or event.
* - Exit
- `on_exit_state()`, `on_exit_<state.id>()`
- `source`
- Callbacks declared in the source state.
* - On
- `on_transition()`, `on_<event>()`
- `source`
- Callbacks declared in the transition or event.
* - **State updated**
-
-
- Current state is updated.
* - Enter
- `on_enter_state()`, `on_enter_<state.id>()`
- `destination`
- Callbacks declared in the destination state.
* - After
- `after_<event>()`, `after_transition()`
- `destination`
- Callbacks declared in the transition or event.
- `after_transition()`
```


## Return values
Expand Down
75 changes: 56 additions & 19 deletions docs/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,15 @@ The {ref}`StateMachine` fully supports asynchronous code. You can write async {r

This is achieved through a new concept called "engine," an internal strategy pattern abstraction that manages transitions and callbacks.

There are two engines:
There are two engines, {ref}`SyncEngine` and {ref}`AsyncEngine`.

SyncEngine
: Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.

AsyncEngine
: Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.
## Sync vs async engines

These engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios:
Engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios.


```{list-table} Sync vs async engines
:widths: 15 10 25 10 10
:header-rows: 1
* - Outer scope
Expand All @@ -30,32 +26,40 @@ These engines are internal and are activated automatically by inspecting the reg
- Reuses external loop
* - Sync
- No
- Sync
- SyncEngine
- No
- No
* - Sync
- Yes
- Async
- AsyncEngine
- Yes
- No
* - Async
- No
- Sync
- SyncEngine
- No
- No
* - Async
- Yes
- Async
- AsyncEngine
- No
- Yes
```


```{note}
All handlers will run on the same thread they are called. Therefore, mixing synchronous and asynchronous code is not recommended unless you are confident in your implementation.
```

### SyncEngine
Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.
There's no event loop.

### AsyncEngine
Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.



## Asynchronous Support

We support native coroutine callbacks using asyncio, enabling seamless integration with asynchronous code. There is no change in the public API of the library to work with asynchronous codebases.
Expand All @@ -72,6 +76,7 @@ async code with a state machine.
... initial = State('Initial', initial=True)
... final = State('Final', final=True)
...
... keep = initial.to.itself(internal=True)
... advance = initial.to(final)
...
... async def on_advance(self):
Expand All @@ -91,7 +96,10 @@ Final

## Sync codebase with async callbacks

The same state machine can be executed in a synchronous codebase, even if it contains async callbacks. The callbacks will be awaited using `asyncio.get_event_loop()` if needed.
The same state machine with async callbacks can be executed in a synchronous codebase,
even if the calling context don't have an asyncio loop.

If needed, the state machine will create a loop using `asyncio.new_event_loop()` and callbacks will be awaited using `loop.run_until_complete()`.


```py
Expand All @@ -109,24 +117,53 @@ Final
## Initial State Activation for Async Code


If you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then on async code you must manually
If **on async code** you perform checks against the `current_state`, like a loop `while sm.current_state.is_final:`, then you must manually
await for the [activate initial state](statemachine.StateMachine.activate_initial_state) to be able to check the current state.

```{hint}
This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited.
```

If you don't do any check for current state externally, just ignore this as the initial state is activated automatically before the first event trigger is handled.

You get an error checking the current state before the initial state activation:

```py
>>> async def initialize_sm():
... sm = AsyncStateMachine()
... print(sm.current_state)

>>> asyncio.run(initialize_sm())
Traceback (most recent call last):
...
InvalidStateValue: There's no current state set. In async code, did you activate the initial state? (e.g., `await sm.activate_initial_state()`)

```

You can activate the initial state explicitly:


```py
>>> async def initialize_sm():
... sm = AsyncStateMachine()
... await sm.activate_initial_state()
... return sm
... print(sm.current_state)

>>> sm = asyncio.run(initialize_sm())
>>> print(sm.current_state)
>>> asyncio.run(initialize_sm())
Initial

```

```{hint}
This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited.
Or just by sending an event. The first event activates the initial state automatically
before the event is handled:

```py
>>> async def initialize_sm():
... sm = AsyncStateMachine()
... await sm.keep() # first event activates the initial state before the event is handled
... print(sm.current_state)

>>> asyncio.run(initialize_sm())
Initial

```
29 changes: 23 additions & 6 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import os
import sys

import sphinx_rtd_theme
from sphinx_gallery import gen_gallery

# If extensions (or modules to document with autodoc) are in another
Expand Down Expand Up @@ -51,6 +50,7 @@
"sphinx.ext.viewcode",
"sphinx.ext.autosectionlabel",
"sphinx_gallery.gen_gallery",
"sphinx_copybutton",
]

# Add any paths that contain templates here, relative to this directory.
Expand Down Expand Up @@ -108,7 +108,6 @@
# show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
Expand All @@ -122,16 +121,14 @@

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "sphinx_rtd_theme"
html_theme = "furo"
# https://pradyunsg.me/furo/

# Theme options are theme-specific and customize the look and feel of a
# theme further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
Expand Down Expand Up @@ -163,6 +160,23 @@
"https://buttons.github.io/buttons.js",
]

html_title = f"python-statemachine {release}"
html_logo = "images/python-statemachine.png"

html_copy_source = False
html_show_sourcelink = False

html_theme_options = {
"navigation_with_keys": True,
"top_of_page_buttons": ["view", "edit"],
"source_repository": "https://github.com/fgmacedo/python-statemachine/",
# "source_branch": "develop",
"source_directory": "docs/",
}

pygments_style = "monokai"
pygments_dark_style = "monokai"

# If not '', a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
Expand Down Expand Up @@ -274,6 +288,9 @@
}


copybutton_exclude = ".linenos, .gp, .go"


def dummy_write_computation_times(gallery_conf, target_dir, costs):
"patch gen_gallery to disable write_computation_times"
pass
Expand Down
41 changes: 24 additions & 17 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# Installation


## Stable release
## Latest release

To install Python State Machine, if you're using [poetry](https://python-poetry.org/):
To install Python State Machine using [poetry](https://python-poetry.org/):

poetry add python-statemachine
```shell
poetry add python-statemachine
```

Alternatively, if you prefer using [pip](https://pip.pypa.io):

Or to install using [pip](https://pip.pypa.io):
```shell
python3 -m pip install python-statemachine
```

pip install python-statemachine
For those looking to generate diagrams from your state machines, [pydot](https://github.com/pydot/pydot) and [Graphviz](https://graphviz.org/) are required.
Conveniently, you can install python-statemachine along with the `pydot` dependency using the extras option.
For more information, please refer to our documentation.

```shell
python3 -m pip install "python-statemachine[diagrams]"
```

To generate diagrams from your machines, you'll also need `pydot` and `Graphviz`. You can
install this library already with `pydot` dependency using the `extras` install option. See
our docs for more details.

pip install python-statemachine[diagrams]


If you don't have [pip](https://pip.pypa.io) installed, this [Python installation guide](http://docs.python-guide.org/en/latest/starting/installation/) can guide
you through the process.


## From sources
Expand All @@ -30,12 +31,18 @@ The sources for Python State Machine can be downloaded from the [Github repo](ht

You can either clone the public repository:

git clone git://github.com/fgmacedo/python-statemachine
```shell
git clone git://github.com/fgmacedo/python-statemachine
```

Or download the `tarball`:

curl -OL https://github.com/fgmacedo/python-statemachine/tarball/master
```shell
curl -OL https://github.com/fgmacedo/python-statemachine/tarball/main
```

Once you have a copy of the source, you can install it with:

python setup.py install
```shell
python3 -m pip install -e .
```
Loading

0 comments on commit 7de1489

Please sign in to comment.