python-statemachine 2.5.0__py3-none-any.whl → 2.6.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.5.0.dist-info → python_statemachine-2.6.0.dist-info}/METADATA +6 -2
- {python_statemachine-2.5.0.dist-info → python_statemachine-2.6.0.dist-info}/RECORD +17 -17
- {python_statemachine-2.5.0.dist-info → python_statemachine-2.6.0.dist-info}/WHEEL +1 -1
- statemachine/__init__.py +1 -1
- statemachine/engines/async_.py +28 -0
- statemachine/engines/sync.py +26 -0
- statemachine/event.py +2 -2
- statemachine/locale/en/LC_MESSAGES/statemachine.po +35 -20
- statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +52 -26
- statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +53 -26
- statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +34 -20
- statemachine/mixins.py +11 -0
- statemachine/signature.py +79 -7
- statemachine/spec_parser.py +62 -8
- statemachine/statemachine.py +22 -3
- statemachine/transition_mixin.py +9 -2
- {python_statemachine-2.5.0.dist-info → python_statemachine-2.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-statemachine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: Python Finite State Machines made easy.
|
|
5
5
|
Project-URL: homepage, https://github.com/fgmacedo/python-statemachine
|
|
6
6
|
Author-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
7
7
|
Maintainer-email: Fernando Macedo <fgmacedo@gmail.com>
|
|
8
8
|
License: MIT License
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Framework :: AsyncIO
|
|
11
12
|
Classifier: Framework :: Django
|
|
@@ -19,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
24
|
Classifier: Topic :: Home Automation
|
|
23
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
24
26
|
Requires-Python: >=3.7
|
|
@@ -127,6 +129,8 @@ You can now create an instance:
|
|
|
127
129
|
This state machine can be represented graphically as follows:
|
|
128
130
|
|
|
129
131
|
```py
|
|
132
|
+
>>> # This example will only run on automated tests if dot is present
|
|
133
|
+
>>> getfixture("requires_dot_installed")
|
|
130
134
|
>>> img_path = "docs/images/readme_trafficlightmachine.png"
|
|
131
135
|
>>> sm._graph().write_png(img_path)
|
|
132
136
|
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
statemachine/__init__.py,sha256=
|
|
1
|
+
statemachine/__init__.py,sha256=srtcRn5wkZna0Qd1bV8rTm6SF1JM-tg47_O3kcTkBwM,226
|
|
2
2
|
statemachine/callbacks.py,sha256=UUOuMotQDV3ZkpqjWp3CIL3UrA70Bh_xYDf4Ugjlou8,11475
|
|
3
3
|
statemachine/dispatcher.py,sha256=Ai8i79Lo5HdUJ-toOVg4NDUzAy88sryBHDbYdl_7sWE,7785
|
|
4
|
-
statemachine/event.py,sha256=
|
|
4
|
+
statemachine/event.py,sha256=PceLkT5oQ4LRXHJGMqNyA43-y-CqJL9rUVCX94qaBL0,4583
|
|
5
5
|
statemachine/event_data.py,sha256=H9lp_XnvHSK9YErUOCvMK3ZBjWhC-xDSOJ2gZxtmrq8,2261
|
|
6
6
|
statemachine/events.py,sha256=UTYJu8te_bxiORTQpoXY5tB_x-ymVPWDOREcxCyhExA,1018
|
|
7
7
|
statemachine/exceptions.py,sha256=vHVQPTTMMkVvySNbN6XZPciBryvpY608LDe3MCnmxFU,1124
|
|
8
8
|
statemachine/factory.py,sha256=crL2FPfzdku3STlbxaF9N3oV9L1BFWoCAT9K3zEnBYo,9219
|
|
9
9
|
statemachine/graph.py,sha256=KtwB1CYckaLjTgQD9tEeuaEzJje9q3fPVpBViW5TgSk,487
|
|
10
10
|
statemachine/i18n.py,sha256=NLvGseaORmQ0G-V_J8tkjoxh_piWMOm2CI6mBQpLamc,362
|
|
11
|
-
statemachine/mixins.py,sha256=
|
|
11
|
+
statemachine/mixins.py,sha256=8qxZZfBwVdFcr3oPFVWHGzgmAubH6VgQus5x3c6VEE0,1706
|
|
12
12
|
statemachine/model.py,sha256=OylI3FjMiHpYyDl9mtK1zEJMeSvemaN4giQDonpc8kI,211
|
|
13
13
|
statemachine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
statemachine/registry.py,sha256=HmV9sUGkYVrNUZxJYoZo-trSUis7dIun_WcGktblgc8,922
|
|
15
|
-
statemachine/signature.py,sha256=
|
|
16
|
-
statemachine/spec_parser.py,sha256=
|
|
15
|
+
statemachine/signature.py,sha256=eoSCQkgLo4o68461WrF2_sUrlhS4gR-m8mGs6Xb-CFQ,9866
|
|
16
|
+
statemachine/spec_parser.py,sha256=S2vsxIhSHUKrNhtwv_gXhc8H38n2ZCv-6Tk-18A8uR0,7298
|
|
17
17
|
statemachine/state.py,sha256=a2uJZyn8sM8HdnVN_FesCgcjoeDE8gfXCAChbLm4p9g,9504
|
|
18
|
-
statemachine/statemachine.py,sha256=
|
|
18
|
+
statemachine/statemachine.py,sha256=leIYvPcEyotblUSnZZZHDkUYMsrxJZPFzYzFJCpa9Kk,11584
|
|
19
19
|
statemachine/states.py,sha256=pPROZwIyE3_tlBGL3DJXnM3gr1pWsWICEMMo2c742RY,4889
|
|
20
20
|
statemachine/transition.py,sha256=YDpI6NuCipW4cF4GVIt3o8ACE6VhWAQWHaZKjmjSQRA,5335
|
|
21
21
|
statemachine/transition_list.py,sha256=I9viQ0zr6E8oL3WEESkst9DwQeTNJ6nwDAQXsWeo3_c,4179
|
|
22
|
-
statemachine/transition_mixin.py,sha256=
|
|
22
|
+
statemachine/transition_mixin.py,sha256=wUJ3alWMMM7hch-IJuzUaonmcvLr0rGnvWo962NYLt4,2894
|
|
23
23
|
statemachine/utils.py,sha256=DpcrGqlbrnT-ogh-BogG0L07EG3KirHOsKORHlspDlI,1041
|
|
24
24
|
statemachine/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
statemachine/contrib/diagram.py,sha256=CVNIhBTedik_02b-4KUua_3_HtiO1TYQNWOg3ulkqiE,7159
|
|
26
26
|
statemachine/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
statemachine/engines/async_.py,sha256=
|
|
27
|
+
statemachine/engines/async_.py,sha256=aiNv6NsfEJVm-p16FNnRVZgtwNy9iFRN3tWX01uX6LI,6322
|
|
28
28
|
statemachine/engines/base.py,sha256=d65JvngOrp8sE4RR8i2wrd72wzIci9-3InMLyGdxsZQ,1201
|
|
29
|
-
statemachine/engines/sync.py,sha256=
|
|
30
|
-
statemachine/locale/en/LC_MESSAGES/statemachine.po,sha256=
|
|
31
|
-
statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po,sha256=
|
|
32
|
-
statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po,sha256=
|
|
33
|
-
statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po,sha256=
|
|
34
|
-
python_statemachine-2.
|
|
35
|
-
python_statemachine-2.
|
|
36
|
-
python_statemachine-2.
|
|
37
|
-
python_statemachine-2.
|
|
29
|
+
statemachine/engines/sync.py,sha256=HmHBMpgTtQJov1pbSPOkmWTEYkunfGa8alSXTNSDknw,6081
|
|
30
|
+
statemachine/locale/en/LC_MESSAGES/statemachine.po,sha256=TuhNII8U73A-Gge-gxUKEb73g2DkQBSFHDVs3Eh1DRc,2837
|
|
31
|
+
statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po,sha256=OebfrHo04S65Y_cw_ZmdaPDpX-YitaGvNgrRp3v8VbU,5824
|
|
32
|
+
statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po,sha256=7eWBeXqJz0XV8wTthCFeWtKnUJSbys-oBX73xXaQfLA,4211
|
|
33
|
+
statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po,sha256=lnCRgxeIvyDkOZVdxcGqRTwlSm9BVHjJmycumFDz0YU,3858
|
|
34
|
+
python_statemachine-2.6.0.dist-info/METADATA,sha256=xBVFYCnyHePX5PMrfdLzw8tyXsx65P6njeLj35mATF8,14252
|
|
35
|
+
python_statemachine-2.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
python_statemachine-2.6.0.dist-info/licenses/LICENSE,sha256=zcP7TsJMqaFxuTvLRZPT7nJl3_ppjxR9Z76BE9pL5zc,1074
|
|
37
|
+
python_statemachine-2.6.0.dist-info/RECORD,,
|
statemachine/__init__.py
CHANGED
statemachine/engines/async_.py
CHANGED
|
@@ -97,6 +97,34 @@ class AsyncEngine(BaseEngine):
|
|
|
97
97
|
|
|
98
98
|
return result if executed else None
|
|
99
99
|
|
|
100
|
+
async def enabled_events(self, *args, **kwargs):
|
|
101
|
+
sm = self.sm
|
|
102
|
+
enabled = {}
|
|
103
|
+
for transition in sm.current_state.transitions:
|
|
104
|
+
for event in transition.events:
|
|
105
|
+
if event in enabled:
|
|
106
|
+
continue
|
|
107
|
+
extended_kwargs = kwargs.copy()
|
|
108
|
+
extended_kwargs.update(
|
|
109
|
+
{
|
|
110
|
+
"machine": sm,
|
|
111
|
+
"model": sm.model,
|
|
112
|
+
"event": getattr(sm, event),
|
|
113
|
+
"source": transition.source,
|
|
114
|
+
"target": transition.target,
|
|
115
|
+
"state": sm.current_state,
|
|
116
|
+
"transition": transition,
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
try:
|
|
120
|
+
if await sm._callbacks.async_all(
|
|
121
|
+
transition.cond.key, *args, **extended_kwargs
|
|
122
|
+
):
|
|
123
|
+
enabled[event] = getattr(sm, event)
|
|
124
|
+
except Exception:
|
|
125
|
+
enabled[event] = getattr(sm, event)
|
|
126
|
+
return list(enabled.values())
|
|
127
|
+
|
|
100
128
|
async def _activate(self, trigger_data: TriggerData, transition: "Transition"):
|
|
101
129
|
event_data = EventData(trigger_data=trigger_data, transition=transition)
|
|
102
130
|
args, kwargs = event_data.args, event_data.extended_kwargs
|
statemachine/engines/sync.py
CHANGED
|
@@ -99,6 +99,32 @@ class SyncEngine(BaseEngine):
|
|
|
99
99
|
|
|
100
100
|
return result if executed else None
|
|
101
101
|
|
|
102
|
+
def enabled_events(self, *args, **kwargs):
|
|
103
|
+
sm = self.sm
|
|
104
|
+
enabled = {}
|
|
105
|
+
for transition in sm.current_state.transitions:
|
|
106
|
+
for event in transition.events:
|
|
107
|
+
if event in enabled:
|
|
108
|
+
continue
|
|
109
|
+
extended_kwargs = kwargs.copy()
|
|
110
|
+
extended_kwargs.update(
|
|
111
|
+
{
|
|
112
|
+
"machine": sm,
|
|
113
|
+
"model": sm.model,
|
|
114
|
+
"event": getattr(sm, event),
|
|
115
|
+
"source": transition.source,
|
|
116
|
+
"target": transition.target,
|
|
117
|
+
"state": sm.current_state,
|
|
118
|
+
"transition": transition,
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
if sm._callbacks.all(transition.cond.key, *args, **extended_kwargs):
|
|
123
|
+
enabled[event] = getattr(sm, event)
|
|
124
|
+
except Exception:
|
|
125
|
+
enabled[event] = getattr(sm, event)
|
|
126
|
+
return list(enabled.values())
|
|
127
|
+
|
|
102
128
|
def _activate(self, trigger_data: TriggerData, transition: "Transition"):
|
|
103
129
|
event_data = EventData(trigger_data=trigger_data, transition=transition)
|
|
104
130
|
args, kwargs = event_data.args, event_data.extended_kwargs
|
statemachine/event.py
CHANGED
|
@@ -28,9 +28,9 @@ _event_data_kwargs = {
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class Event(AddCallbacksMixin, str):
|
|
31
|
-
"""An event
|
|
31
|
+
"""An event triggers a signal that something has happened.
|
|
32
32
|
|
|
33
|
-
They are
|
|
33
|
+
They are sent to a state machine and allow the state machine to react.
|
|
34
34
|
|
|
35
35
|
An event starts a :ref:`Transition`, which can be thought of as a “cause” that initiates a
|
|
36
36
|
change in the state of the system.
|
|
@@ -1,24 +1,35 @@
|
|
|
1
|
-
# This file is distributed under the same license as the
|
|
1
|
+
# This file is distributed under the same license as the project.
|
|
2
2
|
# Fernando Macedo <fgmacedo@gmail.com>, 2024.
|
|
3
3
|
#
|
|
4
4
|
msgid ""
|
|
5
5
|
msgstr ""
|
|
6
|
-
"Project-Id-Version:
|
|
6
|
+
"Project-Id-Version: 2.4.0\n"
|
|
7
7
|
"Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
|
|
8
|
-
"POT-Creation-Date:
|
|
8
|
+
"POT-Creation-Date: 2026-02-13 18:28-0300\n"
|
|
9
9
|
"PO-Revision-Date: 2024-06-07 17:41-0300\n"
|
|
10
10
|
"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
|
|
11
|
+
"Language: en\n"
|
|
12
|
+
"Language-Team: en <LL@li.org>\n"
|
|
13
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
11
14
|
"MIME-Version: 1.0\n"
|
|
12
15
|
"Content-Type: text/plain; charset=utf-8\n"
|
|
13
16
|
"Content-Transfer-Encoding: 8bit\n"
|
|
14
|
-
"Generated-By: Babel 2.
|
|
17
|
+
"Generated-By: Babel 2.16.0\n"
|
|
15
18
|
|
|
16
|
-
#: statemachine/callbacks.py:
|
|
19
|
+
#: statemachine/callbacks.py:349 statemachine/callbacks.py:354
|
|
20
|
+
msgid "Did not found name '{}' from model or statemachine"
|
|
21
|
+
msgstr ""
|
|
22
|
+
|
|
23
|
+
#: statemachine/dispatcher.py:126
|
|
17
24
|
msgid "Failed to parse boolean expression '{}'"
|
|
18
25
|
msgstr ""
|
|
19
26
|
|
|
20
|
-
#: statemachine/
|
|
21
|
-
msgid "
|
|
27
|
+
#: statemachine/event.py:90
|
|
28
|
+
msgid "Cannot add callback '{}' to an event with no transitions."
|
|
29
|
+
msgstr ""
|
|
30
|
+
|
|
31
|
+
#: statemachine/event.py:123
|
|
32
|
+
msgid "Event {} cannot be called without a SM instance"
|
|
22
33
|
msgstr ""
|
|
23
34
|
|
|
24
35
|
#: statemachine/exceptions.py:24
|
|
@@ -29,64 +40,68 @@ msgstr ""
|
|
|
29
40
|
msgid "Can't {} when in {}."
|
|
30
41
|
msgstr ""
|
|
31
42
|
|
|
32
|
-
#: statemachine/factory.py:
|
|
43
|
+
#: statemachine/factory.py:73
|
|
33
44
|
msgid "There are no states."
|
|
34
45
|
msgstr ""
|
|
35
46
|
|
|
36
|
-
#: statemachine/factory.py:
|
|
47
|
+
#: statemachine/factory.py:76
|
|
37
48
|
msgid "There are no events."
|
|
38
49
|
msgstr ""
|
|
39
50
|
|
|
40
|
-
#: statemachine/factory.py:
|
|
51
|
+
#: statemachine/factory.py:88
|
|
41
52
|
msgid ""
|
|
42
53
|
"There should be one and only one initial state. You currently have these:"
|
|
43
54
|
" {!r}"
|
|
44
55
|
msgstr ""
|
|
45
56
|
|
|
46
|
-
#: statemachine/factory.py:
|
|
57
|
+
#: statemachine/factory.py:101
|
|
47
58
|
msgid "Cannot declare transitions from final state. Invalid state(s): {}"
|
|
48
59
|
msgstr ""
|
|
49
60
|
|
|
50
|
-
#: statemachine/factory.py:
|
|
61
|
+
#: statemachine/factory.py:109
|
|
51
62
|
msgid ""
|
|
52
63
|
"All non-final states should have at least one outgoing transition. These "
|
|
53
64
|
"states have no outgoing transition: {!r}"
|
|
54
65
|
msgstr ""
|
|
55
66
|
|
|
56
|
-
#: statemachine/factory.py:
|
|
67
|
+
#: statemachine/factory.py:123
|
|
57
68
|
msgid ""
|
|
58
69
|
"All non-final states should have at least one path to a final state. "
|
|
59
70
|
"These states have no path to a final state: {!r}"
|
|
60
71
|
msgstr ""
|
|
61
72
|
|
|
62
|
-
#: statemachine/factory.py:
|
|
73
|
+
#: statemachine/factory.py:147
|
|
63
74
|
msgid ""
|
|
64
75
|
"There are unreachable states. The statemachine graph should have a single"
|
|
65
76
|
" component. Disconnected states: {}"
|
|
66
77
|
msgstr ""
|
|
67
78
|
|
|
68
|
-
#: statemachine/factory.py:
|
|
79
|
+
#: statemachine/factory.py:253
|
|
69
80
|
msgid "An event in the '{}' has no id."
|
|
70
81
|
msgstr ""
|
|
71
82
|
|
|
72
|
-
#: statemachine/mixins.py:
|
|
83
|
+
#: statemachine/mixins.py:28
|
|
73
84
|
msgid "{!r} is not a valid state machine name."
|
|
74
85
|
msgstr ""
|
|
75
86
|
|
|
76
|
-
#: statemachine/state.py:
|
|
87
|
+
#: statemachine/state.py:194
|
|
77
88
|
msgid "State overriding is not allowed. Trying to add '{}' to {}"
|
|
78
89
|
msgstr ""
|
|
79
90
|
|
|
80
|
-
#: statemachine/statemachine.py:
|
|
91
|
+
#: statemachine/statemachine.py:89
|
|
81
92
|
msgid "There are no states or transitions."
|
|
82
93
|
msgstr ""
|
|
83
94
|
|
|
84
|
-
#: statemachine/statemachine.py:
|
|
95
|
+
#: statemachine/statemachine.py:277
|
|
85
96
|
msgid ""
|
|
86
97
|
"There's no current state set. In async code, did you activate the initial"
|
|
87
98
|
" state? (e.g., `await sm.activate_initial_state()`)"
|
|
88
99
|
msgstr ""
|
|
89
100
|
|
|
90
|
-
#: statemachine/
|
|
101
|
+
#: statemachine/transition_mixin.py:15
|
|
102
|
+
msgid "{} only supports the decorator syntax to register callbacks."
|
|
103
|
+
msgstr ""
|
|
104
|
+
|
|
105
|
+
#: statemachine/engines/async_.py:18
|
|
91
106
|
msgid "Only RTC is supported on async engine"
|
|
92
107
|
msgstr ""
|
|
@@ -5,22 +5,32 @@ msgid ""
|
|
|
5
5
|
msgstr ""
|
|
6
6
|
"Project-Id-Version: 2.4.0\n"
|
|
7
7
|
"Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
|
|
8
|
-
"POT-Creation-Date:
|
|
8
|
+
"POT-Creation-Date: 2026-02-13 18:28-0300\n"
|
|
9
9
|
"PO-Revision-Date: 2024-06-07 17:41-0300\n"
|
|
10
10
|
"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
|
|
11
|
-
"Language
|
|
11
|
+
"Language: hi_IN\n"
|
|
12
|
+
"Language-Team: hi_IN <LL@li.org>\n"
|
|
13
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
12
14
|
"MIME-Version: 1.0\n"
|
|
13
15
|
"Content-Type: text/plain; charset=utf-8\n"
|
|
14
16
|
"Content-Transfer-Encoding: 8bit\n"
|
|
15
|
-
"Generated-By: Babel 2.
|
|
17
|
+
"Generated-By: Babel 2.16.0\n"
|
|
16
18
|
|
|
17
|
-
#: statemachine/callbacks.py:
|
|
19
|
+
#: statemachine/callbacks.py:349 statemachine/callbacks.py:354
|
|
20
|
+
msgid "Did not found name '{}' from model or statemachine"
|
|
21
|
+
msgstr "मॉडल या स्टेटमशीन में नाम '{}' नहीं मिला"
|
|
22
|
+
|
|
23
|
+
#: statemachine/dispatcher.py:126
|
|
18
24
|
msgid "Failed to parse boolean expression '{}'"
|
|
19
25
|
msgstr "बूलियन अभिव्यक्ति '{}' को पार्स करने में विफल रहा"
|
|
20
26
|
|
|
21
|
-
#: statemachine/
|
|
22
|
-
msgid "
|
|
23
|
-
msgstr "
|
|
27
|
+
#: statemachine/event.py:90
|
|
28
|
+
msgid "Cannot add callback '{}' to an event with no transitions."
|
|
29
|
+
msgstr "बिना ट्रांज़िशन वाले इवेंट में कॉलबैक '{}' नहीं जोड़ सकते।"
|
|
30
|
+
|
|
31
|
+
#: statemachine/event.py:123
|
|
32
|
+
msgid "Event {} cannot be called without a SM instance"
|
|
33
|
+
msgstr "इवेंट {} को SM इंस्टेंस के बिना कॉल नहीं किया जा सकता"
|
|
24
34
|
|
|
25
35
|
#: statemachine/exceptions.py:24
|
|
26
36
|
msgid "{!r} is not a valid state value."
|
|
@@ -30,64 +40,80 @@ msgstr "{!r} एक मान्य स्टेट मान नहीं ह
|
|
|
30
40
|
msgid "Can't {} when in {}."
|
|
31
41
|
msgstr "{} स्थिति में {} नहीं कर सकते।"
|
|
32
42
|
|
|
33
|
-
#: statemachine/factory.py:
|
|
43
|
+
#: statemachine/factory.py:73
|
|
34
44
|
msgid "There are no states."
|
|
35
45
|
msgstr "कोई स्टेट नहीं है।"
|
|
36
46
|
|
|
37
|
-
#: statemachine/factory.py:
|
|
47
|
+
#: statemachine/factory.py:76
|
|
38
48
|
msgid "There are no events."
|
|
39
49
|
msgstr "कोई इवेंट नहीं है।"
|
|
40
50
|
|
|
41
|
-
#: statemachine/factory.py:
|
|
51
|
+
#: statemachine/factory.py:88
|
|
42
52
|
msgid ""
|
|
43
53
|
"There should be one and only one initial state. You currently have these:"
|
|
44
54
|
" {!r}"
|
|
45
|
-
msgstr "
|
|
55
|
+
msgstr ""
|
|
56
|
+
"एक और केवल एक प्रारंभिक स्टेट होना चाहिए। वर्तमान में आपके पास ये हैं: "
|
|
57
|
+
"{!r}"
|
|
46
58
|
|
|
47
|
-
#: statemachine/factory.py:
|
|
59
|
+
#: statemachine/factory.py:101
|
|
48
60
|
msgid "Cannot declare transitions from final state. Invalid state(s): {}"
|
|
49
61
|
msgstr "अंतिम स्टेट से ट्रांज़िशन घोषित नहीं कर सकते। अमान्य स्टेट: {}"
|
|
50
62
|
|
|
51
|
-
#: statemachine/factory.py:
|
|
63
|
+
#: statemachine/factory.py:109
|
|
52
64
|
msgid ""
|
|
53
65
|
"All non-final states should have at least one outgoing transition. These "
|
|
54
66
|
"states have no outgoing transition: {!r}"
|
|
55
|
-
msgstr "
|
|
67
|
+
msgstr ""
|
|
68
|
+
"सभी गैर-अंतिम स्टेट में कम से कम एक आउटगोइंग ट्रांज़िशन होना चाहिए। इन "
|
|
69
|
+
"स्टेट में कोई आउटगोइंग ट्रांज़िशन नहीं है: {!r}"
|
|
56
70
|
|
|
57
|
-
#: statemachine/factory.py:
|
|
71
|
+
#: statemachine/factory.py:123
|
|
58
72
|
msgid ""
|
|
59
73
|
"All non-final states should have at least one path to a final state. "
|
|
60
74
|
"These states have no path to a final state: {!r}"
|
|
61
|
-
msgstr "
|
|
75
|
+
msgstr ""
|
|
76
|
+
"सभी गैर-अंतिम स्टेट में अंतिम स्टेट तक कम से कम एक पथ होना चाहिए। इन "
|
|
77
|
+
"स्टेट में अंतिम स्टेट तक कोई पथ नहीं है: {!r}"
|
|
62
78
|
|
|
63
|
-
#: statemachine/factory.py:
|
|
79
|
+
#: statemachine/factory.py:147
|
|
64
80
|
msgid ""
|
|
65
81
|
"There are unreachable states. The statemachine graph should have a single"
|
|
66
82
|
" component. Disconnected states: {}"
|
|
67
|
-
msgstr "
|
|
83
|
+
msgstr ""
|
|
84
|
+
"कुछ स्टेट पहुंच योग्य नहीं हैं। स्टेटमशीन ग्राफ में एक ही घटक होना चाहिए।"
|
|
85
|
+
" डिस्कनेक्टेड स्टेट: {}"
|
|
68
86
|
|
|
69
|
-
#: statemachine/factory.py:
|
|
87
|
+
#: statemachine/factory.py:253
|
|
70
88
|
msgid "An event in the '{}' has no id."
|
|
71
89
|
msgstr "'{}' में एक इवेंट का आईडी नहीं है।"
|
|
72
90
|
|
|
73
|
-
#: statemachine/mixins.py:
|
|
91
|
+
#: statemachine/mixins.py:28
|
|
74
92
|
msgid "{!r} is not a valid state machine name."
|
|
75
93
|
msgstr "{!r} एक मान्य स्टेटमशीन नाम नहीं है।"
|
|
76
94
|
|
|
77
|
-
#: statemachine/state.py:
|
|
95
|
+
#: statemachine/state.py:194
|
|
78
96
|
msgid "State overriding is not allowed. Trying to add '{}' to {}"
|
|
79
|
-
msgstr "
|
|
97
|
+
msgstr ""
|
|
98
|
+
"स्टेट ओवरराइड करना अनुमति नहीं है। '{}' को {} में जोड़ने की कोशिश कर रहे "
|
|
99
|
+
"हैं"
|
|
80
100
|
|
|
81
|
-
#: statemachine/statemachine.py:
|
|
101
|
+
#: statemachine/statemachine.py:89
|
|
82
102
|
msgid "There are no states or transitions."
|
|
83
103
|
msgstr "कोई स्टेट या ट्रांज़िशन नहीं हैं।"
|
|
84
104
|
|
|
85
|
-
#: statemachine/statemachine.py:
|
|
105
|
+
#: statemachine/statemachine.py:277
|
|
86
106
|
msgid ""
|
|
87
107
|
"There's no current state set. In async code, did you activate the initial"
|
|
88
108
|
" state? (e.g., `await sm.activate_initial_state()`)"
|
|
89
|
-
msgstr "
|
|
109
|
+
msgstr ""
|
|
110
|
+
"कोई वर्तमान स्टेट सेट नहीं है। असिंक्रोनस कोड में, क्या आपने प्रारंभिक "
|
|
111
|
+
"स्टेट को सक्रिय किया? (उदाहरण: `await sm.activate_initial_state()`)"
|
|
112
|
+
|
|
113
|
+
#: statemachine/transition_mixin.py:15
|
|
114
|
+
msgid "{} only supports the decorator syntax to register callbacks."
|
|
115
|
+
msgstr "{} केवल कॉलबैक रजिस्टर करने के लिए डेकोरेटर सिंटैक्स का समर्थन करता है।"
|
|
90
116
|
|
|
91
|
-
#: statemachine/engines/async_.py:
|
|
117
|
+
#: statemachine/engines/async_.py:18
|
|
92
118
|
msgid "Only RTC is supported on async engine"
|
|
93
119
|
msgstr "असिंक्रोनस इंजन पर केवल RTC समर्थित है"
|
|
@@ -5,22 +5,32 @@ msgid ""
|
|
|
5
5
|
msgstr ""
|
|
6
6
|
"Project-Id-Version: 2.4.0\n"
|
|
7
7
|
"Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
|
|
8
|
-
"POT-Creation-Date:
|
|
8
|
+
"POT-Creation-Date: 2026-02-13 18:28-0300\n"
|
|
9
9
|
"PO-Revision-Date: 2024-06-07 17:41-0300\n"
|
|
10
10
|
"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
|
|
11
|
-
"Language
|
|
11
|
+
"Language: pt_BR\n"
|
|
12
|
+
"Language-Team: pt_BR <LL@li.org>\n"
|
|
13
|
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
12
14
|
"MIME-Version: 1.0\n"
|
|
13
15
|
"Content-Type: text/plain; charset=utf-8\n"
|
|
14
16
|
"Content-Transfer-Encoding: 8bit\n"
|
|
15
|
-
"Generated-By: Babel 2.
|
|
17
|
+
"Generated-By: Babel 2.16.0\n"
|
|
16
18
|
|
|
17
|
-
#: statemachine/callbacks.py:
|
|
19
|
+
#: statemachine/callbacks.py:349 statemachine/callbacks.py:354
|
|
20
|
+
msgid "Did not found name '{}' from model or statemachine"
|
|
21
|
+
msgstr "Nome '{}' não encontrado no modelo ou na máquina de estados"
|
|
22
|
+
|
|
23
|
+
#: statemachine/dispatcher.py:126
|
|
18
24
|
msgid "Failed to parse boolean expression '{}'"
|
|
19
25
|
msgstr "Falha ao interpretar a expressão booleana '{}'"
|
|
20
26
|
|
|
21
|
-
#: statemachine/
|
|
22
|
-
msgid "
|
|
23
|
-
msgstr "
|
|
27
|
+
#: statemachine/event.py:90
|
|
28
|
+
msgid "Cannot add callback '{}' to an event with no transitions."
|
|
29
|
+
msgstr "Não é possível adicionar callback '{}' a um evento sem transições."
|
|
30
|
+
|
|
31
|
+
#: statemachine/event.py:123
|
|
32
|
+
msgid "Event {} cannot be called without a SM instance"
|
|
33
|
+
msgstr "O evento {} não pode ser chamado sem uma instância de SM"
|
|
24
34
|
|
|
25
35
|
#: statemachine/exceptions.py:24
|
|
26
36
|
msgid "{!r} is not a valid state value."
|
|
@@ -30,64 +40,81 @@ msgstr "{!r} não é um valor de estado válido."
|
|
|
30
40
|
msgid "Can't {} when in {}."
|
|
31
41
|
msgstr "Não é possível {} quando em {}."
|
|
32
42
|
|
|
33
|
-
#: statemachine/factory.py:
|
|
43
|
+
#: statemachine/factory.py:73
|
|
34
44
|
msgid "There are no states."
|
|
35
45
|
msgstr "Não há estados."
|
|
36
46
|
|
|
37
|
-
#: statemachine/factory.py:
|
|
47
|
+
#: statemachine/factory.py:76
|
|
38
48
|
msgid "There are no events."
|
|
39
49
|
msgstr "Não há eventos."
|
|
40
50
|
|
|
41
|
-
#: statemachine/factory.py:
|
|
51
|
+
#: statemachine/factory.py:88
|
|
42
52
|
msgid ""
|
|
43
53
|
"There should be one and only one initial state. You currently have these:"
|
|
44
54
|
" {!r}"
|
|
45
|
-
msgstr "
|
|
55
|
+
msgstr ""
|
|
56
|
+
"Deve haver um e apenas um estado inicial. Atualmente, você possui estes: "
|
|
57
|
+
"{!r}"
|
|
46
58
|
|
|
47
|
-
#: statemachine/factory.py:
|
|
59
|
+
#: statemachine/factory.py:101
|
|
48
60
|
msgid "Cannot declare transitions from final state. Invalid state(s): {}"
|
|
49
|
-
msgstr "
|
|
61
|
+
msgstr ""
|
|
62
|
+
"Não é possível declarar transições a partir de um estado final. Estado(s)"
|
|
63
|
+
" inválido(s): {}"
|
|
50
64
|
|
|
51
|
-
#: statemachine/factory.py:
|
|
65
|
+
#: statemachine/factory.py:109
|
|
52
66
|
msgid ""
|
|
53
67
|
"All non-final states should have at least one outgoing transition. These "
|
|
54
68
|
"states have no outgoing transition: {!r}"
|
|
55
|
-
msgstr "
|
|
69
|
+
msgstr ""
|
|
70
|
+
"Todos os estados não finais devem ter pelo menos uma transição de saída. "
|
|
71
|
+
"Estes estados não possuem transição de saída: {!r}"
|
|
56
72
|
|
|
57
|
-
#: statemachine/factory.py:
|
|
73
|
+
#: statemachine/factory.py:123
|
|
58
74
|
msgid ""
|
|
59
75
|
"All non-final states should have at least one path to a final state. "
|
|
60
76
|
"These states have no path to a final state: {!r}"
|
|
61
|
-
msgstr "
|
|
77
|
+
msgstr ""
|
|
78
|
+
"Todos os estados não finais devem ter pelo menos um caminho para um "
|
|
79
|
+
"estado final. Estes estados não possuem caminho para um estado final: "
|
|
80
|
+
"{!r}"
|
|
62
81
|
|
|
63
|
-
#: statemachine/factory.py:
|
|
82
|
+
#: statemachine/factory.py:147
|
|
64
83
|
msgid ""
|
|
65
84
|
"There are unreachable states. The statemachine graph should have a single"
|
|
66
85
|
" component. Disconnected states: {}"
|
|
67
|
-
msgstr "
|
|
86
|
+
msgstr ""
|
|
87
|
+
"Há estados inacessíveis. O grafo da máquina de estados deve ter um único "
|
|
88
|
+
"componente. Estados desconectados: {}"
|
|
68
89
|
|
|
69
|
-
#: statemachine/factory.py:
|
|
90
|
+
#: statemachine/factory.py:253
|
|
70
91
|
msgid "An event in the '{}' has no id."
|
|
71
92
|
msgstr "Um evento em '{}' não possui id."
|
|
72
93
|
|
|
73
|
-
#: statemachine/mixins.py:
|
|
94
|
+
#: statemachine/mixins.py:28
|
|
74
95
|
msgid "{!r} is not a valid state machine name."
|
|
75
96
|
msgstr "{!r} não é um nome de máquina de estados válido."
|
|
76
97
|
|
|
77
|
-
#: statemachine/state.py:
|
|
98
|
+
#: statemachine/state.py:194
|
|
78
99
|
msgid "State overriding is not allowed. Trying to add '{}' to {}"
|
|
79
100
|
msgstr "Sobrescrever estados não é permitido. Tentando adicionar '{}' a {}"
|
|
80
101
|
|
|
81
|
-
#: statemachine/statemachine.py:
|
|
102
|
+
#: statemachine/statemachine.py:89
|
|
82
103
|
msgid "There are no states or transitions."
|
|
83
104
|
msgstr "Não há estados ou transições."
|
|
84
105
|
|
|
85
|
-
#: statemachine/statemachine.py:
|
|
106
|
+
#: statemachine/statemachine.py:277
|
|
86
107
|
msgid ""
|
|
87
108
|
"There's no current state set. In async code, did you activate the initial"
|
|
88
109
|
" state? (e.g., `await sm.activate_initial_state()`)"
|
|
89
|
-
msgstr "
|
|
110
|
+
msgstr ""
|
|
111
|
+
"Nenhum estado atual definido. Em código assíncrono, você ativou o estado "
|
|
112
|
+
"inicial? (ex.: `await sm.activate_initial_state()`)"
|
|
113
|
+
|
|
114
|
+
#: statemachine/transition_mixin.py:15
|
|
115
|
+
msgid "{} only supports the decorator syntax to register callbacks."
|
|
116
|
+
msgstr "{} suporta apenas a sintaxe de decorator para registrar callbacks."
|
|
90
117
|
|
|
91
|
-
#: statemachine/engines/async_.py:
|
|
118
|
+
#: statemachine/engines/async_.py:18
|
|
92
119
|
msgid "Only RTC is supported on async engine"
|
|
93
120
|
msgstr "Apenas RTC é suportado no motor assíncrono"
|
|
@@ -5,22 +5,32 @@ msgid ""
|
|
|
5
5
|
msgstr ""
|
|
6
6
|
"Project-Id-Version: 2.4.0\n"
|
|
7
7
|
"Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
|
|
8
|
-
"POT-Creation-Date:
|
|
8
|
+
"POT-Creation-Date: 2026-02-13 18:28-0300\n"
|
|
9
9
|
"PO-Revision-Date: 2024-06-07 17:41-0300\n"
|
|
10
10
|
"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
|
|
11
|
-
"Language
|
|
11
|
+
"Language: zh_CN\n"
|
|
12
|
+
"Language-Team: zh_CN <LL@li.org>\n"
|
|
13
|
+
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
12
14
|
"MIME-Version: 1.0\n"
|
|
13
15
|
"Content-Type: text/plain; charset=utf-8\n"
|
|
14
16
|
"Content-Transfer-Encoding: 8bit\n"
|
|
15
|
-
"Generated-By: Babel 2.
|
|
17
|
+
"Generated-By: Babel 2.16.0\n"
|
|
16
18
|
|
|
17
|
-
#: statemachine/callbacks.py:
|
|
19
|
+
#: statemachine/callbacks.py:349 statemachine/callbacks.py:354
|
|
20
|
+
msgid "Did not found name '{}' from model or statemachine"
|
|
21
|
+
msgstr "在模型或状态机中未找到名称 '{}'"
|
|
22
|
+
|
|
23
|
+
#: statemachine/dispatcher.py:126
|
|
18
24
|
msgid "Failed to parse boolean expression '{}'"
|
|
19
25
|
msgstr "无法解析布尔表达式 '{}'"
|
|
20
26
|
|
|
21
|
-
#: statemachine/
|
|
22
|
-
msgid "
|
|
23
|
-
msgstr "
|
|
27
|
+
#: statemachine/event.py:90
|
|
28
|
+
msgid "Cannot add callback '{}' to an event with no transitions."
|
|
29
|
+
msgstr "无法将回调 '{}' 添加到没有转换的事件。"
|
|
30
|
+
|
|
31
|
+
#: statemachine/event.py:123
|
|
32
|
+
msgid "Event {} cannot be called without a SM instance"
|
|
33
|
+
msgstr "事件 {} 不能在没有 SM 实例的情况下调用"
|
|
24
34
|
|
|
25
35
|
#: statemachine/exceptions.py:24
|
|
26
36
|
msgid "{!r} is not a valid state value."
|
|
@@ -30,64 +40,68 @@ msgstr "{!r} 不是有效的状态值。"
|
|
|
30
40
|
msgid "Can't {} when in {}."
|
|
31
41
|
msgstr "在 {} 时无法 {}。"
|
|
32
42
|
|
|
33
|
-
#: statemachine/factory.py:
|
|
43
|
+
#: statemachine/factory.py:73
|
|
34
44
|
msgid "There are no states."
|
|
35
45
|
msgstr "没有状态。"
|
|
36
46
|
|
|
37
|
-
#: statemachine/factory.py:
|
|
47
|
+
#: statemachine/factory.py:76
|
|
38
48
|
msgid "There are no events."
|
|
39
49
|
msgstr "没有事件。"
|
|
40
50
|
|
|
41
|
-
#: statemachine/factory.py:
|
|
51
|
+
#: statemachine/factory.py:88
|
|
42
52
|
msgid ""
|
|
43
53
|
"There should be one and only one initial state. You currently have these:"
|
|
44
54
|
" {!r}"
|
|
45
55
|
msgstr "应有且仅有一个初始状态。当前您有这些:{!r}"
|
|
46
56
|
|
|
47
|
-
#: statemachine/factory.py:
|
|
57
|
+
#: statemachine/factory.py:101
|
|
48
58
|
msgid "Cannot declare transitions from final state. Invalid state(s): {}"
|
|
49
59
|
msgstr "无法从终止状态声明转换。无效状态:{}"
|
|
50
60
|
|
|
51
|
-
#: statemachine/factory.py:
|
|
61
|
+
#: statemachine/factory.py:109
|
|
52
62
|
msgid ""
|
|
53
63
|
"All non-final states should have at least one outgoing transition. These "
|
|
54
64
|
"states have no outgoing transition: {!r}"
|
|
55
65
|
msgstr "所有非终止状态都应至少有一个外部转换。这些状态没有外部转换:{!r}"
|
|
56
66
|
|
|
57
|
-
#: statemachine/factory.py:
|
|
67
|
+
#: statemachine/factory.py:123
|
|
58
68
|
msgid ""
|
|
59
69
|
"All non-final states should have at least one path to a final state. "
|
|
60
70
|
"These states have no path to a final state: {!r}"
|
|
61
71
|
msgstr "所有非终止状态应至少有一个到终止状态的路径。这些状态没有到终止状态的路径:{!r}"
|
|
62
72
|
|
|
63
|
-
#: statemachine/factory.py:
|
|
73
|
+
#: statemachine/factory.py:147
|
|
64
74
|
msgid ""
|
|
65
75
|
"There are unreachable states. The statemachine graph should have a single"
|
|
66
76
|
" component. Disconnected states: {}"
|
|
67
77
|
msgstr "存在不可到达的状态。状态机图应具有单个组件。断开的状态:{}"
|
|
68
78
|
|
|
69
|
-
#: statemachine/factory.py:
|
|
79
|
+
#: statemachine/factory.py:253
|
|
70
80
|
msgid "An event in the '{}' has no id."
|
|
71
81
|
msgstr "'{}' 中的事件没有 ID。"
|
|
72
82
|
|
|
73
|
-
#: statemachine/mixins.py:
|
|
83
|
+
#: statemachine/mixins.py:28
|
|
74
84
|
msgid "{!r} is not a valid state machine name."
|
|
75
85
|
msgstr "{!r} 不是有效的状态机名称。"
|
|
76
86
|
|
|
77
|
-
#: statemachine/state.py:
|
|
87
|
+
#: statemachine/state.py:194
|
|
78
88
|
msgid "State overriding is not allowed. Trying to add '{}' to {}"
|
|
79
89
|
msgstr "不允许覆盖状态。尝试将 '{}' 添加到 {}"
|
|
80
90
|
|
|
81
|
-
#: statemachine/statemachine.py:
|
|
91
|
+
#: statemachine/statemachine.py:89
|
|
82
92
|
msgid "There are no states or transitions."
|
|
83
93
|
msgstr "没有状态或转换。"
|
|
84
94
|
|
|
85
|
-
#: statemachine/statemachine.py:
|
|
95
|
+
#: statemachine/statemachine.py:277
|
|
86
96
|
msgid ""
|
|
87
97
|
"There's no current state set. In async code, did you activate the initial"
|
|
88
98
|
" state? (e.g., `await sm.activate_initial_state()`)"
|
|
89
99
|
msgstr "没有设置当前状态。在异步代码中,您是否激活了初始状态?(例如,`await sm.activate_initial_state()`)"
|
|
90
100
|
|
|
91
|
-
#: statemachine/
|
|
101
|
+
#: statemachine/transition_mixin.py:15
|
|
102
|
+
msgid "{} only supports the decorator syntax to register callbacks."
|
|
103
|
+
msgstr "{} 仅支持使用装饰器语法注册回调。"
|
|
104
|
+
|
|
105
|
+
#: statemachine/engines/async_.py:18
|
|
92
106
|
msgid "Only RTC is supported on async engine"
|
|
93
107
|
msgstr "异步引擎仅支持 RTC"
|
statemachine/mixins.py
CHANGED
|
@@ -22,6 +22,8 @@ class MachineMixin:
|
|
|
22
22
|
def __init__(self, *args, **kwargs):
|
|
23
23
|
super().__init__(*args, **kwargs)
|
|
24
24
|
if not self.state_machine_name:
|
|
25
|
+
if self._is_django_historical_model():
|
|
26
|
+
return
|
|
25
27
|
raise ValueError(
|
|
26
28
|
_("{!r} is not a valid state machine name.").format(self.state_machine_name)
|
|
27
29
|
)
|
|
@@ -34,3 +36,12 @@ class MachineMixin:
|
|
|
34
36
|
)
|
|
35
37
|
if self.bind_events_as_methods:
|
|
36
38
|
sm.bind_events_to(self)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _is_django_historical_model(cls) -> bool:
|
|
42
|
+
"""Detect Django historical models created by ``apps.get_model()`` in migrations.
|
|
43
|
+
|
|
44
|
+
Django sets ``__module__ = '__fake__'`` on these dynamically-created classes,
|
|
45
|
+
which lack the user-defined class attributes like ``state_machine_name``.
|
|
46
|
+
"""
|
|
47
|
+
return getattr(cls, "__module__", None) == "__fake__"
|
statemachine/signature.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from functools import partial
|
|
2
4
|
from inspect import BoundArguments
|
|
3
5
|
from inspect import Parameter
|
|
@@ -6,6 +8,12 @@ from inspect import iscoroutinefunction
|
|
|
6
8
|
from itertools import chain
|
|
7
9
|
from types import MethodType
|
|
8
10
|
from typing import Any
|
|
11
|
+
from typing import FrozenSet
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from typing import Tuple
|
|
14
|
+
|
|
15
|
+
BindCacheKey = Tuple[int, FrozenSet[str]]
|
|
16
|
+
BindTemplate = Tuple[Tuple[str, ...], Optional[str], Optional[str]] # noqa: UP007
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
def _make_key(method):
|
|
@@ -44,6 +52,11 @@ def signature_cache(user_function):
|
|
|
44
52
|
|
|
45
53
|
class SignatureAdapter(Signature):
|
|
46
54
|
is_coroutine: bool = False
|
|
55
|
+
_bind_cache: dict[BindCacheKey, BindTemplate]
|
|
56
|
+
|
|
57
|
+
def __init__(self, *args, **kwargs):
|
|
58
|
+
super().__init__(*args, **kwargs)
|
|
59
|
+
self._bind_cache = {}
|
|
47
60
|
|
|
48
61
|
@classmethod
|
|
49
62
|
@signature_cache
|
|
@@ -60,19 +73,71 @@ class SignatureAdapter(Signature):
|
|
|
60
73
|
adapter.is_coroutine = iscoroutinefunction(method)
|
|
61
74
|
return adapter
|
|
62
75
|
|
|
63
|
-
def bind_expected(self, *args: Any, **kwargs: Any) -> BoundArguments:
|
|
76
|
+
def bind_expected(self, *args: Any, **kwargs: Any) -> BoundArguments:
|
|
77
|
+
cache_key: BindCacheKey = (len(args), frozenset(kwargs.keys()))
|
|
78
|
+
template = self._bind_cache.get(cache_key)
|
|
79
|
+
|
|
80
|
+
if template is not None:
|
|
81
|
+
return self._fast_bind(args, kwargs, template)
|
|
82
|
+
|
|
83
|
+
result = self._full_bind(cache_key, *args, **kwargs)
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
def _fast_bind(
|
|
87
|
+
self,
|
|
88
|
+
args: tuple[Any, ...],
|
|
89
|
+
kwargs: dict[str, Any],
|
|
90
|
+
template: BindTemplate,
|
|
91
|
+
) -> BoundArguments:
|
|
92
|
+
param_names, kwargs_param_name, var_positional_name = template
|
|
93
|
+
arguments: dict[str, Any] = {}
|
|
94
|
+
past_var_positional = False
|
|
95
|
+
|
|
96
|
+
for i, name in enumerate(param_names):
|
|
97
|
+
if name == var_positional_name:
|
|
98
|
+
# Collect all remaining positional args into a tuple
|
|
99
|
+
arguments[name] = args[i:]
|
|
100
|
+
past_var_positional = True
|
|
101
|
+
elif past_var_positional:
|
|
102
|
+
# After *args, remaining params are keyword-only
|
|
103
|
+
arguments[name] = kwargs.get(name)
|
|
104
|
+
elif i < len(args):
|
|
105
|
+
# Match _full_bind: if param is also in kwargs, kwargs wins
|
|
106
|
+
# (POSITIONAL_OR_KEYWORD params prefer kwargs over positional args)
|
|
107
|
+
if name in kwargs:
|
|
108
|
+
arguments[name] = kwargs[name]
|
|
109
|
+
else:
|
|
110
|
+
arguments[name] = args[i]
|
|
111
|
+
else:
|
|
112
|
+
arguments[name] = kwargs.get(name)
|
|
113
|
+
|
|
114
|
+
if kwargs_param_name is not None:
|
|
115
|
+
matched = set(param_names)
|
|
116
|
+
arguments[kwargs_param_name] = {k: v for k, v in kwargs.items() if k not in matched}
|
|
117
|
+
|
|
118
|
+
return BoundArguments(self, arguments) # type: ignore[arg-type]
|
|
119
|
+
|
|
120
|
+
def _full_bind( # noqa: C901
|
|
121
|
+
self,
|
|
122
|
+
cache_key: BindCacheKey,
|
|
123
|
+
*args: Any,
|
|
124
|
+
**kwargs: Any,
|
|
125
|
+
) -> BoundArguments:
|
|
64
126
|
"""Get a BoundArguments object, that maps the passed `args`
|
|
65
127
|
and `kwargs` to the function's signature. It avoids to raise `TypeError`
|
|
66
128
|
trying to fill all the required arguments and ignoring the unknown ones.
|
|
67
129
|
|
|
68
130
|
Adapted from the internal `inspect.Signature._bind`.
|
|
69
131
|
"""
|
|
70
|
-
arguments = {}
|
|
132
|
+
arguments: dict[str, Any] = {}
|
|
133
|
+
param_names_used: list[str] = []
|
|
71
134
|
|
|
72
135
|
parameters = iter(self.parameters.values())
|
|
73
136
|
arg_vals = iter(args)
|
|
74
137
|
parameters_ex: Any = ()
|
|
75
138
|
kwargs_param = None
|
|
139
|
+
kwargs_param_name: str | None = None
|
|
140
|
+
var_positional_name: str | None = None
|
|
76
141
|
|
|
77
142
|
while True:
|
|
78
143
|
# Let's iterate through the positional arguments and corresponding
|
|
@@ -95,8 +160,7 @@ class SignatureAdapter(Signature):
|
|
|
95
160
|
elif param.name in kwargs:
|
|
96
161
|
if param.kind == Parameter.POSITIONAL_ONLY:
|
|
97
162
|
msg = (
|
|
98
|
-
"{arg!r} parameter is positional only, "
|
|
99
|
-
"but was passed as a keyword"
|
|
163
|
+
"{arg!r} parameter is positional only, but was passed as a keyword"
|
|
100
164
|
)
|
|
101
165
|
msg = msg.format(arg=param.name)
|
|
102
166
|
raise TypeError(msg) from None
|
|
@@ -141,12 +205,15 @@ class SignatureAdapter(Signature):
|
|
|
141
205
|
values = [arg_val]
|
|
142
206
|
values.extend(arg_vals)
|
|
143
207
|
arguments[param.name] = tuple(values)
|
|
208
|
+
param_names_used.append(param.name)
|
|
209
|
+
var_positional_name = param.name
|
|
144
210
|
break
|
|
145
211
|
|
|
146
212
|
if param.name in kwargs and param.kind != Parameter.POSITIONAL_ONLY:
|
|
147
213
|
arguments[param.name] = kwargs.pop(param.name)
|
|
148
214
|
else:
|
|
149
215
|
arguments[param.name] = arg_val
|
|
216
|
+
param_names_used.append(param.name)
|
|
150
217
|
|
|
151
218
|
# Now, we iterate through the remaining parameters to process
|
|
152
219
|
# keyword arguments
|
|
@@ -172,14 +239,19 @@ class SignatureAdapter(Signature):
|
|
|
172
239
|
# arguments.
|
|
173
240
|
pass
|
|
174
241
|
else:
|
|
175
|
-
arguments[param_name] = arg_val
|
|
242
|
+
arguments[param_name] = arg_val
|
|
243
|
+
param_names_used.append(param_name)
|
|
176
244
|
|
|
177
245
|
if kwargs:
|
|
178
246
|
if kwargs_param is not None:
|
|
179
247
|
# Process our '**kwargs'-like parameter
|
|
180
|
-
arguments[kwargs_param.name] = kwargs # type: ignore
|
|
248
|
+
arguments[kwargs_param.name] = kwargs # type: ignore[assignment]
|
|
249
|
+
kwargs_param_name = kwargs_param.name
|
|
181
250
|
else:
|
|
182
251
|
# 'ignoring we got an unexpected keyword argument'
|
|
183
252
|
pass
|
|
184
253
|
|
|
185
|
-
|
|
254
|
+
template: BindTemplate = (tuple(param_names_used), kwargs_param_name, var_positional_name)
|
|
255
|
+
self._bind_cache[cache_key] = template
|
|
256
|
+
|
|
257
|
+
return BoundArguments(self, arguments) # type: ignore[arg-type]
|
statemachine/spec_parser.py
CHANGED
|
@@ -2,6 +2,7 @@ import ast
|
|
|
2
2
|
import operator
|
|
3
3
|
import re
|
|
4
4
|
from functools import reduce
|
|
5
|
+
from inspect import isawaitable
|
|
5
6
|
from typing import Callable
|
|
6
7
|
|
|
7
8
|
replacements = {"!": "not ", "^": " and ", "v": " or "}
|
|
@@ -33,8 +34,15 @@ def replace_operators(expr: str) -> str:
|
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def custom_not(predicate: Callable) -> Callable:
|
|
36
|
-
def decorated(*args, **kwargs)
|
|
37
|
-
|
|
37
|
+
def decorated(*args, **kwargs):
|
|
38
|
+
result = predicate(*args, **kwargs)
|
|
39
|
+
if isawaitable(result):
|
|
40
|
+
|
|
41
|
+
async def _negate():
|
|
42
|
+
return not await result
|
|
43
|
+
|
|
44
|
+
return _negate()
|
|
45
|
+
return not result
|
|
38
46
|
|
|
39
47
|
decorated.__name__ = f"not({predicate.__name__})"
|
|
40
48
|
unique_key = getattr(predicate, "unique_key", "")
|
|
@@ -43,8 +51,26 @@ def custom_not(predicate: Callable) -> Callable:
|
|
|
43
51
|
|
|
44
52
|
|
|
45
53
|
def custom_and(left: Callable, right: Callable) -> Callable:
|
|
46
|
-
def decorated(*args, **kwargs)
|
|
47
|
-
|
|
54
|
+
def decorated(*args, **kwargs):
|
|
55
|
+
left_result = left(*args, **kwargs)
|
|
56
|
+
if isawaitable(left_result):
|
|
57
|
+
|
|
58
|
+
async def _async_and():
|
|
59
|
+
lr = await left_result
|
|
60
|
+
if not lr:
|
|
61
|
+
return lr
|
|
62
|
+
rr = right(*args, **kwargs)
|
|
63
|
+
if isawaitable(rr):
|
|
64
|
+
return await rr
|
|
65
|
+
return rr
|
|
66
|
+
|
|
67
|
+
return _async_and()
|
|
68
|
+
if not left_result:
|
|
69
|
+
return left_result
|
|
70
|
+
right_result = right(*args, **kwargs)
|
|
71
|
+
if isawaitable(right_result):
|
|
72
|
+
return right_result
|
|
73
|
+
return right_result
|
|
48
74
|
|
|
49
75
|
decorated.__name__ = f"({left.__name__} and {right.__name__})"
|
|
50
76
|
decorated.unique_key = _unique_key(left, right, "and") # type: ignore[attr-defined]
|
|
@@ -52,8 +78,26 @@ def custom_and(left: Callable, right: Callable) -> Callable:
|
|
|
52
78
|
|
|
53
79
|
|
|
54
80
|
def custom_or(left: Callable, right: Callable) -> Callable:
|
|
55
|
-
def decorated(*args, **kwargs)
|
|
56
|
-
|
|
81
|
+
def decorated(*args, **kwargs):
|
|
82
|
+
left_result = left(*args, **kwargs)
|
|
83
|
+
if isawaitable(left_result):
|
|
84
|
+
|
|
85
|
+
async def _async_or():
|
|
86
|
+
lr = await left_result
|
|
87
|
+
if lr:
|
|
88
|
+
return lr
|
|
89
|
+
rr = right(*args, **kwargs)
|
|
90
|
+
if isawaitable(rr):
|
|
91
|
+
return await rr
|
|
92
|
+
return rr
|
|
93
|
+
|
|
94
|
+
return _async_or()
|
|
95
|
+
if left_result:
|
|
96
|
+
return left_result
|
|
97
|
+
right_result = right(*args, **kwargs)
|
|
98
|
+
if isawaitable(right_result):
|
|
99
|
+
return right_result
|
|
100
|
+
return right_result
|
|
57
101
|
|
|
58
102
|
decorated.__name__ = f"({left.__name__} or {right.__name__})"
|
|
59
103
|
decorated.unique_key = _unique_key(left, right, "or") # type: ignore[attr-defined]
|
|
@@ -73,8 +117,18 @@ def build_custom_operator(operator) -> Callable:
|
|
|
73
117
|
operator_repr = comparison_repr[operator]
|
|
74
118
|
|
|
75
119
|
def custom_comparator(left: Callable, right: Callable) -> Callable:
|
|
76
|
-
def decorated(*args, **kwargs)
|
|
77
|
-
|
|
120
|
+
def decorated(*args, **kwargs):
|
|
121
|
+
left_result = left(*args, **kwargs)
|
|
122
|
+
right_result = right(*args, **kwargs)
|
|
123
|
+
if isawaitable(left_result) or isawaitable(right_result):
|
|
124
|
+
|
|
125
|
+
async def _async_compare():
|
|
126
|
+
lr = (await left_result) if isawaitable(left_result) else left_result
|
|
127
|
+
rr = (await right_result) if isawaitable(right_result) else right_result
|
|
128
|
+
return bool(operator(lr, rr))
|
|
129
|
+
|
|
130
|
+
return _async_compare()
|
|
131
|
+
return bool(operator(left_result, right_result))
|
|
78
132
|
|
|
79
133
|
decorated.__name__ = f"({left.__name__} {operator_repr} {right.__name__})"
|
|
80
134
|
decorated.unique_key = _unique_key(left, right, operator_repr) # type: ignore[attr-defined]
|
statemachine/statemachine.py
CHANGED
|
@@ -75,7 +75,7 @@ class StateMachine(metaclass=StateMachineMetaclass):
|
|
|
75
75
|
allow_event_without_transition: bool = False,
|
|
76
76
|
listeners: "List[object] | None" = None,
|
|
77
77
|
):
|
|
78
|
-
self.model = model if model else Model()
|
|
78
|
+
self.model = model if model is not None else Model()
|
|
79
79
|
self.state_field = state_field
|
|
80
80
|
self.start_value = start_value
|
|
81
81
|
self.allow_event_without_transition = allow_event_without_transition
|
|
@@ -147,6 +147,7 @@ class StateMachine(metaclass=StateMachineMetaclass):
|
|
|
147
147
|
self._register_callbacks([])
|
|
148
148
|
self.add_listener(*listeners.keys())
|
|
149
149
|
self._engine = self._get_engine(rtc)
|
|
150
|
+
self._engine.start()
|
|
150
151
|
|
|
151
152
|
def _get_initial_state(self):
|
|
152
153
|
initial_state_value = self.start_value if self.start_value else self.initial_state.value
|
|
@@ -182,7 +183,7 @@ class StateMachine(metaclass=StateMachineMetaclass):
|
|
|
182
183
|
return self
|
|
183
184
|
|
|
184
185
|
def _register_callbacks(self, listeners: List[object]):
|
|
185
|
-
self._listeners.update(
|
|
186
|
+
self._listeners.update(dict.fromkeys(listeners))
|
|
186
187
|
self._add_listener(
|
|
187
188
|
Listeners.from_listeners(
|
|
188
189
|
(
|
|
@@ -223,7 +224,7 @@ class StateMachine(metaclass=StateMachineMetaclass):
|
|
|
223
224
|
|
|
224
225
|
:ref:`listeners`.
|
|
225
226
|
"""
|
|
226
|
-
self._listeners.update(
|
|
227
|
+
self._listeners.update(dict.fromkeys(listeners))
|
|
227
228
|
return self._add_listener(
|
|
228
229
|
Listeners.from_listeners(Listener.from_obj(o) for o in listeners),
|
|
229
230
|
allowed_references=SPECS_SAFE,
|
|
@@ -294,6 +295,24 @@ class StateMachine(metaclass=StateMachineMetaclass):
|
|
|
294
295
|
"""List of the current allowed events."""
|
|
295
296
|
return [getattr(self, event) for event in self.current_state.transitions.unique_events]
|
|
296
297
|
|
|
298
|
+
def enabled_events(self, *args, **kwargs):
|
|
299
|
+
"""List of the current enabled events, considering guard conditions.
|
|
300
|
+
|
|
301
|
+
An event is **enabled** if at least one of its transitions from the current
|
|
302
|
+
state has all ``cond``/``unless`` guards satisfied.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
*args: Positional arguments forwarded to condition callbacks.
|
|
306
|
+
**kwargs: Keyword arguments forwarded to condition callbacks.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
A list of enabled :ref:`Event` instances.
|
|
310
|
+
"""
|
|
311
|
+
result = self._engine.enabled_events(*args, **kwargs)
|
|
312
|
+
if not isawaitable(result):
|
|
313
|
+
return result
|
|
314
|
+
return run_async_from_sync(result)
|
|
315
|
+
|
|
297
316
|
def _put_nonblocking(self, trigger_data: TriggerData):
|
|
298
317
|
"""Put the trigger on the queue without blocking the caller."""
|
|
299
318
|
self._engine.put(trigger_data)
|
statemachine/transition_mixin.py
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
3
|
from .callbacks import CallbackGroup
|
|
4
|
+
from .i18n import _
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class AddCallbacksMixin:
|
|
7
8
|
def _add_callback(self, callback, grouper: CallbackGroup, is_event=False, **kwargs):
|
|
8
9
|
raise NotImplementedError
|
|
9
10
|
|
|
10
|
-
def __call__(self,
|
|
11
|
-
|
|
11
|
+
def __call__(self, *args, **kwargs):
|
|
12
|
+
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
13
|
+
return self._add_callback(args[0], CallbackGroup.ON, is_event=True)
|
|
14
|
+
raise TypeError(
|
|
15
|
+
_("{} only supports the decorator syntax to register callbacks.").format(
|
|
16
|
+
type(self).__name__
|
|
17
|
+
)
|
|
18
|
+
)
|
|
12
19
|
|
|
13
20
|
def before(self, f: Callable):
|
|
14
21
|
"""Adds a ``before`` :ref:`transition actions` callback to every :ref:`transition` in the
|
{python_statemachine-2.5.0.dist-info → python_statemachine-2.6.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|