Requests: لا توجد طريقة لقراءة المحتوى غير المضغوط ككائن يشبه الملف

تم إنشاؤها على ٢٩ فبراير ٢٠١٢  ·  44تعليقات  ·  مصدر: psf/requests

وفقًا للوثائق ، هناك ثلاث طرق لقراءة محتوى الرد: .text ، .content و .raw . يأخذ أول اثنان في الاعتبار تشفير النقل وفك ضغط الدفق تلقائيًا عند إنتاج النتيجة في الذاكرة. ومع ذلك ، خاصة بالنسبة للحالة التي تكون فيها النتيجة كبيرة ، لا توجد حاليًا طريقة بسيطة للحصول على النتيجة غير المضغوطة في شكل كائن يشبه الملف ، على سبيل المثال لتمريرها مباشرة إلى XML أو محلل Json.

من وجهة نظر مكتبة تهدف إلى جعل طلبات HTTP سهلة الاستخدام ، لماذا يجب على المستخدم الاهتمام بشيء منخفض المستوى مثل نوع ضغط الدفق الذي تم التفاوض عليه داخليًا بين خادم الويب والمكتبة؟ بعد كل شيء ، إنها "خطأ" المكتبة إذا كانت تقبل مثل هذا التدفق بشكل افتراضي. في ضوء ذلك ، يعد التدفق .raw خامًا جدًا بالنسبة إلى ذوقي.

ربما توفر خاصية رابعة مثل .stream مستوى تجريد أفضل؟

التعليق الأكثر فائدة

لقد أوضحت بالفعل سبب كون هذا خطأ في التصميم وليس طلب ميزة: تستخدم واجهة برمجة التطبيقات الحالية تجريدًا خاطئًا وتسرب تفاصيل التفاوض الخاصة بالاتصال في مساحة المستخدم التي تقع تحت رحمة الموقع البعيد ، وبالتالي ، لا ينبغي للمستخدم يجب أن تهتم به. هذا يجعل من الصعب استخدام دعم قراءة الدفق الخام الحالي. في الأساس ، هذا طلب لإصلاح ميزة معطلة ، وليس طلبًا لميزة جديدة.

ال 44 كومينتر

Response.iter_content

Erm ، لا ، هذا مكرر. كنت أطلب شيئًا يشبه الملف ، أي شيء يمكن لمعالجات المستندات قراءته مباشرة.

سيكون من السهل جدًا إنشاء كائن يشبه الملف باستخدام iter_content

شكرا على الرد السريع ، راجع للشغل.

أنا موافق. ومع ذلك ، سيكون من الأسهل على requests توفير هذه الوظيفة. نقطتي هي أن .raw هو المستوى الخاطئ من التجريد لمعظم حالات الاستخدام التي تريد القراءة من الدفق ، لأنه يعرض تفاصيل مستوى النقل.

أنا شخصياً لا أرى حالة استخدام رئيسية للتكرار سطرًا بسطر أو حتى مقطعًا تلو الآخر على نتيجة طلب HTTP ، لكنني أرى العديد من حالات الاستخدام الرئيسية للتحليل منه ككائن يشبه الملف ، وتحديداً تنسيقات الاستجابة التي تتطلب محلل مستندات ، مثل HTML و XML و Json وما إلى ذلك.

لاحظ أيضًا أنه من الأسهل بكثير كتابة مكرر يلتف حول كائن يشبه الملف ، من كتابة كائن يشبه الملف يلتف مكررًا.

جئت مع الكود التالي. إنه يتعامل مع جميع الحالات الضرورية ، لكنني أجده معقدًا نوعًا ما. لهذا السبب قلت أنني أريد شيئًا كهذا كجزء من المكتبة. لا يجب على المستخدمين معرفة ذلك بأنفسهم.

أعتقد أن الكود الموجود داخل نماذج الطلبات يستخدم التجريد الخاطئ هنا. يجب أن يفك ضغط التيار الخام قبل أن يبدأ بآلية التكرار ، وليس أثناء التكرار. إن الانتقال من ملف يشبه الملف إلى مكرر فقط للعودة إلى ملف يشبه الملف هو مجرد غباء. يعد تحويل API واحدًا أكثر من كافٍ ولن يهتم معظم المستخدمين بمكررات المحتوى على أي حال.

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

لماذا لا تقوم بإنشاء كائن يشبه الملف من content_iter كما هو مقترح. قد يبدو هذا مثل:

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

قد ترغب في قراءة تعليقي مرة أخرى ، وتحديدًا الفقرة التي تسبق الرمز الذي قمت بنشره.

نعم ، لكن هذا الحل لا يزال أنظف (وإيمو أسهل) من القيام بفك الضغط في المركز الثاني لأن هذا بالفعل طلبات مضمنة.

لكنني أتفق معك بشكل عام ، فإن r.file (أو شيء من هذا القبيل) به حالات استخدام أكثر بكثير من r.raw . لذلك أود أن أرى هذا مدرجًا في الطلبات أيضًا. تضمين التغريدة

يبدو أن "response.stream" اسم جيد بالنسبة لي.

هذا هو ما هو رد.راو ل :)

كان هذا أيضًا ما فكرت به بشكل حدسي عندما رأيته. ولكن بعد ذلك أدركت أن response.raw معطل لأنه يكشف التفاصيل الداخلية لطبقة النقل الأساسية التي لا ينبغي للمستخدمين أن يهتموا بها.

الطريقة الوحيدة التي يجب أن يحتاجوا إليها هي raw.read ؟

حسنًا ، نعم - باستثناء أن raw.read () يتصرف بشكل مختلف اعتمادًا على المفاوضات الداخلية بين العميل والخادم. تقوم أحيانًا بإرجاع البيانات المتوقعة وفي بعض الأحيان تقوم بإرجاع وحدات البايت المضغوطة المجردة.

في الأساس ، response.raw هي ميزة لطيفة يمكن أن يتجاهلها معظم المستخدمين بسعادة وقد يجدها بعض المستخدمين المتميزين مفيدة ، في حين أن ميزة الضغط المستقل response.stream هي ميزة يفضلها معظم مستخدمي البث يريد.

+1

+1

هل سيتم إصلاح هذا الخطأ في التصميم؟

لست متأكدًا من مدى صحة أو كفاءة هذه الطريقة ، ولكن بالنسبة لي ، تعمل الإجراءات التالية :

>>> 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 : هذا شيء غريب يجب القيام به. response.content عبارة عن اختبار بايت بالفعل ، لذا ما تفعله هنا هو فك تشفير المحتوى بأي شيء تختاره Python لبرنامج الترميز الجحيم ، ثم إعادة ترميزه كـ utf-8.

هذا _not_ خطأ ، وهو بالتأكيد ليس الخطأ الذي اقترحته. إذا كنت حقًا بحاجة إلى كائن يشبه الملف ، فإنني أوصي بـ StringIO و BytesIO.

Lukasa هو الصحيح. يجب أن يكون content دائمًا سلسلة بايت (في Python 3 هي سلسلة بايت صريحة ؛ في Python 2 str == bytes). العنصر الوحيد الذي لا يمثل سلسلة بايت هو text .

@ kennethreitz أي أخبار عن هذا؟ هذا خطأ خطير في التصميم ومن الأفضل حله مبكرًا. كلما تمت كتابة المزيد من التعليمات البرمجية لحلها ، زادت تكلفة ذلك على الجميع.

هذا ليس خطأ في التصميم ، إنه مجرد طلب ميزة. وبما أن الطلبات تحتوي على ميزة تجميد ، أفترض أن هذا لن يتم تقديمها في الطلبات في أي وقت قريب (إن وجد) ...

لا أعتقد أن إعادة إعلان خطأ تصميم طويل الأمد "ميزة مفقودة"
يجعلها تختفي بسهولة. سمعت أن المؤلف يفكر فيه
جعل "الطلبات" جزءًا من Python stdlib. هذا سيكون جيدا
فرصة لإصلاح هذا.

سمعت أن المؤلف يفكر فيه
جعل "الطلبات" جزءًا من Python stdlib.

ليس حقًا: http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library

هذا ليس خطأ ، إنه طلب ميزة. الطلبات لا تفعل شيئًا خاطئًا ، إنها ببساطة لا تفعل شيئًا اختياريًا. هذا هو تعريف الميزة.

بالإضافة إلى ذلك ، فإن التحضير لـ stdlib هو بالضبط سبب تجميد الميزات. بمجرد أن تكون الطلبات في stdlib ، يصبح من الصعب جدًا إجراء إصلاحات للأخطاء في الوقت المناسب. نتيجة لذلك ، إذا أدت إضافة الميزة الجديدة إلى إضافة أخطاء أو تراجع في السلوك ، فلا يمكن إصلاح الإصدار الموجود في stdlib حتى الإصدار الثانوي التالي. سيكون ذلك سيئا.

مارك شلايش ، 19.03.2013 08:41:

سمعت أن المؤلف يفكر فيه
جعل "الطلبات" جزءًا من Python stdlib.

ليس حقًا: http://docs.python-requests.org/en/latest/dev/philosophy/#standard -library

قرأته هنا:

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

ستيفان

لقد أوضحت بالفعل سبب كون هذا خطأ في التصميم وليس طلب ميزة: تستخدم واجهة برمجة التطبيقات الحالية تجريدًا خاطئًا وتسرب تفاصيل التفاوض الخاصة بالاتصال في مساحة المستخدم التي تقع تحت رحمة الموقع البعيد ، وبالتالي ، لا ينبغي للمستخدم يجب أن تهتم به. هذا يجعل من الصعب استخدام دعم قراءة الدفق الخام الحالي. في الأساس ، هذا طلب لإصلاح ميزة معطلة ، وليس طلبًا لميزة جديدة.

اسمحوا لي أن ألخص هذا بشكل نظيف. الخطأ هو أن أي استخدام في العالم الحقيقي لميزة قراءة الدفق الأولي يجب أن يعيد تنفيذ جزء من المكتبة ، وتحديداً جزء فك ضغط التدفق الشرطي بالكامل ، لأن الميزة غير مجدية بدونها ، بمجرد أن يسمح العميل بالضغط. نحن نتحدث عن الكود الموجود هنا بالفعل ، في "الطلبات" - لقد تم استخدامه للتو في المكان الخطأ. يجب استخدامه دون مستوى القراءة الأولي ، وليس فوقه ، لأن العميل لا يمكنه التحكم في ما إذا كان الخادم يكرم رأس القبول أم لا. يجب أن يكون الضغط عبارة عن تفاصيل تفاوض شفافة للاتصال ، وليس شيئًا يؤذي أي مستخدم يقوم بتمكين الرأس ذي الصلة.

لا يمكنني التفكير في أي حالة استخدام حيث يكون العميل مهتمًا بالدفق المضغوط ، خاصةً إذا لم يكن بإمكانه التنبؤ بما إذا كان الدفق سيتم ضغطه بالفعل أم لا ، حيث يمكن للخادم أن يتجاهل رغبة العميل بسعادة. إنها تفاصيل تفاوض خالصة. هذا هو السبب في أن قراءة الدفق الخام تستخدم التجريد الخاطئ من خلال تفضيل حالة الاستخدام غير المحتملة للغاية على الحالة الأكثر شيوعًا.

أنا استطيع. على سبيل المثال ، ماذا لو كنت تقوم بتنزيل ملف نصي كبير وأردت إبقائه مضغوطًا؟ يمكنني متابعة هذا التغيير من خلال "خطأ في التصميم" جديد بعنوان " لا توجد طريقة لحفظ البيانات المضغوطة أصلاً على القرص" .

هذه الفكرة مبتذلة وغبية عن قصد ، لكنني أحاول توضيح نقطة ، وهي: الطلبات ليست ملزمة بتقديم آلية التفاعل التي يريدها الجميع بالضبط. في الواقع ، قد يتعارض القيام بذلك بشكل مباشر مع الهدف الرئيسي للطلبات ، وهو بساطة واجهة برمجة التطبيقات. هناك قائمة طويلة وطويلة وطويلة من التغييرات المقترحة للطلبات التي تم الاعتراض عليها لأنها تعقد واجهة برمجة التطبيقات ، على الرغم من أنها أضافت وظائف مفيدة. لا تهدف الطلبات إلى استبدال urllib2 لجميع حالات الاستخدام ، بل تهدف إلى تبسيط الحالات الأكثر شيوعًا.

في هذه الحالة ، تفترض الطلبات أن معظم المستخدمين لا يريدون كائنات تشبه الملفات ، وبالتالي تقترح التفاعلات التالية:

  • Response.text و Response.content : تريد كل البيانات دفعة واحدة.
  • Response.iter_lines() و Response.iter_content() : لا تريد كل البيانات دفعة واحدة.
  • Response.raw : لست سعيدًا بالخيارين الآخرين ، فافعل ذلك بنفسك.

تم اختيار هذه لأنها تمثل بشكل كبير الاستخدامات الشائعة للطلبات. لقد قلت " معظم المستخدمين لن يهتموا بمكررات المحتوى على أي حال " و " response.stream هي ميزة يريدها معظم مستخدمي البث المباشر ". تقودني الخبرة في هذا المشروع إلى الاختلاف: كثير من الناس يستخدمون مكررات المحتوى ، ولا يريد الكثيرون بشدة كائنات تشبه الملفات.

نقطة أخيرة: إذا كان الضغط يجب أن يكون عبارة عن تفاصيل تفاوض شفافة للاتصال ، فيجب عليك رفع الخطأ المناسب ضد urllib3 ، الذي يتعامل مع منطق الاتصال الخاص بنا.

يؤسفني أنك تشعر أن الطلبات غير مناسبة لحالة استخدامك.

لقد فهمت وجهة نظرك التي مفادها أن response.raw معطل في التطبيق الحالي وحتى أتفق جزئيًا مع ذلك (يجب أن تكون قادرًا على الأقل على الحصول على تفاصيل الضغط دون تحليل الرؤوس).

ومع ذلك، اقتراحكم لا يزال هناك طلبات جديدة ...

تضمين التغريدة
لا أستطيع حقًا أن أرى كيف سيؤدي تقديم الخطأ ضد urllib3 إلى إصلاح واجهة برمجة التطبيقات للطلبات ، على الأقل ليس كلها من تلقاء نفسها.

وأنا أوافق على أن "حالة الاستخدام" الخاصة بك مرفوضة. كما قلت ، إذا لم يتمكن العميل من التحكم بشكل إيجابي في الضغط على جانب الخادم (وقام بتعطيله ، ولكن لم يقم بتمكينه بشكل موثوق) ، لذا فإن الاعتماد عليه ليكون قادرًا على حفظ ملف مضغوط على القرص ، حسنًا ، ليس مثيرًا للاهتمام .

تضمين التغريدة
أوافق على أنه يمكن قراءتها على هذا النحو. أؤكد لك أنني بخير مع أي شيء يحل هذه المشكلة. إذا كان فتح تذكرة جديدة مطلوبًا للوصول إلى هناك ، فليكن.

إذا كان فتح تذكرة جديدة مطلوبًا للوصول إلى هناك ، فليكن.

ما زلت أعتقد أن كينيث سيرفض هذا بسبب تجميد الميزة.

أنا بخير مع أي شيء يحل هذه المشكلة

  1. التفاف iter_content ككائن يشبه الملف أو
  2. تحليل الرؤوس وإلغاء ضغط response.raw إذا كان ذلك مناسبًا

كلا الحلين في التعليقات أعلاه ، آخر واحد نشرته أنت. لماذا هي مثل هذه القضايا التي لن تكون في الطلبات مباشرة؟

لنكن واضحين هنا بنسبة 100٪: لا توجد أي فرصة في الأساس للوصول إلى الطلبات أثناء تجميد الميزات. لا شيء معطل ، واجهة برمجة التطبيقات ليست مثالية لاحتياجاتك. لأنه لا يوجد شيء معطل ، فإن الشيء الوحيد المهم هو ما إذا كان كينيث يريد ذلك. الطلبات ليست ديمقراطية ، إنها رجل واحد صوت واحد. كينيث هو الرجل ، لديه حق التصويت . أغلق كينيث هذه القضية منذ 8 أشهر ، لذلك يبدو من الواضح أنه لا يريدها.

لا أستطيع حقًا أن أرى كيف سيؤدي تقديم الخطأ ضد urllib3 إلى إصلاح واجهة برمجة التطبيقات للطلبات ، على الأقل ليس كلها من تلقاء نفسها.

يجب أن يؤدي تصحيح urllib3 دائمًا لإرجاع كائن الملف غير المضغوط إلى حل هذا الأمر بنفسه (لا يُقال إن هذه فكرة جيدة).

أوه ، هذا هو الحل رقم 3 (لم يتم اختباره):

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

راجع https://github.com/shazow/urllib3/blob/master/urllib3/response.py#L112

مثير للاهتمام - لم أكن أعرف أنه موجود الآن. هذا يجعل من السهل التفاف الميزة ، بالتأكيد.

على الرغم من ذلك ، هل هذا يعمل بالفعل؟ أي هل برامج إزالة الضغط ذات حالة وتزايدية؟ الاستدعاء الثاني لقراءة (123) لن يعيد البداية الصالحة لملف gzip ، على سبيل المثال.

على الرغم من ذلك ، هل هذا يعمل بالفعل؟ أي هل برامج إزالة الضغط ذات حالة وتزايدية؟

أوه ، لا يبدو ذلك. لم أقرأ docstring.

ومع ذلك ، ها هو اقتراحي:

  1. تصحيح urllib3 بحيث يعمل HTTPResponse.read مع amt و decode_content بشكل متزامن.
  2. اجعل HTTPResponse._decode_content عضوًا عامًا (لذا يمكنك عمل response.raw.decode_content = True بدلاً من تصحيح طريقة read ).
  3. قم بإسقاط إلغاء الضغط في الطلبات تمامًا باستخدام decode_content=True في iter_content

Lukasa أعتقد أن هذا لن ينتهك تجميد الميزات ، أليس كذلك؟

schlamar : من حيث المبدأ ، بالتأكيد. طالما بقيت واجهة برمجة التطبيقات دون تغيير ، يجب أن تكون التغييرات الداخلية على ما يرام ، وسأكون +1 في هذا. ومع ذلك ، ضع في اعتبارك أنني لست BDFL ، =)

طلبات stream_decompress معطلة على أي حال: # 1249

+1

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات