Index: Doc/library/urllib2.rst =================================================================== --- Doc/library/urllib2.rst (revision 67584) +++ Doc/library/urllib2.rst (working copy) @@ -26,12 +26,14 @@ Open the URL *url*, which can be either a string or a :class:`Request` object. *data* may be a string specifying additional data to send to the server, or - ``None`` if no such data is needed. Currently HTTP requests are the only ones - that use *data*; the HTTP request will be a POST instead of a GET when the - *data* parameter is provided. *data* should be a buffer in the standard + ``None`` if no such data is needed. *data* may also be an iterable object + that provides a ``next()`` method. In this case Content-Length must be + specified in the headers. Currently HTTP requests are the only ones that use + *data*; the HTTP request will be a POST instead of a GET when the *data* + parameter is provided. *data* should be a buffer in the standard :mimetype:`application/x-www-form-urlencoded` format. The - :func:`urllib.urlencode` function takes a mapping or sequence of 2-tuples and - returns a string in this format. + :func:`urllib.urlencode` function takes a mapping or sequence of 2-tuples + and returns a string in this format. The optional *timeout* parameter specifies a timeout in seconds for blocking operations like the connection attempt (if not specified, the global default @@ -56,6 +58,8 @@ .. versionchanged:: 2.6 *timeout* was added. + .. versionchanged:: 2.7 + *data* can be an iterable object. .. function:: install_opener(opener) @@ -125,12 +129,14 @@ *url* should be a string containing a valid URL. *data* may be a string specifying additional data to send to the server, or - ``None`` if no such data is needed. Currently HTTP requests are the only ones - that use *data*; the HTTP request will be a POST instead of a GET when the - *data* parameter is provided. *data* should be a buffer in the standard + ``None`` if no such data is needed. *data* may also be an iterable object + that provides a ``next()`` method. In this case Content-Length must be + specified in the headers. Currently HTTP requests are the only ones that use + *data*; the HTTP request will be a POST instead of a GET when the *data* + parameter is provided. *data* should be a buffer in the standard :mimetype:`application/x-www-form-urlencoded` format. The - :func:`urllib.urlencode` function takes a mapping or sequence of 2-tuples and - returns a string in this format. + :func:`urllib.urlencode` function takes a mapping or sequence of 2-tuples + and returns a string in this format. *headers* should be a dictionary, and will be treated as if :meth:`add_header` was called with each key and value as arguments. This is often used to "spoof" @@ -155,7 +161,10 @@ an image in an HTML document, and the user had no option to approve the automatic fetching of the image, this should be true. + .. versionchanged:: 2.7 + *data* can be an iterable object. + .. class:: OpenerDirector() The :class:`OpenerDirector` class opens URLs via :class:`BaseHandler`\ s chained Index: Doc/library/httplib.rst =================================================================== --- Doc/library/httplib.rst (revision 67584) +++ Doc/library/httplib.rst (working copy) @@ -404,13 +404,18 @@ string of data to send after the headers are finished. Alternatively, it may be an open file object, in which case the contents of the file is sent; this file object should support ``fileno()`` and ``read()`` methods. The header - Content-Length is automatically set to the correct value. The *headers* - argument should be a mapping of extra HTTP headers to send with the request. + Content-Length is automatically set to the correct value in this case. The + *body* argument may also be an interable, supporting a ``next()`` method. + The *headers* argument should be a mapping of extra HTTP headers to send + with the request. .. versionchanged:: 2.6 *body* can be a file object. + .. versionchanged:: 2.7 + *body* can be an interable object. + .. method:: HTTPConnection.getresponse() Should be called after a request is sent to get the response from the server. Index: Lib/httplib.py =================================================================== --- Lib/httplib.py (revision 67584) +++ Lib/httplib.py (working copy) @@ -698,7 +698,13 @@ self.__state = _CS_IDLE def send(self, str): - """Send `str' to the server.""" + """Send `str` to the server. + + ``str`` can be a string object, a file-like object that supports + a .read() method, or an iterable object that supports a .next() + method. + """ + if self.sock is None: if self.auto_open: self.connect() @@ -720,6 +726,10 @@ while data: self.sock.sendall(data) data=str.read(blocksize) + elif hasattr(str,'next'): + if self.debuglevel > 0: print "sendIng an iterable" + for data in str: + self.sock.sendall(data) else: self.sock.sendall(str) except socket.error, v: Index: Lib/urllib2.py =================================================================== --- Lib/urllib2.py (revision 67584) +++ Lib/urllib2.py (working copy) @@ -1052,7 +1052,13 @@ request.add_unredirected_header( 'Content-type', 'application/x-www-form-urlencoded') + if not request.has_header('Content-length'): + # We can't tell ahead of time how much data is in an + # iterable object + if not hasattr(data, 'read') and hasattr(data, 'next'): + raise ValueError( + "No Content-Length specified for iterable body") request.add_unredirected_header( 'Content-length', '%d' % len(data)) Index: Lib/test/test_httplib.py =================================================================== --- Lib/test/test_httplib.py (revision 67584) +++ Lib/test/test_httplib.py (working copy) @@ -162,6 +162,23 @@ conn.request('GET', '/foo', body) self.assertTrue(sock.data.startswith(expected)) + def test_send_iter(self): + expected = 'GET /foo HTTP/1.1\r\nHost: example.com\r\n' \ + 'Accept-Encoding: identity\r\nContent-Length: 11\r\n' \ + '\r\nonetwothree' + + def body(): + yield "one" + yield "two" + yield "three" + + conn = httplib.HTTPConnection('example.com') + sock = FakeSocket("") + conn.sock = sock + conn.request('GET', '/foo', body(), {'Content-Length': '11'}) + self.assertEquals(sock.data, expected) + + def test_chunked(self): chunked_start = ( 'HTTP/1.1 200 OK\r\n' Index: Lib/test/test_urllib2.py =================================================================== --- Lib/test/test_urllib2.py (revision 67584) +++ Lib/test/test_urllib2.py (working copy) @@ -772,6 +772,21 @@ self.assertEqual(req.unredirected_hdrs["Host"], "baz") self.assertEqual(req.unredirected_hdrs["Spam"], "foo") + # Check iterable body support + def iterable_body(): + yield "one" + yield "two" + yield "three" + + for headers in {}, {"Content-Length": 11}: + req = Request("http://example.com/", iterable_body(), headers) + if not headers: + # Having an iterable body without a Content-Length should + # raise an exception + self.assertRaises(ValueError, h.do_request_, req) + else: + newreq = h.do_request_(req) + def test_http_doubleslash(self): # Checks that the presence of an unnecessary double slash in a url doesn't break anything # Previously, a double slash directly after the host could cause incorrect parsing of the url