Skip to content

Commit 7de1489

Browse files
authored
docs: Moving from rtd_theme to furo (#460)
* docs: Moving from rtd_theme to furo * docs: Improving docs and examples * docs: Upd docs deps to python3.8
1 parent 490b86b commit 7de1489

22 files changed

+570
-311
lines changed

docs/actions.md

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -304,33 +304,54 @@ See {ref}`conditions` and {ref}`validators`.
304304

305305
## Ordering
306306

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

309-
- `validators()` (attached to the transition)
310-
311-
- `conditions()` (attached to the transition)
312-
313-
- `unless()` (attached to the transition)
314-
315-
- `before_transition()`
316-
317-
- `before_<event>()`
318-
319-
- `on_exit_state()`
320-
321-
- `on_exit_<state.id>()`
322-
323-
- `on_transition()`
324-
325-
- `on_<event>()`
326-
327-
- `on_enter_state()`
309+
```{warning}
310+
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`.
311+
```
328312

329-
- `on_enter_<state.id>()`
330313

331-
- `after_<event>()`
314+
```{list-table}
315+
:header-rows: 1
316+
317+
* - Group
318+
- Action
319+
- Current state
320+
- Description
321+
* - Validators
322+
- `validators()`
323+
- `source`
324+
- Validators raise exceptions.
325+
* - Conditions
326+
- `cond()`, `unless()`
327+
- `source`
328+
- Conditions are predicates that prevent transitions to occur.
329+
* - Before
330+
- `before_transition()`, `before_<event>()`
331+
- `source`
332+
- Callbacks declared in the transition or event.
333+
* - Exit
334+
- `on_exit_state()`, `on_exit_<state.id>()`
335+
- `source`
336+
- Callbacks declared in the source state.
337+
* - On
338+
- `on_transition()`, `on_<event>()`
339+
- `source`
340+
- Callbacks declared in the transition or event.
341+
* - **State updated**
342+
-
343+
-
344+
- Current state is updated.
345+
* - Enter
346+
- `on_enter_state()`, `on_enter_<state.id>()`
347+
- `destination`
348+
- Callbacks declared in the destination state.
349+
* - After
350+
- `after_<event>()`, `after_transition()`
351+
- `destination`
352+
- Callbacks declared in the transition or event.
332353
333-
- `after_transition()`
354+
```
334355

335356

336357
## Return values

docs/async.md

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,15 @@ The {ref}`StateMachine` fully supports asynchronous code. You can write async {r
88

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

11-
There are two engines:
11+
There are two engines, {ref}`SyncEngine` and {ref}`AsyncEngine`.
1212

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

16-
AsyncEngine
17-
: 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.
14+
## Sync vs async engines
1815

19-
These engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios:
16+
Engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios.
2017

2118

2219
```{list-table} Sync vs async engines
23-
:widths: 15 10 25 10 10
2420
:header-rows: 1
2521
2622
* - Outer scope
@@ -30,32 +26,40 @@ These engines are internal and are activated automatically by inspecting the reg
3026
- Reuses external loop
3127
* - Sync
3228
- No
33-
- Sync
29+
- SyncEngine
3430
- No
3531
- No
3632
* - Sync
3733
- Yes
38-
- Async
34+
- AsyncEngine
3935
- Yes
4036
- No
4137
* - Async
4238
- No
43-
- Sync
39+
- SyncEngine
4440
- No
4541
- No
4642
* - Async
4743
- Yes
48-
- Async
44+
- AsyncEngine
4945
- No
5046
- Yes
5147
5248
```
5349

54-
5550
```{note}
5651
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.
5752
```
5853

54+
### SyncEngine
55+
Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.
56+
There's no event loop.
57+
58+
### AsyncEngine
59+
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.
60+
61+
62+
5963
## Asynchronous Support
6064

6165
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.
@@ -72,6 +76,7 @@ async code with a state machine.
7276
... initial = State('Initial', initial=True)
7377
... final = State('Final', final=True)
7478
...
79+
... keep = initial.to.itself(internal=True)
7580
... advance = initial.to(final)
7681
...
7782
... async def on_advance(self):
@@ -91,7 +96,10 @@ Final
9196

9297
## Sync codebase with async callbacks
9398

94-
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.
99+
The same state machine with async callbacks can be executed in a synchronous codebase,
100+
even if the calling context don't have an asyncio loop.
101+
102+
If needed, the state machine will create a loop using `asyncio.new_event_loop()` and callbacks will be awaited using `loop.run_until_complete()`.
95103

96104

97105
```py
@@ -109,24 +117,53 @@ Final
109117
## Initial State Activation for Async Code
110118

111119

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

123+
```{hint}
124+
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.
125+
```
126+
115127
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.
116128

129+
You get an error checking the current state before the initial state activation:
130+
131+
```py
132+
>>> async def initialize_sm():
133+
... sm = AsyncStateMachine()
134+
... print(sm.current_state)
135+
136+
>>> asyncio.run(initialize_sm())
137+
Traceback (most recent call last):
138+
...
139+
InvalidStateValue: There's no current state set. In async code, did you activate the initial state? (e.g., `await sm.activate_initial_state()`)
140+
141+
```
142+
143+
You can activate the initial state explicitly:
144+
117145

118146
```py
119147
>>> async def initialize_sm():
120148
... sm = AsyncStateMachine()
121149
... await sm.activate_initial_state()
122-
... return sm
150+
... print(sm.current_state)
123151

124-
>>> sm = asyncio.run(initialize_sm())
125-
>>> print(sm.current_state)
152+
>>> asyncio.run(initialize_sm())
126153
Initial
127154

128155
```
129156

130-
```{hint}
131-
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.
157+
Or just by sending an event. The first event activates the initial state automatically
158+
before the event is handled:
159+
160+
```py
161+
>>> async def initialize_sm():
162+
... sm = AsyncStateMachine()
163+
... await sm.keep() # first event activates the initial state before the event is handled
164+
... print(sm.current_state)
165+
166+
>>> asyncio.run(initialize_sm())
167+
Initial
168+
132169
```

docs/conf.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import os
1515
import sys
1616

17-
import sphinx_rtd_theme
1817
from sphinx_gallery import gen_gallery
1918

2019
# If extensions (or modules to document with autodoc) are in another
@@ -51,6 +50,7 @@
5150
"sphinx.ext.viewcode",
5251
"sphinx.ext.autosectionlabel",
5352
"sphinx_gallery.gen_gallery",
53+
"sphinx_copybutton",
5454
]
5555

5656
# Add any paths that contain templates here, relative to this directory.
@@ -108,7 +108,6 @@
108108
# show_authors = False
109109

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

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

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

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

132-
# Add any paths that contain custom themes here, relative to this directory.
133-
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
134-
135132
# The name for this set of Sphinx documents. If None, it defaults to
136133
# "<project> v<release> documentation".
137134
# html_title = None
@@ -163,6 +160,23 @@
163160
"https://buttons.github.io/buttons.js",
164161
]
165162

163+
html_title = f"python-statemachine {release}"
164+
html_logo = "images/python-statemachine.png"
165+
166+
html_copy_source = False
167+
html_show_sourcelink = False
168+
169+
html_theme_options = {
170+
"navigation_with_keys": True,
171+
"top_of_page_buttons": ["view", "edit"],
172+
"source_repository": "https://github.com/fgmacedo/python-statemachine/",
173+
# "source_branch": "develop",
174+
"source_directory": "docs/",
175+
}
176+
177+
pygments_style = "monokai"
178+
pygments_dark_style = "monokai"
179+
166180
# If not '', a 'Last updated on:' timestamp is inserted at every page
167181
# bottom, using the given strftime format.
168182
# html_last_updated_fmt = '%b %d, %Y'
@@ -274,6 +288,9 @@
274288
}
275289

276290

291+
copybutton_exclude = ".linenos, .gp, .go"
292+
293+
277294
def dummy_write_computation_times(gallery_conf, target_dir, costs):
278295
"patch gen_gallery to disable write_computation_times"
279296
pass

docs/installation.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
# Installation
22

33

4-
## Stable release
4+
## Latest release
55

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

8-
poetry add python-statemachine
8+
```shell
9+
poetry add python-statemachine
10+
```
911

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

11-
Or to install using [pip](https://pip.pypa.io):
14+
```shell
15+
python3 -m pip install python-statemachine
16+
```
1217

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

22+
```shell
23+
python3 -m pip install "python-statemachine[diagrams]"
24+
```
1525

16-
To generate diagrams from your machines, you'll also need `pydot` and `Graphviz`. You can
17-
install this library already with `pydot` dependency using the `extras` install option. See
18-
our docs for more details.
19-
20-
pip install python-statemachine[diagrams]
21-
22-
23-
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
24-
you through the process.
2526

2627

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

3132
You can either clone the public repository:
3233

33-
git clone git://github.com/fgmacedo/python-statemachine
34+
```shell
35+
git clone git://github.com/fgmacedo/python-statemachine
36+
```
3437

3538
Or download the `tarball`:
3639

37-
curl -OL https://github.com/fgmacedo/python-statemachine/tarball/master
40+
```shell
41+
curl -OL https://github.com/fgmacedo/python-statemachine/tarball/main
42+
```
3843

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

41-
python setup.py install
46+
```shell
47+
python3 -m pip install -e .
48+
```

0 commit comments

Comments
 (0)