对Python的Cmd类输入流使用协程

时间:2019-01-29 16:39:17

标签: python ssh async-await stdin

我面临的问题是:

  • 我有一个异步方法
  • 调用我无法更改的普通python代码
  • 哪个回调了一个普通的python方法
  • 需要使用await调用异步代码

我在Python's Cmd class之上构建了一个自定义命令解释器。我为它提供了自定义的stdin和stdout。出于这个问题的目的,它看起来像这样:

import cmd
import sys

class CustomStream(object):
    def readline(self):
        return sys.stdin.readline()
    def write(self, msg):
        sys.stdout.write(msg)
    def flush(self):
        pass

class MyShell(cmd.Cmd):
    def do_stuff(self, args):
        print("Getting things done...")
    def do_exit(self, args):
        return True

stream = CustomStream()
shell = MyShell(stdin=stream, stdout=stream)
shell.use_rawinput = False 
shell.cmdloop()

Cmd需要向用户阅读时,它将执行以下操作:

line = self.stdin.readline()

我想使用基于asyncio的AsyncSSH库为自定义互操作器提供SSH接口。我的SSH代码很像Simple Server sample,它像这样读取标准输入接口(注意await关键字):

line_from_client = (await self._process.stdin.readline()).rstrip('\n')

我尝试了很多事情,但是我无法根据Cmd对stdin的期望来键入SSH代码。在为CustomStream提供老式的单线程接口时,我该如何使MyShell对象在内部使用asyncio /协程?

1 个答案:

答案 0 :(得分:0)

解决方案是修补cmdloop方法以使其具有异步意识。

此代码是您设置后获得的Python 3.7.2 Cmd类cmdloop函数的副本

  • raw_input设置为True
  • await放在阅读行的前面

此代码(aoicmd.py available as a gist)的结果:

async def adapter_cmdloop(self, intro=None):
    """Repeatedly issue a prompt, accept input, parse an initial prefix
    off the received input, and dispatch to action methods, passing them
    the remainder of the line as argument.

    """
    self.preloop()

    #This is the same code as the Python 3.7.2 Cmd class, with the
    #following changes
    #  - Remove dead code caused by forcing use_rawinput=False.
    #  - Added a readline in front of readline()
    if intro is not None:
        self.intro = intro
    if self.intro:
        self.stdout.write(str(self.intro)+"\n")
    stop = None
    while not stop:
        if self.cmdqueue:
            line = self.cmdqueue.pop(0)
        else:
            self.stdout.write(self.prompt)
            self.stdout.flush()
            line = await self.stdin.readline()
            if not len(line):
                line = 'EOF'
            else:
                line = line.rstrip('\r\n')
        line = self.precmd(line)
        stop = self.onecmd(line)
        stop = self.postcmd(stop, line)
    self.postloop()

在需要使用Cmd派生类的地方,例如MyShell,请在运行时创建一个名为MyAsyncShell的新类:

#Patch the shell with async aware cmdloop
MyAsyncShell = type('MyAsyncSHell', (MyShell,), {
    'cmdloop' :aiocmd.adapter_cmdloop,
    'use_rawinput':False,
})

按照您认为合适的方式实施writeflush,但您的阅读内容应如下所示:

async def readline(self):
    return await my_implementation_of_readline()