[Twisted-Python] Conch SFTP Questions

2020-09-22 Thread Robert DiFalco
Hey folks, I've cobbled together an SFTP client based on bits and pieces
I've found around the web. The issue is that it appears to be almost one
shot. I will need to send many files (the number not known ahead of time).
It's not clear to me when the connection is closed or how many factories
I'm creating. All the code I've grabbed looks like it's creating a new
factory for every SFTP file I send. Here's some of the code I have. It's
fairly straight forward in that it creates a directory if it doesn't exist
and then writes a file.

@attr.s(frozen=True)
class FileInfo(object):
"""
Class that tells SFTP details about the file to send.
"""
directory = attr.ib(converter=str)  # type: str
name = attr.ib(converter=str)  # type: str
data = attr.ib()  # type: str
chunk_size = attr.ib(converter=int, default=CHUNK_SIZE)  # type: int

def to_path(self):
"""
Turns the folder and file name into a file path.
"""
return self.directory + "/" + self.name


@attr.s(frozen=True)
class SFTPClientOptions(object):
"""
Client options for sending SFTP files.

:param host: the host of the SFTP server
:param port: the port ofo the SFTP server
:param fingerprint: the expected fingerprint of the host
:param user: the user to login as
:param identity: the identity file, optional and like the "-i"
command line option
:param password: an optional password
"""
host = attr.ib(converter=str)  # type: str
port = attr.ib(converter=int)  # type: int
fingerprint = attr.ib(converter=str)  # type: str
user = attr.ib(converter=str)  # type: str
identity = attr.ib(converter=optional(str), default=None)  # type:
Optional[str]
password = attr.ib(converter=optional(str), default=None)  # type:
Optional[str]


@inlineCallbacks
def sftp_send(client_options, file_info):
# type: (SFTPClientOptions, FileInfo)->Deferred
"""
Primary function to send an file over SFTP. You can send a
password, identity, or both.
:param client_options: the client connection options
:param file_info: contains the file info to write
:return: A deferred that signals "OK" if successful.
"""
options = ClientOptions()
options["host"] = client_options.host
options["port"] = client_options.port
options["password"] = client_options.password
options["fingerprint"] = client_options.fingerprint

if client_options.identity:
options.identitys = [client_options.identity]

conn = SFTPConnection()
auth = SFTPUserAuthClient(client_options.user, options, conn)
yield connect(client_options.host, client_options.port, options,
_verify_host_key, auth)

sftpClient = yield conn.getSftpClientDeferred()
yield _send_file(sftpClient, file_info)

returnValue("OK")


def _verify_host_key(transport, host, pubKey, fingerprint):
"""
Verify a host's key. Based on what is specified in options.

@param host: Due to a bug in L{SSHClientTransport.verifyHostKey}, this is
always the dotted-quad IP address of the host being connected to.
@type host: L{str}

@param transport: the client transport which is attempting to connect to
the given host.
@type transport: L{SSHClientTransport}

@param fingerprint: the fingerprint of the given public key, in
xx:xx:xx:... format.

@param pubKey: The public key of the server being connected to.
@type pubKey: L{str}

@return: a L{Deferred} which is success or error
"""
expected = transport.factory.options.get("fingerprint", "no_fingerprint")
if fingerprint == expected:
return succeed(1)

log.error(
"SSH Host Key fingerprint of ({fp}) does not match the
expected value of ({expected}).",
fp=fingerprint, expected=expected)

return fail(ConchError("Host fingerprint is unexpected."))


class SFTPSession(SSHChannel):
"""
Creates an SFTP session.
"""
name = "session"

@inlineCallbacks
def channelOpen(self, whatever):
"""
Called when the channel is opened.  "whatever" is any data that the
other side sent us when opening the channel.

@type whatever: L{bytes}
"""
yield self.conn.sendRequest(self, "subsystem", NS("sftp"),
wantReply=True)

client = FileTransferClient()
client.makeConnection(self)
self.dataReceived = client.dataReceived
self.conn.notifyClientIsReady(client)


class SFTPConnection(SSHConnection):
def __init__(self):
"""
Adds a deferred here so client can add a callback when the
SFTP client is ready.
"""
SSHConnection.__init__(self)
self._sftpClient = Deferred()

def serviceStarted(self):
"""
Opens an SFTP session when the SSH connection has been started.
"""
self.openChannel(SFTPSession())

def notifyClientIsReady(self, client):
"""
Trigger callbacks associated with our SFTP clie

Re: [Twisted-Python] Conch SFTP Questions

2020-09-22 Thread Adi Roiban
Hi Robers

On Tue, 22 Sep 2020 at 16:43, Robert DiFalco 
wrote:

> Hey folks, I've cobbled together an SFTP client based on bits and pieces
> I've found around the web. The issue is that it appears to be almost one
> shot. I will need to send many files (the number not known ahead of time).
> It's not clear to me when the connection is closed or how many factories
> I'm creating. All the code I've grabbed looks like it's creating a new
> factory for every SFTP file I send. Here's some of the code I have. It's
> fairly straight forward in that it creates a directory if it doesn't exist
> and then writes a file.
>
> @attr.s(frozen=True)
> class FileInfo(object):
> """
> Class that tells SFTP details about the file to send.
> """
> directory = attr.ib(converter=str)  # type: str
> name = attr.ib(converter=str)  # type: str
> data = attr.ib()  # type: str
> chunk_size = attr.ib(converter=int, default=CHUNK_SIZE)  # type: int
>
> def to_path(self):
> """
> Turns the folder and file name into a file path.
> """
> return self.directory + "/" + self.name
>
>
> @attr.s(frozen=True)
> class SFTPClientOptions(object):
> """
> Client options for sending SFTP files.
>
> :param host: the host of the SFTP server
> :param port: the port ofo the SFTP server
> :param fingerprint: the expected fingerprint of the host
> :param user: the user to login as
> :param identity: the identity file, optional and like the "-i" command 
> line option
> :param password: an optional password
> """
> host = attr.ib(converter=str)  # type: str
> port = attr.ib(converter=int)  # type: int
> fingerprint = attr.ib(converter=str)  # type: str
> user = attr.ib(converter=str)  # type: str
> identity = attr.ib(converter=optional(str), default=None)  # type: 
> Optional[str]
> password = attr.ib(converter=optional(str), default=None)  # type: 
> Optional[str]
>
>
> @inlineCallbacks
> def sftp_send(client_options, file_info):
> # type: (SFTPClientOptions, FileInfo)->Deferred
> """
> Primary function to send an file over SFTP. You can send a password, 
> identity, or both.
> :param client_options: the client connection options
> :param file_info: contains the file info to write
> :return: A deferred that signals "OK" if successful.
> """
> options = ClientOptions()
> options["host"] = client_options.host
> options["port"] = client_options.port
> options["password"] = client_options.password
> options["fingerprint"] = client_options.fingerprint
>
> if client_options.identity:
> options.identitys = [client_options.identity]
>
> conn = SFTPConnection()
> auth = SFTPUserAuthClient(client_options.user, options, conn)
> yield connect(client_options.host, client_options.port, options, 
> _verify_host_key, auth)
>
> sftpClient = yield conn.getSftpClientDeferred()
> yield _send_file(sftpClient, file_info)
>
> returnValue("OK")
>
>
> def _verify_host_key(transport, host, pubKey, fingerprint):
> """
> Verify a host's key. Based on what is specified in options.
>
> @param host: Due to a bug in L{SSHClientTransport.verifyHostKey}, this is
> always the dotted-quad IP address of the host being connected to.
> @type host: L{str}
>
> @param transport: the client transport which is attempting to connect to
> the given host.
> @type transport: L{SSHClientTransport}
>
> @param fingerprint: the fingerprint of the given public key, in
> xx:xx:xx:... format.
>
> @param pubKey: The public key of the server being connected to.
> @type pubKey: L{str}
>
> @return: a L{Deferred} which is success or error
> """
> expected = transport.factory.options.get("fingerprint", "no_fingerprint")
> if fingerprint == expected:
> return succeed(1)
>
> log.error(
> "SSH Host Key fingerprint of ({fp}) does not match the expected value 
> of ({expected}).",
> fp=fingerprint, expected=expected)
>
> return fail(ConchError("Host fingerprint is unexpected."))
>
>
> class SFTPSession(SSHChannel):
> """
> Creates an SFTP session.
> """
> name = "session"
>
> @inlineCallbacks
> def channelOpen(self, whatever):
> """
> Called when the channel is opened.  "whatever" is any data that the
> other side sent us when opening the channel.
>
> @type whatever: L{bytes}
> """
> yield self.conn.sendRequest(self, "subsystem", NS("sftp"), 
> wantReply=True)
>
> client = FileTransferClient()
> client.makeConnection(self)
> self.dataReceived = client.dataReceived
> self.conn.notifyClientIsReady(client)
>
>
> class SFTPConnection(SSHConnection):
> def __init__(self):
> """
> Adds a deferred here so client can add a callback when the SFTP 
> client is ready.
> """
> SSHConnection.__i

[Twisted-Python] Request for new Twisted release?

2020-09-22 Thread Craig Rodrigues
Amber,

Can we have a new Twisted release within the next two months, say in
Nov./Dec. timeframe, or
sooner if you'd like?

In Twisted trunk, there are a lot of things that would be good to have in a
new release.

Here are some of the high order items:

*Python 3 Fixes*

   - twist dns --pyzone now works on Python 3   (I had a project 6 months
   ago where I would have liked to have this in a release)
   - twisted.web.twcgi now works on Python 3


*Python 3.8 Fixes*

   - all tests pass on all platforms on Python 3.8, including asyncio tests
   on Windows


*Python 3.9 Fixes*

   - all tests pass on Python 3.9.0rc2 (at least on Mac and Linux), (thanks
   to Michał Górny)


*Python 2.7*

   - Python 2.7 support has been dropped in trunk

*Black*

   - black code formatter has been integrated into the CI for Twisted, and
   all Twisted code is now formatted with Black (thanks to Tom Most)

*Mypy*

   - mypy is now run as part of Twisted CI and checks the code for every
   PR.  Type annotations compatible with Python 3.5+ are slowly being added to
   the codebase.


There are a lot of other small fixes and enhancements that people have
contributed since the last Twisted release.

I would like this new release to be the last release supporting Python 3.5,
so that Python 3.5 support can be removed in trunk, and PEP-526 style
variable annotations can be added in trunk.

Thanks for your consideration.

--
Craig
___
Twisted-Python mailing list
Twisted-Python@twistedmatrix.com
https://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python


Re: [Twisted-Python] Conch SFTP Questions

2020-09-22 Thread Robert DiFalco
Thanks! That is the full code. `connect` is from the conch library.

On Tue, Sep 22, 2020 at 12:57 PM Adi Roiban  wrote:

> Hi Robers
>
> On Tue, 22 Sep 2020 at 16:43, Robert DiFalco 
> wrote:
>
>> Hey folks, I've cobbled together an SFTP client based on bits and pieces
>> I've found around the web. The issue is that it appears to be almost one
>> shot. I will need to send many files (the number not known ahead of time).
>> It's not clear to me when the connection is closed or how many factories
>> I'm creating. All the code I've grabbed looks like it's creating a new
>> factory for every SFTP file I send. Here's some of the code I have. It's
>> fairly straight forward in that it creates a directory if it doesn't exist
>> and then writes a file.
>>
>> @attr.s(frozen=True)
>> class FileInfo(object):
>> """
>> Class that tells SFTP details about the file to send.
>> """
>> directory = attr.ib(converter=str)  # type: str
>> name = attr.ib(converter=str)  # type: str
>> data = attr.ib()  # type: str
>> chunk_size = attr.ib(converter=int, default=CHUNK_SIZE)  # type: int
>>
>> def to_path(self):
>> """
>> Turns the folder and file name into a file path.
>> """
>> return self.directory + "/" + self.name
>>
>>
>> @attr.s(frozen=True)
>> class SFTPClientOptions(object):
>> """
>> Client options for sending SFTP files.
>>
>> :param host: the host of the SFTP server
>> :param port: the port ofo the SFTP server
>> :param fingerprint: the expected fingerprint of the host
>> :param user: the user to login as
>> :param identity: the identity file, optional and like the "-i" command 
>> line option
>> :param password: an optional password
>> """
>> host = attr.ib(converter=str)  # type: str
>> port = attr.ib(converter=int)  # type: int
>> fingerprint = attr.ib(converter=str)  # type: str
>> user = attr.ib(converter=str)  # type: str
>> identity = attr.ib(converter=optional(str), default=None)  # type: 
>> Optional[str]
>> password = attr.ib(converter=optional(str), default=None)  # type: 
>> Optional[str]
>>
>>
>> @inlineCallbacks
>> def sftp_send(client_options, file_info):
>> # type: (SFTPClientOptions, FileInfo)->Deferred
>> """
>> Primary function to send an file over SFTP. You can send a password, 
>> identity, or both.
>> :param client_options: the client connection options
>> :param file_info: contains the file info to write
>> :return: A deferred that signals "OK" if successful.
>> """
>> options = ClientOptions()
>> options["host"] = client_options.host
>> options["port"] = client_options.port
>> options["password"] = client_options.password
>> options["fingerprint"] = client_options.fingerprint
>>
>> if client_options.identity:
>> options.identitys = [client_options.identity]
>>
>> conn = SFTPConnection()
>> auth = SFTPUserAuthClient(client_options.user, options, conn)
>> yield connect(client_options.host, client_options.port, options, 
>> _verify_host_key, auth)
>>
>> sftpClient = yield conn.getSftpClientDeferred()
>> yield _send_file(sftpClient, file_info)
>>
>> returnValue("OK")
>>
>>
>> def _verify_host_key(transport, host, pubKey, fingerprint):
>> """
>> Verify a host's key. Based on what is specified in options.
>>
>> @param host: Due to a bug in L{SSHClientTransport.verifyHostKey}, this is
>> always the dotted-quad IP address of the host being connected to.
>> @type host: L{str}
>>
>> @param transport: the client transport which is attempting to connect to
>> the given host.
>> @type transport: L{SSHClientTransport}
>>
>> @param fingerprint: the fingerprint of the given public key, in
>> xx:xx:xx:... format.
>>
>> @param pubKey: The public key of the server being connected to.
>> @type pubKey: L{str}
>>
>> @return: a L{Deferred} which is success or error
>> """
>> expected = transport.factory.options.get("fingerprint", "no_fingerprint")
>> if fingerprint == expected:
>> return succeed(1)
>>
>> log.error(
>> "SSH Host Key fingerprint of ({fp}) does not match the expected 
>> value of ({expected}).",
>> fp=fingerprint, expected=expected)
>>
>> return fail(ConchError("Host fingerprint is unexpected."))
>>
>>
>> class SFTPSession(SSHChannel):
>> """
>> Creates an SFTP session.
>> """
>> name = "session"
>>
>> @inlineCallbacks
>> def channelOpen(self, whatever):
>> """
>> Called when the channel is opened.  "whatever" is any data that the
>> other side sent us when opening the channel.
>>
>> @type whatever: L{bytes}
>> """
>> yield self.conn.sendRequest(self, "subsystem", NS("sftp"), 
>> wantReply=True)
>>
>> client = FileTransferClient()
>> client.makeConnection(self)
>> self.dataReceived = client.dataReceived
>>