Requests: nenhuma maneira de ler o conteúdo não compactado como um objeto semelhante a um arquivo

Criado em 29 fev. 2012  ·  44Comentários  ·  Fonte: psf/requests

De acordo com a documentação, existem três maneiras de ler o conteúdo da resposta: .text , .content e .raw . Os dois primeiros consideram a codificação de transferência e descompactam o fluxo automaticamente ao produzir seu resultado na memória. No entanto, especialmente para o caso em que o resultado é grande, atualmente não há uma maneira simples de obter o resultado descompactado na forma de um objeto semelhante a um arquivo, por exemplo, para passá-lo diretamente para um analisador XML ou Json.

Do ponto de vista de uma biblioteca que visa tornar as solicitações HTTP amigáveis ​​ao usuário, por que um usuário deveria se preocupar com algo tão baixo quanto o tipo de compressão do fluxo que foi negociado internamente entre o servidor web e a biblioteca? Afinal, é "culpa" da biblioteca se o padrão é aceitar tal fluxo. Sob essa luz, o fluxo .raw é um pouco cru para o meu gosto.

Talvez uma quarta propriedade como .stream possa fornecer um melhor nível de abstração?

Comentários muito úteis

Já expliquei por que isso é um bug de design e não uma solicitação de recurso: a API existente usa a abstração errada e vaza detalhes de negociação da conexão para o espaço do usuário que estão à mercê do site remoto e, portanto, que o usuário não deve tem que se preocupar. Isso torna o suporte de leitura de fluxo bruto atual difícil de usar. Essencialmente, esta é uma solicitação para consertar um recurso que está quebrado, não uma solicitação para um novo recurso.

Todos 44 comentários

Response.iter_content

Erm, não, isso é um iterador. Eu estava pedindo um objeto semelhante a um arquivo, ou seja, algo que os processadores de documentos possam ler diretamente.

Seria muito simples fazer um objeto semelhante a um arquivo com iter_content

Obrigado pela resposta rápida, BTW.

Eu concordo. Ainda assim, seria ainda mais fácil para requests fornecer essa funcionalidade. Meu ponto é que .raw é o nível errado de abstração para a maioria dos casos de uso que desejam ler o fluxo, porque expõe detalhes do nível de transferência.

Pessoalmente, não vejo um caso de uso importante para iterar linha por linha ou mesmo pedaço por pedaço sobre o resultado de uma solicitação HTTP, mas vejo vários casos de uso importantes para analisar a partir dele como um objeto semelhante a um arquivo, especificamente formatos de resposta que requerem um analisador de documentos, como HTML, XML, Json etc.

Observe também que é muito mais fácil escrever um iterador que envolve um objeto semelhante a um arquivo do que um objeto semelhante a um arquivo que envolve um iterador.

Eu vim com o seguinte código. Ele lida com todos os casos necessários, mas acho bastante complexo. É por isso que eu disse que queria algo assim como parte da biblioteca. Os usuários não deveriam ter que descobrir isso sozinhos.

Eu acho que o código dentro de models.py das requisições usa a abstração errada aqui. Ele deve descompactar o fluxo bruto _antes_ de começar com seu mecanismo de iteração, não durante a iteração. Passar de um tipo de arquivo para um iterador apenas para voltar a um tipo de arquivo é simplesmente estúpido. Uma única transformação de API é mais do que suficiente e a maioria dos usuários não se importará com os iteradores de conteúdo de qualquer maneira.

class FileLikeDecompressor(object):
    """
    File-like object that wraps and decompresses an HTTP stream transparently.
    """
    def __init__(self, stream, mode='gzip'):
        self.stream = stream
        zlib_mode = 16 + zlib.MAX_WBITS if mode == 'gzip' else -zlib.MAX_WBITS  # magic
        self.dec = zlib.decompressobj(zlib_mode)
        self.data = ''

    def read(self, n=None):
        if self.dec is None:
            return '' # all done
        if n is None:
            data = self.data + self.dec.decompress(self.stream.read())
            self.data = self.dec = None
            return data
        while len(self.data) < n:
            new_data = self.stream.read(n)
            self.data += self.dec.decompress(new_data)
            if not new_data:
                self.dec = None
                break
        if self.data:
            data, self.data = self.data[:n], self.data[n:]
            return data
        return ''

def decompressed(response):
    """
    Return a file-like object that represents the uncompressed HTTP response data.
    For compressed HTTP responses, wraps the stream in a FileLikeDecompressor.
    """
    stream = response.raw
    mode = response.headers.get('content-encoding')
    if mode in ('gzip', 'deflate'):
        return FileLikeDecompressor(stream, mode)
    return stream

Por que você não constrói o objeto semelhante a um arquivo de content_iter conforme proposto. Isso pode ser parecido com:

class FileLikeFromIter(object):
    def __init__(self, content_iter):
        self.iter = content_iter
        self.data = ''

    def __iter__(self):
        return self.iter

    def read(self, n=None):
        if n is None:
            return self.data + '\n'.join(l for l in self.iter)
        else:
            while len(self.data) < n:
                try:
                    self.data = '\n'.join((self.data, self.iter.next()))
                except StopIteration:
                    break
            result, self.data = self.data[:n], self.data[n:]
            return result

Você pode querer ler meu comentário novamente, especificamente o parágrafo que precede o código que postei.

Sim, mas esta solução ainda é mais limpa (e IMO mais fácil) do que fazer a descompressão em um segundo lugar porque isso já está embutido nas solicitações.

Mas eu concordo com você em geral, um r.file (ou algo assim) tem muito mais casos de uso do que r.raw . Então, eu gostaria de ver isso incluído nas solicitações também. @kennethreitz

"response.stream" soa como um bom nome para mim.

É para isso que serve o response.raw :)

Isso também foi o que intuitivamente pensei quando vi. Mas então percebi que o response.raw está quebrado porque expõe detalhes internos da camada de transporte subjacente com os quais os usuários não deveriam se preocupar.

O único método de que eles precisam é raw.read ?

Bem, sim - exceto que raw.read () se comporta de maneira diferente dependendo das negociações internas entre cliente e servidor. Às vezes, ele retorna os dados esperados e às vezes retorna bytes compactados vazios.

Basicamente, response.raw é um recurso interessante que a maioria dos usuários ignora alegremente e alguns usuários avançados podem achar útil, enquanto um response.stream independente de compressão é um recurso que a maioria dos usuários de streaming consideraria quer.

+1

+1

Esse bug de design será corrigido?

não tenho certeza se essa maneira é correta ou eficiente, mas para mim, o seguinte funciona :

>>> import lxml  # a parser that scorns encoding
>>> unicode_response_string = response.text
>>> lxml.etree.XML(bytes(bytearray(unicode_response_string, encoding='utf-8')))  # provided unicode() means utf-8
<Element html at 0x105364870>

@kernc : É uma coisa bizarra de se fazer. response.content já é um bytestring, então o que você está fazendo aqui é decodificar o conteúdo com qualquer codec que Python escolher e, em seguida, recodificá-lo como utf-8.

Este _não_ é um bug, e definitivamente não é o bug que você sugeriu. Se você realmente precisa de um objeto semelhante a um arquivo, recomendo StringIO e BytesIO.

@Lukasa está correto. content sempre deve ser um bytestring (no Python 3 é um bytestring explícito; no Python 2 str == bytes). O único item que não é uma sequência de bytes é text .

@kennethreitz alguma notícia sobre isso? Este é um bug de design bastante sério e é melhor resolvê-lo logo. Quanto mais código for escrito para contorná-lo, mais caro se tornará para todos.

Isso não é um bug de design, é apenas uma solicitação de recurso. E como as solicitações congelaram de recursos , presumo que isso não será exibido nas solicitações em breve (se houver) ...

Não acho que redeclarar um bug de design de longa data é um "recurso ausente"
faz com que desapareça facilmente. Ouvi dizer que o autor está pensando sobre
fazer "solicitações" parte do stdlib do Python. Isso seria um bom
oportunidade de consertar isso.

Ouvi dizer que o autor está pensando sobre
fazer "solicitações" parte do stdlib do Python.

Na verdade, não: http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library

Isso não é um bug, é uma solicitação de recurso. As solicitações não estão fazendo nada de errado, simplesmente não estão fazendo algo opcional. Essa é a própria definição de um recurso.

Além disso, a preparação para o stdlib é exatamente o motivo pelo qual Requests está em congelamento de recursos. Uma vez que Requests está no stdlib, torna-se muito difícil fazer correções de bug oportunas. Como resultado, se adicionar o novo recurso adiciona bugs ou regride o comportamento, a versão em stdlib não pode ser corrigida até o próximo lançamento secundário. Isso seria ruim.

Marc Schlaich, 19.03.2013 08:41:

Ouvi dizer que o autor está pensando sobre
fazer "solicitações" parte do stdlib do Python.

Na verdade, não: http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library

Eu li aqui:

http://python-notes.boredomandlaziness.org/en/latest/conferences/pyconus2013/20130313-language-summit.html

Stefan

Já expliquei por que isso é um bug de design e não uma solicitação de recurso: a API existente usa a abstração errada e vaza detalhes de negociação da conexão para o espaço do usuário que estão à mercê do site remoto e, portanto, que o usuário não deve tem que se preocupar. Isso torna o suporte de leitura de fluxo bruto atual difícil de usar. Essencialmente, esta é uma solicitação para consertar um recurso que está quebrado, não uma solicitação para um novo recurso.

Deixe-me resumir isso de forma limpa. O bug é que qualquer uso real do recurso de leitura de fluxo bruto terá que reimplementar uma parte da biblioteca, especificamente toda a parte de descompressão de fluxo condicional, porque o recurso é inútil sem ele, assim que o cliente permitir a compactação. Estamos falando de código aqui que já está lá, em "requisições" - é usado apenas no lugar errado. Deve ser usado abaixo do nível de leitura bruta, não acima dele, porque o cliente não pode controlar se o servidor honra o cabeçalho de aceitação ou não. A compactação deve ser um detalhe de negociação transparente da conexão, não algo que prejudique qualquer usuário que habilite o cabeçalho relevante.

Não consigo pensar em nenhum caso de uso em que o cliente estaria interessado no fluxo compactado, especialmente se ele não puder prever se o fluxo será realmente compactado ou não, já que o servidor pode ignorar o desejo do cliente. É um detalhe de pura negociação. É por isso que a leitura de fluxo bruto usa a abstração errada, preferindo o caso de uso extremamente improvável ao mais comum.

Eu posso. Por exemplo, e se você estivesse baixando um arquivo grande baseado em texto e quisesse mantê-lo compactado? Eu poderia acompanhar essa mudança com um novo 'bug de design' intitulado Nenhuma maneira de salvar os dados compactados originalmente no disco .

Essa ideia é intencionalmente banal e estúpida, mas estou tentando ilustrar um ponto, que é o seguinte: os Requests não são obrigados a oferecer a todos exatamente o mecanismo de interação que desejam. Na verdade, fazer isso iria contra o objetivo principal do Requests, que é a simplicidade da API. Há uma lista longa, longa, _longa_ de alterações propostas para Solicitações que foram contestadas porque complicam a API, embora adicionem funcionalidades úteis. As solicitações não têm como objetivo substituir o urllib2 para todos os casos de uso, mas sim simplificar os casos mais comuns.

Nesse caso, Requests pressupõe que a maioria dos usuários não deseja objetos semelhantes a arquivos e, portanto, propõe as seguintes interações:

  • Response.text and Response.content : você quer todos os dados de uma vez.
  • Response.iter_lines() and Response.iter_content() : você não quer todos os dados de uma vez.
  • Response.raw : você não está satisfeito com as outras duas opções, então faça você mesmo.

Eles foram escolhidos porque representam de forma esmagadora os usos comuns de Solicitações. Você disse que "a maioria dos usuários não se preocupa com iteradores de conteúdo de qualquer maneira " e " response.stream é um recurso que a maioria dos usuários de streaming deseja ". A experiência neste projeto me leva a discordar: muitas pessoas usam os iteradores de conteúdo e poucas desejam objetos semelhantes a arquivos.

Um ponto final: se a compressão deve ser um detalhe de negociação transparente da conexão, então você deve levantar o bug apropriado contra urllib3, que lida com nossa lógica de conexão.

Lamento que você sinta que as Solicitações são inadequadas para seu caso de uso.

Eu entendi que response.raw está quebrado na implementação atual e até concordo parcialmente com isso (você deve pelo menos ser capaz de obter detalhes de compressão sem analisar os cabeçalhos).

No entanto, sua proposta ainda é uma solicitação de recurso ...

@Lukasa
Eu realmente não consigo ver como preencher o bug contra urllib3 consertaria a API de solicitações, pelo menos não por si só.

E eu concordo que seu "caso de uso" foi planejado. Como eu disse, se o cliente não pode controlar positivamente a compressão no lado do servidor (e ele desabilita, mas não habilita de forma confiável), então confiar nele para salvar um arquivo compactado no disco não é tão interessante .

@schlamar
Eu concordo que pode ser lido como tal. Garanto que estou bem com qualquer coisa que resolva esse problema. Se abrir um novo tíquete é necessário para chegar lá, que seja.

Se abrir um novo tíquete é necessário para chegar lá, que seja.

Ainda acho que Kenneth vai rejeitar isso devido ao congelamento de recursos.

Estou bem com qualquer coisa que resolva este problema

  1. Envolva iter_content como um objeto semelhante a um arquivo ou
  2. Analise os cabeçalhos e descompacte response.raw se apropriado

Ambas as soluções estão nos comentários acima, o último postado por você. Por que é tão problemático não estar diretamente nas solicitações?

Vamos ser 100% claros aqui: basicamente não há chance de isso entrar nas Solicitações enquanto ele estiver no congelamento de recursos. Nada está quebrado, a API simplesmente não é perfeita para suas necessidades. Como nada está quebrado, a única coisa que importa é se Kenneth quer. Pedidos não é uma democracia, é um homem, um voto. Kenneth é o cara, ele tem o direito de voto . Kenneth encerrou esse problema há 8 meses, então parece bem claro que ele não o quer.

Eu realmente não consigo ver como preencher o bug contra urllib3 consertaria a API de solicitações, pelo menos não por si só.

Corrigir urllib3 para sempre retornar o objeto-arquivo descompactado deve resolver isso por si só (não disse que isso é uma boa idéia).

Oh, aqui está a solução número 3 (não testada):

response.raw.read = functools.partial(response.raw.read, decode_content=True)

Consulte https://github.com/shazow/urllib3/blob/master/urllib3/response.py#L112

Interessante - eu não sabia disso agora. Isso torna muito mais fácil envolver o recurso, com certeza.

Embora isso realmente funcione? Ou seja, os descompressores têm estado e são incrementais? A segunda chamada para read (123) não retornará mais o início válido de um arquivo gzip, por exemplo.

Embora isso realmente funcione? Ou seja, os descompressores têm estado e são incrementais?

Oh, não parece. Eu não li o docstring.

No entanto, aqui está minha proposta:

  1. Corrija o urllib3 para que HTTPResponse.read funcione com amt e decode_content simultaneamente.
  2. Tornar HTTPResponse._decode_content um membro público (para que você possa fazer response.raw.decode_content = True vez de corrigir o método read ).
  3. Elimine completamente a descompressão nas solicitações usando decode_content=True em iter_content

@Lukasa Acho que isso não vai violar o congelamento de recursos, certo?

@schlamar : Em princípio, claro. Contanto que a API permaneça inalterada, as mudanças internas _devem_ estar ok, e eu estaria +1 neste aqui. No entanto, lembre-se de que não sou o BDFL, =)

stream_decompress em solicitações quebradas de qualquer maneira: # 1249

+1

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

cnicodeme picture cnicodeme  ·  3Comentários

remram44 picture remram44  ·  4Comentários

8key picture 8key  ·  3Comentários

JimHokanson picture JimHokanson  ·  3Comentários

justlurking picture justlurking  ·  3Comentários