Skip to content

Commit 8eda64a

Browse files
committed
refactor: remove _class_listener_instances, use active_listeners API
The `_class_listener_instances` attribute was redundant — `_listeners` already tracks all listeners (class-level + runtime + add_listener). Remove the internal attribute and migrate all tests to use the public `active_listeners` property instead.
1 parent d4ef917 commit 8eda64a

2 files changed

Lines changed: 39 additions & 38 deletions

File tree

statemachine/statemachine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ def __init__(
157157
if self._abstract:
158158
raise InvalidDefinition(_("There are no states or transitions."))
159159

160-
self._class_listener_instances = self._resolve_class_listeners(**kwargs)
161-
all_listeners = self._class_listener_instances + (listeners or [])
160+
class_listener_instances = self._resolve_class_listeners(**kwargs)
161+
all_listeners = class_listener_instances + (listeners or [])
162162
self._register_callbacks(all_listeners)
163163

164164
# Activate the initial state, this only works if the outer scope is sync code.

tests/test_class_listeners.py

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ class MyChart(StateChart):
4747
sm1.send("go")
4848

4949
# Each SM gets its own listener instance
50-
assert len(sm1._class_listener_instances) == 1
51-
assert len(sm2._class_listener_instances) == 1
52-
assert sm1._class_listener_instances[0] is not sm2._class_listener_instances[0]
50+
assert len(sm1.active_listeners) == 1
51+
assert len(sm2.active_listeners) == 1
52+
assert sm1.active_listeners[0] is not sm2.active_listeners[0]
5353

5454
# Only sm1 should have the transition recorded
55-
assert sm1._class_listener_instances[0].transitions == [("go", "s1", "s2")]
56-
assert sm2._class_listener_instances[0].transitions == []
55+
assert sm1.active_listeners[0].transitions == [("go", "s1", "s2")]
56+
assert sm2.active_listeners[0].transitions == []
5757

5858
def test_class_level_listener_shared_instance(self):
5959
shared = RecordingListener()
@@ -72,8 +72,8 @@ class MyChart(StateChart):
7272
sm2.send("go")
7373

7474
# Both SMs share the same listener instance
75-
assert sm1._class_listener_instances[0] is shared
76-
assert sm2._class_listener_instances[0] is shared
75+
assert sm1.active_listeners[0] is shared
76+
assert sm2.active_listeners[0] is shared
7777
assert len(shared.transitions) == 2
7878

7979
def test_class_level_listener_partial(self):
@@ -95,7 +95,7 @@ class MyChart(StateChart):
9595
sm = MyChart()
9696
sm.send("go")
9797

98-
listener = sm._class_listener_instances[0]
98+
listener = sm.active_listeners[0]
9999
assert listener.prefix == "custom"
100100
assert listener.messages == ["custom: s1 -> s2"]
101101

@@ -112,7 +112,7 @@ class MyChart(StateChart):
112112
go = s1.to(s2)
113113

114114
sm = MyChart()
115-
assert sm._class_listener_instances[0].tag == "from_lambda"
115+
assert sm.active_listeners[0].tag == "from_lambda"
116116

117117
def test_runtime_listeners_merge_with_class_level(self):
118118
class MyChart(StateChart):
@@ -127,12 +127,14 @@ class MyChart(StateChart):
127127

128128
sm.send("go")
129129

130-
# Class-level listener should have recorded
131-
class_listener = sm._class_listener_instances[0]
132-
assert class_listener.transitions == [("go", "s1", "s2")]
130+
assert len(sm.active_listeners) == 2
131+
132+
# Both listeners should have recorded
133+
for listener in sm.active_listeners:
134+
assert listener.transitions == [("go", "s1", "s2")]
133135

134-
# Runtime listener should also have recorded
135-
assert runtime_listener.transitions == [("go", "s1", "s2")]
136+
# Runtime listener is the one we passed in
137+
assert runtime_listener in sm.active_listeners
136138

137139

138140
class TestClassListenerInheritance:
@@ -154,9 +156,9 @@ class Child(Parent):
154156
listeners = [ChildListener]
155157

156158
sm = Child()
157-
assert len(sm._class_listener_instances) == 2
158-
assert isinstance(sm._class_listener_instances[0], ParentListener)
159-
assert isinstance(sm._class_listener_instances[1], ChildListener)
159+
assert len(sm.active_listeners) == 2
160+
assert isinstance(sm.active_listeners[0], ParentListener)
161+
assert isinstance(sm.active_listeners[1], ChildListener)
160162

161163
def test_child_replaces_parent_listeners(self):
162164
class ParentListener:
@@ -177,8 +179,8 @@ class Child(Parent):
177179
listeners = [ChildListener]
178180

179181
sm = Child()
180-
assert len(sm._class_listener_instances) == 1
181-
assert isinstance(sm._class_listener_instances[0], ChildListener)
182+
assert len(sm.active_listeners) == 1
183+
assert isinstance(sm.active_listeners[0], ChildListener)
182184

183185
def test_grandchild_inherits_full_chain(self):
184186
class L1:
@@ -204,10 +206,10 @@ class Leaf(Mid):
204206
listeners = [L3]
205207

206208
sm = Leaf()
207-
assert len(sm._class_listener_instances) == 3
208-
assert isinstance(sm._class_listener_instances[0], L1)
209-
assert isinstance(sm._class_listener_instances[1], L2)
210-
assert isinstance(sm._class_listener_instances[2], L3)
209+
assert len(sm.active_listeners) == 3
210+
assert isinstance(sm.active_listeners[0], L1)
211+
assert isinstance(sm.active_listeners[1], L2)
212+
assert isinstance(sm.active_listeners[2], L3)
211213

212214
def test_no_listeners_declared_inherits_parent(self):
213215
class ParentListener:
@@ -224,8 +226,8 @@ class Child(Parent):
224226
pass
225227

226228
sm = Child()
227-
assert len(sm._class_listener_instances) == 1
228-
assert isinstance(sm._class_listener_instances[0], ParentListener)
229+
assert len(sm.active_listeners) == 1
230+
assert isinstance(sm.active_listeners[0], ParentListener)
229231

230232

231233
class TestListenerSetupProtocol:
@@ -238,7 +240,7 @@ class MyChart(StateChart):
238240
go = s1.to(s2)
239241

240242
sm = MyChart(session="my_db_session")
241-
listener = sm._class_listener_instances[0]
243+
listener = sm.active_listeners[0]
242244
assert listener.session == "my_db_session"
243245

244246
def test_setup_ignores_unknown_kwargs(self):
@@ -250,7 +252,7 @@ class MyChart(StateChart):
250252
go = s1.to(s2)
251253

252254
sm = MyChart(session="db", unknown_arg="ignored")
253-
listener = sm._class_listener_instances[0]
255+
listener = sm.active_listeners[0]
254256
assert listener.session == "db"
255257

256258
def test_setup_not_called_on_shared_instances(self):
@@ -290,8 +292,7 @@ class MyChart(StateChart):
290292
go = s1.to(s2)
291293

292294
sm = MyChart(session="db_conn", redis="redis_conn")
293-
db = sm._class_listener_instances[0]
294-
cache = sm._class_listener_instances[1]
295+
db, cache = sm.active_listeners
295296
assert db.session == "db_conn"
296297
assert cache.redis == "redis_conn"
297298

@@ -311,7 +312,7 @@ class MyChart(StateChart):
311312
go = s1.to(s2)
312313

313314
sm = MyChart()
314-
listener = sm._class_listener_instances[0]
315+
listener = sm.active_listeners[0]
315316
assert listener.sm is sm
316317

317318
def test_setup_optional_kwargs_default_to_none(self):
@@ -323,7 +324,7 @@ class MyChart(StateChart):
323324
go = s1.to(s2)
324325

325326
sm = MyChart() # No session kwarg provided
326-
listener = sm._class_listener_instances[0]
327+
listener = sm.active_listeners[0]
327328
assert listener.session is None
328329

329330
def test_setup_required_kwarg_missing_raises_error(self):
@@ -354,7 +355,7 @@ class MyChart(StateChart):
354355
go = s1.to(s2)
355356

356357
sm = MyChart(session="db_conn")
357-
assert sm._class_listener_instances[0].session == "db_conn"
358+
assert sm.active_listeners[0].session == "db_conn"
358359

359360

360361
class TestListenerValidation:
@@ -426,8 +427,8 @@ def test_pickle_with_class_listeners(self):
426427
sm2 = pickle.loads(data)
427428

428429
# Class listener instances are preserved through serialization
429-
assert len(sm2._class_listener_instances) == 1
430-
assert sm2._class_listener_instances[0].transitions == [("go", "s1", "s2")]
430+
assert len(sm2.active_listeners) == 1
431+
assert sm2.active_listeners[0].transitions == [("go", "s1", "s2")]
431432
assert "s2" in sm2.configuration_values
432433

433434
def test_pickle_does_not_duplicate_class_listeners(self):
@@ -462,7 +463,7 @@ class MyChart(StateChart):
462463
go = s1.to(s2)
463464

464465
sm = MyChart()
465-
assert sm._class_listener_instances == []
466+
assert sm.active_listeners == []
466467

467468
def test_empty_listeners_list(self):
468469
class MyChart(StateChart):
@@ -473,4 +474,4 @@ class MyChart(StateChart):
473474
go = s1.to(s2)
474475

475476
sm = MyChart()
476-
assert sm._class_listener_instances == []
477+
assert sm.active_listeners == []

0 commit comments

Comments
 (0)