Selon la documentation, il existe trois façons de lire le contenu de la réponse : .text
, .content
et .raw
. Les deux premiers prennent en compte l'encodage du transfert et dĂ©compressent le flux automatiquement lors de la production de leur rĂ©sultat en mĂ©moire. Cependant, en particulier dans le cas oĂč le rĂ©sultat est volumineux, il n'existe actuellement aucun moyen simple d'obtenir le rĂ©sultat dĂ©compressĂ© sous la forme d'un objet de type fichier, par exemple pour le passer directement dans un analyseur XML ou Json.
Du point de vue d'une bibliothĂšque qui vise Ă rendre les requĂȘtes HTTP conviviales, pourquoi un utilisateur devrait-il se soucier de quelque chose d'aussi bas niveau que le type de compression du flux qui a Ă©tĂ© nĂ©gociĂ© en interne entre le serveur Web et la bibliothĂšque ? AprĂšs tout, c'est la "faute" de la bibliothĂšque si elle accepte par dĂ©faut un tel flux. Dans cette optique, le stream .raw
est un peu trop cru à mon goût.
Peut-ĂȘtre qu'une quatriĂšme propriĂ©tĂ© comme .stream
pourrait fournir un meilleur niveau d'abstraction ?
Response.iter_content
Euh, non, c'est un itérateur. Je demandais un objet de type fichier, c'est-à -dire quelque chose que les processeurs de documents peuvent lire directement.
Il serait assez simple de créer un objet de type fichier avec iter_content
Merci pour la réponse rapide, BTW.
Je suis d'accord. Pourtant, il serait encore plus facile pour requests
de fournir cette fonctionnalité. Mon argument est que .raw
n'est pas le bon niveau d'abstraction pour la plupart des cas d'utilisation qui souhaitent lire à partir du flux, car il expose les détails du niveau de transfert.
Personnellement, je ne vois pas de cas d'utilisation majeur pour l'itĂ©ration ligne par ligne ou mĂȘme morceau par morceau sur le rĂ©sultat d'une requĂȘte HTTP, mais je vois plusieurs cas d'utilisation majeurs pour l'analyser en tant qu'objet de type fichier, en particulier les formats de rĂ©ponse qui nĂ©cessitent un analyseur de document, tel que HTML, XML, Json, etc.
Notez également qu'il est beaucoup plus facile d'écrire un itérateur qui encapsule un objet de type fichier qu'un objet de type fichier qui encapsule un itérateur.
Je suis venu avec le code suivant. Il gĂšre tous les cas nĂ©cessaires, mais je le trouve assez complexe. C'est pourquoi j'ai dit que je voulais quelque chose comme ça dans le cadre de la bibliothĂšque. Les utilisateurs ne devraient pas avoir Ă le dĂ©couvrir eux-mĂȘmes.
Je pense que le code Ă l'intĂ©rieur de models.py des requĂȘtes utilise la mauvaise abstraction ici. Il doit dĂ©compresser le flux brut _avant_ de dĂ©marrer avec sa machinerie d'itĂ©ration, pas pendant l'itĂ©ration. Passer d'un type de fichier Ă un itĂ©rateur juste pour revenir Ă un type de fichier est tout simplement stupide. Une seule transformation d'API est plus que suffisante et la plupart des utilisateurs ne se soucieront pas des itĂ©rateurs de contenu de toute façon.
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
Pourquoi ne pas construire l'objet de type fichier Ă partir de content_iter
comme proposé. Cela pourrait ressembler à :
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
Vous voudrez peut-ĂȘtre relire mon commentaire, en particulier le paragraphe qui prĂ©cĂšde le code que j'ai postĂ©.
Oui, mais cette solution est toujours plus propre (et IMO plus facile) que de faire la dĂ©compression Ă un deuxiĂšme endroit car cela est dĂ©jĂ intĂ©grĂ© dans les requĂȘtes.
Mais je suis d'accord avec vous en général, un r.file
(ou quelque chose comme ça) a beaucoup plus de cas d'utilisation que r.raw
. J'aimerais donc que cela soit Ă©galement inclus dans les demandes. @kennethreitz
"response.stream" me semble ĂȘtre un bon nom.
C'est à ça que sert response.raw :)
C'est aussi ce que j'ai pensé intuitivement quand je l'ai vu. Mais ensuite, j'ai réalisé que response.raw est cassé car il expose des détails internes de la couche de transport sous-jacente dont les utilisateurs ne devraient pas avoir à se soucier.
La seule méthode dont ils devraient avoir besoin est raw.read
?
Eh bien, oui - sauf que raw.read() se comporte différemment selon les négociations internes entre le client et le serveur. Il renvoie parfois les données attendues et parfois il renvoie des octets compressés nus.
Fondamentalement, response.raw
est une fonctionnalité agréable que la plupart des utilisateurs ignoreraient volontiers et que certains utilisateurs expérimentés pourraient trouver utile, alors qu'un response.stream
indépendant
+1
+1
Ce bug de conception va-t-il ĂȘtre corrigĂ© ?
Je ne sais pas à quel point cette méthode est correcte ou efficace, mais pour moi, ce qui suit fonctionne :
>>> 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 : C'est une chose bizarre Ă faire. response.content
est déjà une chaßne d'octets, donc ce que vous faites ici est de décoder le contenu avec le codec choisi par Python, puis de le ré-encoder en utf-8.
Ce n'est _pas_ un bogue, et ce n'est certainement pas le bogue que vous avez suggéré. Si vous avez vraiment besoin d'un objet de type fichier, je recommande StringIO et BytesIO.
@Lukasa a raison. content
doit toujours ĂȘtre une chaĂźne d'octets (en Python 3, c'est une chaĂźne d'octets explicite ; en Python 2 str == octets). Le seul Ă©lĂ©ment qui n'est pas une chaĂźne d'octets est text
.
@kennethreitz des nouvelles à ce sujet ? Il s'agit d'un bug de conception assez sérieux et il est préférable de le régler tÎt. Plus le code est écrit pour le contourner, plus il devient coûteux pour tout le monde.
Ce n'est pas un bug de conception, c'est juste une demande de fonctionnalité. Et comme les demandes ont un gel des fonctionnalités, je suppose que cela ne sera pas dans les demandes de sitÎt (voire pas du tout) ...
Je ne pense pas que redéclarer un bug de conception de longue date une "fonctionnalité manquante"
le fait disparaĂźtre si facilement. J'ai entendu dire que l'auteur pense Ă
faire des "requĂȘtes" une partie de la stdlib Python. ce serait une bonne
opportunité de corriger cela.
J'ai entendu dire que l'auteur pense Ă
faire des "requĂȘtes" une partie de la stdlib Python.
Pas vraiment : http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library
Ce n'est pas un bug, c'est une demande de fonctionnalitĂ©. Requests ne fait rien de mal, il ne fait tout simplement pas quelque chose qui est facultatif. C'est la dĂ©finition mĂȘme d'une fonctionnalitĂ©.
De plus, la prĂ©paration de la stdlib est exactement la raison pour laquelle Requests est en gel de fonctionnalitĂ©s. Une fois que Requests est dans la stdlib, il devient trĂšs difficile de corriger les bogues en temps voulu. En consĂ©quence, si l'ajout de la nouvelle fonctionnalitĂ© ajoute des bogues ou rĂ©gresse le comportement, la version dans stdlib ne peut pas ĂȘtre corrigĂ©e avant la prochaine version mineure. Ce serait mauvais.
Marc Schlaich, 19.03.2013 08:41:
J'ai entendu dire que l'auteur pense Ă
faire des "requĂȘtes" une partie de la stdlib Python.Pas vraiment : http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library
Je l'ai lu ici :
Stéphane
J'ai déjà expliqué pourquoi il s'agit d'un bogue de conception et non d'une demande de fonctionnalité : l'API existante utilise la mauvaise abstraction et diffuse les détails de négociation de la connexion dans l'espace utilisateur qui sont à la merci du site distant, et donc, que l'utilisateur ne doit pas avoir à se soucier. Cela rend le support de lecture de flux brut actuel difficile à utiliser. Il s'agit essentiellement d'une demande de réparation d'une fonctionnalité défaillante, et non d'une demande de nouvelle fonctionnalité.
Permettez-moi de rĂ©sumer cela proprement. Le bogue est que toute utilisation rĂ©elle de la fonctionnalitĂ© de lecture de flux brut devra rĂ©implĂ©menter une partie de la bibliothĂšque, en particulier toute la partie de dĂ©compression conditionnelle de flux, car la fonctionnalitĂ© est inutile sans elle, dĂšs que le client autorise la compression. Nous parlons ici de code qui est dĂ©jĂ lĂ , dans "requests" - il est simplement utilisĂ© au mauvais endroit. Il doit ĂȘtre utilisĂ© en dessous du niveau de lecture brut, pas au-dessus, car le client ne peut pas contrĂŽler si le serveur respecte l'en-tĂȘte d'acceptation ou non. La compression doit ĂȘtre un dĂ©tail de nĂ©gociation transparent de la connexion, et non quelque chose qui blesse un utilisateur qui active l'en-tĂȘte correspondant.
Je ne peux penser Ă aucun cas d'utilisation oĂč le client serait intĂ©ressĂ© par le flux compressĂ©, surtout s'il ne peut pas prĂ©dire si le flux sera vraiment compressĂ© ou non, car le serveur peut ignorer avec plaisir le souhait du client. C'est un pur dĂ©tail de nĂ©gociation. C'est pourquoi la lecture de flux bruts utilise la mauvaise abstraction en prĂ©fĂ©rant le cas d'utilisation extrĂȘmement improbable au plus courant.
Je peux. Par exemple, que se passe-t-il si vous téléchargez un fichier texte volumineux et que vous souhaitez le conserver compressé ? Je pourrais suivre ce changement avec un nouveau "bug de conception" intitulé Aucun moyen d'enregistrer les données compressées à l'origine sur le disque .
Cette idĂ©e est intentionnellement banale et stupide, mais j'essaie d'illustrer un point, qui est celui-ci : Requests n'est pas obligĂ© d'offrir Ă chacun exactement le mĂ©canisme d'interaction qu'il souhaite. En fait, cela irait directement Ă l'encontre de l'objectif principal de Requests, qui est la simplicitĂ© de l'API. Il existe une longue, longue, _longue_ liste de modifications proposĂ©es aux demandes qui ont fait l'objet d'une objection car elles compliquent l'API, mĂȘme si elles ajoutent des fonctionnalitĂ©s utiles. Requests ne vise pas Ă remplacer urllib2 pour tous les cas d'utilisation, il vise Ă simplifier les cas les plus courants.
Dans ce cas, Requests suppose que la plupart des utilisateurs ne veulent pas d'objets de type fichier et propose donc les interactions suivantes :
Response.text
et Response.content
: Vous voulez toutes les données en une seule fois.Response.iter_lines()
et Response.iter_content()
: Vous ne voulez pas toutes les données en une seule fois.Response.raw
: Vous n'ĂȘtes pas satisfait des deux autres options, alors faites-le vous-mĂȘme.Ceux-ci ont Ă©tĂ© choisis parce qu'ils reprĂ©sentent en trĂšs grande majoritĂ© les utilisations courantes des requĂȘtes. Vous avez dit " la plupart des utilisateurs ne se soucieront pas des itĂ©rateurs de contenu de toute façon " et " response.stream
est une fonctionnalitĂ© que la plupart des utilisateurs de streaming voudraient ". L'expĂ©rience sur ce projet m'amĂšne Ă ne pas ĂȘtre d'accord : un grand nombre de personnes utilisent les itĂ©rateurs de contenu, et peu veulent dĂ©sespĂ©rĂ©ment des objets de type fichier.
Un dernier point : si la compression doit ĂȘtre un dĂ©tail de nĂ©gociation transparent de la connexion, alors vous devez signaler le bogue appropriĂ© contre urllib3, qui gĂšre notre logique de connexion.
Je suis désolé que vous ayez l'impression que Requests est inapproprié pour votre cas d'utilisation.
Je comprends que response.raw
est cassĂ© dans l'implĂ©mentation actuelle et mĂȘme partiellement d'accord avec cela (vous devriez au moins pouvoir obtenir les dĂ©tails de la compression sans analyser les en-tĂȘtes).
Cependant, votre proposition est toujours une demande de fonctionnalité...
@Lukasa
Je ne vois pas vraiment comment le dĂ©pĂŽt du bogue contre urllib3 corrigerait l'API des requĂȘtes, du moins pas tout seul.
Et je suis d'accord que votre "cas d'utilisation" est inventé. Comme je l'ai dit, si le client ne peut pas contrÎler positivement la compression cÎté serveur (et qu'il la désactive, mais ne l'active pas de maniÚre fiable), alors compter sur lui pour pouvoir enregistrer un fichier compressé sur le disque n'est, eh bien, pas si intéressant .
@schlamar
Je suis d'accord qu'il peut ĂȘtre lu comme tel. Je vous assure que je suis d'accord avec tout ce qui rĂ©sout ce problĂšme. Si l'ouverture d'un nouveau billet est nĂ©cessaire pour s'y rendre, qu'il en soit ainsi.
Si l'ouverture d'un nouveau billet est nécessaire pour s'y rendre, qu'il en soit ainsi.
Je pense toujours que Kenneth rejettera cela en raison du gel des fonctionnalités.
Je suis d'accord avec tout ce qui résout ce problÚme
- Enveloppez
iter_content
tant qu'objet de type fichier ou- Analysez les en-tĂȘtes et dĂ©compressez
response.raw
si nécessaire
Les deux solutions sont dans les commentaires ci-dessus, la derniÚre postée par vous. Pourquoi est-ce un tel problÚme que cela ne figure pas directement dans les demandes ?
Soyons clairs à 100 % ici : il n'y a pratiquement aucune chance que cela entre dans les demandes pendant le gel des fonctionnalités. Rien n'est cassé, l'API n'est tout simplement pas parfaite pour vos besoins. Parce que rien n'est cassé, la seule chose qui compte est de savoir si Kenneth le veut. Requests n'est pas une démocratie, c'est un homme une voix. Kenneth est l'homme, il a le vote . Kenneth a fermé ce problÚme il y a 8 mois, il semble donc assez clair qu'il n'en veut pas.
Je ne vois pas vraiment comment le dĂ©pĂŽt du bogue contre urllib3 corrigerait l'API des requĂȘtes, du moins pas tout seul.
Patcher urllib3 pour toujours renvoyer l'objet-fichier non compressĂ© devrait rĂ©soudre ce problĂšme par lui-mĂȘme (on ne dit pas que c'est une bonne idĂ©e).
Oh, voici la solution numéro 3 (non testée) :
response.raw.read = functools.partial(response.raw.read, decode_content=True)
Voir https://github.com/shazow/urllib3/blob/master/urllib3/response.py#L112
Intéressant - je ne savais pas que cela existait maintenant. Cela rend beaucoup plus facile d'envelopper la fonctionnalité, bien sûr.
Cependant, cela fonctionne-t-il réellement ? C'est-à -dire que les décompresseurs sont dynamiques et incrémentiels ? Le deuxiÚme appel à read(123) ne renverra plus le début valide d'un fichier gzip, par exemple.
Cependant, cela fonctionne-t-il réellement ? C'est-à -dire que les décompresseurs sont dynamiques et incrémentiels ?
Oh, ça n'a pas l'air d'ĂȘtre le cas. Je n'ai pas lu la docstring.
Cependant, voici ma proposition :
HTTPResponse.read
fonctionne avec amt
et decode_content
simultanément.response.raw.decode_content = True
au lieu de patcher la méthode read
).decode_content=True
dans iter_content
@Lukasa Je pense que cela ne violera pas le gel des fonctionnalités, n'est-ce pas ?
@schlamar : En principe, bien sĂ»r. Tant que l'API reste inchangĂ©e, les changements internes _devraient_ ĂȘtre ok, et je serais +1 sur celui-ci. Cependant, gardez Ă l'esprit que je ne suis pas le BDFL, =)
stream_decompress
dans les requĂȘtes est cassĂ© de toute façon : #1249
+1
Commentaire le plus utile
J'ai déjà expliqué pourquoi il s'agit d'un bogue de conception et non d'une demande de fonctionnalité : l'API existante utilise la mauvaise abstraction et diffuse les détails de négociation de la connexion dans l'espace utilisateur qui sont à la merci du site distant, et donc, que l'utilisateur ne doit pas avoir à se soucier. Cela rend le support de lecture de flux brut actuel difficile à utiliser. Il s'agit essentiellement d'une demande de réparation d'une fonctionnalité défaillante, et non d'une demande de nouvelle fonctionnalité.