1"""

2 Call loop machinery

3"""

4 import sys

5

6 from ._result import HookCallError, _Result, _raise_wrapfail

7

8

9 def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):

10 """Execute a call into multiple python functions/methods and return the

11 result(s).

12

13 ``caller_kwargs`` comes from _HookCaller.__call__().

14 """

15 __tracebackhide__ = True

16 results = []

17 excinfo = None

18 try: # run impl and wrapper setup functions in a loop

19 teardowns = []

20 try:

21 for hook_impl in reversed(hook_impls):

22 try:

23 args = [caller_kwargs[argname] for argname in hook_impl.argnames]

24 except KeyError:

25 for argname in hook_impl.argnames:

26 if argname not in caller_kwargs:

27 raise HookCallError(

28 f"hook call must provide argument {argname!r}"

29 )

30

31 if hook_impl.hookwrapper:

32 try:

33 gen = hook_impl.function(*args)

34 next(gen) # first yield

35 teardowns.append(gen)

36 except StopIteration:

37 _raise_wrapfail(gen, "did not yield")

38 else:

39 res = hook_impl.function(*args)

40 if res is not None:

41 results.append(res)

42 if firstresult: # halt further impl calls

43 break

44 except BaseException:

45 excinfo = sys.exc_info()

46 finally:

47 if firstresult: # first result hooks return a single value

48 outcome = _Result(results[0] if results else None, excinfo)

49 else:

50 outcome = _Result(results, excinfo)

51

52 # run all wrapper post-yield blocks

53 for gen in reversed(teardowns):

54 try:

55 gen.send(outcome)

56 _raise_wrapfail(gen, "has second yield")

57 except StopIteration:

58 pass

59

60 return outcome.get_result()