--- /dev/null
+import asyncio
+import signal
+
+
+def run(*steps):
+ """
+ Helper to run one or more async functions synchronously, with graceful
+ handling of SIGINT / Ctrl-C.
+
+ Returns the return value of the last function.
+ """
+ if not steps:
+ return
+
+ task = None
+ run._sigint = False # function attr to allow setting from closure
+ loop = asyncio.get_event_loop()
+
+ def abort():
+ task.cancel()
+ run._sigint = True
+
+ added = False
+ try:
+ loop.add_signal_handler(signal.SIGINT, abort)
+ added = True
+ except ValueError as e:
+ # add_signal_handler doesn't work in a thread
+ if 'main thread' not in str(e):
+ raise
+ try:
+ for step in steps:
+ task = loop.create_task(step)
+ loop.run_until_complete(asyncio.wait([task], loop=loop))
+ if run._sigint:
+ raise KeyboardInterrupt()
+ if task.exception():
+ raise task.exception()
+ return task.result()
+ finally:
+ if added:
+ loop.remove_signal_handler(signal.SIGINT)