_callers.py 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. """
  2. Call loop machinery
  3. """
  4. import sys
  5. from ._result import HookCallError, _Result, _raise_wrapfail
  6. def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
  7. """Execute a call into multiple python functions/methods and return the
  8. result(s).
  9. ``caller_kwargs`` comes from _HookCaller.__call__().
  10. """
  11. __tracebackhide__ = True
  12. results = []
  13. excinfo = None
  14. try: # run impl and wrapper setup functions in a loop
  15. teardowns = []
  16. try:
  17. for hook_impl in reversed(hook_impls):
  18. try:
  19. args = [caller_kwargs[argname] for argname in hook_impl.argnames]
  20. except KeyError:
  21. for argname in hook_impl.argnames:
  22. if argname not in caller_kwargs:
  23. raise HookCallError(
  24. f"hook call must provide argument {argname!r}"
  25. )
  26. if hook_impl.hookwrapper:
  27. try:
  28. gen = hook_impl.function(*args)
  29. next(gen) # first yield
  30. teardowns.append(gen)
  31. except StopIteration:
  32. _raise_wrapfail(gen, "did not yield")
  33. else:
  34. res = hook_impl.function(*args)
  35. if res is not None:
  36. results.append(res)
  37. if firstresult: # halt further impl calls
  38. break
  39. except BaseException:
  40. excinfo = sys.exc_info()
  41. finally:
  42. if firstresult: # first result hooks return a single value
  43. outcome = _Result(results[0] if results else None, excinfo)
  44. else:
  45. outcome = _Result(results, excinfo)
  46. # run all wrapper post-yield blocks
  47. for gen in reversed(teardowns):
  48. try:
  49. gen.send(outcome)
  50. _raise_wrapfail(gen, "has second yield")
  51. except StopIteration:
  52. pass
  53. return outcome.get_result()