Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequence dispatcher #45

Merged
merged 7 commits into from
Mar 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))