From 82f1320ed54abd2676a2c09abb6d4c6e7723794d Mon Sep 17 00:00:00 2001 From: Johannes Date: Sun, 22 May 2011 23:43:00 +0200 Subject: [PATCH 01/47] Refactor api.request --- requests/api.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/requests/api.py b/requests/api.py index 234f17ab..164eb629 100644 --- a/requests/api.py +++ b/requests/api.py @@ -18,9 +18,8 @@ from .models import Request, Response, AuthManager, AuthObject, auth_manager __all__ = ('request', 'get', 'head', 'post', 'put', 'delete') - - -def request(method, url, **kwargs): +def request(method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, + timeout=config.settings.timeout, allow_redirects=False): """Constructs and sends a :class:`Request `. Returns :class:`Response ` object. :param method: method for the new :class:`Request` object. @@ -34,21 +33,26 @@ def request(method, url, **kwargs): :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. """ - data = kwargs.pop('data', dict()) or kwargs.pop('params', dict()) - r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', {}), - cookiejar=kwargs.pop('cookies', None), - files=kwargs.pop('files', None), - auth=kwargs.pop('auth', auth_manager.get_auth(url)), - timeout=kwargs.pop('timeout', config.settings.timeout), - allow_redirects=kwargs.pop('allow_redirects', None) + if params and data: + raise StandardError('You may provide either params or data to a request, but not both.') + + r = Request( + method = method, + url = url, + data = params or data, + headers = headers, + cookiejar = cookies, + files = files, + auth = auth or auth_manager.get_auth(url), + timeout = timeout, + allow_redirects = allow_redirects ) r.send() return r.response - def get(url, params={}, headers={}, cookies=None, auth=None, **kwargs): """Sends a GET request. Returns :class:`Response` object. From 3189d8690e5b6a90d1921e9697e9231269f806e4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 22 May 2011 18:01:21 -0400 Subject: [PATCH 02/47] refactor^2 --- requests/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/api.py b/requests/api.py index 164eb629..a81e487e 100644 --- a/requests/api.py +++ b/requests/api.py @@ -19,7 +19,7 @@ from .models import Request, Response, AuthManager, AuthObject, auth_manager __all__ = ('request', 'get', 'head', 'post', 'put', 'delete') def request(method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, - timeout=config.settings.timeout, allow_redirects=False): + timeout=None, allow_redirects=False): """Constructs and sends a :class:`Request `. Returns :class:`Response ` object. :param method: method for the new :class:`Request` object. @@ -45,7 +45,7 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil cookiejar = cookies, files = files, auth = auth or auth_manager.get_auth(url), - timeout = timeout, + timeout = timeout or config.settings.timeout, allow_redirects = allow_redirects ) From 5593f12347419c33063b19a3d9c33fee54348d1b Mon Sep 17 00:00:00 2001 From: Daniel Schauenberg Date: Mon, 23 May 2011 08:39:15 -0700 Subject: [PATCH 03/47] e.g. should be i.e. I guess --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ce4eb581..72c30529 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Requests: The Simple (e.g. usable) HTTP Module +Requests: The Simple (i.e. usable) HTTP Module ============================================== Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging). From ef02d02b1f4db1053c6e124ed740ff061d248fc4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 24 May 2011 17:18:19 -0400 Subject: [PATCH 04/47] testimonial cleanup --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index c270663b..6890a962 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,7 +36,7 @@ all the hard work and crazy hacks for you. Testimonals ----------- -`Twitter, Inc `_ and `The Library of Congress `_ use Requests internally. +`Twitter, Inc `_ uses Requests internally. **Daniel Greenfeld** Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to @kennethreitz's request library. Today has been AWESOME. From 7e3d265b0f1fa52f5ce0889b18e501732c7c3660 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 24 May 2011 18:38:45 -0400 Subject: [PATCH 05/47] gauge.es --- docs/_themes/kr/layout.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index abb6f8a8..623e3ef0 100644 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -30,4 +30,18 @@ })(); + + {%- endblock %} From 9b2d7382bfc8656630b70fd68831fc47ae1c5eee Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 26 May 2011 20:04:17 +0200 Subject: [PATCH 06/47] Conform api.* arguments to api.request arguments. Fixes #40. Fixes #41. --- requests/api.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/requests/api.py b/requests/api.py index a81e487e..e7552be0 100644 --- a/requests/api.py +++ b/requests/api.py @@ -53,7 +53,7 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil return r.response -def get(url, params={}, headers={}, cookies=None, auth=None, **kwargs): +def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None): """Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -64,10 +64,10 @@ def get(url, params={}, headers={}, cookies=None, auth=None, **kwargs): :param timeout: (optional) Float describing the timeout of the request. """ - return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs) + return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout) -def head(url, params={}, headers={}, cookies=None, auth=None, **kwargs): +def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None): """Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -78,10 +78,10 @@ def head(url, params={}, headers={}, cookies=None, auth=None, **kwargs): :param timeout: (optional) Float describing the timeout of the request. """ - return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs) + return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout) -def post(url, data={}, headers={}, files=None, cookies=None, auth=None, **kwargs): +def post(url, data=None, headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -91,12 +91,14 @@ def post(url, data={}, headers={}, files=None, cookies=None, auth=None, **kwargs :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. + :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. """ - return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs) + return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, + timeout=timeout, allow_redirects=allow_redirects) -def put(url, data='', headers={}, files={}, cookies=None, auth=None, **kwargs): +def put(url, data=None, headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -106,12 +108,14 @@ def put(url, data='', headers={}, files={}, cookies=None, auth=None, **kwargs): :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. + :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. """ - return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs) + return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, + timeout=timeout, allow_redirects=allow_redirects) -def delete(url, params={}, headers={}, cookies=None, auth=None, **kwargs): +def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None, allow_redirects=False): """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -120,6 +124,8 @@ def delete(url, params={}, headers={}, cookies=None, auth=None, **kwargs): :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. + :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. """ - return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs) + return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, + timeout=timeout, allow_redirects=allow_redirects) From 5962c0ea8609837454e79bad9e0a94ae5099ad23 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Fri, 27 May 2011 00:17:44 -0700 Subject: [PATCH 07/47] Subclass dict for CaseInsensitiveDict. --- requests/structures.py | 44 ++++-------------------------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/requests/structures.py b/requests/structures.py index 0c82c7b4..bfee7b19 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -8,30 +8,14 @@ Datastructures that power Requests. """ -from UserDict import DictMixin - - -class CaseInsensitiveDict(DictMixin): +class CaseInsensitiveDict(dict): """Case-insensitive Dictionary for :class:`Response ` Headers. For example, ``headers['content-encoding']`` will return the value of a ``'Content-Encoding'`` response header.""" - def __init__(self, *args, **kwargs): - # super(CaseInsensitiveDict, self).__init__() - self.data = dict(*args, **kwargs) - - def __repr__(self): - return self.data.__repr__() - - def __getstate__(self): - return self.data.copy() - - def __setstate__(self, d): - self.data = d - def _lower_keys(self): - return map(str.lower, self.data.keys()) + return map(str.lower, self.keys()) def __contains__(self, key): @@ -39,26 +23,6 @@ class CaseInsensitiveDict(DictMixin): def __getitem__(self, key): - - if key.lower() in self: + # We allow fall-through here, so values default to None + if key in self: return self.items()[self._lower_keys().index(key.lower())][1] - - - def __setitem__(self, key, value): - return self.data.__setitem__(key, value) - - - def __delitem__(self, key): - return self.data.__delitem__(key) - - - def __keys__(self): - return self.data.__keys__() - - - def __iter__(self): - return self.data.__iter__() - - - def iteritems(self): - return self.data.iteritems() From 52bedf4c0b03a85c7e1361d0c3089b96f623ab10 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 27 May 2011 07:59:28 -0400 Subject: [PATCH 08/47] default post data to empty byte string Refs #45 --- requests/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/api.py b/requests/api.py index e7552be0..426cfaf3 100644 --- a/requests/api.py +++ b/requests/api.py @@ -81,7 +81,7 @@ def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None): return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout) -def post(url, data=None, headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): +def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -98,7 +98,7 @@ def post(url, data=None, headers=None, files=None, cookies=None, auth=None, time timeout=timeout, allow_redirects=allow_redirects) -def put(url, data=None, headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): +def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. From e63569041a1635574c1d2931875622152b015749 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 27 May 2011 08:02:57 -0400 Subject: [PATCH 09/47] Update AUTHORS Refs #46 --- AUTHORS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 280977da..b19e40a2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,4 +18,6 @@ Patches and Suggestions - Rob Madole - Aram Dulyan - Johannes Gorset -- 村山めがね (Megane Murayama) \ No newline at end of file +- 村山めがね (Megane Murayama) +- James Rowe +- Daniel Schauenberg \ No newline at end of file From cd0d0a2b1779f38206ec0029f8ed7efd321aa2cc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 5 Jun 2011 21:22:35 -0400 Subject: [PATCH 10/47] whitespace --- test_requests.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test_requests.py b/test_requests.py index 358dfce6..e84ef0bb 100755 --- a/test_requests.py +++ b/test_requests.py @@ -15,33 +15,40 @@ class RequestsTestSuite(unittest.TestCase): def setUp(self): pass + def tearDown(self): """Teardown.""" pass + def test_invalid_url(self): self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw') + def test_HTTP_200_OK_GET(self): r = requests.get('http://google.com') self.assertEqual(r.status_code, 200) + def test_HTTPS_200_OK_GET(self): r = requests.get('https://google.com') self.assertEqual(r.status_code, 200) + def test_HTTP_200_OK_GET_WITH_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} r = requests.get('http://www.google.com/search', params={'q': 'test'}, headers=heads) self.assertEqual(r.status_code, 200) + def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} r = requests.get('http://google.com/search?test=true', params={'q': 'test'}, headers=heads) self.assertEqual(r.status_code, 200) + def test_user_agent_transfers(self): """Issue XX""" heads = { @@ -60,14 +67,17 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('http://whatsmyua.com', headers=heads); self.assertTrue(heads['user-agent'] in r.content) + def test_HTTP_200_OK_HEAD(self): r = requests.head('http://google.com') self.assertEqual(r.status_code, 200) + def test_HTTPS_200_OK_HEAD(self): r = requests.head('https://google.com') self.assertEqual(r.status_code, 200) + def test_AUTH_HTTPS_200_OK_GET(self): auth = ('requeststest', 'requeststest') url = 'https://convore.com/api/account/verify.json' @@ -81,6 +91,7 @@ class RequestsTestSuite(unittest.TestCase): # reset auto authentication requests.auth_manager.empty() + def test_POSTBIN_GET_POST_FILES(self): bin = requests.post('http://www.postbin.org/') self.assertEqual(bin.status_code, 302) @@ -125,10 +136,12 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('http://google.com/') self.assertEqual(bool(r), True) + def test_request_ok_set(self): r = requests.get('http://google.com/some-404-url') self.assertEqual(r.ok, False) + def test_status_raising(self): r = requests.get('http://google.com/some-404-url') self.assertRaises(requests.HTTPError, r.raise_for_status) @@ -137,6 +150,7 @@ class RequestsTestSuite(unittest.TestCase): self.assertFalse(r.error) r.raise_for_status() + def test_cookie_jar(self): """ .. todo:: This really doesn't test to make sure the cookie is working @@ -148,11 +162,13 @@ class RequestsTestSuite(unittest.TestCase): requests.get('http://google.com', cookies=jar) self.assertTrue(jar) + def test_decompress_gzip(self): r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags') r.content.decode('ascii') + def test_autoauth(self): conv_auth = ('requeststest', 'requeststest') @@ -161,19 +177,23 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('https://convore.com/api/account/verify.json') self.assertEquals(r.status_code, 200) + def test_unicode_get(self): + requests.get('http://google.com', params={'foo': u'føø'}) requests.get('http://google.com', params={u'føø': u'føø'}) requests.get('http://google.com', params={'føø': 'føø'}) requests.get('http://google.com', params={'foo': u'foo'}) requests.get('http://google.com/ø', params={'foo': u'foo'}) + def test_httpauth_recursion(self): conv_auth = ('requeststest', 'bad_password') r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth) self.assertEquals(r.status_code, 401) + def test_settings(self): with requests.settings(timeout=0.0001): self.assertRaises(requests.Timeout, requests.get, 'http://google.com') @@ -181,6 +201,7 @@ class RequestsTestSuite(unittest.TestCase): with requests.settings(timeout=10): requests.get('http://google.com') + def test_nonurlencoded_post_data(self): requests.post('http://google.com', data='foo') From 1fa1cdd2a334d5acb9f3d9ca11ab4eaaec794292 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 8 Jun 2011 19:11:08 -0400 Subject: [PATCH 11/47] WHITESPACE --- test_requests.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test_requests.py b/test_requests.py index e84ef0bb..6b15596c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -15,40 +15,40 @@ class RequestsTestSuite(unittest.TestCase): def setUp(self): pass - + def tearDown(self): """Teardown.""" pass - + def test_invalid_url(self): self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw') - + def test_HTTP_200_OK_GET(self): r = requests.get('http://google.com') self.assertEqual(r.status_code, 200) - + def test_HTTPS_200_OK_GET(self): r = requests.get('https://google.com') self.assertEqual(r.status_code, 200) - + def test_HTTP_200_OK_GET_WITH_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} r = requests.get('http://www.google.com/search', params={'q': 'test'}, headers=heads) self.assertEqual(r.status_code, 200) - + def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} r = requests.get('http://google.com/search?test=true', params={'q': 'test'}, headers=heads) self.assertEqual(r.status_code, 200) - + def test_user_agent_transfers(self): """Issue XX""" heads = { @@ -67,17 +67,17 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('http://whatsmyua.com', headers=heads); self.assertTrue(heads['user-agent'] in r.content) - + def test_HTTP_200_OK_HEAD(self): r = requests.head('http://google.com') self.assertEqual(r.status_code, 200) - + def test_HTTPS_200_OK_HEAD(self): r = requests.head('https://google.com') self.assertEqual(r.status_code, 200) - + def test_AUTH_HTTPS_200_OK_GET(self): auth = ('requeststest', 'requeststest') url = 'https://convore.com/api/account/verify.json' @@ -91,7 +91,7 @@ class RequestsTestSuite(unittest.TestCase): # reset auto authentication requests.auth_manager.empty() - + def test_POSTBIN_GET_POST_FILES(self): bin = requests.post('http://www.postbin.org/') self.assertEqual(bin.status_code, 302) @@ -136,12 +136,12 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('http://google.com/') self.assertEqual(bool(r), True) - + def test_request_ok_set(self): r = requests.get('http://google.com/some-404-url') self.assertEqual(r.ok, False) - + def test_status_raising(self): r = requests.get('http://google.com/some-404-url') self.assertRaises(requests.HTTPError, r.raise_for_status) @@ -150,7 +150,7 @@ class RequestsTestSuite(unittest.TestCase): self.assertFalse(r.error) r.raise_for_status() - + def test_cookie_jar(self): """ .. todo:: This really doesn't test to make sure the cookie is working @@ -162,13 +162,13 @@ class RequestsTestSuite(unittest.TestCase): requests.get('http://google.com', cookies=jar) self.assertTrue(jar) - + def test_decompress_gzip(self): r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags') r.content.decode('ascii') - + def test_autoauth(self): conv_auth = ('requeststest', 'requeststest') @@ -177,23 +177,23 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('https://convore.com/api/account/verify.json') self.assertEquals(r.status_code, 200) - + def test_unicode_get(self): - + requests.get('http://google.com', params={'foo': u'føø'}) requests.get('http://google.com', params={u'føø': u'føø'}) requests.get('http://google.com', params={'føø': 'føø'}) requests.get('http://google.com', params={'foo': u'foo'}) requests.get('http://google.com/ø', params={'foo': u'foo'}) - + def test_httpauth_recursion(self): conv_auth = ('requeststest', 'bad_password') r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth) self.assertEquals(r.status_code, 401) - + def test_settings(self): with requests.settings(timeout=0.0001): self.assertRaises(requests.Timeout, requests.get, 'http://google.com') From 74013d751c410ff2157d66de0864c8ed3052f75c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 8 Jun 2011 19:18:11 -0400 Subject: [PATCH 12/47] rackspace cloud's net connection is insane --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 6b15596c..b2e570de 100755 --- a/test_requests.py +++ b/test_requests.py @@ -195,7 +195,7 @@ class RequestsTestSuite(unittest.TestCase): def test_settings(self): - with requests.settings(timeout=0.0001): + with requests.settings(timeout=0.0000001): self.assertRaises(requests.Timeout, requests.get, 'http://google.com') with requests.settings(timeout=10): From 2153b21152ae86ff760b5e693ac88d998fd9ddcc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 8 Jun 2011 19:22:19 -0400 Subject: [PATCH 13/47] less hostility --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 72c30529..d9cb223e 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Requests: The Simple (i.e. usable) HTTP Module -============================================== +Requests: HTTP for Humans +========================= Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging). From 54fd24b7575af3c9fb8218aeae4d06c9d0ea3317 Mon Sep 17 00:00:00 2001 From: Zbigniew Siciarz Date: Thu, 9 Jun 2011 12:30:43 +0200 Subject: [PATCH 14/47] Testing that cookies actually work. --- test_requests.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test_requests.py b/test_requests.py index b2e570de..73c822ad 100755 --- a/test_requests.py +++ b/test_requests.py @@ -152,15 +152,17 @@ class RequestsTestSuite(unittest.TestCase): def test_cookie_jar(self): - """ - .. todo:: This really doesn't test to make sure the cookie is working - """ - jar = cookielib.CookieJar() self.assertFalse(jar) - - requests.get('http://google.com', cookies=jar) + data = {'cn': 'requests_cookie', 'cv': 'awesome'} + r = requests.post('http://www.html-kit.com/tools/cookietester/', data=data, cookies=jar, allow_redirects=True) self.assertTrue(jar) + cookie_found = False + for cookie in jar: + if cookie.name == 'requests_cookie': + self.assertEquals(cookie.value, 'awesome') + cookie_found = True + self.assertTrue(cookie_found) def test_decompress_gzip(self): From d6cde6a5dfee65da0c0fb8eacacd8c500618f674 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 9 Jun 2011 18:09:39 -0400 Subject: [PATCH 15/47] add Zbigniew Siciarz to authors --- AUTHORS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b19e40a2..0eee7f62 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,4 +20,5 @@ Patches and Suggestions - Johannes Gorset - 村山めがね (Megane Murayama) - James Rowe -- Daniel Schauenberg \ No newline at end of file +- Daniel Schauenberg +- Zbigniew Siciarz \ No newline at end of file From 8f3da7b72c132e51e22e64f91d05230ac126a5dc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 12 Jun 2011 13:14:56 -0400 Subject: [PATCH 16/47] added Daniele Tricoli 'Eriol' to authors --- AUTHORS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 0eee7f62..8a279f58 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,4 +21,5 @@ Patches and Suggestions - 村山めがね (Megane Murayama) - James Rowe - Daniel Schauenberg -- Zbigniew Siciarz \ No newline at end of file +- Zbigniew Siciarz +- Daniele Tricoli 'Eriol' \ No newline at end of file From 3b86c3c0e34e8f986726a5929acd1d45ee184c12 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 12 Jun 2011 22:00:17 -0400 Subject: [PATCH 17/47] httpbin --- test_requests.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test_requests.py b/test_requests.py index 73c822ad..3ec2bf9b 100755 --- a/test_requests.py +++ b/test_requests.py @@ -26,26 +26,28 @@ class RequestsTestSuite(unittest.TestCase): def test_HTTP_200_OK_GET(self): - r = requests.get('http://google.com') + r = requests.get('http://httpbin.org/') self.assertEqual(r.status_code, 200) def test_HTTPS_200_OK_GET(self): - r = requests.get('https://google.com') + r = requests.get('https://httpbin.org/') self.assertEqual(r.status_code, 200) def test_HTTP_200_OK_GET_WITH_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} - r = requests.get('http://www.google.com/search', params={'q': 'test'}, headers=heads) + r = requests.get('http://httpbin.org/user-agent', headers=heads) + + assert heads['User-agent'] in r.content self.assertEqual(r.status_code, 200) def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} - r = requests.get('http://google.com/search?test=true', params={'q': 'test'}, headers=heads) + r = requests.get('http://httpbin.org/get?test=true', params={'q': 'test'}, headers=heads) self.assertEqual(r.status_code, 200) From 1749260c974265144a811389f47bde54b79c1cb5 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 12 Jun 2011 22:02:35 -0400 Subject: [PATCH 18/47] update tests --- test_requests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test_requests.py b/test_requests.py index 3ec2bf9b..3f4b6a1b 100755 --- a/test_requests.py +++ b/test_requests.py @@ -31,7 +31,7 @@ class RequestsTestSuite(unittest.TestCase): def test_HTTPS_200_OK_GET(self): - r = requests.get('https://httpbin.org/') + r = requests.get('https://github.com/') self.assertEqual(r.status_code, 200) @@ -53,12 +53,13 @@ class RequestsTestSuite(unittest.TestCase): def test_user_agent_transfers(self): """Issue XX""" + heads = { 'User-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)' } - r = requests.get('http://whatsmyua.com', headers=heads); + r = requests.get('http://httpbin.org/user-agent', headers=heads); self.assertTrue(heads['User-agent'] in r.content) heads = { @@ -66,17 +67,17 @@ class RequestsTestSuite(unittest.TestCase): 'Mozilla/5.0 (github.com/kennethreitz/requests)' } - r = requests.get('http://whatsmyua.com', headers=heads); + r = requests.get('http://httpbin.org/user-agent', headers=heads); self.assertTrue(heads['user-agent'] in r.content) def test_HTTP_200_OK_HEAD(self): - r = requests.head('http://google.com') + r = requests.head('http://httpbin.org') self.assertEqual(r.status_code, 200) def test_HTTPS_200_OK_HEAD(self): - r = requests.head('https://google.com') + r = requests.head('https://github.com') self.assertEqual(r.status_code, 200) From cb0a96fbf30c2b8bc0b45833bac89ed3c1d4e0eb Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 15 Jun 2011 22:43:26 -0400 Subject: [PATCH 19/47] Redirect chain flag default off. --- requests/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 2c3241d4..c46d8ed1 100644 --- a/requests/models.py +++ b/requests/models.py @@ -32,7 +32,7 @@ class Request(object): def __init__(self, url=None, headers=dict(), files=None, method=None, data=dict(), auth=None, cookiejar=None, timeout=None, - redirect=True, allow_redirects=False): + redirect=False, allow_redirects=False): socket.setdefaulttimeout(timeout) @@ -135,6 +135,7 @@ class Request(object): return opener.open + def _build_response(self, resp): """Build internal :class:`Response ` object from given response.""" @@ -155,6 +156,8 @@ class Request(object): except zlib.error: pass + # TODO: Support deflate + response.url = getattr(resp, 'url', None) return response From 245c6edec34efd3216da2bd685a73fe411399b82 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 15 Jun 2011 22:56:14 -0400 Subject: [PATCH 20/47] todo --- requests/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requests/models.py b/requests/models.py index c46d8ed1..48ba976f 100644 --- a/requests/models.py +++ b/requests/models.py @@ -262,6 +262,9 @@ class Request(object): self._build_response(why) if not self.redirect: self.response.error = why + + # TODO: Support urllib connection refused errors + except urllib2.URLError, error: raise Timeout if isinstance(error.reason, socket.timeout) else error else: From 45e30048f0bca3fc2fad22aba8b7fa4f67f9af2b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 16 Jun 2011 00:11:47 -0400 Subject: [PATCH 21/47] port request tests to :sparkles: httpbin.org :sparkles: --- test_requests.py | 125 ++++++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 51 deletions(-) diff --git a/test_requests.py b/test_requests.py index 3f4b6a1b..87ccf8e9 100755 --- a/test_requests.py +++ b/test_requests.py @@ -9,9 +9,29 @@ import cookielib import requests + +HTTPBIN_URL = 'http://httpbin.org/' +HTTPSBIN_URL = 'https://httpbin.ep.io/' + + + +def httpbin(*suffix): + """Returns url for HTTPBIN resource.""" + + return HTTPBIN_URL + '/'.join(suffix) + + +def httpsbin(*suffix): + """Returns url for HTTPSBIN resource.""" + + return HTTPBIN_URL + '/'.join(suffix) + + + class RequestsTestSuite(unittest.TestCase): """Requests test cases.""" + def setUp(self): pass @@ -26,19 +46,19 @@ class RequestsTestSuite(unittest.TestCase): def test_HTTP_200_OK_GET(self): - r = requests.get('http://httpbin.org/') + r = requests.get(httpbin('/')) self.assertEqual(r.status_code, 200) def test_HTTPS_200_OK_GET(self): - r = requests.get('https://github.com/') + r = requests.get(httpsbin('/')) self.assertEqual(r.status_code, 200) def test_HTTP_200_OK_GET_WITH_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} - r = requests.get('http://httpbin.org/user-agent', headers=heads) + r = requests.get(httpbin('user-agent'), headers=heads) assert heads['User-agent'] in r.content self.assertEqual(r.status_code, 200) @@ -47,7 +67,7 @@ class RequestsTestSuite(unittest.TestCase): def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self): heads = {'User-agent': 'Mozilla/5.0'} - r = requests.get('http://httpbin.org/get?test=true', params={'q': 'test'}, headers=heads) + r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads) self.assertEqual(r.status_code, 200) @@ -59,7 +79,7 @@ class RequestsTestSuite(unittest.TestCase): 'Mozilla/5.0 (github.com/kennethreitz/requests)' } - r = requests.get('http://httpbin.org/user-agent', headers=heads); + r = requests.get(httpbin('user-agent'), headers=heads); self.assertTrue(heads['User-agent'] in r.content) heads = { @@ -67,23 +87,23 @@ class RequestsTestSuite(unittest.TestCase): 'Mozilla/5.0 (github.com/kennethreitz/requests)' } - r = requests.get('http://httpbin.org/user-agent', headers=heads); + r = requests.get(httpbin('user-agent'), headers=heads); self.assertTrue(heads['user-agent'] in r.content) def test_HTTP_200_OK_HEAD(self): - r = requests.head('http://httpbin.org') + r = requests.head(httpbin('/')) self.assertEqual(r.status_code, 200) def test_HTTPS_200_OK_HEAD(self): - r = requests.head('https://github.com') + r = requests.head(httpsbin('/')) self.assertEqual(r.status_code, 200) def test_AUTH_HTTPS_200_OK_GET(self): - auth = ('requeststest', 'requeststest') - url = 'https://convore.com/api/account/verify.json' + auth = ('user', 'pass') + url = httpsbin('basic-auth', 'user', 'pass') r = requests.get(url, auth=auth) self.assertEqual(r.status_code, 200) @@ -96,70 +116,71 @@ class RequestsTestSuite(unittest.TestCase): def test_POSTBIN_GET_POST_FILES(self): - bin = requests.post('http://www.postbin.org/') - self.assertEqual(bin.status_code, 302) + url = httpbin('post') + post = requests.post(url).raise_for_status() - post_url = bin.headers['location'] - post = requests.post(post_url, data={'some': 'data'}) - self.assertEqual(post.status_code, 201) + post = requests.post(url, data={'some': 'data'}) + self.assertEqual(post.status_code, 200) - post2 = requests.post(post_url, files={'some': open('test_requests.py')}) - self.assertEqual(post2.status_code, 201) + post2 = requests.post(url, files={'some': open('test_requests.py')}) + self.assertEqual(post2.status_code, 200) - post3 = requests.post(post_url, data='[{"some": "json"}]') - self.assertEqual(post.status_code, 201) + post3 = requests.post(url, data='[{"some": "json"}]') + self.assertEqual(post.status_code, 200) def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self): - bin = requests.post('http://www.postbin.org/') - self.assertEqual(bin.status_code, 302) - post_url = bin.headers['location'] - - post2 = requests.post(post_url, files={'some': open('test_requests.py')}, data={'some': 'data'}) - self.assertEqual(post2.status_code, 201) + url = httpbin('post') + post = requests.post(url, files={'some': open('test_requests.py')}, data={'some': 'data'}) + self.assertEqual(post.status_code, 200) def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self): - bin = requests.post('http://www.postbin.org/') - self.assertEqual(bin.status_code, 302) - post_url = bin.headers['location'] + url = httpbin('post') - post2 = requests.post(post_url, files={'some': open('test_requests.py')}, - headers = {'User-Agent': 'requests-tests'}) + post2 = requests.post(url, files={'some': open('test_requests.py')}, + headers = {'User-Agent': 'requests-tests'}) - self.assertEqual(post2.status_code, 201) + self.assertEqual(post2.status_code, 200) def test_nonzero_evaluation(self): - r = requests.get('http://google.com/some-404-url') + r = requests.get(httpbin('status', '500')) self.assertEqual(bool(r), False) - r = requests.get('http://google.com/') + r = requests.get(httpbin('/')) self.assertEqual(bool(r), True) def test_request_ok_set(self): - r = requests.get('http://google.com/some-404-url') + r = requests.get(httpbin('status', '404')) self.assertEqual(r.ok, False) def test_status_raising(self): - r = requests.get('http://google.com/some-404-url') + r = requests.get(httpbin('status', '404')) self.assertRaises(requests.HTTPError, r.raise_for_status) - r = requests.get('http://google.com/') + r = requests.get(httpbin('status', '200')) self.assertFalse(r.error) r.raise_for_status() def test_cookie_jar(self): + + # TODO: port to httpbin + jar = cookielib.CookieJar() self.assertFalse(jar) + data = {'cn': 'requests_cookie', 'cv': 'awesome'} + r = requests.post('http://www.html-kit.com/tools/cookietester/', data=data, cookies=jar, allow_redirects=True) + self.assertTrue(jar) + cookie_found = False for cookie in jar: if cookie.name == 'requests_cookie': @@ -170,45 +191,47 @@ class RequestsTestSuite(unittest.TestCase): def test_decompress_gzip(self): - r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags') + r = requests.get(httpbin('gzip')) r.content.decode('ascii') def test_autoauth(self): - conv_auth = ('requeststest', 'requeststest') - requests.auth_manager.add_auth('convore.com', conv_auth) + http_auth = ('user', 'pass') + requests.auth_manager.add_auth('httpbin.org', http_auth) - r = requests.get('https://convore.com/api/account/verify.json') + r = requests.get(httpbin('basic-auth', 'user', 'pass')) self.assertEquals(r.status_code, 200) def test_unicode_get(self): - requests.get('http://google.com', params={'foo': u'føø'}) - requests.get('http://google.com', params={u'føø': u'føø'}) - requests.get('http://google.com', params={'føø': 'føø'}) - requests.get('http://google.com', params={'foo': u'foo'}) - requests.get('http://google.com/ø', params={'foo': u'foo'}) + url = httpbin('/') + + requests.get(url, params={'foo': u'føø'}) + requests.get(url, params={u'føø': u'føø'}) + requests.get(url, params={'føø': 'føø'}) + requests.get(url, params={'foo': u'foo'}) + requests.get(httpbin('ø'), params={'foo': u'foo'}) def test_httpauth_recursion(self): - conv_auth = ('requeststest', 'bad_password') + http_auth = ('user', 'BADpass') - r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth) + r = requests.get(httpbin('basic-auth', 'user', 'pass'), auth=http_auth) self.assertEquals(r.status_code, 401) def test_settings(self): with requests.settings(timeout=0.0000001): - self.assertRaises(requests.Timeout, requests.get, 'http://google.com') + self.assertRaises(requests.Timeout, requests.get, httpbin('')) - with requests.settings(timeout=10): - requests.get('http://google.com') + with requests.settings(timeout=100): + requests.get(httpbin('')) def test_nonurlencoded_post_data(self): - requests.post('http://google.com', data='foo') + r = requests.post(httpbin('post'), data='fooaowpeuf') From 4d3a9defd25bebae524c15647b897a3459365410 Mon Sep 17 00:00:00 2001 From: Zbigniew Siciarz Date: Thu, 16 Jun 2011 09:08:32 +0200 Subject: [PATCH 22/47] Porting cookie test to httpbin.org. --- test_requests.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test_requests.py b/test_requests.py index 87ccf8e9..3c1a437a 100755 --- a/test_requests.py +++ b/test_requests.py @@ -170,15 +170,11 @@ class RequestsTestSuite(unittest.TestCase): def test_cookie_jar(self): - # TODO: port to httpbin - jar = cookielib.CookieJar() self.assertFalse(jar) - data = {'cn': 'requests_cookie', 'cv': 'awesome'} - - r = requests.post('http://www.html-kit.com/tools/cookietester/', data=data, cookies=jar, allow_redirects=True) - + url = httpbin('cookies', 'set', 'requests_cookie', 'awesome') + r = requests.get(url, cookies=jar) self.assertTrue(jar) cookie_found = False @@ -188,6 +184,9 @@ class RequestsTestSuite(unittest.TestCase): cookie_found = True self.assertTrue(cookie_found) + r = requests.get(httpbin('cookies'), cookies=jar) + self.assertTrue('awesome' in r.content) + def test_decompress_gzip(self): From 3d1444b829f63582b0acb6aa53b82b2693c19213 Mon Sep 17 00:00:00 2001 From: Richard Boulton Date: Thu, 16 Jun 2011 03:18:47 -0700 Subject: [PATCH 23/47] Update documentation of the "data" parameter for requests.post() to indicate that it allows bytes. Update documentation of requests.put() to refer to the "data" parameter rather than the (hidden, but functional) "params" parameter, and note that that also allows both bytes and dictionaries. --- requests/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/api.py b/requests/api.py index 426cfaf3..8967c26b 100644 --- a/requests/api.py +++ b/requests/api.py @@ -85,7 +85,7 @@ def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeou """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary of POST data to send with the :class:`Request`. + :param data: (optional) Dictionary or bytes of POST data to send with the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param cookies: (optional) CookieJar object to send with the :class:`Request`. @@ -102,8 +102,8 @@ def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param params: (optional) Bytes of PUT Data to send with the :class:`Request`. - :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. + :param data: (optional) Bytes of PUT Data to send with the :class:`Request`. + :param headers: (optional) Dictionary or bytes of HTTP Headers to sent with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. From 47f1f283857080b797c4b29a0aa6fb07793bb4d2 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 16 Jun 2011 06:27:13 -0400 Subject: [PATCH 24/47] + Richard Boulton to AUTHOURS --- AUTHORS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 8a279f58..c9751891 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,4 +22,5 @@ Patches and Suggestions - James Rowe - Daniel Schauenberg - Zbigniew Siciarz -- Daniele Tricoli 'Eriol' \ No newline at end of file +- Daniele Tricoli 'Eriol' +- Richard Boulton \ No newline at end of file From 4320f81bf4c6a6b6f97dbfb7a6ba667dd71c4977 Mon Sep 17 00:00:00 2001 From: Richard Boulton Date: Thu, 16 Jun 2011 13:06:22 +0100 Subject: [PATCH 25/47] Actually use the HTTPSBIN variable for test https requests --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 3c1a437a..6aaf0b9a 100755 --- a/test_requests.py +++ b/test_requests.py @@ -24,7 +24,7 @@ def httpbin(*suffix): def httpsbin(*suffix): """Returns url for HTTPSBIN resource.""" - return HTTPBIN_URL + '/'.join(suffix) + return HTTPSBIN_URL + '/'.join(suffix) From 023ab755e5404fe379a22e0b8536aa668792b71a Mon Sep 17 00:00:00 2001 From: Richard Boulton Date: Thu, 16 Jun 2011 13:35:33 +0100 Subject: [PATCH 26/47] Move the code to encode the request data into a static helper method - I think this makes the code clearer, but it also prepares the way for reusing the encoding code --- requests/models.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/requests/models.py b/requests/models.py index 48ba976f..23555b67 100644 --- a/requests/models.py +++ b/requests/models.py @@ -52,16 +52,7 @@ class Request(object): #: Set to True if full redirects are allowed (e.g. re-POST-ing of data at new ``Location``) self.allow_redirects = allow_redirects - if hasattr(data, 'items'): - for (k, v) in data.items(): - self.data.update({ - k.encode('utf-8') if isinstance(k, unicode) else k: - v.encode('utf-8') if isinstance(v, unicode) else v - }) - self._enc_data = urllib.urlencode(self.data) - else: - self._enc_data = self.data = data - + self.data, self._enc_data = self._encode_params(data) #: :class:`Response ` instance, containing #: content and metadata of HTTP Response, once :attr:`sent `. self.response = Response() @@ -204,6 +195,28 @@ class Request(object): self.response = r + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + If the data supplied is a dictionary, encodes each parameter in it, and + returns the dictionary of encoded parameters, and a urlencoded version + of that. + + Otherwise, assumes the data is already encoded appropriately, and + returns it twice. + + """ + if hasattr(data, 'items'): + result = {} + for (k, v) in data.items(): + result[k.encode('utf-8') if isinstance(k, unicode) else k] \ + = v.encode('utf-8') if isinstance(v, unicode) else v + return result, urllib.urlencode(result) + else: + return data, data + + @staticmethod def _build_url(url, data=None): """Build URLs.""" From f31ade335de9df096ba3c67c9507dd4eb32667a8 Mon Sep 17 00:00:00 2001 From: Richard Boulton Date: Thu, 16 Jun 2011 15:40:14 +0100 Subject: [PATCH 27/47] Change documentation comments for "params" and "data" parameters to refer to the request body or the query string, as appropriate. "GET parameters", "POST data", etc aren't the ideal terminology to use - they come from common usage with web browsers, rather than the HTTP specification, so are off-putting in other contexts. --- requests/api.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requests/api.py b/requests/api.py index 8967c26b..c3c211c1 100644 --- a/requests/api.py +++ b/requests/api.py @@ -24,8 +24,8 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil :param method: method for the new :class:`Request` object. :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary of GET/HEAD/DELETE Parameters to send with the :class:`Request`. - :param data: (optional) Bytes/Dictionary of PUT/POST Data to send with the :class:`Request`. + :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. + :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. @@ -57,7 +57,7 @@ def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None): """Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`. + :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. @@ -71,7 +71,7 @@ def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None): """Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`. + :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. @@ -85,7 +85,7 @@ def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeou """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary or bytes of POST data to send with the :class:`Request`. + :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param cookies: (optional) CookieJar object to send with the :class:`Request`. @@ -102,8 +102,8 @@ def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param data: (optional) Bytes of PUT Data to send with the :class:`Request`. - :param headers: (optional) Dictionary or bytes of HTTP Headers to sent with the :class:`Request`. + :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. @@ -119,7 +119,7 @@ def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary of DELETE Parameters to send with the :class:`Request`. + :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. From 80d860d2d409b6d2a6b4c133ef0eef4de6b32ef2 Mon Sep 17 00:00:00 2001 From: Richard Boulton Date: Thu, 16 Jun 2011 15:48:43 +0100 Subject: [PATCH 28/47] Allow POST and PUT requests to take both querystring params and request body data. --- requests/api.py | 24 +++++++++-------- requests/models.py | 41 +++++++++++++++++------------ test_requests.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 27 deletions(-) diff --git a/requests/api.py b/requests/api.py index c3c211c1..cf2e575e 100644 --- a/requests/api.py +++ b/requests/api.py @@ -34,13 +34,11 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. """ - if params and data: - raise StandardError('You may provide either params or data to a request, but not both.') - r = Request( method = method, url = url, - data = params or data, + data = data, + params = params, headers = headers, cookiejar = cookies, files = files, @@ -81,7 +79,8 @@ def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None): return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout) -def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): +def post(url, data='', headers=None, files=None, cookies=None, auth=None, + timeout=None, allow_redirects=False, params=None): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -92,13 +91,16 @@ def post(url, data='', headers=None, files=None, cookies=None, auth=None, timeou :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. + :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. """ - return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, - timeout=timeout, allow_redirects=allow_redirects) + return request('POST', url, params=params, data=data, headers=headers, + files=files, cookies=cookies, auth=auth, timeout=timeout, + allow_redirects=allow_redirects) -def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout=None, allow_redirects=False): +def put(url, data='', headers=None, files=None, cookies=None, auth=None, + timeout=None, allow_redirects=False, params=None): """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -109,10 +111,12 @@ def put(url, data='', headers=None, files=None, cookies=None, auth=None, timeout :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. + :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. """ - return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, - timeout=timeout, allow_redirects=allow_redirects) + return request('PUT', url, params=params, data=data, headers=headers, + files=files, cookies=cookies, auth=auth, timeout=timeout, + allow_redirects=allow_redirects) def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None, allow_redirects=False): diff --git a/requests/models.py b/requests/models.py index 23555b67..6f19718c 100644 --- a/requests/models.py +++ b/requests/models.py @@ -31,8 +31,8 @@ class Request(object): _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE') def __init__(self, url=None, headers=dict(), files=None, method=None, - data=dict(), auth=None, cookiejar=None, timeout=None, - redirect=False, allow_redirects=False): + data=dict(), params=dict(), auth=None, cookiejar=None, + timeout=None, redirect=False, allow_redirects=False): socket.setdefaulttimeout(timeout) @@ -44,8 +44,12 @@ class Request(object): self.files = files #: HTTP Method to use. Available: GET, HEAD, PUT, POST, DELETE. self.method = method - #: Form or Byte data to attach to the :class:`Request `. - self.data = dict() + #: Dictionary or byte of request body data to attach to the + #: :class:`Request `. + self.data = None + #: Dictionary or byte of querystring data to attach to the + #: :class:`Request `. + self.params = None #: True if :class:`Request ` is part of a redirect chain (disables history #: and HTTPError storage). self.redirect = redirect @@ -53,6 +57,8 @@ class Request(object): self.allow_redirects = allow_redirects self.data, self._enc_data = self._encode_params(data) + self.params, self._enc_params = self._encode_params(params) + #: :class:`Response ` instance, containing #: content and metadata of HTTP Response, once :attr:`sent `. self.response = Response() @@ -185,7 +191,8 @@ class Request(object): request = Request( url, self.headers, self.files, method, - self.data, self.auth, self.cookiejar, redirect=False + self.data, self.params, self.auth, self.cookiejar, + redirect=False ) request.send() r = request.response @@ -217,17 +224,16 @@ class Request(object): return data, data - @staticmethod - def _build_url(url, data=None): - """Build URLs.""" + def _build_url(self): + """Build the actual URL to use""" - if urlparse(url).query: - return '%s&%s' % (url, data) - else: - if data: - return '%s?%s' % (url, data) + if self._enc_params: + if urlparse(self.url).query: + return '%s&%s' % (self.url, self._enc_params) else: - return url + return '%s?%s' % (self.url, self._enc_params) + else: + return self.url def send(self, anyway=False): @@ -243,8 +249,9 @@ class Request(object): self._checks() success = False + url = self._build_url() if self.method in ('GET', 'HEAD', 'DELETE'): - req = _Request(self._build_url(self.url, self._enc_data), method=self.method) + req = _Request(url, method=self.method) else: if self.files: @@ -254,10 +261,10 @@ class Request(object): self.files.update(self.data) datagen, headers = multipart_encode(self.files) - req = _Request(self.url, data=datagen, headers=headers, method=self.method) + req = _Request(url, data=datagen, headers=headers, method=self.method) else: - req = _Request(self.url, data=self._enc_data, method=self.method) + req = _Request(url, data=self._enc_data, method=self.method) if self.headers: req.headers.update(self.headers) diff --git a/test_requests.py b/test_requests.py index 6aaf0b9a..b1fe22cd 100755 --- a/test_requests.py +++ b/test_requests.py @@ -5,6 +5,10 @@ from __future__ import with_statement import unittest import cookielib +try: + import simplejson as json +except ImportError: + import json import requests @@ -229,8 +233,68 @@ class RequestsTestSuite(unittest.TestCase): requests.get(httpbin('')) + def test_urlencoded_post_data(self): + r = requests.post(httpbin('post'), data=dict(test='fooaowpeuf')) + self.assertEquals(r.status_code, 200) + self.assertEquals(r.headers['content-type'], 'application/json') + self.assertEquals(r.url, httpbin('post')) + rbody = json.loads(r.content) + self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf')) + self.assertEquals(rbody.get('data'), '') + + def test_nonurlencoded_post_data(self): r = requests.post(httpbin('post'), data='fooaowpeuf') + self.assertEquals(r.status_code, 200) + self.assertEquals(r.headers['content-type'], 'application/json') + self.assertEquals(r.url, httpbin('post')) + rbody = json.loads(r.content) + # Body wasn't valid url encoded data, so the server returns None as + # "form" and the raw body as "data". + self.assertEquals(rbody.get('form'), None) + self.assertEquals(rbody.get('data'), 'fooaowpeuf') + + + def test_urlencoded_post_querystring(self): + r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf')) + self.assertEquals(r.status_code, 200) + self.assertEquals(r.headers['content-type'], 'application/json') + self.assertEquals(r.url, httpbin('post?test=fooaowpeuf')) + rbody = json.loads(r.content) + self.assertEquals(rbody.get('form'), {}) # No form supplied + self.assertEquals(rbody.get('data'), '') + + + def test_nonurlencoded_post_querystring(self): + r = requests.post(httpbin('post'), params='fooaowpeuf') + self.assertEquals(r.status_code, 200) + self.assertEquals(r.headers['content-type'], 'application/json') + self.assertEquals(r.url, httpbin('post?fooaowpeuf')) + rbody = json.loads(r.content) + self.assertEquals(rbody.get('form'), {}) # No form supplied + self.assertEquals(rbody.get('data'), '') + + + def test_urlencoded_post_query_and_data(self): + r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'), + data=dict(test2="foobar")) + self.assertEquals(r.status_code, 200) + self.assertEquals(r.headers['content-type'], 'application/json') + self.assertEquals(r.url, httpbin('post?test=fooaowpeuf')) + rbody = json.loads(r.content) + self.assertEquals(rbody.get('form'), dict(test2='foobar')) + self.assertEquals(rbody.get('data'), '') + + + def test_nonurlencoded_post_query_and_data(self): + r = requests.post(httpbin('post'), params='fooaowpeuf', + data="foobar") + self.assertEquals(r.status_code, 200) + self.assertEquals(r.headers['content-type'], 'application/json') + self.assertEquals(r.url, httpbin('post?fooaowpeuf')) + rbody = json.loads(r.content) + self.assertEquals(rbody.get('form'), None) + self.assertEquals(rbody.get('data'), 'foobar') From a25f5ff1dd599fc6701d3c58073d9597fce9d948 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 17 Jun 2011 13:51:29 -0400 Subject: [PATCH 29/47] that works --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index c9751891..1576f9f1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,4 +23,4 @@ Patches and Suggestions - Daniel Schauenberg - Zbigniew Siciarz - Daniele Tricoli 'Eriol' -- Richard Boulton \ No newline at end of file +- Richard Boulton From bbd1e40bd57322fb0f41a0ab202c2b788de053c5 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 17 Jun 2011 14:09:33 -0400 Subject: [PATCH 30/47] no pypy default --- tox.ini | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index 8e191e16..87e79ac4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,7 @@ [tox] -envlist = py25,py26,py27,pypy +envlist = py25,py26,py27 [testenv] commands=py.test --junitxml=junit-{envname}.xml deps = pytest - -[testenv:pypy] -basepython=/usr/bin/pypy - From 3e7c682e66fb4b276c8343bb718d8b13037cea15 Mon Sep 17 00:00:00 2001 From: moliware Date: Sun, 19 Jun 2011 22:36:50 +0200 Subject: [PATCH 31/47] Support for proxies --- AUTHORS | 1 + requests/api.py | 34 ++++++++++++++++++++++------------ requests/models.py | 7 ++++++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/AUTHORS b/AUTHORS index 1576f9f1..f4787ec3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,3 +24,4 @@ Patches and Suggestions - Zbigniew Siciarz - Daniele Tricoli 'Eriol' - Richard Boulton +- Miguel Olivares diff --git a/requests/api.py b/requests/api.py index cf2e575e..f3445e21 100644 --- a/requests/api.py +++ b/requests/api.py @@ -19,7 +19,7 @@ from .models import Request, Response, AuthManager, AuthObject, auth_manager __all__ = ('request', 'get', 'head', 'post', 'put', 'delete') def request(method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, - timeout=None, allow_redirects=False): + timeout=None, allow_redirects=False, proxies=None): """Constructs and sends a :class:`Request `. Returns :class:`Response ` object. :param method: method for the new :class:`Request` object. @@ -32,6 +32,7 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ r = Request( @@ -44,14 +45,15 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil files = files, auth = auth or auth_manager.get_auth(url), timeout = timeout or config.settings.timeout, - allow_redirects = allow_redirects + allow_redirects = allow_redirects, + proxies = proxies ) r.send() return r.response -def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None): +def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None, proxies=None): """Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -60,12 +62,14 @@ def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None): :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout) + return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout, + proxies=proxies) -def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None): +def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None, proxies=None): """Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -74,13 +78,15 @@ def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None): :param cookies: (optional) CookieJar object to send with the :class:`Request`. :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout) + return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout, + proxies=proxies) def post(url, data='', headers=None, files=None, cookies=None, auth=None, - timeout=None, allow_redirects=False, params=None): + timeout=None, allow_redirects=False, params=None, proxies=None): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -92,15 +98,16 @@ def post(url, data='', headers=None, files=None, cookies=None, auth=None, :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ return request('POST', url, params=params, data=data, headers=headers, files=files, cookies=cookies, auth=auth, timeout=timeout, - allow_redirects=allow_redirects) + allow_redirects=allow_redirects, proxies=proxies) def put(url, data='', headers=None, files=None, cookies=None, auth=None, - timeout=None, allow_redirects=False, params=None): + timeout=None, allow_redirects=False, params=None, proxies=None): """Sends a PUT request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -112,14 +119,16 @@ def put(url, data='', headers=None, files=None, cookies=None, auth=None, :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ return request('PUT', url, params=params, data=data, headers=headers, files=files, cookies=cookies, auth=auth, timeout=timeout, - allow_redirects=allow_redirects) + allow_redirects=allow_redirects, proxies=proxies) -def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None, allow_redirects=False): +def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None, allow_redirects=False, + proxies=None): """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -129,7 +138,8 @@ def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, - timeout=timeout, allow_redirects=allow_redirects) + timeout=timeout, allow_redirects=allow_redirects, proxies=proxies) diff --git a/requests/models.py b/requests/models.py index 6f19718c..e34ef3c4 100644 --- a/requests/models.py +++ b/requests/models.py @@ -32,7 +32,8 @@ class Request(object): def __init__(self, url=None, headers=dict(), files=None, method=None, data=dict(), params=dict(), auth=None, cookiejar=None, - timeout=None, redirect=False, allow_redirects=False): + timeout=None, redirect=False, allow_redirects=False, + proxies=None): socket.setdefaulttimeout(timeout) @@ -55,6 +56,8 @@ class Request(object): self.redirect = redirect #: Set to True if full redirects are allowed (e.g. re-POST-ing of data at new ``Location``) self.allow_redirects = allow_redirects + # Dictionary mapping protocol to the URL of the proxy (e.g. {'http': 'foo.bar:3128'}) + self.proxies = proxies self.data, self._enc_data = self._encode_params(data) self.params, self._enc_params = self._encode_params(params) @@ -110,6 +113,8 @@ class Request(object): _handlers.append(self.auth.handler) + if self.proxies: + _handlers.append(urllib2.ProxyHandler(self.proxies)) _handlers.append(HTTPRedirectHandler) From bd49da067fe865d78bb78047aee41564fa17be08 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 00:07:41 -0400 Subject: [PATCH 32/47] updated history --- HISTORY.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index c119319b..3a23a4eb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,13 @@ History ------- +0.4.2 (?) ++++++++++ + +* Support for Proxies +* HTTPBin Test Suite + + 0.4.1 (2011-05-22) ++++++++++++++++++ From bcd30f05abfa3295a4189427c4271b6d93b98035 Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Tue, 21 Jun 2011 22:48:31 +0200 Subject: [PATCH 33/47] Added setuptools management to setup.py to allow to install in develop mode --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f0cebcd5..1b15bc15 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,10 @@ import os import sys import requests -from distutils.core import setup +try: + from setuptools import setup +except ImportError: + from distutils.core import setup From 428678b2e4601b6c8b484a9c15e389474b7067d5 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 17:35:29 -0400 Subject: [PATCH 34/47] Added Alberto Paro to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index f4787ec3..224dda26 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,3 +25,4 @@ Patches and Suggestions - Daniele Tricoli 'Eriol' - Richard Boulton - Miguel Olivares +- Alberto Paro \ No newline at end of file From bdf9f90c8a3015ff7b7d03259ce57ec0ee98eb12 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 17:59:24 -0400 Subject: [PATCH 35/47] omnijson for tests --- test_requests.py | 6 ++---- tox.ini | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test_requests.py b/test_requests.py index b1fe22cd..8a6b0ae0 100755 --- a/test_requests.py +++ b/test_requests.py @@ -5,10 +5,8 @@ from __future__ import with_statement import unittest import cookielib -try: - import simplejson as json -except ImportError: - import json + +import omnijson as json import requests diff --git a/tox.ini b/tox.ini index 87e79ac4..3c2ef1f6 100644 --- a/tox.ini +++ b/tox.ini @@ -5,3 +5,4 @@ envlist = py25,py26,py27 commands=py.test --junitxml=junit-{envname}.xml deps = pytest + omnijson From 00f066a467901bdbb107396c015c17275fb59cc6 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 18:03:06 -0400 Subject: [PATCH 36/47] PATCH support --- requests/api.py | 83 ++++++++++++++++++++++++++++++++++------------ requests/models.py | 10 +++--- test_requests.py | 16 ++++++++- 3 files changed, 81 insertions(+), 28 deletions(-) diff --git a/requests/api.py b/requests/api.py index f3445e21..8e328d2b 100644 --- a/requests/api.py +++ b/requests/api.py @@ -11,15 +11,16 @@ This module impliments the Requests API. """ -import requests import config from .models import Request, Response, AuthManager, AuthObject, auth_manager -__all__ = ('request', 'get', 'head', 'post', 'put', 'delete') +__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete') + +def request(method, url, + params=None, data=None, headers=None, cookies=None, files=None, auth=None, + timeout=None, allow_redirects=False, proxies=None): -def request(method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, - timeout=None, allow_redirects=False, proxies=None): """Constructs and sends a :class:`Request `. Returns :class:`Response ` object. :param method: method for the new :class:`Request` object. @@ -53,7 +54,11 @@ def request(method, url, params=None, data=None, headers=None, cookies=None, fil return r.response -def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None, proxies=None): + +def get(url, + params=None, headers=None, cookies=None, auth=None, timeout=None, + proxies=None): + """Sends a GET request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -65,11 +70,15 @@ def get(url, params=None, headers=None, cookies=None, auth=None, timeout=None, p :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout, - proxies=proxies) + return request('GET', url, + params=params, headers=headers, cookies=cookies, auth=auth, + timeout=timeout, proxies=proxies) -def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None, proxies=None): +def head(url, + params=None, headers=None, cookies=None, auth=None, timeout=None, + proxies=None): + """Sends a HEAD request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -81,12 +90,15 @@ def head(url, params=None, headers=None, cookies=None, auth=None, timeout=None, :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, timeout=timeout, - proxies=proxies) + return request('HEAD', url, + params=params, headers=headers, cookies=cookies, auth=auth, + timeout=timeout, proxies=proxies) -def post(url, data='', headers=None, files=None, cookies=None, auth=None, - timeout=None, allow_redirects=False, params=None, proxies=None): +def post(url, + data='', headers=None, files=None, cookies=None, auth=None, timeout=None, + allow_redirects=False, params=None, proxies=None): + """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -101,9 +113,10 @@ def post(url, data='', headers=None, files=None, cookies=None, auth=None, :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('POST', url, params=params, data=data, headers=headers, - files=files, cookies=cookies, auth=auth, timeout=timeout, - allow_redirects=allow_redirects, proxies=proxies) + return request('POST', url, + params=params, data=data, headers=headers, files=files, + cookies=cookies, auth=auth, timeout=timeout, + allow_redirects=allow_redirects, proxies=proxies) def put(url, data='', headers=None, files=None, cookies=None, auth=None, @@ -122,13 +135,38 @@ def put(url, data='', headers=None, files=None, cookies=None, auth=None, :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('PUT', url, params=params, data=data, headers=headers, - files=files, cookies=cookies, auth=auth, timeout=timeout, - allow_redirects=allow_redirects, proxies=proxies) + return request('PUT', url, + params=params, data=data, headers=headers, files=files, + cookies=cookies, auth=auth, timeout=timeout, + allow_redirects=allow_redirects, proxies=proxies) -def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None, allow_redirects=False, - proxies=None): +def patch(url, data='', headers=None, files=None, cookies=None, auth=None, + timeout=None, allow_redirects=False, params=None, proxies=None): + """Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`. + :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. + :param cookies: (optional) CookieJar object to send with the :class:`Request`. + :param auth: (optional) AuthObject to enable Basic HTTP Auth. + :param timeout: (optional) Float describing the timeout of the request. + :param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed. + :param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`. + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + """ + + return request('PATCH', url, + params=params, data=data, headers=headers, files=files, + cookies=cookies, auth=auth, timeout=timeout, + allow_redirects=allow_redirects, proxies=proxies) + + +def delete(url, + params=None, headers=None, cookies=None, auth=None, timeout=None, + allow_redirects=False, proxies=None): + """Sends a DELETE request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. @@ -141,5 +179,6 @@ def delete(url, params=None, headers=None, cookies=None, auth=None, timeout=None :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. """ - return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, - timeout=timeout, allow_redirects=allow_redirects, proxies=proxies) + return request('DELETE', url, + params=params, headers=headers, cookies=cookies, auth=auth, + timeout=timeout, allow_redirects=allow_redirects, proxies=proxies) diff --git a/requests/models.py b/requests/models.py index e34ef3c4..5765af4b 100644 --- a/requests/models.py +++ b/requests/models.py @@ -28,12 +28,12 @@ class Request(object): Requests. Recommended interface is with the Requests functions. """ - _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE') + _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH') - def __init__(self, url=None, headers=dict(), files=None, method=None, - data=dict(), params=dict(), auth=None, cookiejar=None, - timeout=None, redirect=False, allow_redirects=False, - proxies=None): + def __init__(self, + url=None, headers=dict(), files=None, method=None, data=dict(), + params=dict(), auth=None, cookiejar=None, timeout=None, redirect=False, + allow_redirects=False, proxies=None): socket.setdefaulttimeout(timeout) diff --git a/test_requests.py b/test_requests.py index 8a6b0ae0..153c6d91 100755 --- a/test_requests.py +++ b/test_requests.py @@ -128,7 +128,21 @@ class RequestsTestSuite(unittest.TestCase): self.assertEqual(post2.status_code, 200) post3 = requests.post(url, data='[{"some": "json"}]') - self.assertEqual(post.status_code, 200) + self.assertEqual(post3.status_code, 200) + + + def test_POSTBIN_GET_PATCH_FILES(self): + url = httpbin('patch') + patch = requests.patch(url).raise_for_status() + + patch = requests.post(url, data={'some': 'data'}) + self.assertEqual(patch.status_code, 200) + + patch2 = requests.post(url, files={'some': open('test_requests.py')}) + self.assertEqual(patch2.status_code, 200) + + patch3 = requests.post(url, data='[{"some": "json"}]') + self.assertEqual(patch3.status_code, 200) def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self): From 8c81cf64deb2a310fbd74c15fd1f70a6e1a6241d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 18:24:28 -0400 Subject: [PATCH 37/47] proper PATCH testing --- test_requests.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/test_requests.py b/test_requests.py index 153c6d91..aa2577dd 100755 --- a/test_requests.py +++ b/test_requests.py @@ -15,6 +15,8 @@ import requests HTTPBIN_URL = 'http://httpbin.org/' HTTPSBIN_URL = 'https://httpbin.ep.io/' +# HTTPBIN_URL = 'http://staging.httpbin.org/' +# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/' def httpbin(*suffix): @@ -103,6 +105,26 @@ class RequestsTestSuite(unittest.TestCase): self.assertEqual(r.status_code, 200) + def test_HTTP_200_OK_PUT(self): + r = requests.put(httpbin('put')) + self.assertEqual(r.status_code, 200) + + + def test_HTTPS_200_OK_PUT(self): + r = requests.put(httpsbin('put')) + self.assertEqual(r.status_code, 200) + + + def test_HTTP_200_OK_PATCH(self): + r = requests.patch(httpbin('patch')) + self.assertEqual(r.status_code, 200) + + + def test_HTTPS_200_OK_PATCH(self): + r = requests.patch(httpsbin('patch')) + self.assertEqual(r.status_code, 200) + + def test_AUTH_HTTPS_200_OK_GET(self): auth = ('user', 'pass') url = httpsbin('basic-auth', 'user', 'pass') @@ -131,20 +153,6 @@ class RequestsTestSuite(unittest.TestCase): self.assertEqual(post3.status_code, 200) - def test_POSTBIN_GET_PATCH_FILES(self): - url = httpbin('patch') - patch = requests.patch(url).raise_for_status() - - patch = requests.post(url, data={'some': 'data'}) - self.assertEqual(patch.status_code, 200) - - patch2 = requests.post(url, files={'some': open('test_requests.py')}) - self.assertEqual(patch2.status_code, 200) - - patch3 = requests.post(url, data='[{"some": "json"}]') - self.assertEqual(patch3.status_code, 200) - - def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self): url = httpbin('post') From b4a4f38ccecba498085d3d0bb38463aac3b79419 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 21:59:57 -0400 Subject: [PATCH 38/47] verbose output stream support --- requests/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/config.py b/requests/config.py index 63d3fa99..0878da92 100644 --- a/requests/config.py +++ b/requests/config.py @@ -12,7 +12,7 @@ class Settings(object): _singleton = {} # attributes with defaults - __attrs__ = ('timeout',) + __attrs__ = ('timeout', 'verbose') def __init__(self, **kwargs): super(Settings, self).__init__() From b4eac4c993219626ed96fcc0d5191c2cac6c8c0d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:00:04 -0400 Subject: [PATCH 39/47] redirect fix --- requests/models.py | 20 +++++++++++++++++--- requests/monkeys.py | 1 - 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index 5765af4b..41108cf3 100644 --- a/requests/models.py +++ b/requests/models.py @@ -6,7 +6,6 @@ requests.models """ -import requests import urllib import urllib2 import socket @@ -14,7 +13,9 @@ import zlib from urllib2 import HTTPError from urlparse import urlparse +from datetime import datetime +from .config import settings from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler from .structures import CaseInsensitiveDict from .packages.poster.encode import multipart_encode @@ -22,6 +23,8 @@ from .packages.poster.streaminghttp import register_openers, get_handlers from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod +REDIRECT_STATI = (301, 302, 303, 307) + class Request(object): """The :class:`Request ` object. It carries out all functionality of @@ -169,6 +172,9 @@ class Request(object): r = build(resp) + if r.status_code in REDIRECT_STATI: + self.redirect = True + if self.redirect: while ( @@ -182,7 +188,7 @@ class Request(object): url = r.headers['location'] - # Facilitate for non-RFC2616-compliant 'location' headers + # Facilitate non-RFC2616-compliant 'location' headers # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') if not urlparse(url).netloc: parent_url_components = urlparse(self.url) @@ -197,7 +203,7 @@ class Request(object): request = Request( url, self.headers, self.files, method, self.data, self.params, self.auth, self.cookiejar, - redirect=False + redirect=True ) request.send() r = request.response @@ -254,6 +260,13 @@ class Request(object): self._checks() success = False + # Logging + if settings.verbose: + settings.verbose.write('%s %s %s\n' % ( + datetime.now().isoformat(), self.method, self.url + )) + + url = self._build_url() if self.method in ('GET', 'HEAD', 'DELETE'): req = _Request(url, method=self.method) @@ -302,6 +315,7 @@ class Request(object): self.sent = self.response.ok + return self.sent diff --git a/requests/monkeys.py b/requests/monkeys.py index b8fe5041..41cd3706 100644 --- a/requests/monkeys.py +++ b/requests/monkeys.py @@ -26,7 +26,6 @@ class Request(urllib2.Request): return urllib2.Request.get_method(self) - class HTTPRedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): From 2da86daa3a5869185d80ca90dc0acdf08d3690c1 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:03:56 -0400 Subject: [PATCH 40/47] update history --- HISTORY.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 3a23a4eb..8b134c56 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,8 +4,12 @@ History 0.4.2 (?) +++++++++ +* PATCH Support * Support for Proxies * HTTPBin Test Suite +* Redirect Fixes +* settings.verbose stream writing +* Querystrings for all methods 0.4.1 (2011-05-22) From 34a583b8217a1eaf65131958cec0ad659e85fb52 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:06:31 -0400 Subject: [PATCH 41/47] PATCH in Docs --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 6890a962..663bda58 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,8 +27,8 @@ Things shouldn’t be this way. Not in Python. See `the same code, without Requests `_. -Requests allow you to send **GET**, **HEAD**, **PUT**, -**POST**, and **DELETE** HTTP requests. You can add headers, form data, +Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**, +**PATCH**, and **DELETE** HTTP requests. You can add headers, form data, multipart files, and parameters with simple Python dictionaries, and access the response data in the same way. It's powered by :py:class:`urllib2`, but it does all the hard work and crazy hacks for you. From 15de7d9dbabd6acc97267c1c6addb59a08c6367b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:21:54 -0400 Subject: [PATCH 42/47] add PATCH to docs --- docs/api.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 9d9bc0a8..06939f19 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -16,11 +16,13 @@ Main Interface All of Request's functionality can be accessed by these 5 methods. They all return a :class:`Response ` object. +.. autofunction:: head .. autofunction:: get .. autofunction:: post .. autofunction:: put +.. autofunction:: patch .. autofunction:: delete -.. autofunction:: head + ----------- From 483d00736c2e61a0e9285eb49bda32946ae990e8 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:22:02 -0400 Subject: [PATCH 43/47] readme update --- README.rst | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index d9cb223e..69b83796 100644 --- a/README.rst +++ b/README.rst @@ -10,12 +10,13 @@ Really simple. Features -------- -- Extremely simple GET, HEAD, POST, PUT, DELETE Requests +- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests + Simple HTTP Header Request Attachment + Simple Data/Params Request Attachment + Simple Multipart File Uploads + CookieJar Support + Redirection History + + Proxy Support + Redirection Recursion Urllib Fix + Auto Decompression of GZipped Content + Unicode URL Support @@ -35,15 +36,14 @@ It couldn't be simpler. :: HTTPS? Basic Authentication? :: - >>> r = requests.get('https://convore.com/api/account/verify.json') + >>> r = requests.get('https://httpbin.ep.ip/basic-auth/user/pass') >>> r.status_code 401 Uh oh, we're not authorized! Let's add authentication. :: - >>> conv_auth = ('requeststest', 'requeststest') - >>> r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth) + >>> r = requests.get(https://httpbin.ep.ip/basic-auth/user/pass', auth=('user', 'pass')) >>> r.status_code 200 @@ -52,7 +52,7 @@ Uh oh, we're not authorized! Let's add authentication. :: 'application/json' >>> r.content - '{"username": "requeststest", "url": "/users/requeststest/", "id": "9408", "img": "censored-long-url"}' + '{"authenticated": true, "user": "user"}' @@ -66,19 +66,23 @@ All request functions return a Response object (see below). If a {filename: fileobject} dictionary is passed in (files=...), a multipart_encode upload will be performed. If CookieJar object is is passed in (cookies=...), the cookies will be sent with the request. - GET Requests - >>> requests.get(url, params={}, headers={}, cookies=None, auth=None) - - HEAD Requests - >>> requests.head(url, params={}, headers={}, cookies=None, auth=None) + >>> requests.head(url, params={}, headers={}, cookies=None, auth=None, timeout=None, proxies={}) - PUT Requests - >>> requests.put(url, data='', headers={}, files={}, cookies=None, auth=None) + GET Requests + >>> requests.get(url, params={}, headers={}, cookies=None, auth=None, timeout=None, proxies={}) POST Requests + >>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None, timeout=None, allow_redirects=False, params{}, proxies={}) + + + PUT Requests + >>> requests.put(url, data={}, headers={}, files={}, cookies=None, auth=None, timeout=None, allow_redirects=False, params{}, proxies={}) + + + PATCH Requests >>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None) From 2374d0fd7a6ac34bd6e5cd7d39c73e856162740b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:22:46 -0400 Subject: [PATCH 44/47] so many paramatersssss --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 69b83796..d73a48f3 100644 --- a/README.rst +++ b/README.rst @@ -83,11 +83,11 @@ If CookieJar object is is passed in (cookies=...), the cookies will be sent with PATCH Requests - >>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None) + >>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None, timeout=None, allow_redirects=False, params{}, proxies={}) DELETE Requests - >>> requests.delete(url, params={}, headers={}, cookies=None, auth=None) + >>> requests.delete(url, params={}, headers={}, cookies=None, auth=None, timeout=None, allow_redirects=False, params{}, proxies={}) From 20aed5488b117d82abc4a22cdb6585d06a02a70f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:26:06 -0400 Subject: [PATCH 45/47] Release the v0.5.0! --- HISTORY.rst | 4 ++-- requests/core.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 8b134c56..65eec3b8 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,8 +1,8 @@ History ------- -0.4.2 (?) -+++++++++ +0.5.0 (2011-06-21) +++++++++++++++++++ * PATCH Support * Support for Proxies diff --git a/requests/core.py b/requests/core.py index 7f3d723a..87f55e45 100644 --- a/requests/core.py +++ b/requests/core.py @@ -12,8 +12,8 @@ This module implements the main Requests system. """ __title__ = 'requests' -__version__ = '0.4.1' -__build__ = 0x000401 +__version__ = '0.5.0' +__build__ = 0x000500 __author__ = 'Kenneth Reitz' __license__ = 'ISC' __copyright__ = 'Copyright 2011 Kenneth Reitz' From 162b751f6db97c1c3ae4fea4af4d75f89b2ec698 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:40:27 -0400 Subject: [PATCH 46/47] Timeouts are normal errors now. -- BREAK ALL TEH TESTS! --- requests/models.py | 10 +++++----- test_requests.py | 7 ++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/requests/models.py b/requests/models.py index 41108cf3..099f1c66 100644 --- a/requests/models.py +++ b/requests/models.py @@ -296,15 +296,15 @@ class Request(object): if self.cookiejar is not None: self.cookiejar.extract_cookies(resp, req) - except urllib2.HTTPError, why: + except (urllib2.HTTPError, urllib2.URLError), why: + if hasattr(why, 'reason'): + if isinstance(why.reason, socket.timeout): + why = Timeout(why) + self._build_response(why) if not self.redirect: self.response.error = why - # TODO: Support urllib connection refused errors - - except urllib2.URLError, error: - raise Timeout if isinstance(error.reason, socket.timeout) else error else: self._build_response(resp) self.response.ok = True diff --git a/test_requests.py b/test_requests.py index aa2577dd..848297e4 100755 --- a/test_requests.py +++ b/test_requests.py @@ -246,8 +246,13 @@ class RequestsTestSuite(unittest.TestCase): def test_settings(self): + + def test(): + r = requests.get(httpbin('')) + r.raise_for_status() + with requests.settings(timeout=0.0000001): - self.assertRaises(requests.Timeout, requests.get, httpbin('')) + self.assertRaises(requests.Timeout, test) with requests.settings(timeout=100): requests.get(httpbin('')) From e2d6a92150de44722a305959b2326376343aedce Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Jun 2011 22:43:15 -0400 Subject: [PATCH 47/47] Timeouts are normal errors now. -- FIX ALL TEH TESTS! --- HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 65eec3b8..dd7f97e2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,8 @@ History * Redirect Fixes * settings.verbose stream writing * Querystrings for all methods +* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised + ``r.requests.get('hwe://blah'); r.raise_for_status()`` 0.4.1 (2011-05-22)