|
8 | 8 |
|
9 | 9 | from contextlib import contextmanager |
10 | 10 | from functools import partial |
| 11 | +from operator import attrgetter |
11 | 12 | import sys |
12 | 13 |
|
13 | 14 | import attr |
|
20 | 21 |
|
21 | 22 | __all__ = [ |
22 | 23 | 'perform_sequence', |
| 24 | + 'parallel_sequence', |
| 25 | + 'nested_sequence', |
23 | 26 | 'SequenceDispatcher', |
24 | 27 | 'noop', |
| 28 | + 'const', |
| 29 | + 'conste', |
| 30 | + 'intent_func', |
25 | 31 | 'resolve_effect', |
26 | 32 | 'fail_effect', |
27 | 33 | 'EQDispatcher', |
|
30 | 36 | 'ESConstant', 'ESError', 'ESFunc', |
31 | 37 | 'resolve_stubs', |
32 | 38 | 'resolve_stub', |
33 | | - 'const', |
34 | | - 'conste', |
35 | | - 'intent_func', |
36 | 39 | ] |
37 | 40 |
|
38 | 41 |
|
@@ -86,6 +89,7 @@ def test_code(): |
86 | 89 | :param fallback_dispatcher: A dispatcher to use for intents that aren't |
87 | 90 | found in the sequence. if None is provided, ``base_dispatcher`` is |
88 | 91 | used. |
| 92 | + :return: Result of performed sequence |
89 | 93 | """ |
90 | 94 | def fmt_log(): |
91 | 95 | next_item = '' |
@@ -162,6 +166,8 @@ def test_code(): |
162 | 166 | what :func:`perform_sequence` accepts. |
163 | 167 | :param fallback_dispatcher: an optional dispatcher to compose onto the |
164 | 168 | sequence dispatcher. |
| 169 | + :return: (intent, performer) tuple as expected by :func:`perform_sequence` |
| 170 | + where intent is ParallelEffects object |
165 | 171 | """ |
166 | 172 | perf = partial(perform_sequence, fallback_dispatcher=fallback_dispatcher) |
167 | 173 |
|
@@ -467,6 +473,43 @@ def consume(self): |
467 | 473 | [x[0] for x in self.sequence])) |
468 | 474 |
|
469 | 475 |
|
| 476 | +def nested_sequence(seq, get_effect=attrgetter('effect'), |
| 477 | + fallback_dispatcher=base_dispatcher): |
| 478 | + """ |
| 479 | + Return a function of Intent -> a that performs an effect retrieved from the |
| 480 | + intent (by accessing its `effect` attribute, by default) with the given |
| 481 | + intent-sequence. |
| 482 | +
|
| 483 | + A demonstration is best:: |
| 484 | +
|
| 485 | + SequenceDispatcher([ |
| 486 | + (BoundFields(effect=mock.ANY, fields={...}), |
| 487 | + nested_sequence([(SomeIntent(), perform_some_intent)])) |
| 488 | + ]) |
| 489 | +
|
| 490 | + The point is that sometimes you have an intent that wraps another effect, |
| 491 | + and you want to ensure that the nested effects follow some sequence in the |
| 492 | + context of that wrapper intent. |
| 493 | +
|
| 494 | + ``get_effect`` defaults to ``attrgetter('effect')``, so you can override it if |
| 495 | + your intent stores its nested effect in a different attribute. Or, more |
| 496 | + interestingly, if it's something other than a single effect, e.g. for |
| 497 | + ParallelEffects see the :func:`parallel_sequence` function. |
| 498 | +
|
| 499 | + :param list seq: sequence of intents like :obj:`SequenceDispatcher` takes |
| 500 | + :param get_effect: callable to get the inner effect from the wrapper |
| 501 | + intent. |
| 502 | + :param fallback_dispatcher: an optional dispatcher to compose onto the |
| 503 | + sequence dispatcher. |
| 504 | + :return: ``callable`` that can be used as performer of a wrapped intent |
| 505 | + """ |
| 506 | + def performer(intent): |
| 507 | + effect = get_effect(intent) |
| 508 | + return perform_sequence(seq, effect, fallback_dispatcher=fallback_dispatcher) |
| 509 | + |
| 510 | + return performer |
| 511 | + |
| 512 | + |
470 | 513 | def noop(intent): |
471 | 514 | """ |
472 | 515 | Return None. This is just a handy way to make your intent sequences (as |
|
0 commit comments