python-statemachine 2.1.2__tar.gz → 2.2.0__tar.gz
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.1.2 → python_statemachine-2.2.0}/PKG-INFO +4 -4
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/pyproject.toml +43 -39
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/__init__.py +1 -1
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/callbacks.py +98 -73
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/contrib/diagram.py +20 -6
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/dispatcher.py +63 -31
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/factory.py +71 -7
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/graph.py +6 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/mixins.py +1 -3
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/signature.py +2 -3
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/state.py +22 -25
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/statemachine.py +59 -84
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/states.py +6 -4
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/transition.py +31 -22
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/utils.py +4 -0
- python_statemachine-2.1.2/statemachine/locale/en/LC_MESSAGES/statemachine.mo +0 -0
- python_statemachine-2.1.2/statemachine/locale/pt_BR/LC_MESSAGES/statemachine.mo +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/LICENSE +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/README.md +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/contrib/__init__.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/event.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/event_data.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/events.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/exceptions.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/i18n.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/locale/en/LC_MESSAGES/statemachine.po +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/model.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/registry.py +0 -0
- {python_statemachine-2.1.2 → python_statemachine-2.2.0}/statemachine/transition_list.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-statemachine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python Finite State Machines made easy.
|
|
5
5
|
Home-page: https://github.com/fgmacedo/python-statemachine
|
|
6
6
|
License: MIT
|
|
@@ -8,17 +8,17 @@ Author: Fernando Macedo
|
|
|
8
8
|
Author-email: fgmacedo@gmail.com
|
|
9
9
|
Maintainer: Fernando Macedo
|
|
10
10
|
Maintainer-email: fgmacedo@gmail.com
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.9,<3.13
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Natural Language :: English
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries
|
|
23
23
|
Provides-Extra: diagrams
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-statemachine"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.2.0"
|
|
4
4
|
description = "Python Finite State Machines made easy."
|
|
5
5
|
authors = ["Fernando Macedo <fgmacedo@gmail.com>"]
|
|
6
6
|
maintainers = [
|
|
@@ -34,29 +34,27 @@ classifiers = [
|
|
|
34
34
|
diagrams = ["pydot"]
|
|
35
35
|
|
|
36
36
|
[tool.poetry.dependencies]
|
|
37
|
-
python = ">=3.
|
|
37
|
+
python = ">=3.9, <3.13"
|
|
38
38
|
|
|
39
39
|
[tool.poetry.group.dev.dependencies]
|
|
40
|
-
pytest = "^
|
|
41
|
-
pytest-cov = "^
|
|
42
|
-
pytest-sugar = "^0.
|
|
43
|
-
pydot = "^
|
|
44
|
-
ruff = "^0.
|
|
45
|
-
pre-commit = "^
|
|
46
|
-
mypy = "^0
|
|
47
|
-
black = "^22.12.0"
|
|
48
|
-
pdbpp = "^0.10.3"
|
|
40
|
+
pytest = "^8.1.1"
|
|
41
|
+
pytest-cov = "^5.0.0"
|
|
42
|
+
pytest-sugar = "^1.0.0"
|
|
43
|
+
pydot = "^2.0.0"
|
|
44
|
+
ruff = "^0.3.7"
|
|
45
|
+
pre-commit = "^3.7.0"
|
|
46
|
+
mypy = "^1.9.0"
|
|
49
47
|
pytest-mock = "^3.10.0"
|
|
50
48
|
pytest-profiling = "^1.7.0"
|
|
51
49
|
pytest-benchmark = "^4.0.0"
|
|
52
50
|
|
|
53
51
|
[tool.poetry.group.docs.dependencies]
|
|
54
|
-
Sphinx = "
|
|
55
|
-
sphinx-rtd-theme = "
|
|
56
|
-
myst-parser = "^0.
|
|
57
|
-
sphinx-gallery = "^0.
|
|
58
|
-
pillow = "^
|
|
59
|
-
sphinx-autobuild = "^
|
|
52
|
+
Sphinx = "7.2.6"
|
|
53
|
+
sphinx-rtd-theme = "2.0.0"
|
|
54
|
+
myst-parser = "^2.0.0"
|
|
55
|
+
sphinx-gallery = "^0.15.0"
|
|
56
|
+
pillow = "^10.3.0"
|
|
57
|
+
sphinx-autobuild = "^2024.4.16"
|
|
60
58
|
|
|
61
59
|
[build-system]
|
|
62
60
|
requires = ["poetry-core"]
|
|
@@ -91,24 +89,8 @@ max-line-length = 99
|
|
|
91
89
|
[tool.ruff]
|
|
92
90
|
src = ["statemachine"]
|
|
93
91
|
|
|
94
|
-
# Enable Pyflakes and pycodestyle rules.
|
|
95
|
-
select = [
|
|
96
|
-
"E", # pycodestyle errors
|
|
97
|
-
"W", # pycodestyle warnings
|
|
98
|
-
"F", # pyflakes
|
|
99
|
-
"I", # isort
|
|
100
|
-
"UP", # pyupgrade
|
|
101
|
-
"C", # flake8-comprehensions
|
|
102
|
-
"B", # flake8-bugbear
|
|
103
|
-
"PT", # flake8-pytest-style
|
|
104
|
-
]
|
|
105
|
-
ignore = [
|
|
106
|
-
"UP006", # `use-pep585-annotation` Requires Python3.9+
|
|
107
|
-
"UP035", # `use-pep585-annotation` Requires Python3.9+
|
|
108
|
-
"UP038", # `use-pep585-annotation` Requires Python3.9+
|
|
109
|
-
]
|
|
110
|
-
|
|
111
92
|
line-length = 99
|
|
93
|
+
target-version = "py312"
|
|
112
94
|
|
|
113
95
|
# Exclude a variety of commonly ignored directories.
|
|
114
96
|
exclude = [
|
|
@@ -131,19 +113,41 @@ exclude = [
|
|
|
131
113
|
"venv",
|
|
132
114
|
]
|
|
133
115
|
|
|
116
|
+
[tool.ruff.lint]
|
|
117
|
+
|
|
118
|
+
# Enable Pyflakes and pycodestyle rules.
|
|
119
|
+
select = [
|
|
120
|
+
"E", # pycodestyle errors
|
|
121
|
+
"W", # pycodestyle warnings
|
|
122
|
+
"F", # pyflakes
|
|
123
|
+
"I", # isort
|
|
124
|
+
"UP", # pyupgrade
|
|
125
|
+
"C", # flake8-comprehensions
|
|
126
|
+
"B", # flake8-bugbear
|
|
127
|
+
"PT", # flake8-pytest-style
|
|
128
|
+
]
|
|
129
|
+
ignore = [
|
|
130
|
+
"UP006", # `use-pep585-annotation` Requires Python3.9+
|
|
131
|
+
"UP035", # `use-pep585-annotation` Requires Python3.9+
|
|
132
|
+
"UP038", # `use-pep585-annotation` Requires Python3.9+
|
|
133
|
+
]
|
|
134
|
+
|
|
134
135
|
# Allow unused variables when underscore-prefixed.
|
|
135
136
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
[tool.ruff.lint.per-file-ignores]
|
|
139
|
+
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
|
|
140
|
+
"__init__.py" = ["E402"]
|
|
141
|
+
"path/to/file.py" = ["E402"]
|
|
142
|
+
"tests/examples/**.py" = ["B018"]
|
|
139
143
|
|
|
140
|
-
[tool.ruff.mccabe]
|
|
144
|
+
[tool.ruff.lint.mccabe]
|
|
141
145
|
max-complexity = 6
|
|
142
146
|
|
|
143
|
-
[tool.ruff.isort]
|
|
147
|
+
[tool.ruff.lint.isort]
|
|
144
148
|
force-single-line = true
|
|
145
149
|
|
|
146
|
-
[tool.ruff.pydocstyle]
|
|
150
|
+
[tool.ruff.lint.pydocstyle]
|
|
147
151
|
# Use Google-style docstrings.
|
|
148
152
|
convention = "google"
|
|
149
153
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from bisect import insort
|
|
1
2
|
from collections import defaultdict
|
|
2
3
|
from collections import deque
|
|
4
|
+
from enum import IntEnum
|
|
3
5
|
from typing import Callable
|
|
4
6
|
from typing import Dict
|
|
7
|
+
from typing import Generator
|
|
5
8
|
from typing import List
|
|
6
9
|
|
|
7
10
|
from .exceptions import AttrNotFound
|
|
@@ -9,27 +12,42 @@ from .i18n import _
|
|
|
9
12
|
from .utils import ensure_iterable
|
|
10
13
|
|
|
11
14
|
|
|
15
|
+
class CallbackPriority(IntEnum):
|
|
16
|
+
GENERIC = 0
|
|
17
|
+
INLINE = 10
|
|
18
|
+
DECORATOR = 20
|
|
19
|
+
NAMING = 30
|
|
20
|
+
AFTER = 40
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def allways_true(*args, **kwargs):
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
|
|
12
27
|
class CallbackWrapper:
|
|
13
28
|
def __init__(
|
|
14
29
|
self,
|
|
15
30
|
callback: Callable,
|
|
16
31
|
condition: Callable,
|
|
32
|
+
meta: "CallbackMeta",
|
|
17
33
|
unique_key: str,
|
|
18
|
-
expected_value: "bool | None" = None,
|
|
19
34
|
) -> None:
|
|
20
35
|
self._callback = callback
|
|
21
36
|
self.condition = condition
|
|
37
|
+
self.meta = meta
|
|
22
38
|
self.unique_key = unique_key
|
|
23
|
-
self.expected_value = expected_value
|
|
24
39
|
|
|
25
40
|
def __repr__(self):
|
|
26
41
|
return f"{type(self).__name__}({self.unique_key})"
|
|
27
42
|
|
|
43
|
+
def __str__(self):
|
|
44
|
+
return str(self.meta)
|
|
45
|
+
|
|
46
|
+
def __lt__(self, other):
|
|
47
|
+
return self.meta.priority < other.meta.priority
|
|
48
|
+
|
|
28
49
|
def __call__(self, *args, **kwargs):
|
|
29
|
-
|
|
30
|
-
if self.expected_value is not None:
|
|
31
|
-
return bool(result) == self.expected_value
|
|
32
|
-
return result
|
|
50
|
+
return self._callback(*args, **kwargs)
|
|
33
51
|
|
|
34
52
|
|
|
35
53
|
class CallbackMeta:
|
|
@@ -42,14 +60,22 @@ class CallbackMeta:
|
|
|
42
60
|
call is performed, to allow the proper callback resolution.
|
|
43
61
|
"""
|
|
44
62
|
|
|
45
|
-
def __init__(
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
func,
|
|
66
|
+
suppress_errors=False,
|
|
67
|
+
cond=None,
|
|
68
|
+
priority: CallbackPriority = CallbackPriority.NAMING,
|
|
69
|
+
expected_value=None,
|
|
70
|
+
):
|
|
46
71
|
self.func = func
|
|
47
72
|
self.suppress_errors = suppress_errors
|
|
48
|
-
self.cond =
|
|
73
|
+
self.cond = cond
|
|
49
74
|
self.expected_value = expected_value
|
|
75
|
+
self.priority = priority
|
|
50
76
|
|
|
51
77
|
def __repr__(self):
|
|
52
|
-
return f"{type(self).__name__}({self.func!r})"
|
|
78
|
+
return f"{type(self).__name__}({self.func!r}, suppress_errors={self.suppress_errors!r})"
|
|
53
79
|
|
|
54
80
|
def __str__(self):
|
|
55
81
|
return getattr(self.func, "__name__", self.func)
|
|
@@ -63,7 +89,10 @@ class CallbackMeta:
|
|
|
63
89
|
def _update_func(self, func):
|
|
64
90
|
self.func = func
|
|
65
91
|
|
|
66
|
-
def
|
|
92
|
+
def _wrap_callable(self, func, _expected_value):
|
|
93
|
+
return func
|
|
94
|
+
|
|
95
|
+
def build(self, resolver) -> Generator["CallbackWrapper", None, None]:
|
|
67
96
|
"""
|
|
68
97
|
Resolves the `func` into a usable callable.
|
|
69
98
|
|
|
@@ -71,25 +100,14 @@ class CallbackMeta:
|
|
|
71
100
|
resolver (callable): A method responsible to build and return a valid callable that
|
|
72
101
|
can receive arbitrary parameters like `*args, **kwargs`.
|
|
73
102
|
"""
|
|
74
|
-
callback
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
callback=callback,
|
|
81
|
-
condition=conditions.all,
|
|
103
|
+
for callback in resolver(self.func):
|
|
104
|
+
condition = next(resolver(self.cond)) if self.cond is not None else allways_true
|
|
105
|
+
yield CallbackWrapper(
|
|
106
|
+
callback=self._wrap_callable(callback, self.expected_value),
|
|
107
|
+
condition=condition,
|
|
108
|
+
meta=self,
|
|
82
109
|
unique_key=callback.unique_key,
|
|
83
|
-
expected_value=self.expected_value,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if not self.suppress_errors:
|
|
87
|
-
raise AttrNotFound(
|
|
88
|
-
_("Did not found name '{}' from model or statemachine").format(
|
|
89
|
-
self.func
|
|
90
|
-
)
|
|
91
110
|
)
|
|
92
|
-
return None
|
|
93
111
|
|
|
94
112
|
|
|
95
113
|
class BoolCallbackMeta(CallbackMeta):
|
|
@@ -102,18 +120,32 @@ class BoolCallbackMeta(CallbackMeta):
|
|
|
102
120
|
call is performed, to allow the proper callback resolution.
|
|
103
121
|
"""
|
|
104
122
|
|
|
105
|
-
def __init__(
|
|
106
|
-
self
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
func,
|
|
126
|
+
suppress_errors=False,
|
|
127
|
+
cond=None,
|
|
128
|
+
priority: CallbackPriority = CallbackPriority.NAMING,
|
|
129
|
+
expected_value=True,
|
|
130
|
+
):
|
|
131
|
+
super().__init__(
|
|
132
|
+
func, suppress_errors, cond, priority=priority, expected_value=expected_value
|
|
133
|
+
)
|
|
110
134
|
|
|
111
135
|
def __str__(self):
|
|
112
136
|
name = super().__str__()
|
|
113
137
|
return name if self.expected_value else f"!{name}"
|
|
114
138
|
|
|
139
|
+
def _wrap_callable(self, func, expected_value):
|
|
140
|
+
def bool_wrapper(*args, **kwargs):
|
|
141
|
+
return bool(func(*args, **kwargs)) == expected_value
|
|
142
|
+
|
|
143
|
+
return bool_wrapper
|
|
144
|
+
|
|
115
145
|
|
|
116
146
|
class CallbackMetaList:
|
|
147
|
+
"""List of `CallbackMeta` instances"""
|
|
148
|
+
|
|
117
149
|
def __init__(self, factory=CallbackMeta):
|
|
118
150
|
self.items: List[CallbackMeta] = []
|
|
119
151
|
self.factory = factory
|
|
@@ -157,6 +189,7 @@ class CallbackMetaList:
|
|
|
157
189
|
return func
|
|
158
190
|
|
|
159
191
|
def __call__(self, callback):
|
|
192
|
+
"""Allows usage of the callback list as a decorator."""
|
|
160
193
|
return self._add_unbounded_callback(callback)
|
|
161
194
|
|
|
162
195
|
def __iter__(self):
|
|
@@ -165,18 +198,13 @@ class CallbackMetaList:
|
|
|
165
198
|
def clear(self):
|
|
166
199
|
self.items = []
|
|
167
200
|
|
|
168
|
-
def _add(self, func,
|
|
201
|
+
def _add(self, func, **kwargs):
|
|
169
202
|
meta = self.factory(func, **kwargs)
|
|
170
|
-
if registry is not None and not registry(self, meta, prepend=prepend):
|
|
171
|
-
return
|
|
172
203
|
|
|
173
204
|
if meta in self.items:
|
|
174
205
|
return
|
|
175
206
|
|
|
176
|
-
|
|
177
|
-
self.items.insert(0, meta)
|
|
178
|
-
else:
|
|
179
|
-
self.items.append(meta)
|
|
207
|
+
self.items.append(meta)
|
|
180
208
|
return meta
|
|
181
209
|
|
|
182
210
|
def add(self, callbacks, **kwargs):
|
|
@@ -191,6 +219,8 @@ class CallbackMetaList:
|
|
|
191
219
|
|
|
192
220
|
|
|
193
221
|
class CallbacksExecutor:
|
|
222
|
+
"""A list of callbacks that can be executed in order."""
|
|
223
|
+
|
|
194
224
|
def __init__(self):
|
|
195
225
|
self.items: List[CallbackWrapper] = deque()
|
|
196
226
|
self.items_already_seen = set()
|
|
@@ -201,34 +231,26 @@ class CallbacksExecutor:
|
|
|
201
231
|
def __repr__(self):
|
|
202
232
|
return f"{type(self).__name__}({self.items!r})"
|
|
203
233
|
|
|
204
|
-
def
|
|
205
|
-
|
|
206
|
-
) -> "CallbackWrapper | None":
|
|
207
|
-
callback = callback_info.build(resolver)
|
|
208
|
-
if callback is None:
|
|
209
|
-
return None
|
|
234
|
+
def __str__(self):
|
|
235
|
+
return ", ".join(str(c) for c in self)
|
|
210
236
|
|
|
211
|
-
|
|
212
|
-
|
|
237
|
+
def _add(self, callback_meta: CallbackMeta, resolver: Callable):
|
|
238
|
+
for callback in callback_meta.build(resolver):
|
|
239
|
+
if callback.unique_key in self.items_already_seen:
|
|
240
|
+
continue
|
|
213
241
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
self.items.insert(0, callback)
|
|
217
|
-
else:
|
|
218
|
-
self.items.append(callback)
|
|
219
|
-
return callback
|
|
242
|
+
self.items_already_seen.add(callback.unique_key)
|
|
243
|
+
insort(self.items, callback)
|
|
220
244
|
|
|
221
245
|
def add(self, items: CallbackMetaList, resolver: Callable):
|
|
222
246
|
"""Validate configurations"""
|
|
223
247
|
for item in items:
|
|
224
|
-
self.
|
|
248
|
+
self._add(item, resolver)
|
|
225
249
|
return self
|
|
226
250
|
|
|
227
251
|
def call(self, *args, **kwargs):
|
|
228
252
|
return [
|
|
229
|
-
callback(*args, **kwargs)
|
|
230
|
-
for callback in self
|
|
231
|
-
if callback.condition(*args, **kwargs)
|
|
253
|
+
callback(*args, **kwargs) for callback in self if callback.condition(*args, **kwargs)
|
|
232
254
|
]
|
|
233
255
|
|
|
234
256
|
def all(self, *args, **kwargs):
|
|
@@ -237,24 +259,27 @@ class CallbacksExecutor:
|
|
|
237
259
|
|
|
238
260
|
class CallbacksRegistry:
|
|
239
261
|
def __init__(self) -> None:
|
|
240
|
-
self._registry: Dict[CallbackMetaList, CallbacksExecutor] = defaultdict(
|
|
241
|
-
CallbacksExecutor
|
|
242
|
-
)
|
|
262
|
+
self._registry: Dict[CallbackMetaList, CallbacksExecutor] = defaultdict(CallbacksExecutor)
|
|
243
263
|
|
|
244
|
-
def register(self,
|
|
245
|
-
executor_list = self[
|
|
246
|
-
executor_list.add(
|
|
264
|
+
def register(self, meta_list: CallbackMetaList, resolver):
|
|
265
|
+
executor_list = self[meta_list]
|
|
266
|
+
executor_list.add(meta_list, resolver)
|
|
247
267
|
return executor_list
|
|
248
268
|
|
|
249
|
-
def
|
|
250
|
-
|
|
269
|
+
def clear(self):
|
|
270
|
+
self._registry.clear()
|
|
271
|
+
|
|
272
|
+
def __getitem__(self, meta_list: CallbackMetaList) -> CallbacksExecutor:
|
|
273
|
+
return self._registry[meta_list]
|
|
251
274
|
|
|
252
|
-
def
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
meta:
|
|
256
|
-
|
|
257
|
-
):
|
|
258
|
-
return self[meta_list].add_one(meta, resolver, prepend=prepend)
|
|
275
|
+
def check(self, meta_list: CallbackMetaList):
|
|
276
|
+
executor = self[meta_list]
|
|
277
|
+
for meta in meta_list:
|
|
278
|
+
if meta.suppress_errors:
|
|
279
|
+
continue
|
|
259
280
|
|
|
260
|
-
|
|
281
|
+
if any(callback for callback in executor if callback.meta == meta):
|
|
282
|
+
continue
|
|
283
|
+
raise AttrNotFound(
|
|
284
|
+
_("Did not found name '{}' from model or statemachine").format(meta.func)
|
|
285
|
+
)
|
|
@@ -66,21 +66,35 @@ class DotGraphMachine:
|
|
|
66
66
|
fontsize=self.transition_font_size,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
def _actions_getter(self):
|
|
70
|
+
if isinstance(self.machine, StateMachine):
|
|
71
|
+
|
|
72
|
+
def getter(x):
|
|
73
|
+
return self.machine._callbacks(x)
|
|
74
|
+
else:
|
|
75
|
+
|
|
76
|
+
def getter(x):
|
|
77
|
+
return x
|
|
78
|
+
|
|
79
|
+
return getter
|
|
80
|
+
|
|
69
81
|
def _state_actions(self, state):
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
getter = self._actions_getter()
|
|
83
|
+
|
|
84
|
+
entry = str(getter(state.enter))
|
|
85
|
+
exit_ = str(getter(state.exit))
|
|
72
86
|
internal = ", ".join(
|
|
73
|
-
f"{transition.event} / {transition.on
|
|
87
|
+
f"{transition.event} / {str(getter(transition.on))}"
|
|
74
88
|
for transition in state.transitions
|
|
75
89
|
if transition.internal
|
|
76
90
|
)
|
|
77
91
|
|
|
78
92
|
if entry:
|
|
79
93
|
entry = f"entry / {entry}"
|
|
80
|
-
if
|
|
81
|
-
|
|
94
|
+
if exit_:
|
|
95
|
+
exit_ = f"exit / {exit_}"
|
|
82
96
|
|
|
83
|
-
actions = "\n".join(x for x in [entry,
|
|
97
|
+
actions = "\n".join(x for x in [entry, exit_, internal] if x)
|
|
84
98
|
|
|
85
99
|
if actions:
|
|
86
100
|
actions = f"\n{actions}"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections import namedtuple
|
|
2
2
|
from operator import attrgetter
|
|
3
3
|
from typing import Any
|
|
4
|
+
from typing import Generator
|
|
4
5
|
|
|
5
6
|
from .signature import SignatureAdapter
|
|
6
7
|
|
|
@@ -18,17 +19,10 @@ class ObjectConfig(namedtuple("ObjectConfig", "obj skip_attrs resolver_id")):
|
|
|
18
19
|
if isinstance(obj, ObjectConfig):
|
|
19
20
|
return obj
|
|
20
21
|
else:
|
|
21
|
-
return cls(obj,
|
|
22
|
-
|
|
23
|
-
def getattr(self, attr):
|
|
24
|
-
if attr in self.skip_attrs:
|
|
25
|
-
return
|
|
26
|
-
return getattr(self.obj, attr, None)
|
|
22
|
+
return cls(obj, skip_attrs or set(), str(id(obj)))
|
|
27
23
|
|
|
28
24
|
|
|
29
25
|
class WrapSearchResult:
|
|
30
|
-
is_empty = False
|
|
31
|
-
|
|
32
26
|
def __init__(self, attribute, resolver_id) -> None:
|
|
33
27
|
self.attribute = attribute
|
|
34
28
|
self.resolver_id = resolver_id
|
|
@@ -48,10 +42,6 @@ class WrapSearchResult:
|
|
|
48
42
|
return self._cache(*args, **kwds)
|
|
49
43
|
|
|
50
44
|
|
|
51
|
-
class EmptyWrapSearchResult(WrapSearchResult):
|
|
52
|
-
is_empty = True
|
|
53
|
-
|
|
54
|
-
|
|
55
45
|
class CallableSearchResult(WrapSearchResult):
|
|
56
46
|
def __init__(self, attribute, a_callable, resolver_id) -> None:
|
|
57
47
|
self.a_callable = a_callable
|
|
@@ -93,32 +83,74 @@ class EventSearchResult(WrapSearchResult):
|
|
|
93
83
|
return wrapper
|
|
94
84
|
|
|
95
85
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
def _search_callable_attr_is_property(
|
|
87
|
+
attr, configs: tuple[ObjectConfig]
|
|
88
|
+
) -> "WrapSearchResult | None":
|
|
89
|
+
# if the attr is a property, we'll try to find the object that has the
|
|
90
|
+
# property on the configs
|
|
91
|
+
attr_name = attr.fget.__name__
|
|
92
|
+
for obj, _skip_attrs, resolver_id in configs:
|
|
93
|
+
func = getattr(type(obj), attr_name, None)
|
|
94
|
+
if func is not None and func is attr:
|
|
95
|
+
return AttributeCallableSearchResult(attr_name, obj, resolver_id)
|
|
96
|
+
return None
|
|
99
97
|
|
|
100
|
-
for config in configs:
|
|
101
|
-
func = config.getattr(attr)
|
|
102
|
-
if func is not None:
|
|
103
|
-
if not callable(func):
|
|
104
|
-
return AttributeCallableSearchResult(
|
|
105
|
-
attr, config.obj, config.resolver_id
|
|
106
|
-
)
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
def _search_callable_attr_is_callable(attr, configs: tuple[ObjectConfig]) -> WrapSearchResult:
|
|
100
|
+
# if the attr is an unbounded method, we'll try to find the bounded method
|
|
101
|
+
# on the configs
|
|
102
|
+
if not hasattr(attr, "__self__"):
|
|
103
|
+
for obj, _skip_attrs, resolver_id in configs:
|
|
104
|
+
func = getattr(obj, attr.__name__, None)
|
|
105
|
+
if func is not None and func.__func__ is attr:
|
|
106
|
+
return CallableSearchResult(attr.__name__, func, resolver_id)
|
|
110
107
|
|
|
111
|
-
|
|
108
|
+
return CallableSearchResult(attr, attr, None)
|
|
112
109
|
|
|
113
|
-
return EmptyWrapSearchResult(attr, None)
|
|
114
110
|
|
|
111
|
+
def _search_callable_in_configs(
|
|
112
|
+
attr, configs: tuple[ObjectConfig]
|
|
113
|
+
) -> Generator[WrapSearchResult, None, None]:
|
|
114
|
+
for obj, skip_attrs, resolver_id in configs:
|
|
115
|
+
if attr in skip_attrs:
|
|
116
|
+
continue
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
if not hasattr(obj, attr):
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
func = getattr(obj, attr)
|
|
122
|
+
if not callable(func):
|
|
123
|
+
yield AttributeCallableSearchResult(attr, obj, resolver_id)
|
|
124
|
+
|
|
125
|
+
if getattr(func, "_is_sm_event", False):
|
|
126
|
+
yield EventSearchResult(attr, func, resolver_id)
|
|
127
|
+
|
|
128
|
+
yield CallableSearchResult(attr, func, resolver_id)
|
|
118
129
|
|
|
119
|
-
objects = [ObjectConfig.from_obj(obj) for obj in objects]
|
|
120
130
|
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
def search_callable(attr, configs: tuple) -> Generator[WrapSearchResult, None, None]: # noqa: C901
|
|
132
|
+
if isinstance(attr, property):
|
|
133
|
+
result = _search_callable_attr_is_property(attr, configs)
|
|
134
|
+
if result is not None:
|
|
135
|
+
yield result
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
if callable(attr):
|
|
139
|
+
yield _search_callable_attr_is_callable(attr, configs)
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
yield from _search_callable_in_configs(attr, configs)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def resolver_factory(objects: tuple[ObjectConfig]):
|
|
146
|
+
"""Factory that returns a configured resolver."""
|
|
147
|
+
|
|
148
|
+
def wrapper(attr) -> Generator[WrapSearchResult, None, None]:
|
|
149
|
+
yield from search_callable(attr, objects)
|
|
123
150
|
|
|
124
151
|
return wrapper
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def resolver_factory_from_objects(*objects: tuple[Any]):
|
|
155
|
+
configs = tuple(ObjectConfig.from_obj(o) for o in objects)
|
|
156
|
+
return resolver_factory(configs)
|