Requests: tidak ada cara untuk membaca konten yang tidak terkompresi sebagai objek seperti file

Dibuat pada 29 Feb 2012  ·  44Komentar  ·  Sumber: psf/requests

Menurut dokumentasi, ada tiga cara untuk membaca isi dari respon: .text , .content dan .raw . Dua yang pertama mempertimbangkan pengkodean transfer dan mendekompresi aliran secara otomatis saat menghasilkan hasil dalam memori mereka. Namun, terutama untuk kasus yang hasilnya besar, saat ini tidak ada cara sederhana untuk mendapatkan hasil yang didekompresi dalam bentuk objek seperti file, misalnya dengan meneruskannya langsung ke parser XML atau Json.

Dari sudut pandang perpustakaan yang bertujuan untuk membuat permintaan HTTP ramah pengguna, mengapa pengguna harus peduli tentang sesuatu yang tingkat rendah seperti jenis kompresi aliran yang dinegosiasikan secara internal antara server web dan perpustakaan? Lagi pula, itu adalah "kesalahan" perpustakaan jika defaultnya menerima aliran seperti itu. Dalam hal ini, aliran .raw agak terlalu mentah untuk selera saya.

Mungkin properti keempat seperti .stream dapat memberikan tingkat abstraksi yang lebih baik?

Komentar yang paling membantu

Saya sudah menjelaskan mengapa ini adalah bug desain dan bukan permintaan fitur: API yang ada menggunakan abstraksi yang salah dan membocorkan detail negosiasi koneksi ke ruang pengguna yang bergantung pada situs jarak jauh, dan dengan demikian, pengguna tidak boleh harus peduli. Itu membuat dukungan pembacaan aliran mentah saat ini sulit digunakan. Pada dasarnya, ini adalah permintaan untuk memperbaiki fitur yang rusak, bukan permintaan untuk fitur baru.

Semua 44 komentar

Response.iter_content

Erm, tidak, itu iterator. Saya meminta objek seperti file, yaitu sesuatu yang dapat dibaca oleh pemroses dokumen secara langsung.

Akan sangat sederhana untuk membuat objek seperti file dengan iter_content

Terima kasih atas balasan cepatnya, BTW.

Saya setuju. Namun, akan lebih mudah bagi requests untuk menyediakan fungsionalitas ini. Maksud saya adalah .raw adalah tingkat abstraksi yang salah untuk sebagian besar kasus penggunaan yang ingin membaca dari aliran, karena memperlihatkan detail tingkat transfer.

Secara pribadi, saya tidak melihat kasus penggunaan utama untuk mengulangi baris demi baris atau bahkan potongan demi potongan atas hasil permintaan HTTP, tetapi saya melihat beberapa kasus penggunaan utama untuk menguraikannya sebagai objek seperti file, khususnya format respons yang membutuhkan pengurai dokumen, seperti HTML, XML, Json dll.

Perhatikan juga bahwa jauh lebih mudah untuk menulis iterator yang membungkus objek seperti file, daripada objek seperti file yang membungkus iterator.

Saya datang dengan kode berikut. Ini menangani semua kasus yang diperlukan, tetapi saya merasa agak rumit. Itu sebabnya saya mengatakan saya menginginkan sesuatu seperti ini sebagai bagian dari perpustakaan. Pengguna tidak perlu mencari tahu sendiri.

Saya pikir kode di dalam model.py permintaan menggunakan abstraksi yang salah di sini. Itu harus mendekompresi aliran mentah _sebelum_ dimulai dengan mesin iterasinya, bukan selama iterasi. Beralih dari file-like ke iterator hanya untuk kembali ke file-like benar-benar bodoh. Transformasi API tunggal sudah lebih dari cukup dan sebagian besar pengguna tidak akan peduli dengan iterator konten.

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

Mengapa Anda tidak membuat objek seperti file dari content_iter seperti yang diusulkan. Ini bisa terlihat seperti:

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

Anda mungkin ingin membaca komentar saya lagi, khususnya paragraf yang mendahului kode yang saya posting.

Ya, tetapi solusi ini masih lebih bersih (dan IMO lebih mudah) daripada melakukan dekompresi di tempat kedua karena ini sudah ada di permintaan.

Tetapi saya setuju dengan Anda secara umum, r.file (atau sesuatu seperti ini) memiliki lebih banyak kasus penggunaan daripada r.raw . Jadi saya ingin melihat ini termasuk dalam permintaan juga. @kennethreitz

"response.stream" terdengar seperti nama yang bagus untuk saya.

Inilah gunanya response.raw :)

Itu juga yang secara intuitif saya pikirkan ketika saya melihatnya. Tapi kemudian saya menyadari bahwa response.raw rusak karena memperlihatkan detail internal dari lapisan transport yang mendasarinya yang tidak perlu dipedulikan oleh pengguna.

Satu-satunya metode yang mereka butuhkan adalah raw.read ?

Ya, kecuali bahwa raw.read() berperilaku berbeda tergantung pada negosiasi internal antara klien dan server. Terkadang mengembalikan data yang diharapkan dan terkadang mengembalikan byte terkompresi kosong.

Pada dasarnya, response.raw adalah fitur yang bagus untuk dimiliki yang sebagian besar pengguna akan dengan senang hati mengabaikannya dan beberapa pengguna yang kuat dapat merasa berguna, sedangkan response.stream tidak bergantung pada kompresi adalah fitur yang sebagian besar pengguna streaming akan ingin.

+1

+1

Apakah bug desain ini akan diperbaiki?

tidak yakin seberapa benar atau efisien cara ini, tetapi bagi saya, berikut ini berfungsi :

>>> 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 : Itu hal yang aneh untuk dilakukan. response.content sudah menjadi bytestring, jadi yang Anda lakukan di sini adalah mendekode konten dengan codec apa pun yang dipilih Python, lalu menyandikannya kembali sebagai utf-8.

Ini _bukan_ bug, dan ini jelas bukan bug yang Anda sarankan. Jika Anda benar-benar membutuhkan objek seperti file, saya merekomendasikan StringIO dan BytesIO.

@Lukasa benar. content harus selalu berupa bytestring (dalam Python 3 ini adalah bytestring eksplisit; dalam Python 2 str == byte). Satu-satunya item yang bukan bytestring adalah text .

@kennethreitz ada berita tentang ini? Ini adalah bug desain yang cukup serius dan yang terbaik adalah menyelesaikannya lebih awal. Semakin banyak kode yang ditulis untuk mengatasinya, semakin mahal biayanya untuk semua orang.

Ini bukan bug desain, ini hanya permintaan fitur. Dan karena permintaan memiliki fitur yang dibekukan, saya berasumsi ini tidak akan berhasil dalam permintaan dalam waktu dekat (jika ada) ...

Saya tidak berpikir mendeklarasikan ulang bug desain lama sebagai "fitur yang hilang"
membuatnya pergi dengan mudah. Saya mendengar bahwa penulis sedang memikirkan
menjadikan "permintaan" sebagai bagian dari Python stdlib. Itu bagus
kesempatan untuk memperbaiki ini.

Saya mendengar bahwa penulis sedang memikirkan
menjadikan "permintaan" sebagai bagian dari Python stdlib.

Tidak juga: http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library

Ini bukan bug, ini permintaan fitur. Permintaan tidak melakukan kesalahan, itu hanya tidak melakukan sesuatu yang opsional. Seperti itu penjelasan definisi sebenarnya dari kata fitur.

Selain itu, mempersiapkan stdlib adalah alasan mengapa Permintaan dalam pembekuan fitur. Setelah Permintaan ada di stdlib, menjadi sangat sulit untuk melakukan perbaikan bug tepat waktu. Akibatnya, jika menambahkan fitur baru menambahkan bug atau perilaku regresi, versi di stdlib tidak dapat diperbaiki hingga rilis minor berikutnya. Itu akan buruk.

Marc Schlaich, 19.03.2013 08:41:

Saya mendengar bahwa penulis sedang memikirkan
menjadikan "permintaan" sebagai bagian dari Python stdlib.

Tidak juga: http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library

Saya membacanya di sini:

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

Stefan

Saya sudah menjelaskan mengapa ini adalah bug desain dan bukan permintaan fitur: API yang ada menggunakan abstraksi yang salah dan membocorkan detail negosiasi koneksi ke ruang pengguna yang bergantung pada situs jarak jauh, dan dengan demikian, pengguna tidak boleh harus peduli. Itu membuat dukungan pembacaan aliran mentah saat ini sulit digunakan. Pada dasarnya, ini adalah permintaan untuk memperbaiki fitur yang rusak, bukan permintaan untuk fitur baru.

Biarkan saya meringkas ini dengan bersih. Bugnya adalah bahwa setiap penggunaan dunia nyata dari fitur pembacaan aliran mentah harus mengimplementasikan kembali bagian dari perpustakaan, khususnya seluruh bagian dekompresi aliran bersyarat, karena fitur tersebut tidak berguna tanpanya, segera setelah klien mengizinkan kompresi. Kita berbicara tentang kode di sini yang sudah ada, dalam "permintaan" - itu hanya digunakan di tempat yang salah. Itu harus digunakan di bawah tingkat pembacaan mentah, bukan di atasnya, karena klien tidak dapat mengontrol apakah server menghormati header terima atau tidak. Kompresi harus menjadi detail negosiasi yang transparan dari koneksi, bukan sesuatu yang merugikan pengguna mana pun yang mengaktifkan header yang relevan.

Saya tidak dapat memikirkan kasus penggunaan di mana klien akan tertarik pada aliran terkompresi, terutama jika tidak dapat memprediksi apakah aliran benar-benar akan dikompresi atau tidak, karena server dapat dengan senang hati mengabaikan keinginan klien. Ini adalah detail negosiasi murni. Itu sebabnya pembacaan aliran mentah menggunakan abstraksi yang salah dengan lebih memilih kasus penggunaan yang sangat tidak mungkin daripada yang paling umum.

Saya bisa. Misalnya, bagaimana jika Anda mengunduh file besar berbasis teks dan ingin tetap mengompresnya? Saya dapat menindaklanjuti perubahan ini dengan 'bug desain' baru berjudul Tidak ada cara untuk menyimpan data yang dikompresi ke disk .

Gagasan itu sengaja basi dan bodoh, tetapi saya mencoba menggambarkan satu hal, yaitu: Permintaan tidak wajib menawarkan mekanisme interaksi yang mereka inginkan kepada semua orang. Faktanya, melakukan hal itu akan langsung bertentangan dengan tujuan utama yang dimiliki Permintaan, yaitu kesederhanaan API. Ada daftar panjang, panjang, _panjang_ dari perubahan yang diusulkan pada Permintaan yang ditolak karena memperumit API, meskipun mereka menambahkan fungsionalitas yang berguna. Permintaan tidak bertujuan untuk menggantikan urllib2 untuk semua kasus penggunaan, ini bertujuan untuk menyederhanakan kasus yang paling umum.

Dalam hal ini, Permintaan mengasumsikan bahwa sebagian besar pengguna tidak menginginkan objek seperti file, dan oleh karena itu mengusulkan interaksi berikut:

  • Response.text dan Response.content : Anda ingin semua data sekaligus.
  • Response.iter_lines() dan Response.iter_content() : Anda tidak ingin semua data sekaligus.
  • Response.raw : Anda tidak puas dengan dua opsi lainnya, jadi lakukan sendiri.

Ini dipilih karena mereka sangat mewakili penggunaan umum dari Permintaan. Anda telah mengatakan " sebagian besar pengguna tidak akan peduli dengan iterator konten " dan " response.stream adalah fitur yang diinginkan sebagian besar pengguna streaming ". Pengalaman pada proyek ini membuat saya tidak setuju: banyak orang menggunakan iterator konten, dan tidak banyak yang sangat menginginkan objek seperti file.

Satu poin terakhir: jika kompresi harus menjadi detail negosiasi yang transparan dari koneksi, maka Anda harus meningkatkan bug yang sesuai terhadap urllib3, yang menangani logika koneksi kami.

Saya minta maaf karena Anda merasa Permintaan tidak sesuai dengan kasus penggunaan Anda.

Saya mengerti maksud Anda bahwa response.raw rusak dalam implementasi saat ini dan bahkan sebagian setuju dengan itu (Anda setidaknya harus bisa mendapatkan detail kompresi tanpa menguraikan header).

Namun, proposal Anda masih merupakan permintaan fitur...

@Lukasa
Saya benar-benar tidak dapat melihat bagaimana mengajukan bug terhadap urllib3 akan memperbaiki API permintaan, setidaknya tidak dengan sendirinya.

Dan saya setuju bahwa "kasus penggunaan" Anda dibuat-buat. Seperti yang saya katakan, jika klien tidak dapat mengontrol kompresi secara positif di sisi server (dan menonaktifkannya, tetapi tidak mengaktifkannya dengan andal), jadi mengandalkannya untuk dapat menyimpan file terkompresi ke disk, yah, tidak begitu menarik .

@schlamar
Saya setuju bahwa itu dapat dibaca seperti itu. Saya meyakinkan Anda bahwa saya baik-baik saja dengan apa pun yang memecahkan masalah ini. Jika membuka tiket baru diperlukan untuk sampai ke sana, biarlah.

Jika membuka tiket baru diperlukan untuk sampai ke sana, biarlah.

Saya masih berpikir bahwa Kenneth akan menolak ini karena pembekuan fitur.

Saya baik-baik saja dengan apa pun yang memecahkan masalah ini

  1. Bungkus iter_content sebagai objek seperti file atau
  2. Parsing header dan dekompresi response.raw jika sesuai

Kedua solusi ada di komentar di atas, yang terakhir diposting oleh Anda. Mengapa ini menjadi masalah sehingga ini tidak akan diminta secara langsung?

Mari kita perjelas 100% di sini: pada dasarnya tidak ada kemungkinan ini akan masuk ke dalam Permintaan saat dalam fitur pembekuan. Tidak ada yang rusak, API tidak sempurna untuk kebutuhan Anda. Karena tidak ada yang rusak, satu-satunya hal yang penting adalah apakah Kenneth menginginkannya. Permintaan bukanlah demokrasi, itu satu orang satu suara. Kenneth adalah orangnya, dia memiliki suara . Kenneth menutup masalah ini 8 bulan yang lalu, jadi tampaknya cukup jelas dia tidak menginginkannya.

Saya benar-benar tidak dapat melihat bagaimana mengajukan bug terhadap urllib3 akan memperbaiki API permintaan, setidaknya tidak dengan sendirinya.

Menambal urllib3 untuk selalu mengembalikan objek file yang tidak terkompresi harus menyelesaikan ini dengan sendirinya (tidak dikatakan bahwa ini adalah ide yang bagus).

Oh, ini solusi nomor 3 (belum diuji):

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

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

Menarik - Saya tidak tahu itu ada sekarang. Itu membuatnya lebih mudah untuk membungkus fitur, tentu saja.

Meskipun, apakah itu benar-benar berhasil? Yaitu apakah dekompresor stateful dan inkremental? Panggilan kedua untuk membaca (123) tidak akan mengembalikan awal yang valid dari file gzip lagi, misalnya.

Meskipun, apakah itu benar-benar berhasil? Yaitu apakah dekompresor stateful dan inkremental?

Oh, sepertinya tidak. Saya tidak membaca docstring.

Namun, inilah proposal saya:

  1. Patch urllib3 sehingga HTTPResponse.read bekerja dengan amt dan decode_content secara bersamaan.
  2. Jadikan HTTPResponse._decode_content sebagai anggota publik (sehingga Anda dapat melakukan response.raw.decode_content = True alih-alih menambal metode read ).
  3. Jatuhkan dekompresi dalam permintaan sepenuhnya dengan menggunakan decode_content=True di iter_content

@Lukasa Saya pikir ini tidak akan melanggar pembekuan fitur, kan?

@schlamar : Pada prinsipnya, tentu. Selama API tetap tidak berubah, perubahan internal _harus_ baik-baik saja, dan saya akan memberi +1 untuk yang satu ini. Namun, ingatlah bahwa saya bukan BDFL, =)

stream_decompress dalam permintaan rusak: #1249

+1

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

ReimarBauer picture ReimarBauer  ·  4Komentar

ghtyrant picture ghtyrant  ·  3Komentar

iLaus picture iLaus  ·  3Komentar

remram44 picture remram44  ·  4Komentar

NoahCardoza picture NoahCardoza  ·  4Komentar