Requests: Diffusion de réponses gzippées

Créé le 31 juil. 2014  ·  10Commentaires  ·  Source: psf/requests

J'ai besoin de traiter de grandes réponses XML en tant que flux. Les réponses non compressées peuvent avoir une taille de plusieurs centaines de mégaoctets, donc les charger entièrement en mémoire avant de les transmettre à l'analyseur XML n'est pas une option.

J'utilise lxml pour analyser et je remets simplement le response.raw à sa fonction iterparse() , comme décrit quelque part dans la documentation des requêtes. Cela fonctionne très bien pour les réponses non compressées.

Malheureusement, l'API que j'appelle n'est pas particulièrement bonne. Il renverra donc parfois Content-Encoding: gzip même si je demande explicitement des données non compressées. De plus, le taux de compression de ces fichiers XML extrêmement répétitifs et verbeux est vraiment bon (10x+), donc j'aimerais vraiment utiliser des réponses compressées.

Est-ce possible avec les demandes ? Je ne l'ai pas trouvé dans la documentation. En recherchant plus profondément dans urllib3, sa méthode HTTPResponse.read() semble prendre en charge un paramètre decode_content . S'il n'est pas défini, urllib3 revient à ce qui est défini dans le constructeur. Lorsque request appelle le constructeur dans request.adapters.HTTPAdapter.send() , il définit explicitement decode_content sur False.

Y a-t-il une raison pour laquelle les demandes font cela?

Curieusement, iter_content() définit en fait decode_content=True lors de la lecture. Pourquoi ici? Tout semble un peu arbitraire. Je ne comprends pas vraiment la motivation pour le faire d'une manière ici et d'une autre manière là-bas.
Personnellement, je ne peux pas vraiment utiliser iter_content() bien sûr car j'ai besoin d'un objet de type fichier pour lxml.

J'ai déjà écrit mon propre objet de type fichier que je peux accrocher entre les requêtes et lxml, mais bien sûr, la mise en mémoire tampon est difficile et j'ai l'impression que des personnes plus intelligentes que moi l'ont déjà écrit, donc je préférerais ne pas avoir à rouler le mien .

Quel est votre conseil pour gérer cela ? Les requêtes doivent-elles être modifiées par défaut pour définir decode_content=True dans urllib3 ?

Contributor Friendly Documentation Planned

Commentaire le plus utile

j'ai fait ça dans le passé

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

Tous les 10 commentaires

Non, cela ne devrait pas être défini par défaut pour une grande variété de raisons. Ce que vous devez faire est d'utiliser functools.partial pour remplacer la méthode read sur la réponse (ou simplement l'envelopper d'une autre manière) afin que vous fassiez quelque chose comme :

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

puis transmettez response.raw à votre analyseur.

@ sigmavirus24 Merci, c'est certainement une solution élégante au problème que j'ai décrit ci-dessus !

Je recommanderais d'ajouter cela à la documentation des demandes, par exemple dans la FAQ : http://docs.python-requests.org/en/latest/community/faq/#encoded -data
Actuellement, l'instruction « Requests décompresse automatiquement les réponses encodées en gzip » n'est pas correcte pour le cas stream=True et peut entraîner des surprises.

Quant à mon problème, comme vous l'avez lu sur le problème urllib3 , l'implémentation urllib3 de la décompression gzip a ses propres petites bizarreries que je dois contourner dans mon code, mais ce n'est plus un problème pour les requêtes.

mais ce n'est plus un problème pour les demandes.

Comme dans vous pensez que cela peut être fermé?

@ sigmavirus24 Je pense que cela devrait être documenté, car la documentation actuelle est incorrecte.

Mais si vous n'êtes pas d'accord avec cela, oui, fermez-vous !

La documentation pourrait être plus claire. Pour moi (et c'est entièrement parce que je suis un développeur principal), le premier paragraphe s'adresse aux 90 % d'utilisateurs qui ne toucheront jamais à la réponse brute, tandis que le deuxième paragraphe contredit le premier en disant « mais si vous avez besoin d'accéder au données brutes, elles sont là pour vous". Comme je l'ai dit, c'est évident pour moi, mais je peux voir comment cela pourrait être rendu plus clair. Je vais travailler dessus ce soir.

Pour moi, c'est plus que j'aurais interprété "données brutes" comme "charge utile brute", c'est-à-dire un flux décompressé. Je dois juste le lire dans tous les morceaux dont j'ai besoin. Contrairement à .content , qui est un blob décompressé (également la charge utile, mais sous une forme différente).

La décompression réelle me semble être une préoccupation de la bibliothèque HTTP - un détail d'implémentation de HTTP si vous voulez, un détail dont je m'attendrais à ce que les requêtes soient abstraites. Que je lis la charge utile des requêtes en tant que flux ou en tant que blob de données préchargé ne ferait aucune différence. Dans tous les cas, les requêtes abstraireraient le détail de l'implémentation « compression ».

(Cette hypothèse était également au cœur de ma demande initiale de valeur par défaut de decode_content à True . Bien sûr, maintenant que je vois à quel point il s'agit d'une abstraction qui fuit, je ne le suggère plus.)

Mais oui, je suis absolument d'accord pour dire que 99% de vos utilisateurs ne seront jamais affectés par ce détail.

N'hésitez pas à clore ce sujet.

Cela conduit donc en fait à quelque chose qui me trotte dans la tête depuis un moment et que je n'ai pas encore proposé car ce serait un changement d'API important.

Je n'aime pas le fait que nous suggérions aux gens d'utiliser r.raw parce que c'est un objet que nous ne documentons pas et c'est un objet fourni par urllib3 (que nous avons prétendu dans le passé est plus un détail de mise en œuvre). Dans cet esprit, j'ai joué avec l'idée de fournir des méthodes sur un objet Response qui ne ferait qu'un proxy pour les méthodes urllib3 ( read serait juste proxy pour raw.read , etc.). Cela nous donne une flexibilité supplémentaire autour de urllib3 et nous permet de gérer (au nom des utilisateurs) un changement d'API dans urllib3 (qui historiquement n'a presque jamais été un problème, donc il n'y a pas de urgence là-dedans).

Cela dit, nous avons déjà suffisamment de méthodes sur un objet Response à mon avis et développer notre API n'est pas idéal. La meilleure API est l'API dont il n'y a plus rien à supprimer. Je suis donc continuellement sur la clôture à ce sujet.


Cette hypothèse était également au cœur de ma demande initiale de définir par défaut decode_content sur True. Bien sûr, maintenant que je vois à quel point il s'agit d'une abstraction qui fuit, je ne le suggère plus.

Pour les autres qui trouvent cela et ne savent peut-être pas pourquoi cela est vrai, permettez-moi de vous expliquer.

Il y a plusieurs utilisateurs de requêtes qui désactivent la décompression automatique pour valider la longueur d'une réponse, ou pour faire d'autres choses importantes avec elle. Un consommateur du premier type est OpenStack. De nombreux clients OpenStack valident l'en-tête Content-Length envoyé au client et la longueur réelle du corps reçu. Pour eux, gérer la décompression est un compromis équitable pour être certain qu'ils reçoivent et gèrent une réponse valide.

Un autre consommateur est Betamax (ou vraiment tout outil qui (re)construit des objets de réponse) car lorsqu'il gère le processus complet de création d'une réponse totalement valide, il a besoin que le contenu soit au format compressé.

Je suis sûr qu'il y en a d'autres que ni @Lukasa ni moi ne connaissons qui dépendent également fortement de ce comportement.

J'ai rencontré le même problème aujourd'hui et j'ai fini par faire la même hypothèse car il n'y a pas d'autre moyen de diffuser les réponses pour le moment.

Plutôt que plusieurs nouvelles méthodes sur Response, pourquoi pas un seul nouvel attribut par exemple response.stream qui jouerait le même rôle de proxy pour .raw ? Cela refléterait également bien le paramètre/paramètre stream=True et n'affecterait pas les utilisateurs ayant besoin du comportement actuel de .raw .

j'ai fait ça dans le passé

r = requests.get('url', stream=True)
r.raw.decode_content = True
...

Notez que la solution de @sigmavirus24 brise la sémantique de la méthode tell , qui renverra des décalages incorrects.

J'ai rencontré cela lors de la diffusion d'une réponse en tant que téléchargement avec reprise dans l'API Google Cloud Storage, qui utilise tell() pour déterminer le nombre d'octets qui viennent d'être lus ( ici ).

Cette page vous a été utile?
0 / 5 - 0 notes