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 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 diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 0c89b40e..d81ce0f0 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 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 ---------------- diff --git a/requests/cookies.py b/requests/cookies.py index d62f0cd1..d1e5d75b 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.replace('\\"', '') + 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/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 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: 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. diff --git a/requests/status_codes.py b/requests/status_codes.py index de384865..ed7a8660 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -18,7 +18,8 @@ _codes = { 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), - 208: ('im_used',), + 208: ('already_reported',), + 226: ('im_used',), # Redirection. 300: ('multiple_choices',), diff --git a/test_requests.py b/test_requests.py index 7ad10ee9..f2b633b5 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' @@ -234,6 +239,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')