Создание экземпляра#

В синхронном варианте, при создании класса который подключается по SSH, само подключение обычно будет выполняться в методе __init__ (напрямую или через вызов другого метода). В асинхронных классах так сделать не получится, так как для выполнения подключения в __init__, надо сделать init сопрограммой. Так как init должен возвращать None, если сделать init сопрограммой, возникнет ошибка:

In [3]: class ConnectSSH:
   ...:     async def __init__(self):
   ...:         await asyncio.sleep(0.1)
   ...:

In [5]: r1 = ConnectSSH()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ceb9b33fd87d> in <module>
----> 1 r1 = ConnectSSH()

TypeError: __init__() should return None, not 'coroutine'

Пожалуй, самый распространенный вариант решения проблемы - создание отдельного метода для подключения (вместе с добавлением асинхронного менеджера контекста).

from pprint import pprint
import asyncio
import asyncssh


class ConnectAsyncSSH:
    def __init__(self, host, username, password, enable_password, connection_timeout=5):
        self.host = host
        self.username = username
        self.password = password
        self.enable_password = enable_password
        self.connection_timeout = connection_timeout

    async def connect(self):
        self._ssh = await asyncio.wait_for(
            asyncssh.connect(
                host=self.host,
                username=self.username,
                password=self.password,
                encryption_algs="+aes128-cbc,aes256-cbc",
            ),
            timeout=self.connection_timeout,
        )
        self.writer, self.reader, _ = await self._ssh.open_session(
            term_type="Dumb", term_size=(200, 24)
        )
        await self.reader.readuntil(">")
        self.writer.write("enable\n")
        await self.reader.readuntil("Password")
        self.writer.write(f"{self.enable_password}\n")
        await self.reader.readuntil("#")
        self.writer.write("terminal length 0\n")
        await self.reader.readuntil("#")

    async def get_prompt(self):
        self.writer.write("\n")
        output = await self.reader.readuntil("#")
        return output

В этом случае для подключения надо сначала создать экземпляр класса, а потом вызвать метод connect где выполняется само подключение:

async def main():
    r1 = {
        "host": "192.168.100.1",
        "username": "cisco",
        "password": "cisco",
        "enable_password": "cisco",
    }
    ssh = ConnectAsyncSSH(**r1)
    await ssh.connect()
    print(await ssh.get_prompt())


if __name__ == "__main__":
    asyncio.run(main())

Как правило, вместе с таким вариантом используется асинхронный менеджер контекста. В этом случае, метод connect вызывается в методе __aenter__ (аналог __enter__). Асинхронный менеджер контекста рассматривается позже.

classmethod#

Еще один распространенный вариант - использование classmethod для создания экземпляра. В примере ниже classmethod connect единственный способ создания экземлпяра:

from pprint import pprint
import asyncio
import asyncssh


class ConnectAsyncSSH:

    @classmethod
    async def connect(cls, host, username, password, enable_password, connection_timeout=5):
        self = cls()

        self.host = host
        self.username = username
        self.password = password
        self.enable_password = enable_password
        self.connection_timeout = connection_timeout
        self._ssh = await asyncio.wait_for(
            asyncssh.connect(
                host=self.host,
                username=self.username,
                password=self.password,
                encryption_algs="+aes128-cbc,aes256-cbc",
            ),
            timeout=self.connection_timeout,
        )
        self.writer, self.reader, _ = await self._ssh.open_session(
            term_type="Dumb", term_size=(200, 24)
        )
        await self.reader.readuntil(">")
        self.writer.write("enable\n")
        await self.reader.readuntil("Password")
        self.writer.write(f"{self.enable_password}\n")
        await self.reader.readuntil("#")
        self.writer.write("terminal length 0\n")
        await self.reader.readuntil("#")
        return self

    async def get_prompt(self):
        self.writer.write("\n")
        output = await self.reader.readuntil("#")
        return output

Создание экземпяра выглядит так:

async def main():
    r1 = {
        "host": "192.168.100.1",
        "username": "cisco",
        "password": "cisco",
        "enable_password": "cisco",
    }
    ssh = await ConnectAsyncSSH.connect(**r1)
    print(await ssh.get_prompt())


if __name__ == "__main__":
    asyncio.run(main())

Второй вариант - оставить init и обычный метод connect и добавить отдельный classmethod:

class ConnectAsyncSSH:
    def __init__(self, host, username, password, enable_password, connection_timeout=5):
        self.host = host
        self.username = username
        self.password = password
        self.enable_password = enable_password
        self.connection_timeout = connection_timeout

    async def connect(self):
        self._ssh = await asyncio.wait_for(
            asyncssh.connect(
                host=self.host,
                username=self.username,
                password=self.password,
                encryption_algs="+aes128-cbc,aes256-cbc",
            ),
            timeout=self.connection_timeout,
        )
        self.writer, self.reader, _ = await self._ssh.open_session(
            term_type="Dumb", term_size=(200, 24)
        )
        await self.reader.readuntil(">")
        self.writer.write("enable\n")
        await self.reader.readuntil("Password")
        self.writer.write(f"{self.enable_password}\n")
        await self.reader.readuntil("#")
        self.writer.write("terminal length 0\n")
        await self.reader.readuntil("#")

    @classmethod
    async def create_connection(cls, host, username, password, enable_password, connection_timeout=5):
        self = cls(host, username, password, enable_password, connection_timeout)
        await self.connect()
        return self

    async def get_prompt(self):
        self.writer.write("\n")
        output = await self.reader.readuntil("#")
        return output

Создание экземпяра через classmethod выглядит так, но при этом можно создавать экземпляр и через init + connect:

async def main():
    r1 = {
        "host": "192.168.100.1",
        "username": "cisco",
        "password": "cisco",
        "enable_password": "cisco",
    }
    ssh = await ConnectAsyncSSH.create_connection(**r1)
    print(await ssh.get_prompt())