Skip to content

Commit

Permalink
this resolves #134
Browse files Browse the repository at this point in the history
also fixes an issue with positionally passing data to callbacks
(positional passing had not been covered by tests before)
  • Loading branch information
aleneum committed Aug 29, 2016
1 parent 736ef8e commit d4abd73
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 8 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,14 @@ lump.state
lump.evaporate()
lump.state
>>> 'gas'
lump.ionize()
lump.trigger('ionize')
lump.state
>>> 'plasma'
```

Notice the shiny new methods attached to the `Matter` instance (`evaporate()`, `ionize()`, etc.). Each method triggers the corresponding transition. You don't have to explicitly define these methods anywhere; the name of each transition is bound to the model passed to the `Machine` initializer (in this case, `lump`).
Additionally, there is a method called `trigger` now attached to your model.
This method lets you execute transitions by name in case dynamic triggering is required.

### <a name="states"></a>States

Expand Down Expand Up @@ -532,6 +534,7 @@ Note that condition-checking methods will passively receive optional arguments a

```python
lump.heat(temp=74)
# equivalent to lump.trigger('heat', temp=74)
```

... would pass the `temp=74` optional kwarg to the `is_flammable()` check (possibly wrapped in an `EventData` instance). For more on this, see the [Passing data](#passing-data) section below.
Expand Down Expand Up @@ -627,7 +630,8 @@ lump = Matter()
machine = Machine(lump, ['solid', 'liquid'], initial='solid')
machine.add_transition('melt', 'solid', 'liquid', before='set_environment')

lump.melt(45) # positional arg
lump.melt(45) # positional arg;
# equivalent to lump.trigger('melt', 45)
lump.print_temperature()
>>> 'Current temperature is 45 degrees celsius.'

Expand Down
19 changes: 17 additions & 2 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,9 @@ def test_send_event_data_callbacks(self):
trigger='advance', source='A', dest='B', before='set_message')
s.advance(message='Hallo. My name is Inigo Montoya.')
self.assertTrue(s.message.startswith('Hallo.'))
# Make sure callbacks handle arguments properly
s.to_B()
s.to_A()
s.advance('Test as positional argument')
self.assertTrue(s.message.startswith('Test as'))
# Now wrap arguments in an EventData instance
m.send_event = True
m.add_transition(
Expand Down Expand Up @@ -599,3 +600,17 @@ def test_multiple_models(self):
# for backwards compatibility model should return a model instance
# rather than a list
self.assertNotIsInstance(m.model, list)

def test_string_trigger(self):
def return_value(value):
return value

self.stuff.machine.add_transition('do', '*', 'C')
self.stuff.trigger('do')
self.assertTrue(self.stuff.is_C())
self.stuff.machine.add_transition('maybe', 'C', 'A', conditions=return_value)
self.assertFalse(self.stuff.trigger('maybe', value=False))
self.assertTrue(self.stuff.trigger('maybe', value=True))
self.assertTrue(self.stuff.is_A())
with self.assertRaises(AttributeError):
self.stuff.trigger('not_available')
2 changes: 1 addition & 1 deletion tests/test_reuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def test_blueprint_remap(self):

new_states = ['A', 'B', {'name': 'C', 'children':
[counter, {'name': 'X', 'children': ['will', 'be', 'filtered', 'out']}],
'remap': {'finished': 'A', 'X': 'A'}}]
'remap': {'finished': 'A', 'X': 'A'}}]
new_transitions = [
{'trigger': 'forward', 'source': 'A', 'dest': 'B'},
{'trigger': 'forward', 'source': 'B', 'dest': 'C%s1' % State.separator},
Expand Down
15 changes: 13 additions & 2 deletions transitions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ def listify(obj):
return obj if isinstance(obj, (list, tuple, type(None))) else [obj]


def get_trigger(model, trigger_name, *args, **kwargs):
func = getattr(model, trigger_name, None)
if func:
return func(*args, **kwargs)
raise AttributeError("Model has no trigger named %s" % trigger_name)


class State(object):

def __init__(self, name, on_enter=None, on_exit=None,
Expand Down Expand Up @@ -97,7 +104,8 @@ def check(self, event_data):
model attached to the current machine which is used to invoke
the condition.
"""
predicate = getattr(event_data.model, self.func)
predicate = getattr(event_data.model, self.func) if isinstance(self.func, string_types) else self.func

if event_data.machine.send_event:
return predicate(event_data) == self.target
else:
Expand Down Expand Up @@ -368,6 +376,9 @@ def __init__(self, model=None, states=None, initial=None, transitions=None,
if ordered_transitions:
self.add_ordered_transitions()

for model in self.models:
model.trigger = partial(get_trigger, model)

@staticmethod
def _create_transition(*args, **kwargs):
return Transition(*args, **kwargs)
Expand Down Expand Up @@ -497,7 +508,7 @@ def add_transition(self, trigger, source, dest, conditions=None,
if trigger not in self.events:
self.events[trigger] = self._create_event(trigger, self)
for model in self.models:
trig_func = partial(self.events[trigger].trigger, model=model)
trig_func = partial(self.events[trigger].trigger, model)
setattr(model, trigger, trig_func)

if isinstance(source, string_types):
Expand Down
2 changes: 1 addition & 1 deletion transitions/extensions/locking.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class LockedEvent(Event):

def trigger(self, model, *args, **kwargs):
with self.machine.rlock:
super(LockedEvent, self).trigger(model, *args, **kwargs)
return super(LockedEvent, self).trigger(model, *args, **kwargs)


class LockedMachine(Machine):
Expand Down

0 comments on commit d4abd73

Please sign in to comment.