python-statemachine 2.3.1__py3-none-any.whl → 2.3.2__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.2.dist-info}/METADATA +3 -23
- python_statemachine-2.3.2.dist-info/RECORD +31 -0
- statemachine/__init__.py +1 -1
- statemachine/callbacks.py +183 -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 -167
- statemachine/states.py +4 -3
- 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.2.dist-info}/LICENSE +0 -0
- {python_statemachine-2.3.1.dist-info → python_statemachine-2.3.2.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.2
|
|
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=iuxPx2JXDhZXU8XQ1rhBRx_q4q1aQQOrUvRi4-k_s9Y,192
|
|
2
|
+
statemachine/callbacks.py,sha256=0MX2swHpxp7yZokz2I0GnoBfUOm8dReH5IgFvrTJVos,10974
|
|
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=6t5D-mRegNcBoJv0GKVyrcafUKzogxEFMZhm80_-6tw,5548
|
|
8
|
+
statemachine/engines/sync.py,sha256=SqhACkjQPNY8GPGkPPz331iOssR94EwQOITOXNSwMSQ,5499
|
|
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=1G0bQmzDykHXGsPt2WYoOHQjazYgUDPm3VOJcMJXB7E,11387
|
|
24
|
+
statemachine/states.py,sha256=aLo5qesqrQLtP3QkVx28mAqfU-e20qC0EHgQi0XZmiw,4247
|
|
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.2.dist-info/LICENSE,sha256=zcP7TsJMqaFxuTvLRZPT7nJl3_ppjxR9Z76BE9pL5zc,1074
|
|
29
|
+
python_statemachine-2.3.2.dist-info/METADATA,sha256=zvFK8eZfgK_ybIPxElQYQ5lmbEKvTtDLedzf7sfgC8E,13932
|
|
30
|
+
python_statemachine-2.3.2.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
|
31
|
+
python_statemachine-2.3.2.dist-info/RECORD,,
|
statemachine/__init__.py
CHANGED
statemachine/callbacks.py
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from bisect import insort
|
|
3
|
+
from collections import Counter
|
|
2
4
|
from collections import defaultdict
|
|
3
5
|
from collections import deque
|
|
4
6
|
from enum import IntEnum
|
|
7
|
+
from enum import auto
|
|
8
|
+
from inspect import isawaitable
|
|
9
|
+
from inspect import iscoroutinefunction
|
|
5
10
|
from typing import Callable
|
|
6
11
|
from typing import Dict
|
|
7
12
|
from typing import Generator
|
|
13
|
+
from typing import Iterable
|
|
8
14
|
from typing import List
|
|
15
|
+
from typing import Type
|
|
9
16
|
|
|
10
17
|
from .exceptions import AttrNotFound
|
|
11
18
|
from .i18n import _
|
|
@@ -20,77 +27,84 @@ class CallbackPriority(IntEnum):
|
|
|
20
27
|
AFTER = 40
|
|
21
28
|
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
class SpecReference(IntEnum):
|
|
31
|
+
NAME = 1
|
|
32
|
+
CALLABLE = 2
|
|
33
|
+
PROPERTY = 3
|
|
25
34
|
|
|
26
35
|
|
|
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
|
|
36
|
+
class CallbackGroup(IntEnum):
|
|
37
|
+
ENTER = auto()
|
|
38
|
+
EXIT = auto()
|
|
39
|
+
VALIDATOR = auto()
|
|
40
|
+
BEFORE = auto()
|
|
41
|
+
ON = auto()
|
|
42
|
+
AFTER = auto()
|
|
43
|
+
COND = auto()
|
|
39
44
|
|
|
40
|
-
def
|
|
41
|
-
return f"{
|
|
42
|
-
|
|
43
|
-
def __str__(self):
|
|
44
|
-
return str(self.meta)
|
|
45
|
+
def build_key(self, specs: "CallbackSpecList") -> str:
|
|
46
|
+
return f"{self.name}@{id(specs)}"
|
|
45
47
|
|
|
46
|
-
def __lt__(self, other):
|
|
47
|
-
return self.meta.priority < other.meta.priority
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
def allways_true(*args, **kwargs):
|
|
50
|
+
return True
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class
|
|
54
|
-
"""
|
|
53
|
+
class CallbackSpec:
|
|
54
|
+
"""Specs about callbacks.
|
|
55
55
|
|
|
56
|
-
At first, `func` can be a string or a callable
|
|
57
|
-
a callable, his signature can mismatch.
|
|
56
|
+
At first, `func` can be a name (string), a property or a callable.
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
call is performed
|
|
58
|
+
Names, properties and unbounded callables should be resolved to a callable
|
|
59
|
+
before any real call is performed.
|
|
61
60
|
"""
|
|
62
61
|
|
|
63
62
|
def __init__(
|
|
64
63
|
self,
|
|
65
64
|
func,
|
|
66
|
-
|
|
65
|
+
group: CallbackGroup,
|
|
66
|
+
is_convention=False,
|
|
67
67
|
cond=None,
|
|
68
68
|
priority: CallbackPriority = CallbackPriority.NAMING,
|
|
69
69
|
expected_value=None,
|
|
70
70
|
):
|
|
71
71
|
self.func = func
|
|
72
|
-
self.
|
|
72
|
+
self.group = group
|
|
73
|
+
self.is_convention = is_convention
|
|
73
74
|
self.cond = cond
|
|
74
75
|
self.expected_value = expected_value
|
|
75
76
|
self.priority = priority
|
|
76
77
|
|
|
78
|
+
if isinstance(func, property):
|
|
79
|
+
self.reference = SpecReference.PROPERTY
|
|
80
|
+
self.attr_name: str = func and func.fget and func.fget.__name__ or ""
|
|
81
|
+
elif callable(func):
|
|
82
|
+
self.reference = SpecReference.CALLABLE
|
|
83
|
+
self.is_bounded = hasattr(func, "__self__")
|
|
84
|
+
self.attr_name = func.__name__
|
|
85
|
+
else:
|
|
86
|
+
self.reference = SpecReference.NAME
|
|
87
|
+
self.attr_name = func
|
|
88
|
+
|
|
77
89
|
def __repr__(self):
|
|
78
|
-
return f"{type(self).__name__}({self.func!r},
|
|
90
|
+
return f"{type(self).__name__}({self.func!r}, is_convention={self.is_convention!r})"
|
|
79
91
|
|
|
80
92
|
def __str__(self):
|
|
81
|
-
|
|
93
|
+
name = getattr(self.func, "__name__", self.func)
|
|
94
|
+
if self.expected_value is False:
|
|
95
|
+
name = f"!{name}"
|
|
96
|
+
return name
|
|
82
97
|
|
|
83
98
|
def __eq__(self, other):
|
|
84
|
-
return self.func == other.func
|
|
99
|
+
return self.func == other.func and self.group == other.group
|
|
85
100
|
|
|
86
101
|
def __hash__(self):
|
|
87
102
|
return id(self)
|
|
88
103
|
|
|
89
|
-
def _update_func(self, func):
|
|
104
|
+
def _update_func(self, func: Callable, attr_name: str):
|
|
90
105
|
self.func = func
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return func
|
|
106
|
+
self.reference = SpecReference.CALLABLE
|
|
107
|
+
self.attr_name = attr_name
|
|
94
108
|
|
|
95
109
|
def build(self, resolver) -> Generator["CallbackWrapper", None, None]:
|
|
96
110
|
"""
|
|
@@ -100,62 +114,57 @@ class CallbackMeta:
|
|
|
100
114
|
resolver (callable): A method responsible to build and return a valid callable that
|
|
101
115
|
can receive arbitrary parameters like `*args, **kwargs`.
|
|
102
116
|
"""
|
|
103
|
-
for callback in resolver(self
|
|
104
|
-
condition =
|
|
117
|
+
for callback in resolver.search(self):
|
|
118
|
+
condition = self.cond if self.cond is not None else allways_true
|
|
105
119
|
yield CallbackWrapper(
|
|
106
|
-
callback=
|
|
120
|
+
callback=callback,
|
|
107
121
|
condition=condition,
|
|
108
122
|
meta=self,
|
|
109
123
|
unique_key=callback.unique_key,
|
|
110
124
|
)
|
|
111
125
|
|
|
112
126
|
|
|
113
|
-
class
|
|
114
|
-
|
|
127
|
+
class SpecListGrouper:
|
|
128
|
+
def __init__(
|
|
129
|
+
self, list: "CallbackSpecList", group: CallbackGroup, factory=CallbackSpec
|
|
130
|
+
) -> None:
|
|
131
|
+
self.list = list
|
|
132
|
+
self.group = group
|
|
133
|
+
self.factory = factory
|
|
134
|
+
self.key = group.build_key(list)
|
|
115
135
|
|
|
116
|
-
|
|
117
|
-
|
|
136
|
+
def add(self, callbacks, **kwargs):
|
|
137
|
+
self.list.add(callbacks, group=self.group, factory=self.factory, **kwargs)
|
|
138
|
+
return self
|
|
118
139
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"""
|
|
140
|
+
def __call__(self, callback):
|
|
141
|
+
return self.list._add_unbounded_callback(callback, group=self.group, factory=self.factory)
|
|
122
142
|
|
|
123
|
-
def
|
|
124
|
-
self
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
super().__init__(
|
|
132
|
-
func, suppress_errors, cond, priority=priority, expected_value=expected_value
|
|
143
|
+
def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs):
|
|
144
|
+
self.list._add_unbounded_callback(
|
|
145
|
+
func,
|
|
146
|
+
is_event=is_event,
|
|
147
|
+
transitions=transitions,
|
|
148
|
+
group=self.group,
|
|
149
|
+
factory=self.factory,
|
|
150
|
+
**kwargs,
|
|
133
151
|
)
|
|
134
152
|
|
|
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
|
|
153
|
+
def __iter__(self):
|
|
154
|
+
return (item for item in self.list if item.group == self.group)
|
|
144
155
|
|
|
145
156
|
|
|
146
|
-
class
|
|
147
|
-
"""List of `
|
|
157
|
+
class CallbackSpecList:
|
|
158
|
+
"""List of {ref}`CallbackSpec` instances"""
|
|
148
159
|
|
|
149
|
-
def __init__(self, factory=
|
|
150
|
-
self.items: List[
|
|
160
|
+
def __init__(self, factory=CallbackSpec):
|
|
161
|
+
self.items: List[CallbackSpec] = []
|
|
162
|
+
self.conventional_specs = set()
|
|
151
163
|
self.factory = factory
|
|
152
164
|
|
|
153
165
|
def __repr__(self):
|
|
154
166
|
return f"{type(self).__name__}({self.items!r}, factory={self.factory!r})"
|
|
155
167
|
|
|
156
|
-
def __str__(self):
|
|
157
|
-
return ", ".join(str(c) for c in self)
|
|
158
|
-
|
|
159
168
|
def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs):
|
|
160
169
|
"""This list was a target for adding a func using decorator
|
|
161
170
|
`@<state|event>[.on|before|after|enter|exit]` syntax.
|
|
@@ -179,45 +188,90 @@ class CallbackMetaList:
|
|
|
179
188
|
event.
|
|
180
189
|
|
|
181
190
|
"""
|
|
182
|
-
|
|
183
|
-
if not getattr(func, "
|
|
184
|
-
func.
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
spec = self._add(func, **kwargs)
|
|
192
|
+
if not getattr(func, "_specs_to_update", None):
|
|
193
|
+
func._specs_to_update = set()
|
|
194
|
+
if is_event:
|
|
195
|
+
func._specs_to_update.add(spec._update_func)
|
|
187
196
|
func._transitions = transitions
|
|
188
197
|
|
|
189
198
|
return func
|
|
190
199
|
|
|
191
|
-
def __call__(self, callback):
|
|
192
|
-
"""Allows usage of the callback list as a decorator."""
|
|
193
|
-
return self._add_unbounded_callback(callback)
|
|
194
|
-
|
|
195
200
|
def __iter__(self):
|
|
196
201
|
return iter(self.items)
|
|
197
202
|
|
|
198
203
|
def clear(self):
|
|
199
204
|
self.items = []
|
|
200
205
|
|
|
201
|
-
def
|
|
202
|
-
|
|
206
|
+
def grouper(
|
|
207
|
+
self, group: CallbackGroup, factory: Type[CallbackSpec] = CallbackSpec
|
|
208
|
+
) -> SpecListGrouper:
|
|
209
|
+
return SpecListGrouper(self, group, factory=factory)
|
|
210
|
+
|
|
211
|
+
def _add(self, func, group: CallbackGroup, factory=None, **kwargs):
|
|
212
|
+
if factory is None:
|
|
213
|
+
factory = self.factory
|
|
214
|
+
spec = factory(func, group, **kwargs)
|
|
203
215
|
|
|
204
|
-
if
|
|
216
|
+
if spec in self.items:
|
|
205
217
|
return
|
|
206
218
|
|
|
207
|
-
self.items.append(
|
|
208
|
-
|
|
219
|
+
self.items.append(spec)
|
|
220
|
+
if spec.is_convention:
|
|
221
|
+
self.conventional_specs.add(spec.func)
|
|
222
|
+
return spec
|
|
209
223
|
|
|
210
|
-
def add(self, callbacks, **kwargs):
|
|
224
|
+
def add(self, callbacks, group: CallbackGroup, **kwargs):
|
|
211
225
|
if callbacks is None:
|
|
212
226
|
return self
|
|
213
227
|
|
|
214
228
|
unprepared = ensure_iterable(callbacks)
|
|
215
229
|
for func in unprepared:
|
|
216
|
-
self._add(func, **kwargs)
|
|
230
|
+
self._add(func, group=group, **kwargs)
|
|
217
231
|
|
|
218
232
|
return self
|
|
219
233
|
|
|
220
234
|
|
|
235
|
+
class CallbackWrapper:
|
|
236
|
+
def __init__(
|
|
237
|
+
self,
|
|
238
|
+
callback: Callable,
|
|
239
|
+
condition: Callable,
|
|
240
|
+
meta: "CallbackSpec",
|
|
241
|
+
unique_key: str,
|
|
242
|
+
) -> None:
|
|
243
|
+
self._callback = callback
|
|
244
|
+
self._iscoro = iscoroutinefunction(callback)
|
|
245
|
+
self.condition = condition
|
|
246
|
+
self.meta = meta
|
|
247
|
+
self.unique_key = unique_key
|
|
248
|
+
self.expected_value = self.meta.expected_value
|
|
249
|
+
|
|
250
|
+
def __repr__(self):
|
|
251
|
+
return f"{type(self).__name__}({self.unique_key})"
|
|
252
|
+
|
|
253
|
+
def __str__(self):
|
|
254
|
+
return str(self.meta)
|
|
255
|
+
|
|
256
|
+
def __lt__(self, other):
|
|
257
|
+
return self.meta.priority < other.meta.priority
|
|
258
|
+
|
|
259
|
+
async def __call__(self, *args, **kwargs):
|
|
260
|
+
value = self._callback(*args, **kwargs)
|
|
261
|
+
if isawaitable(value):
|
|
262
|
+
value = await value
|
|
263
|
+
|
|
264
|
+
if self.expected_value is not None:
|
|
265
|
+
return bool(value) == self.expected_value
|
|
266
|
+
return value
|
|
267
|
+
|
|
268
|
+
def call(self, *args, **kwargs):
|
|
269
|
+
value = self._callback(*args, **kwargs)
|
|
270
|
+
if self.expected_value is not None:
|
|
271
|
+
return bool(value) == self.expected_value
|
|
272
|
+
return value
|
|
273
|
+
|
|
274
|
+
|
|
221
275
|
class CallbacksExecutor:
|
|
222
276
|
"""A list of callbacks that can be executed in order."""
|
|
223
277
|
|
|
@@ -234,57 +288,75 @@ class CallbacksExecutor:
|
|
|
234
288
|
def __str__(self):
|
|
235
289
|
return ", ".join(str(c) for c in self)
|
|
236
290
|
|
|
237
|
-
def _add(self,
|
|
238
|
-
for callback in
|
|
291
|
+
def _add(self, spec: CallbackSpec, resolver: Callable):
|
|
292
|
+
for callback in spec.build(resolver):
|
|
239
293
|
if callback.unique_key in self.items_already_seen:
|
|
240
294
|
continue
|
|
241
295
|
|
|
242
296
|
self.items_already_seen.add(callback.unique_key)
|
|
243
297
|
insort(self.items, callback)
|
|
244
298
|
|
|
245
|
-
def add(self, items:
|
|
299
|
+
def add(self, items: Iterable[CallbackSpec], resolver: Callable):
|
|
246
300
|
"""Validate configurations"""
|
|
247
301
|
for item in items:
|
|
248
302
|
self._add(item, resolver)
|
|
249
303
|
return self
|
|
250
304
|
|
|
251
|
-
async def
|
|
305
|
+
async def async_call(self, *args, **kwargs):
|
|
306
|
+
return await asyncio.gather(
|
|
307
|
+
*(
|
|
308
|
+
callback(*args, **kwargs)
|
|
309
|
+
for callback in self
|
|
310
|
+
if callback.condition(*args, **kwargs)
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
async def async_all(self, *args, **kwargs):
|
|
315
|
+
coros = [condition(*args, **kwargs) for condition in self]
|
|
316
|
+
for coro in asyncio.as_completed(coros):
|
|
317
|
+
if not await coro:
|
|
318
|
+
return False
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
def call(self, *args, **kwargs):
|
|
252
322
|
return [
|
|
253
|
-
|
|
323
|
+
callback.call(*args, **kwargs)
|
|
254
324
|
for callback in self
|
|
255
|
-
if
|
|
325
|
+
if callback.condition(*args, **kwargs)
|
|
256
326
|
]
|
|
257
327
|
|
|
258
|
-
|
|
328
|
+
def all(self, *args, **kwargs):
|
|
259
329
|
for condition in self:
|
|
260
|
-
if not
|
|
330
|
+
if not condition.call(*args, **kwargs):
|
|
261
331
|
return False
|
|
262
332
|
return True
|
|
263
333
|
|
|
264
334
|
|
|
265
335
|
class CallbacksRegistry:
|
|
266
336
|
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
|
|
337
|
+
self._registry: Dict[str, CallbacksExecutor] = defaultdict(CallbacksExecutor)
|
|
338
|
+
self._method_types: Counter = Counter()
|
|
273
339
|
|
|
274
340
|
def clear(self):
|
|
275
341
|
self._registry.clear()
|
|
276
342
|
|
|
277
|
-
def __getitem__(self,
|
|
278
|
-
return self._registry[
|
|
343
|
+
def __getitem__(self, key: str) -> CallbacksExecutor:
|
|
344
|
+
return self._registry[key]
|
|
279
345
|
|
|
280
|
-
def check(self,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if meta.suppress_errors:
|
|
346
|
+
def check(self, specs: CallbackSpecList):
|
|
347
|
+
for meta in specs:
|
|
348
|
+
if meta.is_convention:
|
|
284
349
|
continue
|
|
285
350
|
|
|
286
|
-
if any(
|
|
351
|
+
if any(
|
|
352
|
+
callback for callback in self[meta.group.build_key(specs)] if callback.meta == meta
|
|
353
|
+
):
|
|
287
354
|
continue
|
|
288
355
|
raise AttrNotFound(
|
|
289
356
|
_("Did not found name '{}' from model or statemachine").format(meta.func)
|
|
290
357
|
)
|
|
358
|
+
|
|
359
|
+
def async_or_sync(self):
|
|
360
|
+
self._method_types.update(
|
|
361
|
+
callback._iscoro for executor in self._registry.values() for callback in executor
|
|
362
|
+
)
|
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
|
|