From 5a616ee980ae2cc9d42f8d0221420ad9eed4b2e0 Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Tue, 6 Jun 2017 22:44:22 +0100 Subject: [PATCH 1/5] Add the pytest .cache/ directory to .gitignore For example, when a pytest run fails, it saves failure metadata to: `.cache/v/cache/lastfailed` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b2abf8c2..19ebfd79 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ nosetests.xml junit-report.xml pylint.txt toy.py +.cache/ cover/ build/ docs/_build From 4847f5b8cd4071099f06e178856bc9e1b9193b2e Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Tue, 6 Jun 2017 23:26:07 +0100 Subject: [PATCH 2/5] Allow Requests.Response to be used as a context manager This saves having to wrap the call to requests with `contextlib.closing()`, allowing it to be used directly in a `with` statement, like so: ``` with requests.get('http://httpbin.org/get', stream=True) as r: # Do things with the response here. ``` Fixes #4136. --- AUTHORS.rst | 1 + HISTORY.rst | 5 +++++ docs/user/advanced.rst | 8 ++------ requests/models.py | 6 ++++++ tests/test_requests.py | 6 ++++++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 7e0bddf0..cda8e223 100755 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -183,3 +183,4 @@ Patches and Suggestions - Shmuel Amar (`@shmuelamar `_) - Gary Wu (`@garywu `_) - Ryan Pineo (`@ryanpineo `_) +- Ed Morley (`@edmorley `_) diff --git a/HISTORY.rst b/HISTORY.rst index edd07ae1..865169a5 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,6 +6,11 @@ Release History dev +++ +**Improvements** + +- ``Response`` is now a context manager, so can be used directly in a `with` statement + without first having to be wrapped by ``contextlib.closing()``. + **Bugfixes** - Resolve installation failure if multiprocessing is not available diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 2aac434c..a1b68707 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -301,15 +301,11 @@ release the connection back to the pool unless you consume all the data or call :meth:`Response.close `. This can lead to inefficiency with connections. If you find yourself partially reading request bodies (or not reading them at all) while using ``stream=True``, you should -consider using ``contextlib.closing`` (`documented here`_), like this:: +make the request within a ``with`` statement to ensure it's always closed:: - from contextlib import closing - - with closing(requests.get('http://httpbin.org/get', stream=True)) as r: + with requests.get('http://httpbin.org/get', stream=True) as r: # Do things with the response here. -.. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing - .. _keep-alive: Keep-Alive diff --git a/requests/models.py b/requests/models.py index 1375a3af..2148024f 100644 --- a/requests/models.py +++ b/requests/models.py @@ -634,6 +634,12 @@ class Response(object): #: is a response. self.request = None + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + def __getstate__(self): # Consume everything; accessing the content attribute makes # sure the content has been fully read. diff --git a/tests/test_requests.py b/tests/test_requests.py index b8350cb7..365ffcb2 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1668,6 +1668,12 @@ class TestRequests: next(it) assert len(list(it)) == 3 + def test_response_context_manager(self, httpbin): + with requests.get(httpbin('stream/4'), stream=True) as response: + assert isinstance(response, requests.Response) + + assert response.raw.closed + def test_unconsumed_session_response_closes_connection(self, httpbin): s = requests.session() From f8ccc604987cdad57426b2852951402734b994bb Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 7 Jun 2017 13:50:00 +0100 Subject: [PATCH 3/5] Clearer makefile for building README --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f90248f7..92d1c344 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ci: py.test -n 8 --boxed --junitxml=report.xml test-readme: - python setup.py check -r -s + @python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!" flake8: flake8 --ignore=E501,F401,E128,E402,E731,F821 requests From d8e236780981f28228441029c689a5e8dccdd71b Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Thu, 8 Jun 2017 12:07:45 -0600 Subject: [PATCH 4/5] convert version compatibility checks to warning --- requests/__init__.py | 52 ++++++++++++++++++++++++------------------ requests/exceptions.py | 5 ++++ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index d4461ec9..90a0d11a 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -40,34 +40,44 @@ is at . :license: Apache 2.0, see LICENSE for more details. """ -# Check urllib3 for compatibility. import urllib3 -urllib3_version = urllib3.__version__.split('.') -# Sometimes, urllib3 only reports its version as 16.1. -if len(urllib3_version) == 2: - urllib3_version.append('0') -major, minor, patch = urllib3_version -major, minor, patch = int(major), int(minor), int(patch) -# urllib3 >= 1.21.1, < 1.22 -try: +import chardet +import warnings +from .exceptions import RequestsDependencyWarning + + +def check_compatibility(urllib3_version, chardet_version): + urllib3_version = urllib3_version.split('.') + assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append('0') + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, < 1.22 assert major == 1 assert minor >= 21 assert minor <= 22 -except AssertionError: - raise RuntimeError('Requests dependency \'urllib3\' must be version >= 1.21.1, < 1.22!') - -# Check chardet for compatibility. -import chardet -major, minor, patch = chardet.__version__.split('.')[:3] -major, minor, patch = int(major), int(minor), int(patch) -# chardet >= 3.0.2, < 3.1.0 -try: + # Check chardet for compatibility. + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet >= 3.0.2, < 3.1.0 assert major == 3 assert minor < 1 assert patch >= 2 -except AssertionError: - raise RuntimeError('Requests dependency \'chardet\' must be version >= 3.0.2, < 3.1.0!') + + +# Check imported dependencies for compatibility. +try: + check_compatibility(urllib3.__version__, chardet.__version__) +except (AssertionError, ValueError): + warnings.warn("urllib3 ({0}) or chardet ({1}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet.__version__), + RequestsDependencyWarning) # Attempt to enable urllib3's SNI support, if possible try: @@ -76,8 +86,6 @@ try: except ImportError: pass -import warnings - # urllib3's DependencyWarnings should be silenced. from urllib3.exceptions import DependencyWarning warnings.simplefilter('ignore', DependencyWarning) diff --git a/requests/exceptions.py b/requests/exceptions.py index da5af10b..be7eaed6 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -115,3 +115,8 @@ class RequestsWarning(Warning): class FileModeWarning(RequestsWarning, DeprecationWarning): """A file was opened in text mode, but Requests determined its binary length.""" pass + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" + pass From 22d12b0501fa633b80bcda303a718696e408ebfb Mon Sep 17 00:00:00 2001 From: JiuLi Gao Date: Fri, 9 Jun 2017 16:56:14 +0800 Subject: [PATCH 5/5] typo --- requests/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/api.py b/requests/api.py index b2ce1a96..bc2115c1 100644 --- a/requests/api.py +++ b/requests/api.py @@ -73,7 +73,7 @@ def get(url, params=None, **kwargs): def options(url, **kwargs): - r"""Sends a OPTIONS request. + r"""Sends an OPTIONS request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes.