Упаковка urllib3.HTTPResponse в io.TextIOWrapper

Я использую AWS boto3 библиотека, которая возвращает мне экземпляр urllib3.response.HTTPResponse, Этот ответ является подклассом io.IOBase и, следовательно, ведет себя как двоичный файл. это read() метод возвращает bytes экземпляров.

Теперь мне нужно расшифровать csv данные из файла, полученные таким образом. Я хочу, чтобы мой код работал на обоих py2 а также py3 с минимальными накладными расходами кода, поэтому я использую backports.csv который опирается на io.IOBase объекты в качестве входных данных, а не на py2 file() объекты.

Первая проблема заключается в том, что HTTPResponse доходность bytes данные для файла CSV, и у меня есть csv.reader что ожидает str данные.

>>> import io
>>> from backports import csv  # actually try..catch statement here
>>> from mymodule import get_file

>>> f = get_file()  # returns instance of urllib3.HTTPResponse
>>> r = csv.reader(f)
>>> list(r)
Error: iterator should return strings, not bytes (did you open the file in text mode?)

Я пытался завернуть HTTPResponse с io.TextIOWrapper и получил ошибку 'HTTPResponse' object has no attribute 'read1', Это ожидается, потому что TextIOWrapper предназначен для использования с BufferedIOBase объекты, а не IOBase объекты. И это происходит только на python2Реализация TextIOWrapper потому что он всегда ожидает, что базовый объект имеет read1 ( источник), в то время как python3проверки выполнения для read1 существование и возвращается к read изящно ( источник).

>>> f = get_file()
>>> tw = io.TextIOWrapper(f)
>>> list(csv.reader(tw))
AttributeError: 'HTTPResponse' object has no attribute 'read1'

Затем я попытался завернуть HTTPResponse с io.BufferedReader а затем с io.TextIOWrapper, И я получил следующую ошибку:

>>> f = get_file()
>>> br = io.BufferedReader(f)
>>> tw = io.TextIOWrapper(br)
>>> list(csv.reader(f))
ValueError: I/O operation on closed file.

После некоторого расследования выясняется, что ошибка возникает только тогда, когда файл не заканчивается \n, Если это заканчивается \n тогда проблемы не бывает и все отлично работает.

Существует некоторая дополнительная логика для закрытия базового объекта в HTTPResponse ( источник), который, по-видимому, вызывает проблему.

Вопрос: как я могу написать свой код

  • работать как на python2, так и на python3, желательно без try..catch или ветвления, зависящего от версии;
  • правильно обрабатывать файлы CSV, представленные в виде HTTPResponse независимо от того, заканчиваются ли они \n или нет?

Одним из возможных решений было бы сделать обертку вокруг TextIOWrapper что сделало бы read() вернуть b'' когда объект закрыт вместо поднятия ValueError, Но есть ли лучшее решение без таких взломов?

1 ответ

Похоже, это несоответствие интерфейса между urllib3.HTTPResponse а также file объекты. Это описано в этом urllib3 выпуск № 1305.

На данный момент нет никакого исправления, поэтому я использовал следующий код-обертку, который, казалось бы, работает нормально:

class ResponseWrapper(io.IOBase):
    """
    This is the wrapper around urllib3.HTTPResponse
    to work-around an issue shazow/urllib3#1305.

    Here we decouple HTTPResponse's "closed" status from ours.
    """
    # FIXME drop this wrapper after shazow/urllib3#1305 is fixed

    def __init__(self, resp):
        self._resp = resp

    def close(self):
        self._resp.close()
        super(ResponseWrapper, self).close()

    def readable(self):
        return True

    def read(self, amt=None):
        if self._resp.closed:
            return b''
        return self._resp.read(amt)

    def readinto(self, b):
        val = self.read(len(b))
        if not val:
            return 0
        b[:len(val)] = val
        return len(val)

И используйте его следующим образом:

>>> f = get_file()
>>> r = csv.reader(ResponseWrapper(io.TextIOWrapper(io.BufferedReader(f))))
>>> list(r)

Подобное исправление было предложено urllib3 сопровождающие в комментариях к отчету об ошибках, но это будет серьезное изменение, поэтому на данный момент, скорее всего, ничего не изменится, поэтому я должен использовать обертку (или сделать какое-то исправление обезьяны, что, вероятно, хуже).

Другие вопросы по тегам