Python ftplib: открыть файл в режиме записи? - PullRequest
0 голосов
/ 06 февраля 2019

Как открыть файл на 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?

update

Пустьскажем, у нас есть непосредственная реализация записи в 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?

1 Ответ

0 голосов
/ 06 февраля 2019

Так что после некоторой борьбы я смог сделать эту работу.Решение состояло в том, чтобы реализовать пользовательские контекстные менеджеры для метода open в режиме чтения (пришлось переопределить режим чтения, потому что он работал только с обычным чтением файлов, но не работал, если, скажем, я буду пытаться использовать csv reader) и в режиме записиmode.

Для режима чтения я решил использовать tempfile, потому что, используя другие подходы, я не мог правильно читать данные, используя разные программы чтения (обычная программа чтения файлов, программа для чтения csv и т. д.).Хотя при использовании открытого файла tempfile в режиме чтения все работает как положено.

Для режима записи я смог использовать буфер памяти -> 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)

PS это может не работать должным образом без менеджера контекста, но пока это нормальное решение для меня.Если у кого-то есть лучшая реализация, он будет рад поделиться ею.

Обновление

Решено использовать пакет ftputil вместо стандартного ftplib.Таким образом, весь этот взлом не нужен, потому что ftputil позаботится об этом, и он на самом деле использует много тех же именованных методов, что и paramiko, которые делают то же самое, так что гораздо проще унифицировать использование протоколов.

...