【问题标题】:Python ftplib: open file in write mode?Python ftplib:以写入模式打开文件?
【发布时间】:2019-02-06 08:16:10
【问题描述】:

如何在 FTP 服务器上以写入模式打开文件?我知道我可以直接写入/创建文件(当我有数据时),但我想先打开它进行写入,然后才能像在本地使用 contextmanager 那样写入它。

原因是,我想创建具有统一方法的接口来与传输协议服务器一起工作。特别是 SFTP 和 FTP。

所以使用 SFTP 很容易(使用 paramiko):

def open(sftp, fname, mode='r'):
    return sftp.open(fname, mode=mode)

现在我可以这样做了:

with open(sftp, 'some_file.txt', 'w') as f:
    f.write(data)

然后我可以阅读所写的内容

with open(sftp, 'some_file.txt', 'r') as f:
    print(f.read().decode('utf-8'))

如何为 FTP 执行相同的实现(使用 ftplib)?

FTP的读取部分,我能够实现并且可以像使用SFTP一样以读取模式打开文件。但是我怎样才能在写模式下打开它呢? ftplib 方法storbinary 要求“立即”提供数据。我的意思是我应该已经通过open 方法传递了我想写入的数据(但它会破坏统一方法的目的)?

import io

def open(ftp, filename, mode='r'):
    """Open a file on FTP server."""
    def handle_buffer(buffer_data):
        bio.write(buffer_data)

    # Reading implementation
    if mode == 'r':
        bio = io.BytesIO()
        ftp.retrbinary(
            'RETR %s' % filename, callback=handle_buffer)
        bio.seek(0)
        return bio
    # Writing implementation.
    if mode == 'w':
        # how to open in write mode?

更新

假设我们在 FTP 中有即时编写实现:

bio = io.BytesIO
# Write some data
data = csv.writer(bio)
data.writerows(data_to_export)
bio.seek(0)
# Store. So it looks like storbinary does not open file in w mode, it does everything in one go?
ftp.storbinary("STOR " + file_name, sio)

所以问题是如何将写入数据与仅以写入模式打开文件分开。 ftplib 甚至可以吗?

【问题讨论】:

  • ftp.storbinary 也许?
  • @Will 这是一个错字,我的意思是 storbinary ..:)
  • @Ephemeral 这与访问有什么关系?我知道如何在 ftp 中写入,但我想先打开文件进行写入。
  • 不确定这是否有意义。 open 应该给你一个文件句柄,你可以调用它的方法(比如读和写)。您在“r”模式下的打开已经读取了文件。为什么不尝试创建一个继承 io.IOBase 的类并提供您自己的读/写/关闭方法,然后在“打开”中使用它?
  • @Ephemeral 抱歉,我不是想侮辱你。只是不明白你为什么带来访问权限。

标签: python python-3.x file-writing ftplib


【解决方案1】:

所以经过一番努力,我能够完成这项工作。解决方案是在读取时为open 方法实现自定义上下文管理器(必须重新实现读取模式,因为它仅适用于纯文件读取,但如果假设我会尝试使用 csv 读取器则失败)模式和写入时模式。

对于读取模式,我选择使用 tempfile,因为使用其他方法,我无法使用不同的读取器(普通文件读取器、csv 读取器等)正确读取数据。虽然在读取模式下使用打开的临时文件时,一切都按预期工作。

对于写入模式,我能够利用内存缓冲区 -> io.BytesIO。所以对于写作来说,没有必要使用 tempfile。

import tempfile


class OpenRead(object):

    def _open_tempfile(self):
        self.tfile = tempfile.NamedTemporaryFile()
        # Write data on tempfile.
        self.ftp.retrbinary(
            'RETR %s' % self.filename, self.tfile.write)
        # Get back to start of file, so it would be possible to
        # read it.
        self.tfile.seek(0)
        return open(self.tfile.name, 'r')

    def __init__(self, ftp, filename):
        self.ftp = ftp
        self.filename = filename
        self.tfile = None

    def __enter__(self):
        return self._open_tempfile()

    def __exit__(self, exception_type, exception_value, traceback):
        # Remove temporary file.
        self.tfile.close()

class OpenWrite(object):
    def __init__(self, ftp, filename):
        self.ftp = ftp
        self.filename = filename
        self.data = ''

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        bio = io.BytesIO()
        if isinstance(self.data, six.string_types):
            self.data = self.data.encode()
        bio.write(self.data)
        bio.seek(0)
        res = self.ftp.storbinary('STOR %s' % self.filename, bio)
        bio.close()
        return res

    def write(self, data):
        self.data += data

def open(ftp, filename, mode='r'):
    """Open a file on FTP server."""
    if mode == 'r':
        return OpenRead(ftp, filename)
    if mode == 'w':
        return OpenWrite(ftp, filename)

附:如果没有上下文管理器,这可能无法正常工作,但现在对我来说是可以的解决方案。如果有人有更好的实现,非常欢迎分享。

更新

决定使用ftputil 包而不是标准的ftplib。所以不需要所有这些黑客攻击,因为ftputil 会处理它,并且它实际上使用了许多与paramiko 相同的命名方法,它们做同样的事情,因此统一协议使用要容易得多。

【讨论】:

  • 有趣。不确定这是 100% 你想要的。如果出现问题或正确,上下文管理器允许您拥有自动“清理”代码。您正在让上下文管理器在退出时写入 FTP。那么如果出现错误怎么办?它仍然会尝试写
  • @Will 是的,它并不理想,但这就是我目前想出的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-21
  • 2020-06-23
  • 2020-03-20
相关资源
最近更新 更多