python-statemachine 2.3.1__py3-none-any.whl → 2.3.3__py3-none-any.whl
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.3.1.dist-info → python_statemachine-2.3.3.dist-info}/METADATA +3 -23
- python_statemachine-2.3.3.dist-info/RECORD +31 -0
- statemachine/__init__.py +1 -1
- statemachine/callbacks.py +182 -111
- statemachine/contrib/diagram.py +7 -4
- statemachine/dispatcher.py +127 -135
- statemachine/engines/__init__.py +0 -0
- statemachine/engines/async_.py +136 -0
- statemachine/engines/sync.py +139 -0
- statemachine/event.py +9 -28
- statemachine/event_data.py +2 -4
- statemachine/events.py +2 -2
- statemachine/factory.py +10 -11
- statemachine/mixins.py +10 -4
- statemachine/registry.py +5 -8
- statemachine/signature.py +6 -5
- statemachine/state.py +13 -17
- statemachine/statemachine.py +111 -170
- statemachine/states.py +19 -2
- statemachine/transition.py +22 -45
- statemachine/transition_list.py +6 -8
- statemachine/utils.py +1 -1
- python_statemachine-2.3.1.dist-info/RECORD +0 -28
- {python_statemachine-2.3.1.dist-info → python_statemachine-2.3.3.dist-info}/LICENSE +0 -0
- {python_statemachine-2.3.1.dist-info → python_statemachine-2.3.3.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-statemachine
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Python Finite State Machines made easy.
|
|
5
5
|
Home-page: https://github.com/fgmacedo/python-statemachine
|
|
6
6
|
License: MIT
|
|
@@ -103,7 +103,7 @@ Define your state machine:
|
|
|
103
103
|
... | red.to(green)
|
|
104
104
|
... )
|
|
105
105
|
...
|
|
106
|
-
...
|
|
106
|
+
... def before_cycle(self, event: str, source: State, target: State, message: str = ""):
|
|
107
107
|
... message = ". " + message if message else ""
|
|
108
108
|
... return f"Running {event} from {source.id} to {target.id}{message}"
|
|
109
109
|
...
|
|
@@ -146,26 +146,6 @@ Then start sending events to your new state machine:
|
|
|
146
146
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
You can use the exactly same state machine from an async codebase:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
```py
|
|
153
|
-
>>> async def run_sm():
|
|
154
|
-
... asm = TrafficLightMachine()
|
|
155
|
-
... results = []
|
|
156
|
-
... for _i in range(4):
|
|
157
|
-
... result = await asm.send("cycle")
|
|
158
|
-
... results.append(result)
|
|
159
|
-
... return results
|
|
160
|
-
|
|
161
|
-
>>> asyncio.run(run_sm())
|
|
162
|
-
Don't move.
|
|
163
|
-
Go ahead!
|
|
164
|
-
['Running cycle from green to yellow', 'Running cycle from yellow to red', ...
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
|
|
169
149
|
**That's it.** This is all an external object needs to know about your state machine: How to send events.
|
|
170
150
|
Ideally, all states, transitions, and actions should be kept internally and not checked externally to avoid unnecessary coupling.
|
|
171
151
|
|
|
@@ -253,7 +233,7 @@ callback method.
|
|
|
253
233
|
Note how `before_cycle` was declared:
|
|
254
234
|
|
|
255
235
|
```py
|
|
256
|
-
|
|
236
|
+
def before_cycle(self, event: str, source: State, target: State, message: str = ""):
|
|
257
237
|
message = ". " + message if message else ""
|
|
258
238
|
return f"Running {event} from {source.id} to {target.id}{message}"
|
|
259
239
|
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
statemachine/__init__.py,sha256=ZOsh7Du9N_DZCAZKm6FHONs0DODQb0w5P-9Vi5ofgTQ,192
|
|
2
|
+
statemachine/callbacks.py,sha256=m9AS3G4qumJBjFwQNJZi3JbWbEOxjcW50H59VC7bLLY,10946
|
|
3
|
+
statemachine/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
statemachine/contrib/diagram.py,sha256=JJx52yZqgDNIebvcUJ82IGT4EyerXQmOcF5mR21_kao,7198
|
|
5
|
+
statemachine/dispatcher.py,sha256=oYlld3gvQDn2iXRPjk9ujtd5JnqWHxcft5oV80i-jMY,5175
|
|
6
|
+
statemachine/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
statemachine/engines/async_.py,sha256=Z-AS4zQ2xDj2uexJ7fepQTSaw5PjqesemWGXX9g0rpU,5546
|
|
8
|
+
statemachine/engines/sync.py,sha256=bYCyzoYo03LLfxzUl2XyyNlmMmxDLn_w0AMa5IHCF3U,5497
|
|
9
|
+
statemachine/event.py,sha256=LfvO-imBVyVX6krBK0Pz7q-iTRudo_5OjTFaYJpc6IQ,1556
|
|
10
|
+
statemachine/event_data.py,sha256=x9m9Q2q-78kVFnJVJmOAwq6lo9zgSVxko3q4Omwjqxk,2228
|
|
11
|
+
statemachine/events.py,sha256=QgXcxu25WkUaxJvonh0K7hOlYYSQlV0jbhTCAi_gpwc,736
|
|
12
|
+
statemachine/exceptions.py,sha256=RWq1vTjajxt8CzfYCD4DfI2nrgkfCPBL0DjRDOzKkQI,1086
|
|
13
|
+
statemachine/factory.py,sha256=8rRvDXWGjEX6v6mP7YiXOJPTCcm-GhDmuTvyotw1zoc,7948
|
|
14
|
+
statemachine/graph.py,sha256=KtwB1CYckaLjTgQD9tEeuaEzJje9q3fPVpBViW5TgSk,487
|
|
15
|
+
statemachine/i18n.py,sha256=NLvGseaORmQ0G-V_J8tkjoxh_piWMOm2CI6mBQpLamc,362
|
|
16
|
+
statemachine/locale/en/LC_MESSAGES/statemachine.po,sha256=zJYaV2DxrHdPngDOt1bji8-lPOZMSDjWG83Ypru5DOI,2126
|
|
17
|
+
statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po,sha256=SPrt-KaCBCKQq7PoOMtyyqU1bihrw_3MPL_NbqZzTVs,3214
|
|
18
|
+
statemachine/mixins.py,sha256=Y1fa52Cj20JaGkyNk3P7Gpqkt4cGTjJ0YyV_VQyCl0M,1231
|
|
19
|
+
statemachine/model.py,sha256=OylI3FjMiHpYyDl9mtK1zEJMeSvemaN4giQDonpc8kI,211
|
|
20
|
+
statemachine/registry.py,sha256=HmV9sUGkYVrNUZxJYoZo-trSUis7dIun_WcGktblgc8,922
|
|
21
|
+
statemachine/signature.py,sha256=HwuyWX3p_GZwKKrGbt9wk4-bHpymqs_pgqW7OlDgGRc,7877
|
|
22
|
+
statemachine/state.py,sha256=IKdkMBkXCgcvswMKQZ-hn0-739Hhf6z6q8mSEoevV7o,8262
|
|
23
|
+
statemachine/statemachine.py,sha256=zJeGHQxKItuVsmaLdMwsvRoJ29oSI2Ff3B9pYwVi8H4,11274
|
|
24
|
+
statemachine/states.py,sha256=gkwF9y1x0-t6ULQL0JrJcR1uflHGQNRZoyaMTXrPuKw,4886
|
|
25
|
+
statemachine/transition.py,sha256=R7gfSZjqR59OIdpKePgdr66zTp5azow-tDqqYJsECR4,4711
|
|
26
|
+
statemachine/transition_list.py,sha256=_tdFD1O7hw8xVAU6nRYmGrj6TCAM0SD-Sa0_DYKa88Y,6034
|
|
27
|
+
statemachine/utils.py,sha256=DpcrGqlbrnT-ogh-BogG0L07EG3KirHOsKORHlspDlI,1041
|
|
28
|
+
python_statemachine-2.3.3.dist-info/LICENSE,sha256=zcP7TsJMqaFxuTvLRZPT7nJl3_ppjxR9Z76BE9pL5zc,1074
|
|
29
|
+
python_statemachine-2.3.3.dist-info/METADATA,sha256=f9c8HTfQptQe4ocTS85DdYpG4ZrXZWi6cDVIIvxoIvo,13932
|
|
30
|
+
python_statemachine-2.3.3.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
|
31
|
+
python_statemachine-2.3.3.dist-info/RECORD,,
|
statemachine/__init__.py
CHANGED
statemachine/callbacks.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from bisect import insort
|
|
2
3
|
from collections import defaultdict
|
|
3
4
|
from collections import deque
|
|
4
5
|
from enum import IntEnum
|
|
6
|
+
from enum import auto
|
|
7
|
+
from inspect import isawaitable
|
|
8
|
+
from inspect import iscoroutinefunction
|
|
5
9
|
from typing import Callable
|
|
6
10
|
from typing import Dict
|
|
7
11
|
from typing import Generator
|
|
12
|
+
from typing import Iterable
|
|
8
13
|
from typing import List
|
|
14
|
+
from typing import Type
|
|
9
15
|
|
|
10
16
|
from .exceptions import AttrNotFound
|
|
11
17
|
from .i18n import _
|
|
@@ -20,77 +26,84 @@ class CallbackPriority(IntEnum):
|
|
|
20
26
|
AFTER = 40
|
|
21
27
|
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
class SpecReference(IntEnum):
|
|
30
|
+
NAME = 1
|
|
31
|
+
CALLABLE = 2
|
|
32
|
+
PROPERTY = 3
|
|
25
33
|
|
|
26
34
|
|
|
27
|
-
class
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
self._callback = callback
|
|
36
|
-
self.condition = condition
|
|
37
|
-
self.meta = meta
|
|
38
|
-
self.unique_key = unique_key
|
|
35
|
+
class CallbackGroup(IntEnum):
|
|
36
|
+
ENTER = auto()
|
|
37
|
+
EXIT = auto()
|
|
38
|
+
VALIDATOR = auto()
|
|
39
|
+
BEFORE = auto()
|
|
40
|
+
ON = auto()
|
|
41
|
+
AFTER = auto()
|
|
42
|
+
COND = auto()
|
|
39
43
|
|
|
40
|
-
def
|
|
41
|
-
return f"{
|
|
42
|
-
|
|
43
|
-
def __str__(self):
|
|
44
|
-
return str(self.meta)
|
|
44
|
+
def build_key(self, specs: "CallbackSpecList") -> str:
|
|
45
|
+
return f"{self.name}@{id(specs)}"
|
|
45
46
|
|
|
46
|
-
def __lt__(self, other):
|
|
47
|
-
return self.meta.priority < other.meta.priority
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
def allways_true(*args, **kwargs):
|
|
49
|
+
return True
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
class
|
|
54
|
-
"""
|
|
52
|
+
class CallbackSpec:
|
|
53
|
+
"""Specs about callbacks.
|
|
55
54
|
|
|
56
|
-
At first, `func` can be a string or a callable
|
|
57
|
-
a callable, his signature can mismatch.
|
|
55
|
+
At first, `func` can be a name (string), a property or a callable.
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
call is performed
|
|
57
|
+
Names, properties and unbounded callables should be resolved to a callable
|
|
58
|
+
before any real call is performed.
|
|
61
59
|
"""
|
|
62
60
|
|
|
63
61
|
def __init__(
|
|
64
62
|
self,
|
|
65
63
|
func,
|
|
66
|
-
|
|
64
|
+
group: CallbackGroup,
|
|
65
|
+
is_convention=False,
|
|
67
66
|
cond=None,
|
|
68
67
|
priority: CallbackPriority = CallbackPriority.NAMING,
|
|
69
68
|
expected_value=None,
|
|
70
69
|
):
|
|
71
70
|
self.func = func
|
|
72
|
-
self.
|
|
71
|
+
self.group = group
|
|
72
|
+
self.is_convention = is_convention
|
|
73
73
|
self.cond = cond
|
|
74
74
|
self.expected_value = expected_value
|
|
75
75
|
self.priority = priority
|
|
76
76
|
|
|
77
|
+
if isinstance(func, property):
|
|
78
|
+
self.reference = SpecReference.PROPERTY
|
|
79
|
+
self.attr_name: str = func and func.fget and func.fget.__name__ or ""
|
|
80
|
+
elif callable(func):
|
|
81
|
+
self.reference = SpecReference.CALLABLE
|
|
82
|
+
self.is_bounded = hasattr(func, "__self__")
|
|
83
|
+
self.attr_name = func.__name__
|
|
84
|
+
else:
|
|
85
|
+
self.reference = SpecReference.NAME
|
|
86
|
+
self.attr_name = func
|
|
87
|
+
|
|
77
88
|
def __repr__(self):
|
|
78
|
-
return f"{type(self).__name__}({self.func!r},
|
|
89
|
+
return f"{type(self).__name__}({self.func!r}, is_convention={self.is_convention!r})"
|
|
79
90
|
|
|
80
91
|
def __str__(self):
|
|
81
|
-
|
|
92
|
+
name = getattr(self.func, "__name__", self.func)
|
|
93
|
+
if self.expected_value is False:
|
|
94
|
+
name = f"!{name}"
|
|
95
|
+
return name
|
|
82
96
|
|
|
83
97
|
def __eq__(self, other):
|
|
84
|
-
return self.func == other.func
|
|
98
|
+
return self.func == other.func and self.group == other.group
|
|
85
99
|
|
|
86
100
|
def __hash__(self):
|
|
87
101
|
return id(self)
|
|
88
102
|
|
|
89
|
-
def _update_func(self, func):
|
|
103
|
+
def _update_func(self, func: Callable, attr_name: str):
|
|
90
104
|
self.func = func
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return func
|
|
105
|
+
self.reference = SpecReference.CALLABLE
|
|
106
|
+
self.attr_name = attr_name
|
|
94
107
|
|
|
95
108
|
def build(self, resolver) -> Generator["CallbackWrapper", None, None]:
|
|
96
109
|
"""
|
|
@@ -100,62 +113,57 @@ class CallbackMeta:
|
|
|
100
113
|
resolver (callable): A method responsible to build and return a valid callable that
|
|
101
114
|
can receive arbitrary parameters like `*args, **kwargs`.
|
|
102
115
|
"""
|
|
103
|
-
for callback in resolver(self
|
|
104
|
-
condition =
|
|
116
|
+
for callback in resolver.search(self):
|
|
117
|
+
condition = self.cond if self.cond is not None else allways_true
|
|
105
118
|
yield CallbackWrapper(
|
|
106
|
-
callback=
|
|
119
|
+
callback=callback,
|
|
107
120
|
condition=condition,
|
|
108
121
|
meta=self,
|
|
109
122
|
unique_key=callback.unique_key,
|
|
110
123
|
)
|
|
111
124
|
|
|
112
125
|
|
|
113
|
-
class
|
|
114
|
-
|
|
126
|
+
class SpecListGrouper:
|
|
127
|
+
def __init__(
|
|
128
|
+
self, list: "CallbackSpecList", group: CallbackGroup, factory=CallbackSpec
|
|
129
|
+
) -> None:
|
|
130
|
+
self.list = list
|
|
131
|
+
self.group = group
|
|
132
|
+
self.factory = factory
|
|
133
|
+
self.key = group.build_key(list)
|
|
115
134
|
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
def add(self, callbacks, **kwargs):
|
|
136
|
+
self.list.add(callbacks, group=self.group, factory=self.factory, **kwargs)
|
|
137
|
+
return self
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"""
|
|
139
|
+
def __call__(self, callback):
|
|
140
|
+
return self.list._add_unbounded_callback(callback, group=self.group, factory=self.factory)
|
|
122
141
|
|
|
123
|
-
def
|
|
124
|
-
self
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
super().__init__(
|
|
132
|
-
func, suppress_errors, cond, priority=priority, expected_value=expected_value
|
|
142
|
+
def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs):
|
|
143
|
+
self.list._add_unbounded_callback(
|
|
144
|
+
func,
|
|
145
|
+
is_event=is_event,
|
|
146
|
+
transitions=transitions,
|
|
147
|
+
group=self.group,
|
|
148
|
+
factory=self.factory,
|
|
149
|
+
**kwargs,
|
|
133
150
|
)
|
|
134
151
|
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
return name if self.expected_value else f"!{name}"
|
|
138
|
-
|
|
139
|
-
def _wrap_callable(self, func, expected_value):
|
|
140
|
-
async def bool_wrapper(*args, **kwargs):
|
|
141
|
-
return bool(await func(*args, **kwargs)) == expected_value
|
|
142
|
-
|
|
143
|
-
return bool_wrapper
|
|
152
|
+
def __iter__(self):
|
|
153
|
+
return (item for item in self.list if item.group == self.group)
|
|
144
154
|
|
|
145
155
|
|
|
146
|
-
class
|
|
147
|
-
"""List of `
|
|
156
|
+
class CallbackSpecList:
|
|
157
|
+
"""List of {ref}`CallbackSpec` instances"""
|
|
148
158
|
|
|
149
|
-
def __init__(self, factory=
|
|
150
|
-
self.items: List[
|
|
159
|
+
def __init__(self, factory=CallbackSpec):
|
|
160
|
+
self.items: List[CallbackSpec] = []
|
|
161
|
+
self.conventional_specs = set()
|
|
151
162
|
self.factory = factory
|
|
152
163
|
|
|
153
164
|
def __repr__(self):
|
|
154
165
|
return f"{type(self).__name__}({self.items!r}, factory={self.factory!r})"
|
|
155
166
|
|
|
156
|
-
def __str__(self):
|
|
157
|
-
return ", ".join(str(c) for c in self)
|
|
158
|
-
|
|
159
167
|
def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs):
|
|
160
168
|
"""This list was a target for adding a func using decorator
|
|
161
169
|
`@<state|event>[.on|before|after|enter|exit]` syntax.
|
|
@@ -179,45 +187,90 @@ class CallbackMetaList:
|
|
|
179
187
|
event.
|
|
180
188
|
|
|
181
189
|
"""
|
|
182
|
-
|
|
183
|
-
if not getattr(func, "
|
|
184
|
-
func.
|
|
185
|
-
|
|
186
|
-
|
|
190
|
+
spec = self._add(func, **kwargs)
|
|
191
|
+
if not getattr(func, "_specs_to_update", None):
|
|
192
|
+
func._specs_to_update = set()
|
|
193
|
+
if is_event:
|
|
194
|
+
func._specs_to_update.add(spec._update_func)
|
|
187
195
|
func._transitions = transitions
|
|
188
196
|
|
|
189
197
|
return func
|
|
190
198
|
|
|
191
|
-
def __call__(self, callback):
|
|
192
|
-
"""Allows usage of the callback list as a decorator."""
|
|
193
|
-
return self._add_unbounded_callback(callback)
|
|
194
|
-
|
|
195
199
|
def __iter__(self):
|
|
196
200
|
return iter(self.items)
|
|
197
201
|
|
|
198
202
|
def clear(self):
|
|
199
203
|
self.items = []
|
|
200
204
|
|
|
201
|
-
def
|
|
202
|
-
|
|
205
|
+
def grouper(
|
|
206
|
+
self, group: CallbackGroup, factory: Type[CallbackSpec] = CallbackSpec
|
|
207
|
+
) -> SpecListGrouper:
|
|
208
|
+
return SpecListGrouper(self, group, factory=factory)
|
|
209
|
+
|
|
210
|
+
def _add(self, func, group: CallbackGroup, factory=None, **kwargs):
|
|
211
|
+
if factory is None:
|
|
212
|
+
factory = self.factory
|
|
213
|
+
spec = factory(func, group, **kwargs)
|
|
203
214
|
|
|
204
|
-
if
|
|
215
|
+
if spec in self.items:
|
|
205
216
|
return
|
|
206
217
|
|
|
207
|
-
self.items.append(
|
|
208
|
-
|
|
218
|
+
self.items.append(spec)
|
|
219
|
+
if spec.is_convention:
|
|
220
|
+
self.conventional_specs.add(spec.func)
|
|
221
|
+
return spec
|
|
209
222
|
|
|
210
|
-
def add(self, callbacks, **kwargs):
|
|
223
|
+
def add(self, callbacks, group: CallbackGroup, **kwargs):
|
|
211
224
|
if callbacks is None:
|
|
212
225
|
return self
|
|
213
226
|
|
|
214
227
|
unprepared = ensure_iterable(callbacks)
|
|
215
228
|
for func in unprepared:
|
|
216
|
-
self._add(func, **kwargs)
|
|
229
|
+
self._add(func, group=group, **kwargs)
|
|
217
230
|
|
|
218
231
|
return self
|
|
219
232
|
|
|
220
233
|
|
|
234
|
+
class CallbackWrapper:
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
callback: Callable,
|
|
238
|
+
condition: Callable,
|
|
239
|
+
meta: "CallbackSpec",
|
|
240
|
+
unique_key: str,
|
|
241
|
+
) -> None:
|
|
242
|
+
self._callback = callback
|
|
243
|
+
self._iscoro = iscoroutinefunction(callback)
|
|
244
|
+
self.condition = condition
|
|
245
|
+
self.meta = meta
|
|
246
|
+
self.unique_key = unique_key
|
|
247
|
+
self.expected_value = self.meta.expected_value
|
|
248
|
+
|
|
249
|
+
def __repr__(self):
|
|
250
|
+
return f"{type(self).__name__}({self.unique_key})"
|
|
251
|
+
|
|
252
|
+
def __str__(self):
|
|
253
|
+
return str(self.meta)
|
|
254
|
+
|
|
255
|
+
def __lt__(self, other):
|
|
256
|
+
return self.meta.priority < other.meta.priority
|
|
257
|
+
|
|
258
|
+
async def __call__(self, *args, **kwargs):
|
|
259
|
+
value = self._callback(*args, **kwargs)
|
|
260
|
+
if isawaitable(value):
|
|
261
|
+
value = await value
|
|
262
|
+
|
|
263
|
+
if self.expected_value is not None:
|
|
264
|
+
return bool(value) == self.expected_value
|
|
265
|
+
return value
|
|
266
|
+
|
|
267
|
+
def call(self, *args, **kwargs):
|
|
268
|
+
value = self._callback(*args, **kwargs)
|
|
269
|
+
if self.expected_value is not None:
|
|
270
|
+
return bool(value) == self.expected_value
|
|
271
|
+
return value
|
|
272
|
+
|
|
273
|
+
|
|
221
274
|
class CallbacksExecutor:
|
|
222
275
|
"""A list of callbacks that can be executed in order."""
|
|
223
276
|
|
|
@@ -234,57 +287,75 @@ class CallbacksExecutor:
|
|
|
234
287
|
def __str__(self):
|
|
235
288
|
return ", ".join(str(c) for c in self)
|
|
236
289
|
|
|
237
|
-
def _add(self,
|
|
238
|
-
for callback in
|
|
290
|
+
def _add(self, spec: CallbackSpec, resolver: Callable):
|
|
291
|
+
for callback in spec.build(resolver):
|
|
239
292
|
if callback.unique_key in self.items_already_seen:
|
|
240
293
|
continue
|
|
241
294
|
|
|
242
295
|
self.items_already_seen.add(callback.unique_key)
|
|
243
296
|
insort(self.items, callback)
|
|
244
297
|
|
|
245
|
-
def add(self, items:
|
|
298
|
+
def add(self, items: Iterable[CallbackSpec], resolver: Callable):
|
|
246
299
|
"""Validate configurations"""
|
|
247
300
|
for item in items:
|
|
248
301
|
self._add(item, resolver)
|
|
249
302
|
return self
|
|
250
303
|
|
|
251
|
-
async def
|
|
304
|
+
async def async_call(self, *args, **kwargs):
|
|
305
|
+
return await asyncio.gather(
|
|
306
|
+
*(
|
|
307
|
+
callback(*args, **kwargs)
|
|
308
|
+
for callback in self
|
|
309
|
+
if callback.condition(*args, **kwargs)
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
async def async_all(self, *args, **kwargs):
|
|
314
|
+
coros = [condition(*args, **kwargs) for condition in self]
|
|
315
|
+
for coro in asyncio.as_completed(coros):
|
|
316
|
+
if not await coro:
|
|
317
|
+
return False
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
def call(self, *args, **kwargs):
|
|
252
321
|
return [
|
|
253
|
-
|
|
322
|
+
callback.call(*args, **kwargs)
|
|
254
323
|
for callback in self
|
|
255
|
-
if
|
|
324
|
+
if callback.condition(*args, **kwargs)
|
|
256
325
|
]
|
|
257
326
|
|
|
258
|
-
|
|
327
|
+
def all(self, *args, **kwargs):
|
|
259
328
|
for condition in self:
|
|
260
|
-
if not
|
|
329
|
+
if not condition.call(*args, **kwargs):
|
|
261
330
|
return False
|
|
262
331
|
return True
|
|
263
332
|
|
|
264
333
|
|
|
265
334
|
class CallbacksRegistry:
|
|
266
335
|
def __init__(self) -> None:
|
|
267
|
-
self._registry: Dict[
|
|
268
|
-
|
|
269
|
-
def register(self, meta_list: CallbackMetaList, resolver):
|
|
270
|
-
executor_list = self[meta_list]
|
|
271
|
-
executor_list.add(meta_list, resolver)
|
|
272
|
-
return executor_list
|
|
336
|
+
self._registry: Dict[str, CallbacksExecutor] = defaultdict(CallbacksExecutor)
|
|
337
|
+
self.has_async_callbacks: bool = False
|
|
273
338
|
|
|
274
339
|
def clear(self):
|
|
275
340
|
self._registry.clear()
|
|
276
341
|
|
|
277
|
-
def __getitem__(self,
|
|
278
|
-
return self._registry[
|
|
342
|
+
def __getitem__(self, key: str) -> CallbacksExecutor:
|
|
343
|
+
return self._registry[key]
|
|
279
344
|
|
|
280
|
-
def check(self,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if meta.suppress_errors:
|
|
345
|
+
def check(self, specs: CallbackSpecList):
|
|
346
|
+
for meta in specs:
|
|
347
|
+
if meta.is_convention:
|
|
284
348
|
continue
|
|
285
349
|
|
|
286
|
-
if any(
|
|
350
|
+
if any(
|
|
351
|
+
callback for callback in self[meta.group.build_key(specs)] if callback.meta == meta
|
|
352
|
+
):
|
|
287
353
|
continue
|
|
288
354
|
raise AttrNotFound(
|
|
289
355
|
_("Did not found name '{}' from model or statemachine").format(meta.func)
|
|
290
356
|
)
|
|
357
|
+
|
|
358
|
+
def async_or_sync(self):
|
|
359
|
+
self.has_async_callbacks = any(
|
|
360
|
+
callback._iscoro for executor in self._registry.values() for callback in executor
|
|
361
|
+
)
|
statemachine/contrib/diagram.py
CHANGED
|
@@ -69,12 +69,15 @@ class DotGraphMachine:
|
|
|
69
69
|
def _actions_getter(self):
|
|
70
70
|
if isinstance(self.machine, StateMachine):
|
|
71
71
|
|
|
72
|
-
def getter(
|
|
73
|
-
return self.machine.
|
|
72
|
+
def getter(grouper):
|
|
73
|
+
return self.machine._get_callbacks(grouper.key)
|
|
74
74
|
else:
|
|
75
75
|
|
|
76
|
-
def getter(
|
|
77
|
-
|
|
76
|
+
def getter(grouper):
|
|
77
|
+
all_names = set(dir(self.machine))
|
|
78
|
+
return ", ".join(
|
|
79
|
+
str(c) for c in grouper if not c.is_convention or c.func in all_names
|
|
80
|
+
)
|
|
78
81
|
|
|
79
82
|
return getter
|
|
80
83
|
|