Skip to content

Commit 3deeead

Browse files
committed
add SequenceDispatcher
1 parent 76f29d8 commit 3deeead

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

effect/test_testing.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
ESFunc,
1919
EQDispatcher,
2020
EQFDispatcher,
21+
IntentMismatchError,
22+
SequenceDispatcher,
2123
fail_effect,
2224
resolve_effect,
2325
resolve_stubs)
@@ -276,3 +278,30 @@ def test_perform(self):
276278
"""When an intent matches, performing it returns the canned result."""
277279
d = EQFDispatcher([('hello', lambda i: (i, 'there'))])
278280
self.assertEqual(sync_perform(d, Effect('hello')), ('hello', 'there'))
281+
282+
283+
class SequenceDispatcherTests(TestCase):
284+
"""Tests for :obj:`SequenceDispatcher`."""
285+
286+
def test_mismatch(self):
287+
"""
288+
When an intent isn't expected, a :obj:`IntentMismatchError` is raised.
289+
"""
290+
d = SequenceDispatcher([('foo', lambda i: 1 / 0)])
291+
e = self.assertRaises(IntentMismatchError, lambda: sync_perform(d, Effect('hello')))
292+
self.assertEqual(e.expected_intent, 'foo')
293+
self.assertEqual(e.got_intent, 'hello')
294+
295+
def test_success(self):
296+
"""
297+
Each intent is performed in sequence with the provided functions, as
298+
long as the intents match.
299+
"""
300+
d = SequenceDispatcher([
301+
('foo', lambda i: ('performfoo', i)),
302+
('bar', lambda i: ('performbar', i)),
303+
])
304+
eff = Effect('foo').on(lambda r: Effect('bar').on(lambda r2: (r, r2)))
305+
self.assertEqual(
306+
sync_perform(d, eff),
307+
(('performfoo', 'foo'), ('performbar', 'bar')))

effect/testing.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,43 @@ 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+
249+
class IntentMismatchError(Exception):
250+
def __init__(self, expected_intent, got_intent):
251+
self.expected_intent = expected_intent
252+
self.got_intent = got_intent
253+
super(IntentMismatchError, self).__init__(
254+
'Expected intent %r, got %r' % (expected_intent, got_intent))
255+
256+
257+
class SequenceDispatcher(object):
258+
"""
259+
A dispatcher which steps through a sequence of (intent, func) tuples and
260+
runs ``func`` to perform intents in strict sequence.
261+
262+
So, if you expect to first perform an intent like ``MyIntent('a')`` and
263+
then an intent like ``OtherIntent('b')``, you can create a dispatcher like
264+
this::
265+
266+
SequenceDispatcher([
267+
(MyIntent('a'), lambda i: 'my-intent-result'),
268+
(OtherIntent('b'), lambda i: 'other-intent-result')
269+
])
270+
271+
Unlike most dispatchers, this one raises an exception
272+
(:obj:`IntentMismatchError`) when an intent is not found, in the order
273+
expected.
274+
"""
275+
def __init__(self, sequence):
276+
""":param list sequence: Sequence of (intent, fn)."""
277+
self.sequence = sequence
278+
279+
def __call__(self, intent):
280+
exp_intent, func = self.sequence[0]
281+
if intent == exp_intent:
282+
self.sequence = self.sequence[1:]
283+
return sync_performer(lambda d, i: func(i))
284+
else:
285+
raise IntentMismatchError(exp_intent, intent)

0 commit comments

Comments
 (0)