如何使用Paramiko上传文件类对象?

13 投票
2 回答
14556 浏览
提问于 2025-04-16 17:08

我有一段代码,看起来是这样的:

with tempfile.NamedTemporaryFile() as tmpfile:
    tmpfile.write(fileobj.read()) # fileobj is some file-like object
    tmpfile.flush()
    try:
        self.sftp.put(tmpfile.name, path)
    except IOError:
        # error handling removed for ease of reading
        pass

有没有办法像这样上传文件,而不需要先把文件写到某个地方呢?

2 个回答

2

你在找的可能是 StringIO 吗?(文档页面

SFTPClientget()put() 函数需要的是文件路径,而不是文件句柄,这样就有点麻烦了。

你可以为 paramiko.SFTPClient 写一个包装器,这样就能实现你想要的功能。

下面是我最好的尝试,虽然还没测试过:

from paramiko import SFTPClient

class SFTPClient2(SFTPClient):
    def put(self, local_file, remotepath, callback=None, confirm=True):
        fl = source_file
        file_size = os.fstat(fl.fileno()).st_size
        try:
            fr = self.file(remotepath, 'wb')
            fr.set_pipelined(True)
            size = 0
            try:
                while True:
                    data = fl.read(32768)
                    if len(data) == 0:
                        break
                    fr.write(data)
                    size += len(data)
                    if callback is not None:
                        callback(size, file_size)
            finally:
                fr.close()
        finally:
            fl.close()
        if confirm:
            s = self.stat(remotepath)
            if s.st_size != size:
                raise IOError('size mismatch in put!  %d != %d' % (s.st_size, size))
        else:
            s = SFTPAttributes()
        return s

    def get(self, remotepath, local_file, callback=None):
        fr = self.file(remotepath, 'rb')
        file_size = self.stat(remotepath).st_size
        fr.prefetch()
        try:
            fl = local_file
            try:
                size = 0
                while True:
                    data = fr.read(32768)
                    if len(data) == 0:
                        break
                    fl.write(data)
                    size += len(data)
                    if callback is not None:
                        callback(size, file_size)
            finally:
                fl.close()
        finally:
            fr.close()
        s = os.fstat(fl.fileno())
        if s.st_size != size:
            raise IOError('size mismatch in get!  %d != %d' % (s.st_size, size))

如果这个方法有效的话,getput 函数现在应该可以接受本地的文件句柄,而不是路径了。

我只需要去掉从路径打开文件的那段代码,并把获取文件大小的代码改成用 os.fstat,而不是 os.stat

23

更新 从 Paramiko 1.10 开始,你可以使用 putfo 方法:

self.sftp.putfo(fileobj, path)

你可以用 paramiko.SFTPClient.open 来代替 paramiko.SFTPClient.put,这个方法可以打开一个像文件一样的对象。你可以往里面写东西。大概是这样的:

f = self.sftp.open(path, 'wb')
f.write(fileobj.read())
f.close()

需要注意的是,给 paramiko 传输数据时,最好分成 32 KiB 的小块,因为这是 SSH 协议能处理的最大数据块,超过这个大小就会被拆分成多个包。

撰写回答