Skip to content

Commit 4f334c3

Browse files
authored
chore: Improved setup by 40% changing state and transitions callbacks setup to factory (run only once per class instead of by instance creation (#433)
1 parent df06976 commit 4f334c3

5 files changed

Lines changed: 35 additions & 29 deletions

File tree

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666

6767
# General information about the project.
6868
project = "Python State Machine"
69-
copyright = "2023, Fernando Macedo"
69+
copyright = "2024, Fernando Macedo"
7070

7171
# The version info for the project you're documenting, acts as replacement
7272
# for |version| and |release|, also used in various other places throughout
7.69 KB
Loading

statemachine/factory.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .event import Event
1111
from .event import trigger_event_factory
1212
from .exceptions import InvalidDefinition
13+
from .graph import iterate_states_and_transitions
1314
from .graph import visit_connected_states
1415
from .i18n import _
1516
from .state import State
@@ -38,6 +39,7 @@ def __init__(
3839
cls._abstract = True
3940
cls._strict_states = strict_states
4041
cls._events: Dict[str, Event] = {}
42+
cls._protected_attrs: set = set()
4143

4244
cls.add_inherited(bases)
4345
cls.add_from_attributes(attrs)
@@ -50,6 +52,7 @@ def __init__(
5052
cls.final_states: List[State] = [state for state in cls.states if state.final]
5153

5254
cls._check()
55+
cls._setup()
5356

5457
if TYPE_CHECKING:
5558
"""Makes mypy happy with dynamic created attributes"""
@@ -148,6 +151,23 @@ def _check_disconnected_state(cls):
148151
).format([s.id for s in disconnected_states])
149152
)
150153

154+
def _setup(cls):
155+
for visited in iterate_states_and_transitions(cls.states):
156+
visited._setup()
157+
158+
cls._protected_attrs = {
159+
"_abstract",
160+
"model",
161+
"state_field",
162+
"start_value",
163+
"initial_state",
164+
"final_states",
165+
"states",
166+
"_events",
167+
"states_map",
168+
"send",
169+
} | {s.id for s in cls.states}
170+
151171
def add_inherited(cls, bases):
152172
for base in bases:
153173
for state in getattr(base, "states", []):

statemachine/graph.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ def visit_connected_states(state):
1212
already_visited.add(state)
1313
yield state
1414
visit.extend(t.target for t in state.transitions)
15+
16+
17+
def iterate_states_and_transitions(states):
18+
for state in states:
19+
yield state
20+
yield from state.transitions

statemachine/statemachine.py

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from typing import Any
66
from typing import Dict
77

8+
from statemachine.graph import iterate_states_and_transitions
9+
810
from .callbacks import CallbackMetaList
911
from .callbacks import CallbacksExecutor
1012
from .callbacks import CallbacksRegistry
@@ -81,7 +83,7 @@ def __init__(
8183
if self._abstract:
8284
raise InvalidDefinition(_("There are no states or transitions."))
8385

84-
self._setup()
86+
self._register_callbacks()
8587
self._activate_initial_state()
8688

8789
def __init_subclass__(cls, strict_states: bool = False):
@@ -109,7 +111,7 @@ def __deepcopy__(self, memo):
109111
self.__deepcopy__ = deepcopy_method
110112
cp.__deepcopy__ = deepcopy_method
111113
cp._callbacks_registry.clear()
112-
cp._setup()
114+
cp._register_callbacks()
113115
cp.add_observer(*cp._observers.keys())
114116
return cp
115117

@@ -138,38 +140,16 @@ def _activate_initial_state(self):
138140
)
139141
self._activate(event_data)
140142

141-
def _get_protected_attrs(self):
142-
return {
143-
"_abstract",
144-
"model",
145-
"state_field",
146-
"start_value",
147-
"initial_state",
148-
"final_states",
149-
"states",
150-
"_events",
151-
"states_map",
152-
"send",
153-
} | {s.id for s in self.states}
154-
155-
def _iterate_states_and_transitions(self):
156-
for state in self.states:
157-
yield state
158-
yield from state.transitions
159-
160-
def _setup(self):
161-
for visited in self._iterate_states_and_transitions():
162-
visited._setup()
163-
143+
def _register_callbacks(self):
164144
self._add_observer(
165145
(
166-
ObjectConfig.from_obj(self, skip_attrs=self._get_protected_attrs()),
146+
ObjectConfig.from_obj(self, skip_attrs=self._protected_attrs),
167147
ObjectConfig.from_obj(self.model, skip_attrs={self.state_field}),
168148
)
169149
)
170150

171151
check_callbacks = self._callbacks_registry.check
172-
for visited in self._iterate_states_and_transitions():
152+
for visited in iterate_states_and_transitions(self.states):
173153
try:
174154
visited._check_callbacks(check_callbacks)
175155
except Exception as err:
@@ -179,7 +159,7 @@ def _setup(self):
179159

180160
def _add_observer(self, observers):
181161
register = partial(self._callbacks_registry.register, resolver=resolver_factory(observers))
182-
for visited in self._iterate_states_and_transitions():
162+
for visited in iterate_states_and_transitions(self.states):
183163
visited._add_observer(register)
184164

185165
return self

0 commit comments

Comments
 (0)