diff --git a/.travis.yml b/.travis.yml index 7f1c4968..03c64b62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,31 +3,18 @@ language: python install: "make" # command to run tests script: - - | - make test-readme + - make test-readme - make ci cache: pip jobs: include: - stage: test - script: - - make test-readme - - make ci python: '2.7' - stage: test - script: - - make test-readme - - make ci python: '3.5' - stage: test - script: - - make test-readme - - make ci python: '3.6' - stage: test - script: - - make test-readme - - make ci python: '3.7' dist: xenial - stage: coverage diff --git a/AUTHORS.rst b/AUTHORS.rst index 73ef386a..607f65e1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -191,3 +191,5 @@ Patches and Suggestions - Antti Kaihola (`@akaihola `_) - "Dull Bananas" (`@dullbananas `_) - Alessio Izzo (`@aless10 `_) +- Belavin Denis (`@luckydenis `_) +- Dull Bananas (`@dullbananas `_) diff --git a/HISTORY.md b/HISTORY.md index af0cfffe..62c254f5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -793,7 +793,7 @@ documentation](http://docs.python-requests.org/en/latest/community/release-proce - Unicode URL improvements for Python 2. - Re-order JSON param for backwards compat. - Automatically defrag authentication schemes from host/pass URIs. - ([\#2249](https://github.com/requests/requests/issues/2249)) + ([\#2249](https://github.com/psf/requests/issues/2249)) 2.4.2 (2014-10-05) ------------------ @@ -801,26 +801,26 @@ documentation](http://docs.python-requests.org/en/latest/community/release-proce **Improvements** - FINALLY! Add json parameter for uploads! - ([\#2258](https://github.com/requests/requests/pull/2258)) + ([\#2258](https://github.com/psf/requests/pull/2258)) - Support for bytestring URLs on Python 3.x - ([\#2238](https://github.com/requests/requests/pull/2238)) + ([\#2238](https://github.com/psf/requests/pull/2238)) **Bugfixes** - Avoid getting stuck in a loop - ([\#2244](https://github.com/requests/requests/pull/2244)) + ([\#2244](https://github.com/psf/requests/pull/2244)) - Multiple calls to iter\* fail with unhelpful error. - ([\#2240](https://github.com/requests/requests/issues/2240), - [\#2241](https://github.com/requests/requests/issues/2241)) + ([\#2240](https://github.com/psf/requests/issues/2240), + [\#2241](https://github.com/psf/requests/issues/2241)) **Documentation** - Correct redirection introduction - ([\#2245](https://github.com/requests/requests/pull/2245/)) + ([\#2245](https://github.com/psf/requests/pull/2245/)) - Added example of how to send multiple files in one request. - ([\#2227](https://github.com/requests/requests/pull/2227/)) + ([\#2227](https://github.com/psf/requests/pull/2227/)) - Clarify how to pass a custom set of CAs - ([\#2248](https://github.com/requests/requests/pull/2248/)) + ([\#2248](https://github.com/psf/requests/pull/2248/)) 2.4.1 (2014-09-09) ------------------ diff --git a/README.md b/README.md index 41e92667..f0465de7 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Requests: HTTP for Humans™ [![image](https://img.shields.io/pypi/v/requests.svg)](https://pypi.org/project/requests/) [![image](https://img.shields.io/pypi/l/requests.svg)](https://pypi.org/project/requests/) [![image](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests/) -[![codecov.io](https://codecov.io/github/requests/requests/coverage.svg?branch=master)](https://codecov.io/github/requests/requests) -[![image](https://img.shields.io/github/contributors/requests/requests.svg)](https://github.com/requests/requests/graphs/contributors) +[![codecov.io](https://codecov.io/github/psf/requests/coverage.svg?branch=master)](https://codecov.io/github/psf/requests) +[![image](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors) [![image](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/kennethreitz) Requests is the only *Non-GMO* HTTP library for Python, safe for human @@ -32,7 +32,7 @@ u'{"type":"User"...' See [the similar code, sans Requests](https://gist.github.com/973705). -[![image](https://raw.githubusercontent.com/requests/requests/master/docs/_static/requests-logo-small.png)](http://docs.python-requests.org/) +[![image](https://raw.githubusercontent.com/psf/requests/master/docs/_static/requests-logo-small.png)](http://docs.python-requests.org/) Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, without the need for manual labor. There's no need to manually add query @@ -41,7 +41,7 @@ HTTP connection pooling are 100% automatic, thanks to [urllib3](https://github.com/shazow/urllib3). Besides, all the cool kids are doing it. Requests is one of the most -downloaded Python packages of all time, pulling in over 11,000,000 +downloaded Python packages of all time, pulling in over 50,000,000 downloads every month. You don't want to be left out! Feature Support @@ -93,15 +93,15 @@ How to Contribute 1. Become more familiar with the project by reading our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/) and our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/). 2. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a [Contributor - Friendly](https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open) + Friendly](https://github.com/psf/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open) tag for issues that should be ideal for people who are not very familiar with the codebase yet. -3. Fork [the repository](https://github.com/requests/requests) on +3. Fork [the repository](https://github.com/psf/requests) on GitHub to start making your changes to the **master** branch (or branch off of it). 4. Write a test which shows that the bug was fixed or that the feature works as expected. 5. Send a [pull request](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork) and bug the maintainer until it gets merged and published. :) Make sure to add yourself to - [AUTHORS](https://github.com/requests/requests/blob/master/AUTHORS.rst). + [AUTHORS](https://github.com/psf/requests/blob/master/AUTHORS.rst). diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 52493c5b..465e8a9a 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -50,29 +50,21 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch /* Native CPC by BuySellAds */ -.native-js { - visibility: hidden; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, - sans-serif; - opacity: 0; +#native-ribbon #_custom_ { + position: fixed; + right: 0; + bottom: 0; + left: 0; + box-shadow: 0 -1px 4px 1px hsla(0, 0%, 0%, .15); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, + Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; transition: all .25s ease-in-out; transform: translateY(calc(100% - 35px)); flex-flow: column nowrap; } -.native-js[data-state=visible] { - position: fixed; - bottom: 0; - left: 0; - right: 0; - visibility: visible; - box-shadow: 0 -1px 4px 1px hsla(0, 0%, 0%, .15); - opacity: 1; -} - -.native-js[data-state=visible]:hover { +#native-ribbon #_custom_:hover { transform: translateY(0); } @@ -92,7 +84,7 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch transform-origin: left; } -.native-js[data-state=visible]:hover .native-sponsor { +#native-ribbon #_custom_:hover .native-sponsor { margin: 0 20px; opacity: 0; transform: scaleY(0); @@ -100,17 +92,14 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch .native-flex { display: flex; - padding: 0 20px 30px; + padding: 10px 20px 25px; text-decoration: none; flex-flow: row nowrap; + justify-content: center; align-items: center; } -.native-flex:hover { - text-decoration: none; -} - .native-main { display: flex; @@ -135,6 +124,7 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch .native-desc { letter-spacing: 1px; font-weight: 300; + font-size: 14px; line-height: 1.4; } @@ -158,6 +148,7 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch @media only screen and (min-width: 320px) and (max-width: 759px) { .native-flex { + padding: 5px 5px 15px; flex-direction: column; flex-wrap: wrap; @@ -165,6 +156,7 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch .native-img { margin: 0; + display: none; } .native-details { @@ -173,10 +165,13 @@ body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-ch .native-main { flex-direction: column; - margin-bottom: 20px; - text-align: center; + text-align: left; flex-wrap: wrap; align-content: center; } + + .native-cta { + display: none; + } } diff --git a/docs/_static/native.js b/docs/_static/native.js deleted file mode 100644 index 65aebecf..00000000 --- a/docs/_static/native.js +++ /dev/null @@ -1,131 +0,0 @@ -var _native = (function () { - var _options = {} - var _construct = function (e) { - var defaultOptions = { - carbonZoneKey: '', - fallback: '', - ignore: 'false', - placement: '', - prefix: 'native', - targetClass: 'native-ad' - } - - if (typeof e === 'undefined') return defaultOptions - Object.keys(defaultOptions).forEach((key, index) => { - if (typeof e[key] === 'undefined') { - e[key] = defaultOptions[key] - } - }) - return e - } - - var init = function (zone, options) { - _options = _construct(options) - - let jsonUrl = `https://srv.buysellads.com/ads/${zone}.json?callback=_native_go` - if (_options['placement'] !== '') { - jsonUrl += '&segment=placement:' + _options['placement'] - } - if (_options['ignore'] === 'true') { - jsonUrl += '&ignore=yes' - } - - let srv = document.createElement('script') - srv.src = jsonUrl - document.getElementsByTagName('head')[0].appendChild(srv) - } - - var carbon = function (e) { - let srv = document.createElement('script') - srv.src = '//cdn.carbonads.com/carbon.js?serve=' + e['carbonZoneKey'] + '&placement=' + e['placement'] - srv.id = '_carbonads_js' - - return srv - } - - var sanitize = function (ads) { - return ads - .filter(ad => { - return Object.keys(ad).length > 0 - }) - .filter(ad => { - return ad.hasOwnProperty('statlink') - }) - } - - var pixel = function (p, timestamp) { - let c = '' - if (p) { - p.split('||').forEach((pixel, index) => { - c += `` - }) - } - return c - } - - var options = function () { - return _options - } - - return { - carbon: carbon, - init: init, - options: options, - pixel: pixel, - sanitize: sanitize - } -})({}) - -var _native_go = function (json) { - let options = _native.options() - let ads = _native.sanitize(json['ads']) - let selectedClass = document.querySelectorAll('.' + options['targetClass']) - - if (ads.length < 1) { - selectedClass.forEach((className, index) => { - let selectedTarget = document.getElementsByClassName(options['targetClass'])[index] - - if (options['fallback'] !== '' || options['carbonZoneKey'] !== '') selectedTarget.setAttribute('data-state', 'visible') - selectedTarget.innerHTML = options['fallback'] - if (options['carbonZoneKey'] !== '') selectedTarget.appendChild(_native.carbon(options)) - }) - - // End at this line if no ads are found, avoiding unnecessary steps - return - } - - selectedClass.forEach((className, index) => { - let selectedTarget = document.getElementsByClassName(options['targetClass'])[index] - let adElement = selectedTarget.innerHTML - let prefix = options['prefix'] - let ad = ads[index] - - if (ad && className) { - let adInnerHtml = adElement - .replace(new RegExp('#' + prefix + '_bg_color#', 'g'), ad['backgroundColor']) - .replace(new RegExp('#' + prefix + '_bg_color_hover#', 'g'), ad['backgroundHoverColor']) - .replace(new RegExp('#' + prefix + '_company#', 'g'), ad['company']) - .replace(new RegExp('#' + prefix + '_cta#', 'g'), ad['callToAction']) - .replace(new RegExp('#' + prefix + '_cta_bg_color#', 'g'), ad['ctaBackgroundColor']) - .replace(new RegExp('#' + prefix + '_cta_bg_color_hover#', 'g'), ad['ctaBackgroundHoverColor']) - .replace(new RegExp('#' + prefix + '_cta_color#', 'g'), ad['ctaTextColor']) - .replace(new RegExp('#' + prefix + '_cta_color_hover#', 'g'), ad['ctaTextColorHover']) - .replace(new RegExp('#' + prefix + '_desc#', 'g'), ad['description']) - .replace(new RegExp('#' + prefix + '_index#', 'g'), prefix + '-' + ad['i']) - .replace(new RegExp('#' + prefix + '_img#', 'g'), ad['image']) - .replace(new RegExp('#' + prefix + '_small_img#', 'g'), ad['smallImage']) - .replace(new RegExp('#' + prefix + '_link#', 'g'), ad['statlink']) - .replace(new RegExp('#' + prefix + '_logo#', 'g'), ad['logo']) - .replace(new RegExp('#' + prefix + '_color#', 'g'), ad['textColor']) - .replace(new RegExp('#' + prefix + '_color_hover#', 'g'), ad['textColorHover']) - .replace(new RegExp('#' + prefix + '_title#', 'g'), ad['title']) - - selectedTarget.innerHTML = null - selectedTarget.innerHTML += adInnerHtml + _native.pixel(ad['pixel'], ad['timestamp']) - selectedTarget.setAttribute('data-state', 'visible') - } else { - selectedTarget.innerHTML = null - selectedTarget.style.display = 'none' - } - }) -} diff --git a/docs/_templates/hacks.html b/docs/_templates/hacks.html index 61252a5c..3af2e578 100644 --- a/docs/_templates/hacks.html +++ b/docs/_templates/hacks.html @@ -57,47 +57,54 @@ - + + +
+
+ - - +` + } + ); + diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 319954d1..ea0f1652 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -71,9 +71,9 @@

-
  • Requests @ GitHub
  • +
  • Requests @ GitHub
  • Requests @ PyPI
  • -
  • Issue Tracker
  • +
  • Issue Tracker
  • Release History
  • @@ -91,41 +91,5 @@
  • Spanish
  • -
    -
    Sponsored by #native_company# — Learn More
    - - -
    - -
    - #native_title# - #native_desc# -
    -
    - #native_cta# -
    +
    diff --git a/docs/community/faq.rst b/docs/community/faq.rst index 945096dc..44d3e6e8 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -21,7 +21,8 @@ Custom User-Agents? ------------------- Requests allows you to easily override User-Agent strings, along with -any other HTTP Header. +any other HTTP Header. See `documentation about headers `_. + Why not Httplib2? diff --git a/docs/community/support.rst b/docs/community/support.rst index 4b72bba8..23e35868 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -29,7 +29,7 @@ File an Issue If you notice some unexpected behaviour in Requests, or want to see support for a new feature, -`file an issue on GitHub `_. +`file an issue on GitHub `_. E-mail diff --git a/docs/community/updates.rst b/docs/community/updates.rst index 3ae0589f..ee08cdc5 100644 --- a/docs/community/updates.rst +++ b/docs/community/updates.rst @@ -14,7 +14,7 @@ GitHub ------ The best way to track the development of Requests is through -`the GitHub repo `_. +`the GitHub repo `_. Twitter ------- diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index 434dc565..308fdd25 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -197,7 +197,7 @@ through the `GitHub issues`_, **both open and closed**, to confirm that the bug hasn't been reported before. Duplicate bug reports are a huge drain on the time of other contributors, and should be avoided as much as possible. -.. _GitHub issues: https://github.com/requests/requests/issues +.. _GitHub issues: https://github.com/psf/requests/issues Feature Requests diff --git a/docs/dev/todo.rst b/docs/dev/todo.rst index 26cd9b71..d09fce39 100644 --- a/docs/dev/todo.rst +++ b/docs/dev/todo.rst @@ -8,11 +8,11 @@ Requests is under active development, and contributions are more than welcome! #. Check for open issues or open a fresh issue to start a discussion around a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet. -#. Fork `the repository `_ on GitHub and start making your +#. Fork `the repository `_ on GitHub and start making your changes to a new branch. #. Write a test which shows that the bug was fixed. #. Send a pull request and bug the maintainer until it gets merged and published. :) - Make sure to add yourself to `AUTHORS `_. + Make sure to add yourself to `AUTHORS `_. Feature Freeze -------------- diff --git a/docs/index.rst b/docs/index.rst index f44340b7..5085503a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,8 +17,8 @@ Release v\ |version|. (:ref:`Installation `) .. image:: https://img.shields.io/pypi/pyversions/requests.svg :target: https://pypi.org/project/requests/ -.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master - :target: https://codecov.io/github/requests/requests +.. image:: https://codecov.io/github/psf/requests/coverage.svg?branch=master + :target: https://codecov.io/github/psf/requests :alt: codecov.io .. image:: https://img.shields.io/badge/Say%20Thanks!-🦉-1EAEDB.svg diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index fce37dc4..728ffbb6 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -300,7 +300,7 @@ immediately. You can override this behaviour and defer downloading the response body until you access the :attr:`Response.content ` attribute with the ``stream`` parameter:: - tarball_url = 'https://github.com/requests/requests/tarball/master' + tarball_url = 'https://github.com/psf/requests/tarball/master' r = requests.get(tarball_url, stream=True) At this point only the response headers have been downloaded and the connection @@ -680,7 +680,7 @@ from GitHub. Suppose we wanted commit ``a050faf`` on Requests. We would get it like so:: >>> import requests - >>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad') + >>> r = requests.get('https://api.github.com/repos/psf/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad') We should confirm that GitHub responded correctly. If it has, we want to work out what type of content it is. Do this like so:: @@ -735,12 +735,12 @@ we should probably avoid making ham-handed POSTS to it. Instead, let's play with the Issues feature of GitHub. This documentation was added in response to -`Issue #482 `_. Given that +`Issue #482 `_. Given that this issue already exists, we will use it as an example. Let's start by getting it. :: - >>> r = requests.get('https://api.github.com/repos/requests/requests/issues/482') + >>> r = requests.get('https://api.github.com/repos/psf/requests/issues/482') >>> r.status_code 200 @@ -783,7 +783,7 @@ is to POST to the thread. Let's do it. :: >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"}) - >>> url = u"https://api.github.com/repos/requests/requests/issues/482/comments" + >>> url = u"https://api.github.com/repos/psf/requests/issues/482/comments" >>> r = requests.post(url=url, data=body) >>> r.status_code @@ -817,7 +817,7 @@ that. 5804413 >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."}) - >>> url = u"https://api.github.com/repos/requests/requests/issues/comments/5804413" + >>> url = u"https://api.github.com/repos/psf/requests/issues/comments/5804413" >>> r = requests.patch(url=url, data=body, auth=auth) >>> r.status_code diff --git a/docs/user/install.rst b/docs/user/install.rst index 3888876a..73e69f29 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -25,15 +25,15 @@ Get the Source Code ------------------- Requests is actively developed on GitHub, where the code is -`always available `_. +`always available `_. You can either clone the public repository:: - $ git clone git://github.com/requests/requests.git + $ git clone git://github.com/psf/requests.git -Or, download the `tarball `_:: +Or, download the `tarball `_:: - $ curl -OL https://github.com/requests/requests/tarball/master + $ curl -OL https://github.com/psf/requests/tarball/master # optionally, zipball is also available (for Windows users). Once you have a copy of the source, you can embed it in your own Python diff --git a/requests/adapters.py b/requests/adapters.py index fa4d9b3c..97ea25b4 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -435,64 +435,19 @@ class HTTPAdapter(BaseAdapter): timeout = TimeoutSauce(connect=timeout, read=timeout) try: - if not chunked: - resp = conn.urlopen( - method=request.method, - url=url, - body=request.body, - headers=request.headers, - redirect=False, - assert_same_host=False, - preload_content=False, - decode_content=False, - retries=self.max_retries, - timeout=timeout - ) - - # Send the request. - else: - if hasattr(conn, 'proxy_pool'): - conn = conn.proxy_pool - - low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) - - try: - low_conn.putrequest(request.method, - url, - skip_accept_encoding=True) - - for header, value in request.headers.items(): - low_conn.putheader(header, value) - - low_conn.endheaders() - - for i in request.body: - low_conn.send(hex(len(i))[2:].encode('utf-8')) - low_conn.send(b'\r\n') - low_conn.send(i) - low_conn.send(b'\r\n') - low_conn.send(b'0\r\n\r\n') - - # Receive the response from the server - try: - # For Python 2.7, use buffering of HTTP responses - r = low_conn.getresponse(buffering=True) - except TypeError: - # For compatibility with Python 3.3+ - r = low_conn.getresponse() - - resp = HTTPResponse.from_httplib( - r, - pool=conn, - connection=low_conn, - preload_content=False, - decode_content=False - ) - except: - # If we hit any problems here, clean up the connection. - # Then, reraise so that we can handle the actual exception. - low_conn.close() - raise + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout, + chunked=chunked + ) except (ProtocolError, socket.error) as err: raise ConnectionError(err, request=request) diff --git a/requests/api.py b/requests/api.py index 13ecd926..e978e203 100644 --- a/requests/api.py +++ b/requests/api.py @@ -16,7 +16,7 @@ from . import sessions def request(method, url, **kwargs): """Constructs and sends a :class:`Request `. - :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS`, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary, list of tuples or bytes to send in the query string for the :class:`Request`. diff --git a/requests/auth.py b/requests/auth.py index 34e7c8b8..eeface39 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -239,7 +239,7 @@ class HTTPDigestAuth(AuthBase): """ # If response is not 4xx, do not auth - # See https://github.com/requests/requests/issues/3772 + # See https://github.com/psf/requests/issues/3772 if not 400 <= r.status_code < 500: self._thread_local.num_401_calls = 1 return r diff --git a/requests/compat.py b/requests/compat.py index c44b35ef..5de0769f 100644 --- a/requests/compat.py +++ b/requests/compat.py @@ -43,6 +43,7 @@ if is_py2: import cookielib from Cookie import Morsel from StringIO import StringIO + # Keep OrderedDict for backwards compatibility. from collections import Callable, Mapping, MutableMapping, OrderedDict @@ -59,6 +60,7 @@ elif is_py3: from http import cookiejar as cookielib from http.cookies import Morsel from io import StringIO + # Keep OrderedDict for backwards compatibility. from collections import OrderedDict from collections.abc import Callable, Mapping, MutableMapping diff --git a/requests/models.py b/requests/models.py index 8265bce6..ea332e7e 100644 --- a/requests/models.py +++ b/requests/models.py @@ -12,7 +12,7 @@ import sys # Import encoding now, to avoid implicit import later. # Implicit import within threads may cause LookupError when standard library is in a ZIP, -# such as in Embedded Python. See https://github.com/requests/requests/issues/3578. +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. import encodings.idna from urllib3.fields import RequestField @@ -359,7 +359,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): #: We're unable to blindly call unicode/str functions #: as this will include the bytestring indicator (b'') #: on python 3.x. - #: https://github.com/requests/requests/pull/2238 + #: https://github.com/psf/requests/pull/2238 if isinstance(url, bytes): url = url.decode('utf8') else: @@ -641,6 +641,10 @@ class Response(object): #: is a response. self.request = None + #: If there was an error in the processing of content, + #: then save the error that would return the same error when you re-appeal. + self._error = None + def __enter__(self): return self @@ -750,12 +754,21 @@ class Response(object): try: for chunk in self.raw.stream(chunk_size, decode_content=True): yield chunk + except ProtocolError as e: - raise ChunkedEncodingError(e) + self._error = ChunkedEncodingError(e) + except DecodeError as e: - raise ContentDecodingError(e) + self._error = ContentDecodingError(e) + except ReadTimeoutError as e: - raise ConnectionError(e) + self._error = ConnectionError(e) + + finally: + # if we had an error - throw the saved error + if self._error: + raise self._error + else: # Standard file-like object. while True: @@ -828,6 +841,10 @@ class Response(object): else: self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' + # if we had an error - throw the saved error + if self._error is not None: + raise self._error + self._content_consumed = True # don't need to release the connection; that's been handled by urllib3 # since we exhausted the data. diff --git a/requests/sessions.py b/requests/sessions.py index c5d01229..6d51820c 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -11,9 +11,10 @@ import os import sys import time from datetime import timedelta +from collections import OrderedDict from .auth import _basic_auth_str -from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping +from .compat import cookielib, is_py3, urljoin, urlparse, Mapping from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT @@ -94,6 +95,10 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): class SessionRedirectMixin(object): + def __init__(self): + #: A list of domains that will be excluded from auth stripping + self.trusted_domains = [] + def get_redirect_target(self, resp): """Receives a Response. Returns a redirect URI or ``None``""" # Due to the nature of how requests processes redirects this method will @@ -119,7 +124,8 @@ class SessionRedirectMixin(object): """Decide whether Authorization header should be removed when redirecting""" old_parsed = urlparse(old_url) new_parsed = urlparse(new_url) - if old_parsed.hostname != new_parsed.hostname: + if (old_parsed.hostname != new_parsed.hostname + and new_parsed.hostname not in self.trusted_domains): return True # Special case: allow http -> https redirect when using the standard # ports. This isn't specified by RFC 7235, but is kept to avoid @@ -162,7 +168,7 @@ class SessionRedirectMixin(object): resp.raw.read(decode_content=False) if len(resp.history) >= self.max_redirects: - raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) + raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) # Release the connection back into the pool. resp.close() @@ -170,7 +176,7 @@ class SessionRedirectMixin(object): # Handle redirection without scheme (see: RFC 1808 Section 4) if url.startswith('//'): parsed_rurl = urlparse(resp.url) - url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url) + url = ':'.join([to_native_string(parsed_rurl.scheme), url]) # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) parsed = urlparse(url) @@ -192,19 +198,16 @@ class SessionRedirectMixin(object): self.rebuild_method(prepared_request, resp) - # https://github.com/requests/requests/issues/1084 + # https://github.com/psf/requests/issues/1084 if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): - # https://github.com/requests/requests/issues/3490 + # https://github.com/psf/requests/issues/3490 purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') for header in purged_headers: prepared_request.headers.pop(header, None) prepared_request.body = None headers = prepared_request.headers - try: - del headers['Cookie'] - except KeyError: - pass + headers.pop('Cookie', None) # Extract any cookies sent on the response to the cookiejar # in the new request. Because we've mutated our copied prepared @@ -271,7 +274,6 @@ class SessionRedirectMixin(object): if new_auth is not None: prepared_request.prepare_auth(new_auth) - return def rebuild_proxies(self, prepared_request, proxies): """This method re-evaluates the proxy configuration by considering the @@ -417,6 +419,8 @@ class Session(SessionRedirectMixin): self.mount('https://', HTTPAdapter()) self.mount('http://', HTTPAdapter()) + super().__init__() + def __enter__(self): return self @@ -728,7 +732,7 @@ class Session(SessionRedirectMixin): return adapter # Nothing matches :-/ - raise InvalidSchema("No connection adapters were found for '%s'" % url) + raise InvalidSchema("No connection adapters were found for {!r}".format(url)) def close(self): """Closes all adapters and as such the session""" diff --git a/requests/structures.py b/requests/structures.py index da930e28..8ee0ba7a 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -7,7 +7,9 @@ requests.structures Data structures that power Requests. """ -from .compat import OrderedDict, Mapping, MutableMapping +from collections import OrderedDict + +from .compat import Mapping, MutableMapping class CaseInsensitiveDict(MutableMapping): diff --git a/requests/utils.py b/requests/utils.py index b64a577c..c1700d7f 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -19,6 +19,7 @@ import sys import tempfile import warnings import zipfile +from collections import OrderedDict from .__version__ import __version__ from . import certs @@ -26,7 +27,7 @@ from . import certs from ._internal_utils import to_native_string from .compat import parse_http_list as _parse_list_header from .compat import ( - quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, + quote, urlparse, bytes, str, unquote, getproxies, proxy_bypass, urlunparse, basestring, integer_types, is_py3, proxy_bypass_environment, getproxies_environment, Mapping) from .cookies import cookiejar_from_dict @@ -179,7 +180,7 @@ def get_netrc_auth(url, raise_errors=False): except KeyError: # os.path.expanduser can fail when $HOME is undefined and # getpwuid fails. See https://bugs.python.org/issue20164 & - # https://github.com/requests/requests/issues/1846 + # https://github.com/psf/requests/issues/1846 return if os.path.exists(loc): diff --git a/setup.py b/setup.py index 3dce2965..ab135a58 100755 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ test_requirements = [ 'pytest-mock', 'pytest-xdist', 'PySocks>=1.5.6, !=1.5.7', - 'pytest>=2.8.0' + 'pytest>=3' ] about = {} @@ -105,4 +105,8 @@ setup( 'socks': ['PySocks>=1.5.6, !=1.5.7'], 'socks:sys_platform == "win32" and python_version == "2.7"': ['win_inet_pton'], }, + project_urls={ + 'Documentation': 'http://docs.python-requests.org', + 'Source': 'https://github.com/kennethreitz/requests', + }, ) diff --git a/tests/test_lowlevel.py b/tests/test_lowlevel.py index 82c3b25a..4178c78a 100644 --- a/tests/test_lowlevel.py +++ b/tests/test_lowlevel.py @@ -3,6 +3,7 @@ import pytest import threading import requests +from requests.exceptions import ChunkedEncodingError from tests.testserver.server import Server, consume_socket_content @@ -28,7 +29,7 @@ def test_digestauth_401_count_reset_on_redirect(): """Ensure we correctly reset num_401_calls after a successful digest auth, followed by a 302 redirect to another digest auth prompt. - See https://github.com/requests/requests/issues/1979. + See https://github.com/psf/requests/issues/1979. """ text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n' b'Content-Length: 0\r\n' @@ -138,7 +139,7 @@ def test_digestauth_401_only_sent_once(): def test_digestauth_only_on_4xx(): """Ensure we only send digestauth on 4xx challenges. - See https://github.com/requests/requests/issues/3772. + See https://github.com/psf/requests/issues/3772. """ text_200_chal = (b'HTTP/1.1 200 OK\r\n' b'Content-Length: 0\r\n' @@ -307,3 +308,43 @@ def test_fragment_update_on_redirect(): assert r.url == 'http://{}:{}/final-url/#relevant-section'.format(host, port) close_server.set() + + +def test_response_content_retains_error(): + """Verify that accessing response.content retains an error. + + See https://github.com/kennethreitz/requests/issues/4965 + """ + + data = "Some random stuff to read from remove server.\n" + + def response_handler(sock): + req = consume_socket_content(sock, timeout=0.5) + + # Send invalid chunked data (length mismatch) + sock.send( + b'HTTP/1.1 200 OK\r\n' + b'Transfer-Encoding: chunked\r\n' + b'\r\n2\r\n42\r\n8\r\n123\r\n' # 5 bytes missing + ) + + close_server = threading.Event() + server = Server(response_handler, wait_to_close_event=close_server) + + with server as (host, port): + url = 'http://{}:{}/path'.format(host, port) + r = requests.post(url, stream=True) + with pytest.raises(ChunkedEncodingError): + r.content + + # Access the bad response data again, I would expect the same + # error again. + + try: + content = r.content + except ChunkedEncodingError: + pass # fine, same exception + else: + assert False, "error response has content: {0!r}".format(content) + close_server.set() + diff --git a/tests/test_requests.py b/tests/test_requests.py index 89eff885..7d4a4eb5 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -18,7 +18,7 @@ from requests.adapters import HTTPAdapter from requests.auth import HTTPDigestAuth, _basic_auth_str from requests.compat import ( Morsel, cookielib, getproxies, str, urlparse, - builtin_str, OrderedDict) + builtin_str) from requests.cookies import ( cookiejar_from_dict, morsel_to_cookie) from requests.exceptions import ( @@ -130,7 +130,7 @@ class TestRequests: assert request.url == expected def test_params_original_order_is_preserved_by_default(self): - param_ordered_dict = OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1))) + param_ordered_dict = collections.OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1))) session = requests.Session() request = requests.Request('GET', 'http://example.com/', params=param_ordered_dict) prep = session.prepare_request(request) @@ -446,11 +446,11 @@ class TestRequests: def test_headers_preserve_order(self, httpbin): """Preserve order when headers provided as OrderedDict.""" ses = requests.Session() - ses.headers = OrderedDict() + ses.headers = collections.OrderedDict() ses.headers['Accept-Encoding'] = 'identity' ses.headers['First'] = '1' ses.headers['Second'] = '2' - headers = OrderedDict([('Third', '3'), ('Fourth', '4')]) + headers = collections.OrderedDict([('Third', '3'), ('Fourth', '4')]) headers['Fifth'] = '5' headers['Second'] = '222' req = requests.Request('GET', httpbin('get'), headers=headers) @@ -466,7 +466,7 @@ class TestRequests: @pytest.mark.parametrize('key', ('User-agent', 'user-agent')) def test_user_agent_transfers(self, httpbin, key): - heads = {key: 'Mozilla/5.0 (github.com/requests/requests)'} + heads = {key: 'Mozilla/5.0 (github.com/psf/requests)'} r = requests.get(httpbin('user-agent'), headers=heads) assert heads[key] in r.text @@ -2212,7 +2212,7 @@ class TestTimeout: pass def test_encoded_methods(self, httpbin): - """See: https://github.com/requests/requests/issues/2316""" + """See: https://github.com/psf/requests/issues/2316""" r = requests.request(b'GET', httpbin('get')) assert r.ok diff --git a/tests/test_utils.py b/tests/test_utils.py index 59b0b0ef..463516b2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -33,7 +33,8 @@ class TestSuperLen: 'stream, value', ( (StringIO.StringIO, 'Test'), (BytesIO, b'Test'), - pytest.mark.skipif('cStringIO is None')((cStringIO, 'Test')), + pytest.param(cStringIO, 'Test', + marks=pytest.mark.skipif('cStringIO is None')), )) def test_io_streams(self, stream, value): """Ensures that we properly deal with different kinds of IO streams.""" @@ -397,7 +398,7 @@ def test_get_auth_from_url(url, auth): ), )) def test_requote_uri_with_unquoted_percents(uri, expected): - """See: https://github.com/requests/requests/issues/2356""" + """See: https://github.com/psf/requests/issues/2356""" assert requote_uri(uri) == expected