Упаковка 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
сопровождающие в комментариях к отчету об ошибках, но это будет серьезное изменение, поэтому на данный момент, скорее всего, ничего не изменится, поэтому я должен использовать обертку (или сделать какое-то исправление обезьяны, что, вероятно, хуже).