python-statemachine 2.4.0__py3-none-any.whl → 2.5.0__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.4.0.dist-info → python_statemachine-2.5.0.dist-info}/METADATA +15 -16
- python_statemachine-2.5.0.dist-info/RECORD +37 -0
- {python_statemachine-2.4.0.dist-info → python_statemachine-2.5.0.dist-info}/WHEEL +1 -1
- statemachine/__init__.py +1 -1
- statemachine/callbacks.py +74 -111
- statemachine/contrib/diagram.py +4 -4
- statemachine/dispatcher.py +119 -48
- statemachine/engines/async_.py +27 -34
- statemachine/engines/base.py +40 -0
- statemachine/engines/sync.py +29 -37
- statemachine/event.py +21 -4
- statemachine/events.py +1 -2
- statemachine/factory.py +7 -11
- statemachine/signature.py +7 -26
- statemachine/spec_parser.py +80 -9
- statemachine/state.py +59 -27
- statemachine/statemachine.py +43 -55
- statemachine/transition.py +22 -2
- statemachine/transition_list.py +12 -78
- statemachine/transition_mixin.py +82 -0
- python_statemachine-2.4.0.dist-info/RECORD +0 -35
- {python_statemachine-2.4.0.dist-info → python_statemachine-2.5.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: python-statemachine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Python Finite State Machines made easy.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Maintainer-email: fgmacedo@gmail.com
|
|
11
|
-
Requires-Python: >=3.7
|
|
5
|
+
Project-URL: homepage, https://github.com/fgmacedo/python-statemachine
|
|
6
|
+
Author-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
7
|
+
Maintainer-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
10
|
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Framework :: Django
|
|
13
12
|
Classifier: Intended Audience :: Developers
|
|
14
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
14
|
Classifier: Natural Language :: English
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.7
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -21,9 +19,11 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Home Automation
|
|
24
23
|
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Requires-Python: >=3.7
|
|
25
25
|
Provides-Extra: diagrams
|
|
26
|
-
Requires-Dist: pydot
|
|
26
|
+
Requires-Dist: pydot>=2.0.0; extra == 'diagrams'
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# Python StateMachine
|
|
@@ -196,19 +196,19 @@ Easily iterate over all states:
|
|
|
196
196
|
|
|
197
197
|
```py
|
|
198
198
|
>>> [s.id for s in sm.states]
|
|
199
|
-
['green', '
|
|
199
|
+
['green', 'yellow', 'red']
|
|
200
200
|
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
Or over events:
|
|
204
204
|
|
|
205
205
|
```py
|
|
206
|
-
>>> [t.
|
|
206
|
+
>>> [t.id for t in sm.events]
|
|
207
207
|
['cycle']
|
|
208
208
|
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
Call an event by its
|
|
211
|
+
Call an event by its id:
|
|
212
212
|
|
|
213
213
|
```py
|
|
214
214
|
>>> sm.cycle()
|
|
@@ -216,7 +216,7 @@ Don't move.
|
|
|
216
216
|
'Running cycle from yellow to red'
|
|
217
217
|
|
|
218
218
|
```
|
|
219
|
-
Or send an event with the event
|
|
219
|
+
Or send an event with the event id:
|
|
220
220
|
|
|
221
221
|
```py
|
|
222
222
|
>>> sm.send('cycle')
|
|
@@ -427,4 +427,3 @@ request. For more information on how to contribute, please see our [contributing
|
|
|
427
427
|
- **Promote the project**: Help spread the word by sharing on social media,
|
|
428
428
|
writing a blog post, or giving a talk about it. Tag me on Twitter
|
|
429
429
|
[@fgmacedo](https://twitter.com/fgmacedo) so I can share it too!
|
|
430
|
-
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
statemachine/__init__.py,sha256=JsTxT_XFohxEg-P6qmBYcxNTwzW-A2a9zLIk7wpRzaM,226
|
|
2
|
+
statemachine/callbacks.py,sha256=UUOuMotQDV3ZkpqjWp3CIL3UrA70Bh_xYDf4Ugjlou8,11475
|
|
3
|
+
statemachine/dispatcher.py,sha256=Ai8i79Lo5HdUJ-toOVg4NDUzAy88sryBHDbYdl_7sWE,7785
|
|
4
|
+
statemachine/event.py,sha256=DhAVtoPGCHv95r3QOuL-Tn93UMWXPhiuODFcc9FPUPA,4586
|
|
5
|
+
statemachine/event_data.py,sha256=H9lp_XnvHSK9YErUOCvMK3ZBjWhC-xDSOJ2gZxtmrq8,2261
|
|
6
|
+
statemachine/events.py,sha256=UTYJu8te_bxiORTQpoXY5tB_x-ymVPWDOREcxCyhExA,1018
|
|
7
|
+
statemachine/exceptions.py,sha256=vHVQPTTMMkVvySNbN6XZPciBryvpY608LDe3MCnmxFU,1124
|
|
8
|
+
statemachine/factory.py,sha256=crL2FPfzdku3STlbxaF9N3oV9L1BFWoCAT9K3zEnBYo,9219
|
|
9
|
+
statemachine/graph.py,sha256=KtwB1CYckaLjTgQD9tEeuaEzJje9q3fPVpBViW5TgSk,487
|
|
10
|
+
statemachine/i18n.py,sha256=NLvGseaORmQ0G-V_J8tkjoxh_piWMOm2CI6mBQpLamc,362
|
|
11
|
+
statemachine/mixins.py,sha256=Y1fa52Cj20JaGkyNk3P7Gpqkt4cGTjJ0YyV_VQyCl0M,1231
|
|
12
|
+
statemachine/model.py,sha256=OylI3FjMiHpYyDl9mtK1zEJMeSvemaN4giQDonpc8kI,211
|
|
13
|
+
statemachine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
statemachine/registry.py,sha256=HmV9sUGkYVrNUZxJYoZo-trSUis7dIun_WcGktblgc8,922
|
|
15
|
+
statemachine/signature.py,sha256=mZjYXjMAF2XNLxI_MfTYoReJxTuPBHExEm7H76yueWY,7117
|
|
16
|
+
statemachine/spec_parser.py,sha256=DRG-_6bWRTCTgU6qZHqRp5aOxFRZP0O_4VvVxyleItA,5686
|
|
17
|
+
statemachine/state.py,sha256=a2uJZyn8sM8HdnVN_FesCgcjoeDE8gfXCAChbLm4p9g,9504
|
|
18
|
+
statemachine/statemachine.py,sha256=mznQ4NpiysrHm1Ef3JPGRQ07gSXzAD9bER3r_ou9d40,10881
|
|
19
|
+
statemachine/states.py,sha256=pPROZwIyE3_tlBGL3DJXnM3gr1pWsWICEMMo2c742RY,4889
|
|
20
|
+
statemachine/transition.py,sha256=YDpI6NuCipW4cF4GVIt3o8ACE6VhWAQWHaZKjmjSQRA,5335
|
|
21
|
+
statemachine/transition_list.py,sha256=I9viQ0zr6E8oL3WEESkst9DwQeTNJ6nwDAQXsWeo3_c,4179
|
|
22
|
+
statemachine/transition_mixin.py,sha256=OGKF-hMyTEZDtlK_XxGaa5OqxYjmUtThMac9tDQUKUY,2615
|
|
23
|
+
statemachine/utils.py,sha256=DpcrGqlbrnT-ogh-BogG0L07EG3KirHOsKORHlspDlI,1041
|
|
24
|
+
statemachine/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
statemachine/contrib/diagram.py,sha256=CVNIhBTedik_02b-4KUua_3_HtiO1TYQNWOg3ulkqiE,7159
|
|
26
|
+
statemachine/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
statemachine/engines/async_.py,sha256=HA9EtS7vYS5tulvdwH2NnTMkpUddcDZPXVNckSQFAak,5212
|
|
28
|
+
statemachine/engines/base.py,sha256=d65JvngOrp8sE4RR8i2wrd72wzIci9-3InMLyGdxsZQ,1201
|
|
29
|
+
statemachine/engines/sync.py,sha256=Cys05fn7zQf3WowdIaVrdD8mAVQ-HEjbTMRngv_Z4cU,5035
|
|
30
|
+
statemachine/locale/en/LC_MESSAGES/statemachine.po,sha256=4Pk-h5nk7twOTHcRQ_Chanfdi5EtFi9aTAzGkVJuCo8,2424
|
|
31
|
+
statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po,sha256=Bs5bbIxDrYtODSNJKNl4FH8ZtjEJwPIidRwbAxMwu5E,4941
|
|
32
|
+
statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po,sha256=gmnhc15-6YVDCLWYT0ZQL5jfXHgIOYq5p5rJLqNPaxE,3593
|
|
33
|
+
statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po,sha256=cDbRHDYpi3pwJkFmaSn79q5KmX9cGmHFHw1ndj37vRw,3325
|
|
34
|
+
python_statemachine-2.5.0.dist-info/METADATA,sha256=LzU6fCMlw6gANXQeCCgRpyVRorsAzvF8UMWERtnowng,14068
|
|
35
|
+
python_statemachine-2.5.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
36
|
+
python_statemachine-2.5.0.dist-info/licenses/LICENSE,sha256=zcP7TsJMqaFxuTvLRZPT7nJl3_ppjxR9Z76BE9pL5zc,1074
|
|
37
|
+
python_statemachine-2.5.0.dist-info/RECORD,,
|
statemachine/__init__.py
CHANGED
statemachine/callbacks.py
CHANGED
|
@@ -5,29 +5,22 @@ from collections import deque
|
|
|
5
5
|
from enum import IntEnum
|
|
6
6
|
from enum import IntFlag
|
|
7
7
|
from enum import auto
|
|
8
|
-
from functools import partial
|
|
9
|
-
from functools import reduce
|
|
10
8
|
from inspect import isawaitable
|
|
11
|
-
from inspect import iscoroutinefunction
|
|
12
9
|
from typing import TYPE_CHECKING
|
|
13
10
|
from typing import Callable
|
|
14
11
|
from typing import Dict
|
|
15
|
-
from typing import Generator
|
|
16
|
-
from typing import Iterable
|
|
17
12
|
from typing import List
|
|
18
|
-
from typing import Set
|
|
19
|
-
from typing import Type
|
|
20
13
|
|
|
21
14
|
from .exceptions import AttrNotFound
|
|
22
|
-
from .exceptions import InvalidDefinition
|
|
23
15
|
from .i18n import _
|
|
24
|
-
from .spec_parser import custom_and
|
|
25
|
-
from .spec_parser import operator_mapping
|
|
26
|
-
from .spec_parser import parse_boolean_expr
|
|
27
16
|
from .utils import ensure_iterable
|
|
28
17
|
|
|
29
18
|
if TYPE_CHECKING:
|
|
30
|
-
from
|
|
19
|
+
from typing import Set
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def allways_true(*args, **kwargs):
|
|
23
|
+
return True
|
|
31
24
|
|
|
32
25
|
|
|
33
26
|
class CallbackPriority(IntEnum):
|
|
@@ -61,21 +54,6 @@ class CallbackGroup(IntEnum):
|
|
|
61
54
|
return f"{self.name}@{id(specs)}"
|
|
62
55
|
|
|
63
56
|
|
|
64
|
-
def allways_true(*args, **kwargs):
|
|
65
|
-
return True
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def take_callback(name: str, resolver: "Listeners", not_found_handler: Callable) -> Callable:
|
|
69
|
-
callbacks = list(resolver.search_name(name))
|
|
70
|
-
if len(callbacks) == 0:
|
|
71
|
-
not_found_handler(name)
|
|
72
|
-
return allways_true
|
|
73
|
-
elif len(callbacks) == 1:
|
|
74
|
-
return callbacks[0]
|
|
75
|
-
else:
|
|
76
|
-
return reduce(custom_and, callbacks)
|
|
77
|
-
|
|
78
|
-
|
|
79
57
|
class CallbackSpec:
|
|
80
58
|
"""Specs about callbacks.
|
|
81
59
|
|
|
@@ -85,11 +63,15 @@ class CallbackSpec:
|
|
|
85
63
|
before any real call is performed.
|
|
86
64
|
"""
|
|
87
65
|
|
|
66
|
+
names_not_found: "Set[str] | None" = None
|
|
67
|
+
"""List of names that were not found on the model or statemachine"""
|
|
68
|
+
|
|
88
69
|
def __init__(
|
|
89
70
|
self,
|
|
90
71
|
func,
|
|
91
72
|
group: CallbackGroup,
|
|
92
73
|
is_convention=False,
|
|
74
|
+
is_event: bool = False,
|
|
93
75
|
cond=None,
|
|
94
76
|
priority: CallbackPriority = CallbackPriority.NAMING,
|
|
95
77
|
expected_value=None,
|
|
@@ -97,6 +79,7 @@ class CallbackSpec:
|
|
|
97
79
|
self.func = func
|
|
98
80
|
self.group = group
|
|
99
81
|
self.is_convention = is_convention
|
|
82
|
+
self.is_event = is_event
|
|
100
83
|
self.cond = cond
|
|
101
84
|
self.expected_value = expected_value
|
|
102
85
|
self.priority = priority
|
|
@@ -107,11 +90,22 @@ class CallbackSpec:
|
|
|
107
90
|
elif callable(func):
|
|
108
91
|
self.reference = SpecReference.CALLABLE
|
|
109
92
|
self.is_bounded = hasattr(func, "__self__")
|
|
110
|
-
self.attr_name =
|
|
93
|
+
self.attr_name = (
|
|
94
|
+
func.__name__ if not self.is_event or self.is_bounded else f"_{func.__name__}_"
|
|
95
|
+
)
|
|
96
|
+
if not self.is_bounded:
|
|
97
|
+
func.attr_name = self.attr_name
|
|
98
|
+
func.is_event = is_event
|
|
111
99
|
else:
|
|
112
100
|
self.reference = SpecReference.NAME
|
|
113
101
|
self.attr_name = func
|
|
114
102
|
|
|
103
|
+
self.may_contain_boolean_expression = (
|
|
104
|
+
not self.is_convention
|
|
105
|
+
and self.group == CallbackGroup.COND
|
|
106
|
+
and self.reference == SpecReference.NAME
|
|
107
|
+
)
|
|
108
|
+
|
|
115
109
|
def __repr__(self):
|
|
116
110
|
return f"{type(self).__name__}({self.func!r}, is_convention={self.is_convention!r})"
|
|
117
111
|
|
|
@@ -127,68 +121,19 @@ class CallbackSpec:
|
|
|
127
121
|
def __hash__(self):
|
|
128
122
|
return id(self)
|
|
129
123
|
|
|
130
|
-
def _update_func(self, func: Callable, attr_name: str):
|
|
131
|
-
self.func = func
|
|
132
|
-
self.reference = SpecReference.CALLABLE
|
|
133
|
-
self.attr_name = attr_name
|
|
134
|
-
|
|
135
|
-
def _wrap(self, callback):
|
|
136
|
-
condition = self.cond if self.cond is not None else allways_true
|
|
137
|
-
return CallbackWrapper(
|
|
138
|
-
callback=callback,
|
|
139
|
-
condition=condition,
|
|
140
|
-
meta=self,
|
|
141
|
-
unique_key=callback.unique_key,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
def build(self, resolver: "Listeners") -> Generator["CallbackWrapper", None, None]:
|
|
145
|
-
"""
|
|
146
|
-
Resolves the `func` into a usable callable.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
resolver (callable): A method responsible to build and return a valid callable that
|
|
150
|
-
can receive arbitrary parameters like `*args, **kwargs`.
|
|
151
|
-
"""
|
|
152
|
-
if (
|
|
153
|
-
not self.is_convention
|
|
154
|
-
and self.group == CallbackGroup.COND
|
|
155
|
-
and self.reference == SpecReference.NAME
|
|
156
|
-
):
|
|
157
|
-
names_not_found: Set[str] = set()
|
|
158
|
-
take_callback_partial = partial(
|
|
159
|
-
take_callback, resolver=resolver, not_found_handler=names_not_found.add
|
|
160
|
-
)
|
|
161
|
-
try:
|
|
162
|
-
expression = parse_boolean_expr(self.func, take_callback_partial, operator_mapping)
|
|
163
|
-
except SyntaxError as err:
|
|
164
|
-
raise InvalidDefinition(
|
|
165
|
-
_("Failed to parse boolean expression '{}'").format(self.func)
|
|
166
|
-
) from err
|
|
167
|
-
if not expression or names_not_found:
|
|
168
|
-
self.names_not_found = names_not_found
|
|
169
|
-
return
|
|
170
|
-
yield self._wrap(expression)
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
for callback in resolver.search(self):
|
|
174
|
-
yield self._wrap(callback)
|
|
175
|
-
|
|
176
124
|
|
|
177
125
|
class SpecListGrouper:
|
|
178
|
-
def __init__(
|
|
179
|
-
self, list: "CallbackSpecList", group: CallbackGroup, factory=CallbackSpec
|
|
180
|
-
) -> None:
|
|
126
|
+
def __init__(self, list: "CallbackSpecList", group: CallbackGroup) -> None:
|
|
181
127
|
self.list = list
|
|
182
128
|
self.group = group
|
|
183
|
-
self.factory = factory
|
|
184
129
|
self.key = group.build_key(list)
|
|
185
130
|
|
|
186
131
|
def add(self, callbacks, **kwargs):
|
|
187
|
-
self.list.add(callbacks, group=self.group,
|
|
132
|
+
self.list.add(callbacks, group=self.group, **kwargs)
|
|
188
133
|
return self
|
|
189
134
|
|
|
190
135
|
def __call__(self, callback):
|
|
191
|
-
return self.list._add_unbounded_callback(callback, group=self.group
|
|
136
|
+
return self.list._add_unbounded_callback(callback, group=self.group)
|
|
192
137
|
|
|
193
138
|
def _add_unbounded_callback(self, func, is_event=False, transitions=None, **kwargs):
|
|
194
139
|
self.list._add_unbounded_callback(
|
|
@@ -196,7 +141,6 @@ class SpecListGrouper:
|
|
|
196
141
|
is_event=is_event,
|
|
197
142
|
transitions=transitions,
|
|
198
143
|
group=self.group,
|
|
199
|
-
factory=self.factory,
|
|
200
144
|
**kwargs,
|
|
201
145
|
)
|
|
202
146
|
|
|
@@ -210,12 +154,13 @@ class CallbackSpecList:
|
|
|
210
154
|
def __init__(self, factory=CallbackSpec):
|
|
211
155
|
self.items: List[CallbackSpec] = []
|
|
212
156
|
self.conventional_specs = set()
|
|
157
|
+
self._groupers: Dict[CallbackGroup, SpecListGrouper] = {}
|
|
213
158
|
self.factory = factory
|
|
214
159
|
|
|
215
160
|
def __repr__(self):
|
|
216
161
|
return f"{type(self).__name__}({self.items!r}, factory={self.factory!r})"
|
|
217
162
|
|
|
218
|
-
def _add_unbounded_callback(self, func,
|
|
163
|
+
def _add_unbounded_callback(self, func, transitions=None, **kwargs):
|
|
219
164
|
"""This list was a target for adding a func using decorator
|
|
220
165
|
`@<state|event>[.on|before|after|enter|exit]` syntax.
|
|
221
166
|
|
|
@@ -238,11 +183,7 @@ class CallbackSpecList:
|
|
|
238
183
|
event.
|
|
239
184
|
|
|
240
185
|
"""
|
|
241
|
-
|
|
242
|
-
if not getattr(func, "_specs_to_update", None):
|
|
243
|
-
func._specs_to_update = set()
|
|
244
|
-
if is_event:
|
|
245
|
-
func._specs_to_update.add(spec._update_func)
|
|
186
|
+
self._add(func, **kwargs)
|
|
246
187
|
func._transitions = transitions
|
|
247
188
|
|
|
248
189
|
return func
|
|
@@ -253,15 +194,16 @@ class CallbackSpecList:
|
|
|
253
194
|
def clear(self):
|
|
254
195
|
self.items = []
|
|
255
196
|
|
|
256
|
-
def grouper(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return
|
|
197
|
+
def grouper(self, group: CallbackGroup) -> SpecListGrouper:
|
|
198
|
+
if group not in self._groupers:
|
|
199
|
+
self._groupers[group] = SpecListGrouper(self, group)
|
|
200
|
+
return self._groupers[group]
|
|
260
201
|
|
|
261
|
-
def _add(self, func, group: CallbackGroup,
|
|
262
|
-
if
|
|
263
|
-
|
|
264
|
-
|
|
202
|
+
def _add(self, func, group: CallbackGroup, **kwargs):
|
|
203
|
+
if isinstance(func, CallbackSpec):
|
|
204
|
+
spec = func
|
|
205
|
+
else:
|
|
206
|
+
spec = self.factory(func, group, **kwargs)
|
|
265
207
|
|
|
266
208
|
if spec in self.items:
|
|
267
209
|
return
|
|
@@ -291,7 +233,7 @@ class CallbackWrapper:
|
|
|
291
233
|
unique_key: str,
|
|
292
234
|
) -> None:
|
|
293
235
|
self._callback = callback
|
|
294
|
-
self._iscoro =
|
|
236
|
+
self._iscoro = getattr(callback, "is_coroutine", False)
|
|
295
237
|
self.condition = condition
|
|
296
238
|
self.meta = meta
|
|
297
239
|
self.unique_key = unique_key
|
|
@@ -338,19 +280,21 @@ class CallbacksExecutor:
|
|
|
338
280
|
def __str__(self):
|
|
339
281
|
return ", ".join(str(c) for c in self)
|
|
340
282
|
|
|
341
|
-
def
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
continue
|
|
283
|
+
def add(self, key: str, spec: CallbackSpec, builder: Callable[[], Callable]):
|
|
284
|
+
if key in self.items_already_seen:
|
|
285
|
+
return
|
|
345
286
|
|
|
346
|
-
|
|
347
|
-
insort(self.items, callback)
|
|
287
|
+
self.items_already_seen.add(key)
|
|
348
288
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
289
|
+
condition = spec.cond if spec.cond is not None else allways_true
|
|
290
|
+
wrapper = CallbackWrapper(
|
|
291
|
+
callback=builder(),
|
|
292
|
+
condition=condition,
|
|
293
|
+
meta=spec,
|
|
294
|
+
unique_key=key,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
insort(self.items, wrapper)
|
|
354
298
|
|
|
355
299
|
async def async_call(self, *args, **kwargs):
|
|
356
300
|
return await asyncio.gather(
|
|
@@ -387,9 +331,6 @@ class CallbacksRegistry:
|
|
|
387
331
|
self._registry: Dict[str, CallbacksExecutor] = defaultdict(CallbacksExecutor)
|
|
388
332
|
self.has_async_callbacks: bool = False
|
|
389
333
|
|
|
390
|
-
def clear(self):
|
|
391
|
-
self._registry.clear()
|
|
392
|
-
|
|
393
334
|
def __getitem__(self, key: str) -> CallbacksExecutor:
|
|
394
335
|
return self._registry[key]
|
|
395
336
|
|
|
@@ -402,7 +343,8 @@ class CallbacksRegistry:
|
|
|
402
343
|
callback for callback in self[meta.group.build_key(specs)] if callback.meta == meta
|
|
403
344
|
):
|
|
404
345
|
continue
|
|
405
|
-
|
|
346
|
+
|
|
347
|
+
if meta.names_not_found:
|
|
406
348
|
raise AttrNotFound(
|
|
407
349
|
_("Did not found name '{}' from model or statemachine").format(
|
|
408
350
|
", ".join(meta.names_not_found)
|
|
@@ -416,3 +358,24 @@ class CallbacksRegistry:
|
|
|
416
358
|
self.has_async_callbacks = any(
|
|
417
359
|
callback._iscoro for executor in self._registry.values() for callback in executor
|
|
418
360
|
)
|
|
361
|
+
|
|
362
|
+
def call(self, key: str, *args, **kwargs):
|
|
363
|
+
if key not in self._registry:
|
|
364
|
+
return []
|
|
365
|
+
return self._registry[key].call(*args, **kwargs)
|
|
366
|
+
|
|
367
|
+
def async_call(self, key: str, *args, **kwargs):
|
|
368
|
+
return self._registry[key].async_call(*args, **kwargs)
|
|
369
|
+
|
|
370
|
+
def all(self, key: str, *args, **kwargs):
|
|
371
|
+
if key not in self._registry:
|
|
372
|
+
return True
|
|
373
|
+
return self._registry[key].all(*args, **kwargs)
|
|
374
|
+
|
|
375
|
+
def async_all(self, key: str, *args, **kwargs):
|
|
376
|
+
return self._registry[key].async_all(*args, **kwargs)
|
|
377
|
+
|
|
378
|
+
def str(self, key: str) -> str:
|
|
379
|
+
if key not in self._registry:
|
|
380
|
+
return ""
|
|
381
|
+
return str(self._registry[key])
|
statemachine/contrib/diagram.py
CHANGED
|
@@ -29,7 +29,7 @@ class DotGraphMachine:
|
|
|
29
29
|
transition_font_size = "9"
|
|
30
30
|
"""Transition font size in points"""
|
|
31
31
|
|
|
32
|
-
def __init__(self, machine):
|
|
32
|
+
def __init__(self, machine: StateMachine):
|
|
33
33
|
self.machine = machine
|
|
34
34
|
|
|
35
35
|
def _get_graph(self):
|
|
@@ -69,11 +69,11 @@ class DotGraphMachine:
|
|
|
69
69
|
def _actions_getter(self):
|
|
70
70
|
if isinstance(self.machine, StateMachine):
|
|
71
71
|
|
|
72
|
-
def getter(grouper):
|
|
73
|
-
return self.machine.
|
|
72
|
+
def getter(grouper) -> str:
|
|
73
|
+
return self.machine._callbacks.str(grouper.key)
|
|
74
74
|
else:
|
|
75
75
|
|
|
76
|
-
def getter(grouper):
|
|
76
|
+
def getter(grouper) -> str:
|
|
77
77
|
all_names = set(dir(self.machine))
|
|
78
78
|
return ", ".join(
|
|
79
79
|
str(c) for c in grouper if not c.is_convention or c.func in all_names
|