Repository: urllib3/urllib3

Issue:
Add documentation for HTTPResponse `io` support
Reminder for @eteq re #187:

> Would you be up for adding a bit to the documentation/comments which talks about the use cases or benefits of this? That would be greatly appreciated. :)



Discussion:
@eteq Did this ever happen? (Any plans if not?)

@shazow has not happened yet, as I've had a very busy travel schedule this summer.  Still on my todo list, though.

Ah thanks for keeping track of it, then. :)

Ping @eteq in case you still want to do this one someday. :)

@shazow - It is still on my todo list, I promise, but things keep bubbling up...  I will try to push it up a bit so that you can finally close this, though!


Resolving patch:
diff --git a/urllib3/response.py b/urllib3/response.py
--- a/urllib3/response.py
+++ b/urllib3/response.py
@@ -48,7 +48,10 @@ class HTTPResponse(io.IOBase):
     HTTP Response container.
 
     Backwards-compatible to httplib's HTTPResponse but the response ``body`` is
-    loaded and decoded on-demand when the ``data`` property is accessed.
+    loaded and decoded on-demand when the ``data`` property is accessed.  This
+    class is also compatible with the Python standard library's :mod:`io`
+    module, and can hence be treated as a readable object in the context of that
+    framework.
 
     Extra parameters for behaviour not present in httplib.HTTPResponse:
 
@@ -317,4 +320,14 @@ def flush(self):
             return self._fp.flush()
 
     def readable(self):
+        # This method is required for `io` module compatibility.
         return True
+
+    def readinto(self, b):
+        # This method is required for `io` module compatibility.
+        temp = self.read(len(b))
+        if len(temp) == 0:
+            return 0
+        else:
+            b[:len(temp)] = temp
+            return len(temp)


Test patch:
diff --git a/test/test_response.py b/test/test_response.py
--- a/test/test_response.py
+++ b/test/test_response.py
@@ -182,6 +182,37 @@ def test_io_bufferedreader(self):
         br.close()
         self.assertEqual(resp.closed, True)
 
+        b = b'fooandahalf'
+        fp = BytesIO(b)
+        resp = HTTPResponse(fp, preload_content=False)
+        br = BufferedReader(resp, 5)
+
+        br.read(1)  # sets up the buffer, reading 5
+        self.assertEqual(len(fp.read()), len(b) - 5)
+
+        # This is necessary to make sure the "no bytes left" part of `readinto`
+        # gets tested.
+        while not br.closed:
+            br.read(5)
+
+    def test_io_readinto(self):
+        # This test is necessary because in py2.6, `readinto` doesn't get called
+        # in `test_io_bufferedreader` like it does for all the other python
+        # versions.  Probably this is because the `io` module in py2.6 is an
+        # old version that has a different underlying implementation.
+
+
+        fp = BytesIO(b'foo')
+        resp = HTTPResponse(fp, preload_content=False)
+
+        barr = bytearray(3)
+        assert resp.readinto(barr) == 3
+        assert b'foo' == barr
+
+        # The reader should already be empty, so this should read nothing.
+        assert resp.readinto(barr) == 0
+        assert b'foo' == barr
+
     def test_streaming(self):
         fp = BytesIO(b'foo')
         resp = HTTPResponse(fp, preload_content=False)