Hi,
I am trying to write a terminal application using cmd
(Python standard library), which is able to invoke async
couroutines from the action callback function.
Minimal example
Code:
# main.py
import cmd
import asyncio
class MyShell(cmd.Cmd):
async def do_myaction(self, arg):
# this async task is not executed anymore, program immediately
# exited, as do_myaction is not blocking
await asyncio.sleep(5)
print("Some app results")
async def main():
await asyncio.gather(shell(), other_task())
print("Exiting...")
async def shell():
def run():
MyShell().cmdloop()
# Run in separate thread to make interactive shell I/IO non-blocking
await asyncio.to_thread(run)
async def other_task():
# represents some concurrent initialization task
await asyncio.sleep(1)
print("Concurrent init task")
asyncio.run(main())
Invocation:
[user@sys test]$ python main.py
(Cmd) Concurrent init task
myaction
/tmp/test/main.py:20: RuntimeWarning: coroutine 'MyShell.do_myaction'
was never awaited
MyShell().cmdloop()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Exiting...
Problem
Obviously cmd
is not able to block on await
ed code. Program exits, before coroutine do_myaction(self, arg)
can be processed.
Here special case is that a synchronous callback function do_myaction()
is invocated from the framework, as soon as myaction
is typed in interactive prompt. I tried to annotate the callback as async def do_myaction()
, but parent handler doesn’t await
this coroutine.
In addition, event loop is missing in spawned thread from asyncio.to_thread()
1. So it is needed to create an additional loop inside. I tried to set it via asyncio.set_event_loop(asyncio.new_event_loop())
in custom MyShell
constructor, but then did not know how to actually run said loop in this context.
1: Tested that by writing asyncio.get_running_loop()
inside constructor - which errors as expected.
Which lead me to following questions:
Questions
[1] Is it possible to use cmd
with async
at all, not using any external libraries?
[2] If no: Is there a native alternative to cmd
in async contexts? I found an example using code
, but I guess the same problem arises with sync callback runsource()
:
import code
class Repl(code.InteractiveConsole):
def runsource(self, source, filename="<input>", symbol="single"):
print("source:", source)
repl = Repl()
repl.interact(banner="", exitmsg="")
[3] In other words, or formulated as more general question: How can I invoke async
logic from callback functions of synchronous frameworks? I am missing some bridge between sync and async here.
(4) Optional question
Keeping number of questions small, I’ll collapse this one as possible unrelated. Still appreciating an answer:
I was thinking of crafting a manual Future
around code that is incompatible to async
. Having some experience with JavaScript await
, what would be the Python equivalent to a Promise
constructor with manual resolve
, like:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
I am still new to async
/await
and would be glad to receive some feedback and possible solutions. Thanks!
Related: