Skip to content

Commit b9bec55

Browse files
committed
Merge pull request #45 from radix/sequence-dispatcher
Sequence dispatcher
2 parents 90a553d + 15c446b commit b9bec55

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,12 @@ but now has contributions from the following people:
113113

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

116+
- `cyli`_
116117
- `lvh`_
117118
- `Manish Tomar`_
118119
- `Tom Prince`_
119120

121+
.. _`cyli`: https://github.com/cyli
120122
.. _`lvh`: https://github.com/lvh
121123
.. _`Manish Tomar`: https://github.com/manishtomar
122124
.. _`Tom Prince`: https://github.com/tomprince

effect/test_testing.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ESFunc,
1919
EQDispatcher,
2020
EQFDispatcher,
21+
SequenceDispatcher,
2122
fail_effect,
2223
resolve_effect,
2324
resolve_stubs)
@@ -276,3 +277,41 @@ def test_perform(self):
276277
"""When an intent matches, performing it returns the canned result."""
277278
d = EQFDispatcher([('hello', lambda i: (i, 'there'))])
278279
self.assertEqual(sync_perform(d, Effect('hello')), ('hello', 'there'))
280+
281+
282+
class SequenceDispatcherTests(TestCase):
283+
"""Tests for :obj:`SequenceDispatcher`."""
284+
285+
def test_mismatch(self):
286+
"""
287+
When an intent isn't expected, a None is returned.
288+
"""
289+
d = SequenceDispatcher([('foo', lambda i: 1 / 0)])
290+
self.assertEqual(d('hello'), None)
291+
292+
def test_success(self):
293+
"""
294+
Each intent is performed in sequence with the provided functions, as
295+
long as the intents match.
296+
"""
297+
d = SequenceDispatcher([
298+
('foo', lambda i: ('performfoo', i)),
299+
('bar', lambda i: ('performbar', i)),
300+
])
301+
eff = Effect('foo').on(lambda r: Effect('bar').on(lambda r2: (r, r2)))
302+
self.assertEqual(
303+
sync_perform(d, eff),
304+
(('performfoo', 'foo'), ('performbar', 'bar')))
305+
306+
def test_ran_out(self):
307+
"""When there are no more items left, None is returned."""
308+
d = SequenceDispatcher([])
309+
self.assertEqual(d('foo'), None)
310+
311+
def test_out_of_order(self):
312+
"""Order of items in the sequence matters."""
313+
d = SequenceDispatcher([
314+
('bar', lambda i: ('performbar', i)),
315+
('foo', lambda i: ('performfoo', i)),
316+
])
317+
self.assertEqual(d('foo'), None)

effect/testing.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,34 @@ def __call__(self, intent):
243243
for k, v in self.mapping:
244244
if k == intent:
245245
return sync_performer(lambda d, i: v(i))
246+
247+
248+
class SequenceDispatcher(object):
249+
"""
250+
A dispatcher which steps through a sequence of (intent, func) tuples and
251+
runs ``func`` to perform intents in strict sequence.
252+
253+
So, if you expect to first perform an intent like ``MyIntent('a')`` and
254+
then an intent like ``OtherIntent('b')``, you can create a dispatcher like
255+
this::
256+
257+
SequenceDispatcher([
258+
(MyIntent('a'), lambda i: 'my-intent-result'),
259+
(OtherIntent('b'), lambda i: 'other-intent-result')
260+
])
261+
262+
:obj:`None` is returned if the next intent in the sequence is not equal to
263+
the intent being performed, or if there are no more items left in the
264+
sequence.
265+
"""
266+
def __init__(self, sequence):
267+
""":param list sequence: Sequence of (intent, fn)."""
268+
self.sequence = sequence
269+
270+
def __call__(self, intent):
271+
if len(self.sequence) == 0:
272+
return
273+
exp_intent, func = self.sequence[0]
274+
if intent == exp_intent:
275+
self.sequence = self.sequence[1:]
276+
return sync_performer(lambda d, i: func(i))

0 commit comments

Comments
 (0)