Skip to content

Commit d6b5a26

Browse files
committed
Merge branch 'pr-289'
2 parents 3d3f0c2 + a0f7d88 commit d6b5a26

File tree

3 files changed

+81
-6
lines changed

3 files changed

+81
-6
lines changed

tests/test_core.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@
2020
from mock import MagicMock
2121

2222

23+
def on_exit_A(event):
24+
event.model.exit_A_called = True
25+
26+
27+
def on_exit_B(event):
28+
event.model.exit_B_called = True
29+
30+
2331
class TestTransitions(TestCase):
2432

2533
def setUp(self):
@@ -266,7 +274,7 @@ def __init__(self, *args, **kwargs):
266274
n.advance()
267275
self.assertTrue(n.is_B())
268276
with self.assertRaises(ValueError):
269-
m = NewMachine(state=['A', 'B'])
277+
NewMachine(state=['A', 'B'])
270278

271279
def test_send_event_data_callbacks(self):
272280
states = ['A', 'B', 'C', 'D', 'E']
@@ -463,6 +471,39 @@ def on_exit_B(self):
463471
self.assertEqual(len(state_b.on_enter), 1)
464472
self.assertEqual(len(state_b.on_exit), 1)
465473

474+
def test_state_callable_callbacks(self):
475+
476+
class Model:
477+
478+
def __init__(self):
479+
self.exit_A_called = False
480+
self.exit_B_called = False
481+
482+
def on_enter_A(self, event):
483+
pass
484+
485+
def on_enter_B(self, event):
486+
pass
487+
488+
states = [State(name='A', on_enter='on_enter_A', on_exit='tests.test_core.on_exit_A'),
489+
State(name='B', on_enter='on_enter_B', on_exit=on_exit_B),
490+
State(name='C', on_enter='tests.test_core.AAAA')]
491+
492+
model = Model()
493+
machine = Machine(model, states=states, send_event=True, initial='A')
494+
state_a = machine.get_state('A')
495+
state_b = machine.get_state('B')
496+
self.assertEqual(len(state_a.on_enter), 1)
497+
self.assertEqual(len(state_a.on_exit), 1)
498+
self.assertEqual(len(state_b.on_enter), 1)
499+
self.assertEqual(len(state_b.on_exit), 1)
500+
model.to_B()
501+
self.assertTrue(model.exit_A_called)
502+
model.to_A()
503+
self.assertTrue(model.exit_B_called)
504+
with self.assertRaises(AttributeError):
505+
model.to_C()
506+
466507
def test_pickle(self):
467508
import sys
468509
if sys.version_info < (3, 4):

tests/test_nesting.py

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
state_separator = State.separator
3030

3131

32+
class Dummy(object):
33+
pass
34+
35+
3236
class TestTransitions(TestsCore):
3337

3438
def setUp(self):
@@ -41,6 +45,10 @@ def tearDown(self):
4145
State.separator = state_separator
4246
pass
4347

48+
def test_add_model(self):
49+
model = Dummy()
50+
self.stuff.machine.add_model(model, initial='E')
51+
4452
def test_function_wrapper(self):
4553
from transitions.extensions.nesting import FunctionWrapper
4654
mo = MagicMock

transitions/core.py

+31-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
except ImportError:
1313
# python2
1414
pass
15+
1516
import inspect
1617
import itertools
1718
import logging
@@ -175,8 +176,7 @@ def check(self, event_data):
175176
model attached to the current machine which is used to invoke
176177
the condition.
177178
"""
178-
predicate = getattr(event_data.model, self.func) if isinstance(self.func, string_types) else self.func
179-
179+
predicate = event_data.machine.resolve_callable(self.func, event_data)
180180
if event_data.machine.send_event:
181181
return predicate(event_data) == self.target
182182
return predicate(*event_data.args, **event_data.kwargs) == self.target
@@ -989,19 +989,45 @@ def callback(self, func, event_data):
989989
the callable will be resolved from the passed model in event_data. This function is not intended to
990990
be called directly but through state and transition callback definitions.
991991
Args:
992-
func (callable or str): The callback function.
992+
func (string, callable): The callback function.
993+
1. First, if the func is callable, just call it
994+
2. Second, we try to import string assuming it is a path to a func
995+
3. Fallback to a model attribute
993996
event_data (EventData): An EventData instance to pass to the
994997
callback (if event sending is enabled) or to extract arguments
995998
from (if event sending is disabled).
996999
"""
997-
if isinstance(func, string_types):
998-
func = getattr(event_data.model, func)
9991000

1001+
func = self.resolve_callable(func, event_data)
10001002
if self.send_event:
10011003
func(event_data)
10021004
else:
10031005
func(*event_data.args, **event_data.kwargs)
10041006

1007+
@staticmethod
1008+
def resolve_callable(func, event_data):
1009+
""" Converts path to a callable into callable
1010+
Args:
1011+
func (string, callable): Path to a callable
1012+
event_data (EventData): Currently processed event
1013+
Returns:
1014+
callable function resolved from string or func
1015+
"""
1016+
if isinstance(func, string_types):
1017+
try:
1018+
func = getattr(event_data.model, func)
1019+
except AttributeError:
1020+
try:
1021+
mod, name = func.rsplit('.', 1)
1022+
m = __import__(mod)
1023+
for n in mod.split('.')[1:]:
1024+
m = getattr(m, n)
1025+
func = getattr(m, name)
1026+
except (ImportError, AttributeError, ValueError):
1027+
raise AttributeError("Callable with name '%s' could neither be retrieved from the passed "
1028+
"model nor imported from a module.")
1029+
return func
1030+
10051031
def _has_state(self, state):
10061032
if isinstance(state, State):
10071033
if state in self.states.values():

0 commit comments

Comments
 (0)