Does it make sense to make client_factory
an optional parameter to create_connection()
?
The “streams” API from the examples, which IMO is nicer to work with, passes in client_factory
as None
.
Put differently, should client_factory
have a default value of None
?
The “streams” API from the examples, which IMO is nicer to work with, passes in
client_factory
asNone
.=
Totally agreed. It’s a small but nice improvement.
I can’t comment on how adding this connect()
helper method would affect the overall coherence of the AsyncSSH API, but I can say that a) it fits my use case more naturally and b) it can be mapped more directly to Paramiko’s API (which is where I’m coming from).
So to add to the example you provided, I would reach straight for the streams API and do something like this:
@asyncio.coroutine
def run_client():
conn, client = yield from asyncssh.connect(host='localhost')
stdin, stdout, stderr = yield from conn.open_session('ls')
output = yield from stdout.read().strip()
print(output)
exit_status = stdout.channel.get_exit_status()
In Paramiko, this would roughly look like:
def run_client():
with paramiko.client.SSHClient() as client:
client.connect(hostname='localhost')
stdin, stdout, stderr = client.exec_command('ls')
output = stdout.read().strip()
print(output)
exit_status = stdout.channel.recv_exit_status()
In both cases there is a minimum of code tangential to what the user is interested in. Whenever possible, sensible defaults are chosen so the user doesn’t have to think about additional details until they become relevant to their problem.
So thumbs up from me.
Nick
asyncssh.
connect
(host, port=22, **kwargs)[source]Make an SSH client connection
This function is a coroutine wrapper around create_connection()
which can be used when a custom SSHClient instance is not needed. It takes all the same arguments as create_connection()
except for client_factory
and returns only the SSHClientConnection
object rather than a tuple of an SSHClientConnection
and SSHClient
.
When using this call, the following restrictions apply:
- No callbacks are called when the connection is successfully opened, when it is closed, or when authentication completes.
- Any authentication information must be provided as arguments to this call, as any authentication callbacks will deny other authentication attempts. Also, authentication banner information will be ignored.
- Any debug messages sent by the server will be ignored.
As I mentioned in the other thread, this new API might be well suited to Python’s concept of context managers.
For example, the example above could be rewritten slightly as:
@asyncio.coroutine
def run_client():
with yield from asyncssh.connect('localhost') as conn:
stdin, stdout, stderr = yield from conn.open_session('bc')
for op in ['2+2', '1*2*3*4', '2^32']:
stdin.write(op + '\n')
result = yield from stdout.readline()
print(op, '=', result, end='')
I’m not sure if context managers play nicely with asyncio, but this approach would be nicely Pythonic and would offer all the usual benefits of context managers.
Nick
As I mentioned in the other thread, this new API might be well suited to Python’s concept of context managers.
For example, the example above could be rewritten slightly as:
@asyncio.coroutine def run_client(): with yield from asyncssh.connect('localhost') as conn: stdin, stdout, stderr = yield from conn.open_session('bc') for op in ['2+2', '1*2*3*4', '2^32']: stdin.write(op + '\n') result = yield from stdout.readline() print(op, '=', result, end='')
I’m not sure if context managers play nicely with asyncio, but this approach would be nicely Pythonic and would offer all the usual benefits of context managers.
@asyncio.coroutine
def run_client():
conn = yield from asyncssh.connect('localhost'
)
with conn:
stdin, stdout, stderr = yield from conn.open_session('bc')
for op in ['2+2', '1*2*3*4', '2^32']:
stdin.write(op + '\n')
result = yield from stdout.readline()
print(op, '=', result, end='')
@asyncio.coroutine
def run_client():
with (yield from asyncssh.connect('localhost’)) as conn:
stdin, stdout, stderr = yield from conn.open_session('bc')
for op in ['2+2', '1*2*3*4', '2^32']:
stdin.write(op + '\n')
result = yield from stdout.readline()
print(op, '=', result, end='')
@asyncio.coroutine
def run_client(): conn, client = yield from asyncssh.create_connection(MySSHClient, 'localhost’) with conn:
chan, session = yield from conn.create_session(MySSHClientSession, 'ls abc') yield from chan.wait_closed()
Ah, the syntax makes sense.
And that’s neat—having the context manager available on SSHConnection
lets it be used with both create_connection()
and the upcoming connect()
. Nice!
I didn’t even know you could call with obj:
like that.
Nick