Использование сопрограммы для входного потока класса Cmd Python - PullRequest
0 голосов
/ 29 января 2019

Проблема, с которой я сталкиваюсь, заключается в том, что:

  • У меня есть асинхронный метод
  • Вызов простого старого кода Python, который я не могу изменить
  • Что вызываетпростой старый метод python
  • который должен вызывать асинхронный код с использованием await

У меня есть собственный интерпретатор команд, построенный поверх класса Python Cmd .Я предоставляю ему пользовательские 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()

Я хочу предоставитьSSH-интерфейс для моего пользовательского интерфейса, использующего библиотеку AsyncSSH на основе asyncio.Мой SSH-код очень похож на пример Simple Server , который читает подобный stdin интерфейс (обратите внимание на ключевое слово await):

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

Я пробовал несколько вещей, ноЯ не могу набрать SSH-код, чтобы Cmd ожидал стандартного ввода.Что я должен сделать, чтобы мой CustomStream объект использовал asyncio / сопрограммы внутри, обеспечивая однопоточный интерфейс старой школы для MyShell?

1 Ответ

0 голосов
/ 20 февраля 2019

Решение состояло в том, чтобы исправить метод cmdloop, чтобы сделать его осведомленным об асинхронности.

Этот код является копией функции cmdloop класса Cmd Python 3.7.2, которую вы получаете, если установить

  • raw_input установлен на True
  • поставить await перед строкой чтения

Результаты в этом коде ( aoicmd.py доступен как гист ):

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,
})

Реализация write и flush, как вы считаете нужным, но ваша readline должна выглядеть так:

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