From 188e7609b36eb2100abe3a6a5c3bbebcf1205cfb Mon Sep 17 00:00:00 2001 From: Vikram Oberoi Date: Thu, 27 Jun 2013 16:43:40 -0400 Subject: [PATCH 01/11] .netrc settings shouldn't blow away explicit auth settings on a session --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 6d1000dc..c24ed5aa 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -289,8 +289,8 @@ class Session(SessionRedirectMixin): for (k, v) in env_proxies.items(): proxies.setdefault(k, v) - # Set environment's basic authentication. - if not auth: + # Set environment's basic authentication if not explicitly set. + if not auth and not self.auth: auth = get_netrc_auth(url) # Look for configuration. From d9c49ad30d1107591f3b5150da6f95ec90c63784 Mon Sep 17 00:00:00 2001 From: Vikram Oberoi Date: Thu, 27 Jun 2013 17:16:42 -0400 Subject: [PATCH 02/11] Add test to verify .netrc authentication behavior. Here's what should happen: - If no credentials are given, use netrc if there's a netrc entry. - If credentials are given, they should override netrc. --- test_requests.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test_requests.py b/test_requests.py index 7ad10ee9..07582105 100755 --- a/test_requests.py +++ b/test_requests.py @@ -234,6 +234,34 @@ class RequestsTestCase(unittest.TestCase): r = s.get(url) self.assertEqual(r.status_code, 200) + def test_basicauth_with_netrc(self): + auth = ('user', 'pass') + wrong_auth = ('wronguser', 'wrongpass') + url = httpbin('basic-auth', 'user', 'pass') + + def get_netrc_auth_mock(url): + return auth + requests.sessions.get_netrc_auth = get_netrc_auth_mock + + # Should use netrc and work. + r = requests.get(url) + self.assertEqual(r.status_code, 200) + + # Given auth should override and fail. + r = requests.get(url, auth=wrong_auth) + self.assertEqual(r.status_code, 401) + + s = requests.session() + + # Should use netrc and work. + r = s.get(url) + self.assertEqual(r.status_code, 200) + + # Given auth should override and fail. + s.auth = wrong_auth + r = s.get(url) + self.assertEqual(r.status_code, 401) + def test_DIGEST_HTTP_200_OK_GET(self): auth = HTTPDigestAuth('user', 'pass') From cdab4fabf448f287f6f537265aa23ac06a080599 Mon Sep 17 00:00:00 2001 From: Flavio Curella Date: Mon, 1 Jul 2013 13:49:43 -0500 Subject: [PATCH 03/11] unquote double-quotes cookie values --- requests/cookies.py | 5 +++++ test_requests.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/requests/cookies.py b/requests/cookies.py index d62f0cd1..a12feb56 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -259,6 +259,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): """Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name().""" remove_cookie_by_name(self, name) + def set_cookie(self, cookie, *args, **kwargs): + if cookie.value.startswith('"') and cookie.value.endswith('"'): + cookie.value = cookie.value.strip('\\"') + return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) + def update(self, other): """Updates this jar with cookies from another CookieJar or dict-like""" if isinstance(other, cookielib.CookieJar): diff --git a/test_requests.py b/test_requests.py index 7ad10ee9..b0e99c77 100755 --- a/test_requests.py +++ b/test_requests.py @@ -170,6 +170,11 @@ class RequestsTestCase(unittest.TestCase): ) assert 'foo' not in s.cookies + def test_cookie_quote_wrapped(self): + s = requests.session() + s.get(httpbin('cookies/set?foo="bar:baz"')) + self.assertTrue(s.cookies['foo'] == 'bar:baz') + def test_request_cookie_overrides_session_cookie(self): s = requests.session() s.cookies['foo'] = 'bar' From a8cf5b85027e523c54d3d496ee5467eac90555bd Mon Sep 17 00:00:00 2001 From: Flavio Curella Date: Mon, 1 Jul 2013 15:48:48 -0500 Subject: [PATCH 04/11] keep the double quotes, but don't escape them --- requests/cookies.py | 2 +- test_requests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index a12feb56..d1e5d75b 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -261,7 +261,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): def set_cookie(self, cookie, *args, **kwargs): if cookie.value.startswith('"') and cookie.value.endswith('"'): - cookie.value = cookie.value.strip('\\"') + cookie.value = cookie.value.replace('\\"', '') return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) def update(self, other): diff --git a/test_requests.py b/test_requests.py index b0e99c77..e22b6ae3 100755 --- a/test_requests.py +++ b/test_requests.py @@ -173,7 +173,7 @@ class RequestsTestCase(unittest.TestCase): def test_cookie_quote_wrapped(self): s = requests.session() s.get(httpbin('cookies/set?foo="bar:baz"')) - self.assertTrue(s.cookies['foo'] == 'bar:baz') + self.assertTrue(s.cookies['foo'] == '"bar:baz"') def test_request_cookie_overrides_session_cookie(self): s = requests.session() From 7da16c584ae6ab9c87d4aeea857278ee886760f0 Mon Sep 17 00:00:00 2001 From: Flavio Curella Date: Mon, 1 Jul 2013 15:54:59 -0500 Subject: [PATCH 05/11] Added myself to AUTHORS --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 96a3d49f..6267ddf0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -131,3 +131,4 @@ Patches and Suggestions - Dave Shawley - James Clarke (jam) - Kevin Burke +- Flavio Curella From 555472bf1e3ab4ad6c2057c93e195427bda5a18b Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 4 Jul 2013 10:34:43 +0100 Subject: [PATCH 06/11] Remove urllib3-specific kwargs from general code --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 6600a91a..3672b377 100644 --- a/requests/models.py +++ b/requests/models.py @@ -544,7 +544,7 @@ class Response(object): except AttributeError: # Standard file-like object. while 1: - chunk = self.raw.read(chunk_size, decode_content=True) + chunk = self.raw.read(chunk_size) if not chunk: break yield chunk From 7bdf37bc272da9ef92a8cf4d58ac886f4c6b2da1 Mon Sep 17 00:00:00 2001 From: Philippe Ndiaye Date: Sat, 13 Jul 2013 09:55:50 +0200 Subject: [PATCH 07/11] Changed the "im_used" informational status code for the value given by IANA (226) See RFC 3229 at http://tools.ietf.org/html/rfc3229#section-10.4.1 and HTTP status codes at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml --- requests/status_codes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/status_codes.py b/requests/status_codes.py index de384865..8992a372 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -18,7 +18,7 @@ _codes = { 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), - 208: ('im_used',), + 226: ('im_used',), # Redirection. 300: ('multiple_choices',), From 18a736fbe67f5dfd03f95cc9e0d16cda54c9ea4d Mon Sep 17 00:00:00 2001 From: Philippe Ndiaye Date: Sat, 13 Jul 2013 11:08:01 +0200 Subject: [PATCH 08/11] Set 208 status_code to "already_reported" --- requests/status_codes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/requests/status_codes.py b/requests/status_codes.py index 8992a372..ed7a8660 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -18,6 +18,7 @@ _codes = { 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), + 208: ('already_reported',), 226: ('im_used',), # Redirection. From d7e80731981dfaf454cb2227171edf0e32148770 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 16 Jul 2013 02:20:23 -0400 Subject: [PATCH 09/11] badge.fury.io --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 3d036415..754f592e 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,8 @@ Requests: HTTP for Humans ========================= +.. image:: https://badge.fury.io/py/requests.png + :target: http://badge.fury.io/py/requests .. image:: https://travis-ci.org/kennethreitz/requests.png?branch=master :target: https://travis-ci.org/kennethreitz/requests From 9f119ee420318c113094e15bc5e6d7dbda5c98b0 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Thu, 18 Jul 2013 21:06:19 +0000 Subject: [PATCH 10/11] Update urllib3 to a43319f --- requests/packages/urllib3/_collections.py | 16 ++++++++-------- requests/packages/urllib3/contrib/pyopenssl.py | 3 +++ requests/packages/urllib3/poolmanager.py | 17 +++++++++-------- requests/packages/urllib3/util.py | 5 +++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index b35a7367..282b8d5e 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -5,7 +5,7 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php from collections import MutableMapping -from threading import Lock +from threading import RLock try: # Python 2.7+ from collections import OrderedDict @@ -40,18 +40,18 @@ class RecentlyUsedContainer(MutableMapping): self.dispose_func = dispose_func self._container = self.ContainerCls() - self._lock = Lock() + self.lock = RLock() def __getitem__(self, key): # Re-insert the item, moving it to the end of the eviction line. - with self._lock: + with self.lock: item = self._container.pop(key) self._container[key] = item return item def __setitem__(self, key, value): evicted_value = _Null - with self._lock: + with self.lock: # Possibly evict the existing value of 'key' evicted_value = self._container.get(key, _Null) self._container[key] = value @@ -65,21 +65,21 @@ class RecentlyUsedContainer(MutableMapping): self.dispose_func(evicted_value) def __delitem__(self, key): - with self._lock: + with self.lock: value = self._container.pop(key) if self.dispose_func: self.dispose_func(value) def __len__(self): - with self._lock: + with self.lock: return len(self._container) def __iter__(self): raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') def clear(self): - with self._lock: + with self.lock: # Copy pointers to all values, then wipe the mapping # under Python 2, this copies the list of values twice :-| values = list(self._container.values()) @@ -90,5 +90,5 @@ class RecentlyUsedContainer(MutableMapping): self.dispose_func(value) def keys(self): - with self._lock: + with self.lock: return self._container.keys() diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 9829e80b..6d0255f6 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -106,6 +106,9 @@ class WrappedSocket(object): self.connection = connection self.socket = socket + def fileno(self): + return self.socket.fileno() + def makefile(self, mode, bufsize=-1): return _fileobject(self.connection, mode, bufsize) diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index 2a1aa48b..804f2b2f 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -104,15 +104,16 @@ class PoolManager(RequestMethods): pool_key = (scheme, host, port) - # If the scheme, host, or port doesn't match existing open connections, - # open a new ConnectionPool. - pool = self.pools.get(pool_key) - if pool: - return pool + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open connections, + # open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool - # Make a fresh ConnectionPool of the desired type - pool = self._new_pool(scheme, host, port) - self.pools[pool_key] = pool + # Make a fresh ConnectionPool of the desired type + pool = self._new_pool(scheme, host, port) + self.pools[pool_key] = pool return pool def connection_from_url(self, url): diff --git a/requests/packages/urllib3/util.py b/requests/packages/urllib3/util.py index f4eb5e94..a9d30e01 100644 --- a/requests/packages/urllib3/util.py +++ b/requests/packages/urllib3/util.py @@ -113,7 +113,7 @@ def parse_url(url): # While this code has overlap with stdlib's urlparse, it is much # simplified for our needs and less annoying. - # Additionally, this imeplementations does silly things to be optimal + # Additionally, this implementations does silly things to be optimal # on CPython. scheme = None @@ -142,7 +142,8 @@ def parse_url(url): # IPv6 if url and url[0] == '[': - host, url = url[1:].split(']', 1) + host, url = url.split(']', 1) + host += ']' # Port if ':' in url: From 07ad75ee043493809f95e22788e5f85e604f96d4 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 19 Jul 2013 17:00:46 +0900 Subject: [PATCH 11/11] Fix #1322: Add note in docs about None not being sent as data In the case: payload = {'key1': 'value1', 'key2': 'value2', 'key3': None} r = requests.get("http://httpbin.org", params=payload) the parameter `key3` will not be sent as a parameter in the URL. Mention this in the documentation. --- docs/user/quickstart.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 59d75ccb..660cdfa2 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -71,6 +71,9 @@ You can see that the URL has been correctly encoded by printing the URL:: >>> print r.url u'http://httpbin.org/get?key2=value2&key1=value1' +Note that any dictionary key whose value is ``None`` will not be added to the +URL's query string. + Response Content ----------------