Skip to content

Commit

Permalink
Merge pull request #45 from radix/sequence-dispatcher
Browse files Browse the repository at this point in the history
Sequence dispatcher
  • Loading branch information
radix committed Mar 24, 2015
2 parents 90a553d + 15c446b commit b9bec55
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ but now has contributions from the following people:

.. _`Christopher Armstrong`: https://github.com/radix

- `cyli`_
- `lvh`_
- `Manish Tomar`_
- `Tom Prince`_

.. _`cyli`: https://github.com/cyli
.. _`lvh`: https://github.com/lvh
.. _`Manish Tomar`: https://github.com/manishtomar
.. _`Tom Prince`: https://github.com/tomprince
Expand Down
39 changes: 39 additions & 0 deletions effect/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
ESFunc,
EQDispatcher,
EQFDispatcher,
SequenceDispatcher,
fail_effect,
resolve_effect,
resolve_stubs)
Expand Down Expand Up @@ -276,3 +277,41 @@ def test_perform(self):
"""When an intent matches, performing it returns the canned result."""
d = EQFDispatcher([('hello', lambda i: (i, 'there'))])
self.assertEqual(sync_perform(d, Effect('hello')), ('hello', 'there'))


class SequenceDispatcherTests(TestCase):
"""Tests for :obj:`SequenceDispatcher`."""

def test_mismatch(self):
"""
When an intent isn't expected, a None is returned.
"""
d = SequenceDispatcher([('foo', lambda i: 1 / 0)])
self.assertEqual(d('hello'), None)

def test_success(self):
"""
Each intent is performed in sequence with the provided functions, as
long as the intents match.
"""
d = SequenceDispatcher([
('foo', lambda i: ('performfoo', i)),
('bar', lambda i: ('performbar', i)),
])
eff = Effect('foo').on(lambda r: Effect('bar').on(lambda r2: (r, r2)))
self.assertEqual(
sync_perform(d, eff),
(('performfoo', 'foo'), ('performbar', 'bar')))

def test_ran_out(self):
"""When there are no more items left, None is returned."""
d = SequenceDispatcher([])
self.assertEqual(d('foo'), None)

def test_out_of_order(self):
"""Order of items in the sequence matters."""
d = SequenceDispatcher([
('bar', lambda i: ('performbar', i)),
('foo', lambda i: ('performfoo', i)),
])
self.assertEqual(d('foo'), None)
31 changes: 31 additions & 0 deletions effect/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,34 @@ def __call__(self, intent):
for k, v in self.mapping:
if k == intent:
return sync_performer(lambda d, i: v(i))


class SequenceDispatcher(object):
"""
A dispatcher which steps through a sequence of (intent, func) tuples and
runs ``func`` to perform intents in strict sequence.
So, if you expect to first perform an intent like ``MyIntent('a')`` and
then an intent like ``OtherIntent('b')``, you can create a dispatcher like
this::
SequenceDispatcher([
(MyIntent('a'), lambda i: 'my-intent-result'),
(OtherIntent('b'), lambda i: 'other-intent-result')
])
:obj:`None` is returned if the next intent in the sequence is not equal to
the intent being performed, or if there are no more items left in the
sequence.
"""
def __init__(self, sequence):
""":param list sequence: Sequence of (intent, fn)."""
self.sequence = sequence

def __call__(self, intent):
if len(self.sequence) == 0:
return
exp_intent, func = self.sequence[0]
if intent == exp_intent:
self.sequence = self.sequence[1:]
return sync_performer(lambda d, i: func(i))

0 comments on commit b9bec55

Please sign in to comment.