mirror of
https://github.com/kennethreitz/requests.git
synced 2026-06-05 22:50:18 +00:00
Merge remote-tracking branch 'upstream/master' into proposed/3.0.0
Conflicts: requests/__init__.py requests/sessions.py tests/test_requests.py
This commit is contained in:
@@ -168,3 +168,6 @@ Patches and Suggestions
|
||||
- Tomáš Heger (`@geckon <https://github.com/geckon>`_)
|
||||
- piotrjurkiewicz
|
||||
- Jesse Shapiro <jesse@jesseshapiro.net> (`@haikuginger <https://github.com/haikuginger>`_)
|
||||
- Nate Prewitt <nate.prewitt@gmail.com> (`@nateprewitt <https://github.com/nateprewitt>`_)
|
||||
- Maik Himstedt
|
||||
- Michael Hunsinger
|
||||
|
||||
+40
@@ -3,6 +3,46 @@
|
||||
Release History
|
||||
---------------
|
||||
|
||||
2.11.1 (2016-08-17)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed a bug when using ``iter_content`` with ``decode_unicode=True`` for
|
||||
streamed bodies would raise ``AttributeError``. This bug was introduced in
|
||||
2.11.
|
||||
- Strip Content-Type and Transfer-Encoding headers from the header block when
|
||||
following a redirect that transforms the verb from POST/PUT to GET.
|
||||
|
||||
2.11.0 (2016-08-08)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Added support for the ``ALL_PROXY`` environment variable.
|
||||
- Reject header values that contain leading whitespace or newline characters to
|
||||
reduce risk of header smuggling.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed occasional ``TypeError`` when attempting to decode a JSON response that
|
||||
occurred in an error case. Now correctly returns a ``ValueError``.
|
||||
- Requests would incorrectly ignore a non-CIDR IP address in the ``NO_PROXY``
|
||||
environment variables: Requests now treats it as a specific IP.
|
||||
- Fixed a bug when sending JSON data that could cause us to encounter obscure
|
||||
OpenSSL errors in certain network conditions (yes, really).
|
||||
- Added type checks to ensure that ``iter_content`` only accepts integers and
|
||||
``None`` for chunk sizes.
|
||||
- Fixed issue where responses whose body had not been fully consumed would have
|
||||
the underlying connection closed but not returned to the connection pool,
|
||||
which could cause Requests to hang in situations where the ``HTTPAdapter``
|
||||
had been configured to use a blocking connection pool.
|
||||
|
||||
**Miscellaneous**
|
||||
|
||||
- Updated bundled urllib3 to 1.16.
|
||||
- Some previous releases accidentally accepted non-strings as acceptable header values. This release does not.
|
||||
|
||||
2.10.0 (2016-04-29)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
+1
-4
@@ -4,9 +4,6 @@ Requests: HTTP for Humans
|
||||
.. image:: https://img.shields.io/pypi/v/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
Requests is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||
@@ -64,7 +61,7 @@ Requests is ready for today's web.
|
||||
- Chunked Requests
|
||||
- Thread-safety
|
||||
|
||||
Requests supports Python 2.6 — 3.5, and runs great on PyPy.
|
||||
Requests officially supports Python 2.6–2.7 & 3.3–3.5, and runs great on PyPy.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
+8
-1
@@ -63,6 +63,9 @@ Lower-Lower-Level Classes
|
||||
.. autoclass:: requests.PreparedRequest
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: requests.adapters.BaseAdapter
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: requests.adapters.HTTPAdapter
|
||||
:inherited-members:
|
||||
|
||||
@@ -258,6 +261,10 @@ Behavioural Changes
|
||||
|
||||
* Keys in the ``headers`` dictionary are now native strings on all Python
|
||||
versions, i.e. bytestrings on Python 2 and unicode on Python 3. If the
|
||||
keys are not native strings (unicode on Python2 or bytestrings on Python 3)
|
||||
keys are not native strings (unicode on Python 2 or bytestrings on Python 3)
|
||||
they will be converted to the native string type assuming UTF-8 encoding.
|
||||
|
||||
* Values in the ``headers`` dictionary should always be strings. This has
|
||||
been the project's position since before 1.0 but a recent change
|
||||
(since version 2.11.0) enforces this more strictly. It's advised to avoid
|
||||
passing header values as unicode when possible.
|
||||
|
||||
@@ -56,12 +56,10 @@ supported:
|
||||
|
||||
* Python 2.6
|
||||
* Python 2.7
|
||||
* Python 3.1
|
||||
* Python 3.2
|
||||
* Python 3.3
|
||||
* Python 3.4
|
||||
* PyPy 1.9
|
||||
* PyPy 2.2
|
||||
* Python 3.5
|
||||
* PyPy
|
||||
|
||||
What are "hostname doesn't match" errors?
|
||||
-----------------------------------------
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
Integrations
|
||||
============
|
||||
|
||||
ScraperWiki
|
||||
------------
|
||||
|
||||
`ScraperWiki <https://scraperwiki.com/>`_ is an excellent service that allows
|
||||
you to run Python, Ruby, and PHP scraper scripts on the web. Now, Requests
|
||||
v0.6.1 is available to use in your scrapers!
|
||||
|
||||
To give it a try, simply::
|
||||
|
||||
import requests
|
||||
|
||||
Python for iOS
|
||||
--------------
|
||||
|
||||
|
||||
+6
-5
@@ -38,14 +38,15 @@ Requests currently supports the following versions of Python:
|
||||
|
||||
- Python 2.6
|
||||
- Python 2.7
|
||||
- Python 3.1
|
||||
- Python 3.2
|
||||
- Python 3.3
|
||||
- PyPy 1.9
|
||||
- Python 3.4
|
||||
- Python 3.5
|
||||
- PyPy
|
||||
|
||||
Support for Python 3.1 and 3.2 may be dropped at any time.
|
||||
Google AppEngine is not officially supported although support is available
|
||||
with the `Requests-Toolbelt`_.
|
||||
|
||||
Google App Engine will never be officially supported. Pull Requests for compatibility will be accepted, as long as they don't complicate the codebase.
|
||||
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/
|
||||
|
||||
|
||||
Are you crazy?
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ which is embedded within Requests.
|
||||
User Testimonials
|
||||
-----------------
|
||||
|
||||
Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku,
|
||||
The NSA, Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku,
|
||||
PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington
|
||||
Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal U.S.
|
||||
Institutions that prefer to be unnamed claim to use Requests internally.
|
||||
@@ -87,7 +87,7 @@ Requests is ready for today's web.
|
||||
- Chunked Requests
|
||||
- Thread-safety
|
||||
|
||||
Requests supports Python 2.6 — 3.5, and runs great on PyPy.
|
||||
Requests officially supports Python 2.6–2.7 & 3.3–3.5, and runs great on PyPy.
|
||||
|
||||
|
||||
The User Guide
|
||||
|
||||
+51
-35
@@ -87,11 +87,11 @@ See the :ref:`Session API Docs <sessionapi>` to learn more.
|
||||
Request and Response Objects
|
||||
----------------------------
|
||||
|
||||
Whenever a call is made to ``requests.get()`` and friends you are doing two
|
||||
Whenever a call is made to ``requests.get()`` and friends, you are doing two
|
||||
major things. First, you are constructing a ``Request`` object which will be
|
||||
sent off to a server to request or query some resource. Second, a ``Response``
|
||||
object is generated once ``requests`` gets a response back from the server.
|
||||
The Response object contains all of the information returned by the server and
|
||||
object is generated once Requests gets a response back from the server.
|
||||
The ``Response`` object contains all of the information returned by the server and
|
||||
also contains the ``Request`` object you created originally. Here is a simple
|
||||
request to get some very important information from Wikipedia's servers::
|
||||
|
||||
@@ -193,7 +193,7 @@ SSL Cert Verification
|
||||
---------------------
|
||||
|
||||
Requests verifies SSL certificates for HTTPS requests, just like a web browser.
|
||||
By default, SSL verification is enabled, and requests will throw a SSLError if
|
||||
By default, SSL verification is enabled, and Requests will throw a SSLError if
|
||||
it's unable to verify the certificate::
|
||||
|
||||
>>> requests.get('https://requestb.in')
|
||||
@@ -208,14 +208,17 @@ You can pass ``verify`` the path to a CA_BUNDLE file or directory with certifica
|
||||
|
||||
>>> requests.get('https://github.com', verify='/path/to/certfile')
|
||||
|
||||
.. note:: If ``verify`` is set to a path to a directory, the directory must have been processed using
|
||||
or persistent::
|
||||
|
||||
s = requests.Session()
|
||||
s.verify = '/path/to/certfile'
|
||||
|
||||
.. note:: If ``verify`` is set to a path to a directory, the directory must have been processed using
|
||||
the c_rehash utility supplied with OpenSSL.
|
||||
|
||||
This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable.
|
||||
|
||||
Requests can also ignore verifying the SSL certificate if you set ``verify`` to False.
|
||||
|
||||
::
|
||||
Requests can also ignore verifying the SSL certificate if you set ``verify`` to False::
|
||||
|
||||
>>> requests.get('https://kennethreitz.com', verify=False)
|
||||
<Response [200]>
|
||||
@@ -229,20 +232,25 @@ file's path::
|
||||
>>> requests.get('https://kennethreitz.com', cert=('/path/client.cert', '/path/client.key'))
|
||||
<Response [200]>
|
||||
|
||||
or persistent::
|
||||
|
||||
s = requests.Session()
|
||||
s.cert = '/path/client.cert'
|
||||
|
||||
If you specify a wrong path or an invalid cert, you'll get a SSLError::
|
||||
|
||||
>>> requests.get('https://kennethreitz.com', cert='/wrong_path/client.pem')
|
||||
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
|
||||
|
||||
.. warning:: The private key to your local certificate *must* be unencrypted.
|
||||
Currently, requests does not support using encrypted keys.
|
||||
Currently, Requests does not support using encrypted keys.
|
||||
|
||||
.. _ca-certificates:
|
||||
|
||||
CA Certificates
|
||||
---------------
|
||||
|
||||
By default Requests bundles a set of root CAs that it trusts, sourced from the
|
||||
By default, Requests bundles a set of root CAs that it trusts, sourced from the
|
||||
`Mozilla trust store`_. However, these are only updated once for each Requests
|
||||
version. This means that if you pin a Requests version your certificates can
|
||||
become extremely out of date.
|
||||
@@ -266,7 +274,7 @@ Body Content Workflow
|
||||
|
||||
By default, when you make a request, the body of the response is downloaded
|
||||
immediately. You can override this behaviour and defer downloading the response
|
||||
body until you access the :class:`Response.content <requests.Response.content>`
|
||||
body until you access the :attr:`Response.content <requests.Response.content>`
|
||||
attribute with the ``stream`` parameter::
|
||||
|
||||
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
|
||||
@@ -279,15 +287,15 @@ remains open, hence allowing us to make content retrieval conditional::
|
||||
content = r.content
|
||||
...
|
||||
|
||||
You can further control the workflow by use of the :class:`Response.iter_content <requests.Response.iter_content>`
|
||||
and :class:`Response.iter_lines <requests.Response.iter_lines>` methods.
|
||||
You can further control the workflow by use of the :meth:`Response.iter_content() <requests.Response.iter_content>`
|
||||
and :meth:`Response.iter_lines() <requests.Response.iter_lines>` methods.
|
||||
Alternatively, you can read the undecoded body from the underlying
|
||||
urllib3 :class:`urllib3.HTTPResponse <urllib3.response.HTTPResponse>` at
|
||||
:class:`Response.raw <requests.Response.raw>`.
|
||||
:attr:`Response.raw <requests.Response.raw>`.
|
||||
|
||||
If you set ``stream`` to ``True`` when making a request, Requests cannot
|
||||
release the connection back to the pool unless you consume all the data or call
|
||||
:class:`Response.close <requests.Response.close>`. This can lead to
|
||||
:meth:`Response.close <requests.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::
|
||||
@@ -349,11 +357,11 @@ a length) for your body::
|
||||
requests.post('http://some.url/chunked', data=gen())
|
||||
|
||||
For chunked encoded responses, it's best to iterate over the data using
|
||||
:meth:`Response.iter_content() <requests.models.Response.iter_content>`. In
|
||||
:meth:`Response.iter_content() <requests.Response.iter_content>`. In
|
||||
an ideal situation you'll have set ``stream=True`` on the request, in which
|
||||
case you can iterate chunk-by-chunk by calling ``iter_content`` with a chunk
|
||||
size parameter of ``None``. If you want to set a maximum size of the chunk,
|
||||
you can set a chunk size parameter to any integer.
|
||||
case you can iterate chunk-by-chunk by calling ``iter_content`` with a ``chunk_size``
|
||||
parameter of ``None``. If you want to set a maximum size of the chunk,
|
||||
you can set a ``chunk_size`` parameter to any integer.
|
||||
|
||||
|
||||
.. _multipart:
|
||||
@@ -440,9 +448,10 @@ Requests allows you to use specify your own authentication mechanism.
|
||||
Any callable which is passed as the ``auth`` argument to a request method will
|
||||
have the opportunity to modify the request before it is dispatched.
|
||||
|
||||
Authentication implementations are subclasses of ``requests.auth.AuthBase``,
|
||||
Authentication implementations are subclasses of :class:`AuthBase <requests.auth.AuthBase>`,
|
||||
and are easy to define. Requests provides two common authentication scheme
|
||||
implementations in ``requests.auth``: ``HTTPBasicAuth`` and ``HTTPDigestAuth``.
|
||||
implementations in ``requests.auth``: :class:`HTTPBasicAuth <requests.auth.HTTPBasicAuth>` and
|
||||
:class:`HTTPDigestAuth <requests.auth.HTTPDigestAuth>`.
|
||||
|
||||
Let's pretend that we have a web service that will only respond if the
|
||||
``X-Pizza`` header is set to a password value. Unlikely, but just go with it.
|
||||
@@ -472,11 +481,11 @@ Then, we can make a request using our Pizza Auth::
|
||||
Streaming Requests
|
||||
------------------
|
||||
|
||||
With :class:`requests.Response.iter_lines()` you can easily
|
||||
With :meth:`Response.iter_lines() <requests.Response.iter_lines>` you can easily
|
||||
iterate over streaming APIs such as the `Twitter Streaming
|
||||
API <https://dev.twitter.com/streaming/overview>`_. Simply
|
||||
set ``stream`` to ``True`` and iterate over the response with
|
||||
:class:`~requests.Response.iter_lines()`::
|
||||
:meth:`~requests.Response.iter_lines()`::
|
||||
|
||||
import json
|
||||
import requests
|
||||
@@ -491,7 +500,7 @@ set ``stream`` to ``True`` and iterate over the response with
|
||||
|
||||
.. warning::
|
||||
|
||||
:class:`~requests.Response.iter_lines()` is not reentrant safe.
|
||||
:meth:`~requests.Response.iter_lines()` is not reentrant safe.
|
||||
Calling this method multiple times causes some of the received data
|
||||
being lost. In case you need to call it from multiple places, use
|
||||
the resulting iterator object instead::
|
||||
@@ -552,7 +561,7 @@ SOCKS
|
||||
|
||||
.. versionadded:: 2.10.0
|
||||
|
||||
In addition to basic HTTP proxies, requests also supports proxies using the
|
||||
In addition to basic HTTP proxies, Requests also supports proxies using the
|
||||
SOCKS protocol. This is an optional feature that requires that additional
|
||||
third-party libraries be installed before use.
|
||||
|
||||
@@ -669,8 +678,9 @@ commits is POST, which creates a new commit. As we're using the Requests repo,
|
||||
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 this issue
|
||||
already exists, we will use it as an example. Let's start by getting it.
|
||||
This documentation was added in response to
|
||||
`Issue #482 <https://github.com/kennethreitz/requests/issues/482>`_. Given that
|
||||
this issue already exists, we will use it as an example. Let's start by getting it.
|
||||
|
||||
::
|
||||
|
||||
@@ -845,8 +855,8 @@ with the given prefix will use the given Transport Adapter.
|
||||
|
||||
Many of the details of implementing a Transport Adapter are beyond the scope of
|
||||
this documentation, but take a look at the next example for a simple SSL use-
|
||||
case. For more than that, you might look at subclassing
|
||||
``requests.adapters.BaseAdapter``.
|
||||
case. For more than that, you might look at subclassing the
|
||||
:class:`BaseAdapter <requests.adapters.BaseAdapter>`.
|
||||
|
||||
Example: Specific SSL Version
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -858,10 +868,8 @@ that uses a version that isn't compatible with the default.
|
||||
|
||||
You can use Transport Adapters for this by taking most of the existing
|
||||
implementation of HTTPAdapter, and adding a parameter *ssl_version* that gets
|
||||
passed-through to `urllib3`. We'll make a TA that instructs the library to use
|
||||
SSLv3:
|
||||
|
||||
::
|
||||
passed-through to `urllib3`. We'll make a Transport Adapter that instructs the
|
||||
library to use SSLv3::
|
||||
|
||||
import ssl
|
||||
|
||||
@@ -899,13 +907,21 @@ Two excellent examples are `grequests`_ and `requests-futures`_.
|
||||
.. _`grequests`: https://github.com/kennethreitz/grequests
|
||||
.. _`requests-futures`: https://github.com/ross/requests-futures
|
||||
|
||||
Header Ordering
|
||||
---------------
|
||||
|
||||
In unusual circumstances you may want to provide headers in an ordered manner. If you pass an ``OrderedDict`` to the ``headers`` keyword argument, that will provide the headers with an ordering. *However*, the ordering of the default headers used by Requests will be preferred, which means that if you override default headers in the ``headers`` keyword argument, they may appear out of order compared to other headers in that keyword argument.
|
||||
|
||||
If this is problematic, users should consider setting the default headers on a :class:`Session <requests.Session>` object, by setting :attr:`Session <requests.Session.headers>` to a custom ``OrderedDict``. That ordering will always be preferred.
|
||||
|
||||
.. _timeouts:
|
||||
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
Most requests to external servers should have a timeout attached, in case the
|
||||
server is not responding in a timely manner. Without a timeout, your code may
|
||||
server is not responding in a timely manner. By default, requests do not time
|
||||
out unless a timeout value is set explicitly. Without a timeout, your code may
|
||||
hang for minutes or more.
|
||||
|
||||
The **connect** timeout is the number of seconds Requests will wait for your
|
||||
@@ -933,7 +949,7 @@ If the remote server is very slow, you can tell Requests to wait forever for
|
||||
a response, by passing None as a timeout value and then retrieving a cup of
|
||||
coffee.
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
r = requests.get('https://github.com', timeout=None)
|
||||
|
||||
|
||||
+30
-14
@@ -27,7 +27,7 @@ Begin by importing the Requests module::
|
||||
>>> import requests
|
||||
|
||||
Now, let's try to get a webpage. For this example, let's get GitHub's public
|
||||
timeline ::
|
||||
timeline::
|
||||
|
||||
>>> r = requests.get('https://api.github.com/events')
|
||||
|
||||
@@ -132,9 +132,9 @@ For example, to create an image from binary data returned by a request, you can
|
||||
use the following code::
|
||||
|
||||
>>> from PIL import Image
|
||||
>>> from StringIO import StringIO
|
||||
>>> from io import BytesIO
|
||||
|
||||
>>> i = Image.open(StringIO(r.content))
|
||||
>>> i = Image.open(BytesIO(r.content))
|
||||
|
||||
|
||||
JSON Response Content
|
||||
@@ -148,11 +148,11 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::
|
||||
>>> r.json()
|
||||
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
|
||||
|
||||
In case the JSON decoding fails, ``r.json`` raises an exception. For example, if
|
||||
In case the JSON decoding fails, ``r.json()`` raises an exception. For example, if
|
||||
the response gets a 204 (No Content), or if the response contains invalid JSON,
|
||||
attempting ``r.json`` raises ``ValueError: No JSON object could be decoded``.
|
||||
attempting ``r.json()`` raises ``ValueError: No JSON object could be decoded``.
|
||||
|
||||
It should be noted that the success of the call to ``r.json`` does **not**
|
||||
It should be noted that the success of the call to ``r.json()`` does **not**
|
||||
indicate the success of the response. Some servers may return a JSON object in a
|
||||
failed response (e.g. error details with HTTP 500). Such JSON will be decoded
|
||||
and returned. To check that a request is successful, use
|
||||
@@ -211,6 +211,7 @@ Note: Custom headers are given less precedence than more specific sources of inf
|
||||
|
||||
Furthermore, Requests does not change its behavior at all based on which custom headers are specified. The headers are simply passed on into the final request.
|
||||
|
||||
Note: All header values must be a ``string``, bytestring, or unicode. While permitted, it's advised to avoid passing unicode header values.
|
||||
|
||||
More complicated POST requests
|
||||
------------------------------
|
||||
@@ -416,6 +417,19 @@ parameter::
|
||||
>>> r.text
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
|
||||
Cookies are returned in a :class:`~requests.cookies.RequestsCookieJar`,
|
||||
which acts like a ``dict`` but also offers a more complete interface,
|
||||
suitable for use over multiple domains or paths. Cookie jars can
|
||||
also be passed in to requests::
|
||||
|
||||
>>> jar = requests.cookies.RequestsCookieJar()
|
||||
>>> jar.set('tasty_cookie', 'yum', site='httpbin.org', path='/cookies')
|
||||
>>> jar.set('gross_cookie', 'blech', site='httpbin.org', path='/elsewhere')
|
||||
>>> url = 'http://httpbin.org/cookies'
|
||||
>>> r = requests.get(url, cookies=jar)
|
||||
>>> r.text
|
||||
'{"cookies": {"tasty_cookie": "yum"}}'
|
||||
|
||||
|
||||
Redirection and History
|
||||
-----------------------
|
||||
@@ -425,7 +439,7 @@ HEAD.
|
||||
|
||||
We can use the ``history`` property of the Response object to track redirection.
|
||||
|
||||
The :meth:`Response.history <requests.Response.history>` list contains the
|
||||
The :attr:`Response.history <requests.Response.history>` list contains the
|
||||
:class:`Response <requests.Response>` objects that were created in order to
|
||||
complete the request. The list is sorted from the oldest to the most recent
|
||||
response.
|
||||
@@ -483,26 +497,28 @@ seconds with the ``timeout`` parameter::
|
||||
``timeout`` is not a time limit on the entire response download;
|
||||
rather, an exception is raised if the server has not issued a
|
||||
response for ``timeout`` seconds (more precisely, if no bytes have been
|
||||
received on the underlying socket for ``timeout`` seconds).
|
||||
received on the underlying socket for ``timeout`` seconds). If no timeout is specified explicitly, requests do
|
||||
not time out.
|
||||
|
||||
|
||||
Errors and Exceptions
|
||||
---------------------
|
||||
|
||||
In the event of a network problem (e.g. DNS failure, refused connection, etc),
|
||||
Requests will raise a :class:`~requests.exceptions.ConnectionError` exception.
|
||||
Requests will raise a :exc:`~requests.exceptions.ConnectionError` exception.
|
||||
|
||||
In the rare event of an invalid HTTP response, Requests will raise an
|
||||
:class:`~requests.exceptions.HTTPError` exception.
|
||||
:meth:`Response.raise_for_status() <requests.Response.raise_for_status>` will
|
||||
raise an :exc:`~requests.exceptions.HTTPError` if the HTTP request
|
||||
returned an unsuccessful status code.
|
||||
|
||||
If a request times out, a :class:`~requests.exceptions.Timeout` exception is
|
||||
If a request times out, a :exc:`~requests.exceptions.Timeout` exception is
|
||||
raised.
|
||||
|
||||
If a request exceeds the configured number of maximum redirections, a
|
||||
:class:`~requests.exceptions.TooManyRedirects` exception is raised.
|
||||
:exc:`~requests.exceptions.TooManyRedirects` exception is raised.
|
||||
|
||||
All exceptions that Requests explicitly raises inherit from
|
||||
:class:`requests.exceptions.RequestException`.
|
||||
:exc:`requests.exceptions.RequestException`.
|
||||
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ is at <http://python-requests.org>.
|
||||
|
||||
:copyright: (c) 2016 by Kenneth Reitz.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
@@ -83,7 +82,5 @@ except ImportError:
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
import warnings
|
||||
|
||||
# FileModeWarnings go off per the default.
|
||||
warnings.simplefilter('default', FileModeWarning, append=True)
|
||||
|
||||
@@ -168,6 +168,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxy: The proxy to return a urllib3 ProxyManager for.
|
||||
:param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
|
||||
:returns: ProxyManager
|
||||
:rtype: requests.packages.urllib3.ProxyManager
|
||||
"""
|
||||
if proxy in self.proxy_manager:
|
||||
manager = self.proxy_manager[proxy]
|
||||
@@ -244,6 +245,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
:param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
|
||||
:param resp: The urllib3 response object.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
response = Response()
|
||||
|
||||
@@ -279,6 +281,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
:param url: The URL to connect to.
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
:rtype: requests.packages.urllib3.ConnectionPool
|
||||
"""
|
||||
proxy = select_proxy(url, proxies)
|
||||
|
||||
@@ -316,6 +319,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
|
||||
:rtype: str
|
||||
"""
|
||||
proxy = select_proxy(request.url, proxies)
|
||||
scheme = urlparse(request.url).scheme
|
||||
@@ -357,6 +361,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param proxies: The url of the proxy being used for this request.
|
||||
:rtype: dict
|
||||
"""
|
||||
headers = {}
|
||||
username, password = get_auth_from_url(proxy)
|
||||
@@ -379,6 +384,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param verify: (optional) Whether to verify SSL certificates.
|
||||
:param cert: (optional) Any user-provided SSL certificate to be trusted.
|
||||
:param proxies: (optional) The proxies dictionary to apply to the request.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
conn = self.get_connection(request.url, proxies)
|
||||
|
||||
@@ -8,7 +8,6 @@ This module implements the Requests API.
|
||||
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: Apache2, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
from . import sessions
|
||||
|
||||
+11
-1
@@ -43,6 +43,7 @@ class AuthBase(object):
|
||||
|
||||
class HTTPBasicAuth(AuthBase):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object."""
|
||||
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
@@ -63,6 +64,7 @@ class HTTPBasicAuth(AuthBase):
|
||||
|
||||
class HTTPProxyAuth(HTTPBasicAuth):
|
||||
"""Attaches HTTP Proxy Authentication to a given Request object."""
|
||||
|
||||
def __call__(self, r):
|
||||
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
@@ -70,6 +72,7 @@ class HTTPProxyAuth(HTTPBasicAuth):
|
||||
|
||||
class HTTPDigestAuth(AuthBase):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object."""
|
||||
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
@@ -87,6 +90,9 @@ class HTTPDigestAuth(AuthBase):
|
||||
self._thread_local.num_401_calls = None
|
||||
|
||||
def build_digest_header(self, method, url):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
realm = self._thread_local.chal['realm']
|
||||
nonce = self._thread_local.chal['nonce']
|
||||
@@ -179,7 +185,11 @@ class HTTPDigestAuth(AuthBase):
|
||||
self._thread_local.num_401_calls = 1
|
||||
|
||||
def handle_401(self, r, **kwargs):
|
||||
"""Takes the given response and tries digest-auth, if needed."""
|
||||
"""
|
||||
Takes the given response and tries digest-auth, if needed.
|
||||
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
if self._thread_local.pos is not None:
|
||||
# Rewind the file position indicator of the body to where
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
certs.py
|
||||
~~~~~~~~
|
||||
requests.certs
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module returns the preferred default CA certificate bundle.
|
||||
|
||||
|
||||
+5
-1
@@ -1,7 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
pythoncompat
|
||||
requests.compat
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This module handles import compatibility issues between Python 2 and
|
||||
Python 3.
|
||||
"""
|
||||
|
||||
from .packages import chardet
|
||||
|
||||
+75
-28
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.cookies
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Compatibility code to be able to use `cookielib.CookieJar` with requests.
|
||||
|
||||
requests.utils imports from here, so be careful with imports.
|
||||
@@ -131,7 +134,11 @@ def extract_cookies_to_jar(jar, request, response):
|
||||
|
||||
|
||||
def get_cookie_header(jar, request):
|
||||
"""Produce an appropriate Cookie header string to be sent with `request`, or None."""
|
||||
"""
|
||||
Produce an appropriate Cookie header string to be sent with `request`, or None.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
r = MockRequest(request)
|
||||
jar.add_cookie_header(r)
|
||||
return r.get_new_headers().get('Cookie')
|
||||
@@ -158,7 +165,8 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
||||
|
||||
class CookieConflictError(RuntimeError):
|
||||
"""There are two cookies that meet the criteria specified in the cookie jar.
|
||||
Use .get and .set and include domain and path args in order to be more specific."""
|
||||
Use .get and .set and include domain and path args in order to be more specific.
|
||||
"""
|
||||
|
||||
|
||||
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
@@ -178,12 +186,14 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
|
||||
.. warning:: dictionary operations that are normally O(1) may be O(n).
|
||||
"""
|
||||
|
||||
def get(self, name, default=None, domain=None, path=None):
|
||||
"""Dict-like get() that also supports optional domain and path args in
|
||||
order to resolve naming collisions from using one cookie jar over
|
||||
multiple domains.
|
||||
|
||||
.. warning:: operation is O(n), not O(1)."""
|
||||
.. warning:: operation is O(n), not O(1).
|
||||
"""
|
||||
try:
|
||||
return self._find_no_duplicates(name, domain, path)
|
||||
except KeyError:
|
||||
@@ -192,7 +202,8 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
def set(self, name, value, **kwargs):
|
||||
"""Dict-like set() that also supports optional domain and path args in
|
||||
order to resolve naming collisions from using one cookie jar over
|
||||
multiple domains."""
|
||||
multiple domains.
|
||||
"""
|
||||
# support client code that unsets cookies by assignment of a None value:
|
||||
if value is None:
|
||||
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
|
||||
@@ -207,37 +218,54 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
|
||||
def iterkeys(self):
|
||||
"""Dict-like iterkeys() that returns an iterator of names of cookies
|
||||
from the jar. See itervalues() and iteritems()."""
|
||||
from the jar.
|
||||
|
||||
.. seealso:: itervalues() and iteritems().
|
||||
"""
|
||||
for cookie in iter(self):
|
||||
yield cookie.name
|
||||
|
||||
def keys(self):
|
||||
"""Dict-like keys() that returns a list of names of cookies from the
|
||||
jar. See values() and items()."""
|
||||
jar.
|
||||
|
||||
.. seealso:: values() and items().
|
||||
"""
|
||||
return list(self.iterkeys())
|
||||
|
||||
def itervalues(self):
|
||||
"""Dict-like itervalues() that returns an iterator of values of cookies
|
||||
from the jar. See iterkeys() and iteritems()."""
|
||||
from the jar.
|
||||
|
||||
.. seealso:: iterkeys() and iteritems().
|
||||
"""
|
||||
for cookie in iter(self):
|
||||
yield cookie.value
|
||||
|
||||
def values(self):
|
||||
"""Dict-like values() that returns a list of values of cookies from the
|
||||
jar. See keys() and items()."""
|
||||
jar.
|
||||
|
||||
.. seealso:: keys() and items().
|
||||
"""
|
||||
return list(self.itervalues())
|
||||
|
||||
def iteritems(self):
|
||||
"""Dict-like iteritems() that returns an iterator of name-value tuples
|
||||
from the jar. See iterkeys() and itervalues()."""
|
||||
from the jar.
|
||||
|
||||
.. seealso:: iterkeys() and itervalues().
|
||||
"""
|
||||
for cookie in iter(self):
|
||||
yield cookie.name, cookie.value
|
||||
|
||||
def items(self):
|
||||
"""Dict-like items() that returns a list of name-value tuples from the
|
||||
jar. See keys() and values(). Allows client-code to call
|
||||
``dict(RequestsCookieJar)`` and get a vanilla python dict of key value
|
||||
pairs."""
|
||||
jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
|
||||
vanilla python dict of key value pairs.
|
||||
|
||||
.. seealso:: keys() and values().
|
||||
"""
|
||||
return list(self.iteritems())
|
||||
|
||||
def list_domains(self):
|
||||
@@ -258,7 +286,10 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
|
||||
def multiple_domains(self):
|
||||
"""Returns True if there are multiple domains in the jar.
|
||||
Returns False otherwise."""
|
||||
Returns False otherwise.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
domains = []
|
||||
for cookie in iter(self):
|
||||
if cookie.domain is not None and cookie.domain in domains:
|
||||
@@ -269,7 +300,10 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
def get_dict(self, domain=None, path=None):
|
||||
"""Takes as an argument an optional domain and path and returns a plain
|
||||
old Python dict of name-value pairs of cookies that meet the
|
||||
requirements."""
|
||||
requirements.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
dictionary = {}
|
||||
for cookie in iter(self):
|
||||
if (domain is None or cookie.domain == domain) and (path is None
|
||||
@@ -288,20 +322,21 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
exception if there are more than one cookie with name. In that case,
|
||||
use the more explicit get() method instead.
|
||||
|
||||
.. warning:: operation is O(n), not O(1)."""
|
||||
|
||||
.. warning:: operation is O(n), not O(1).
|
||||
"""
|
||||
return self._find_no_duplicates(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
"""Dict-like __setitem__ for compatibility with client code. Throws
|
||||
exception if there is already a cookie of that name in the jar. In that
|
||||
case, use the more explicit set() method instead."""
|
||||
|
||||
case, use the more explicit set() method instead.
|
||||
"""
|
||||
self.set(name, value)
|
||||
|
||||
def __delitem__(self, name):
|
||||
"""Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
|
||||
``remove_cookie_by_name()``."""
|
||||
``remove_cookie_by_name()``.
|
||||
"""
|
||||
remove_cookie_by_name(self, name)
|
||||
|
||||
def set_cookie(self, cookie, *args, **kwargs):
|
||||
@@ -318,11 +353,17 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
super(RequestsCookieJar, self).update(other)
|
||||
|
||||
def _find(self, name, domain=None, path=None):
|
||||
"""Requests uses this method internally to get cookie values. Takes as
|
||||
args name and optional domain and path. Returns a cookie.value. If
|
||||
there are conflicting cookies, _find arbitrarily chooses one. See
|
||||
_find_no_duplicates if you want an exception thrown if there are
|
||||
conflicting cookies."""
|
||||
"""Requests uses this method internally to get cookie values.
|
||||
|
||||
If there are conflicting cookies, _find arbitrarily chooses one.
|
||||
See _find_no_duplicates if you want an exception thrown if there are
|
||||
conflicting cookies.
|
||||
|
||||
:param name: a string containing name of cookie
|
||||
:param domain: (optional) string containing domain of cookie
|
||||
:param path: (optional) string containing path of cookie
|
||||
:return: cookie.value
|
||||
"""
|
||||
for cookie in iter(self):
|
||||
if cookie.name == name:
|
||||
if domain is None or cookie.domain == domain:
|
||||
@@ -333,10 +374,16 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
|
||||
def _find_no_duplicates(self, name, domain=None, path=None):
|
||||
"""Both ``__get_item__`` and ``get`` call this function: it's never
|
||||
used elsewhere in Requests. Takes as args name and optional domain and
|
||||
path. Returns a cookie.value. Throws KeyError if cookie is not found
|
||||
and CookieConflictError if there are multiple cookies that match name
|
||||
and optionally domain and path."""
|
||||
used elsewhere in Requests.
|
||||
|
||||
:param name: a string containing name of cookie
|
||||
:param domain: (optional) string containing domain of cookie
|
||||
:param path: (optional) string containing path of cookie
|
||||
:raises KeyError: if cookie is not found
|
||||
:raises CookieConflictError: if there are multiple cookies
|
||||
that match name and optionally domain and path
|
||||
:return: cookie.value
|
||||
"""
|
||||
toReturn = None
|
||||
for cookie in iter(self):
|
||||
if cookie.name == name:
|
||||
|
||||
@@ -5,19 +5,17 @@ requests.exceptions
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains the set of Requests' exceptions.
|
||||
|
||||
"""
|
||||
from .packages.urllib3.exceptions import HTTPError as BaseHTTPError
|
||||
|
||||
|
||||
class RequestException(IOError):
|
||||
"""There was an ambiguous exception that occurred while handling your
|
||||
request."""
|
||||
request.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize RequestException with `request` and `response` objects.
|
||||
"""
|
||||
"""Initialize RequestException with `request` and `response` objects."""
|
||||
response = kwargs.pop('response', None)
|
||||
self.response = response
|
||||
self.request = kwargs.pop('request', None)
|
||||
@@ -80,7 +78,11 @@ class InvalidScheme(RequestException, ValueError):
|
||||
|
||||
|
||||
class InvalidURL(RequestException, ValueError):
|
||||
""" The URL provided was somehow invalid. """
|
||||
"""The URL provided was somehow invalid."""
|
||||
|
||||
|
||||
class InvalidHeader(RequestException, ValueError):
|
||||
"""The header value provided was somehow invalid."""
|
||||
|
||||
|
||||
class InvalidHeader(RequestException, ValueError):
|
||||
@@ -112,7 +114,5 @@ class RequestsWarning(Warning):
|
||||
|
||||
|
||||
class FileModeWarning(RequestsWarning, DeprecationWarning):
|
||||
"""
|
||||
A file was opened in text mode, but Requests determined its binary length.
|
||||
"""
|
||||
"""A file was opened in text mode, but Requests determined its binary length."""
|
||||
pass
|
||||
|
||||
+1
-1
@@ -10,10 +10,10 @@ Available hooks:
|
||||
|
||||
``response``:
|
||||
The response generated from a Request.
|
||||
|
||||
"""
|
||||
HOOKS = ['response']
|
||||
|
||||
|
||||
def default_hooks():
|
||||
return dict((event, []) for event in HOOKS)
|
||||
|
||||
|
||||
+41
-18
@@ -28,7 +28,8 @@ from .exceptions import (
|
||||
from .utils import (
|
||||
guess_filename, get_auth_from_url, requote_uri,
|
||||
stream_decode_response_unicode, to_key_val_list, parse_header_links,
|
||||
iter_slices, guess_json_utf, super_len, to_native_string)
|
||||
iter_slices, guess_json_utf, super_len, to_native_string,
|
||||
check_header_validity)
|
||||
from .compat import (
|
||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
|
||||
is_py2, chardet, builtin_str, basestring)
|
||||
@@ -38,11 +39,11 @@ from .status_codes import codes
|
||||
#: The set of HTTP status codes that indicate an automatically
|
||||
#: processable redirect.
|
||||
REDIRECT_STATI = (
|
||||
codes.moved, # 301
|
||||
codes.found, # 302
|
||||
codes.other, # 303
|
||||
codes.temporary_redirect, # 307
|
||||
codes.permanent_redirect, # 308
|
||||
codes.moved, # 301
|
||||
codes.found, # 302
|
||||
codes.other, # 303
|
||||
codes.temporary_redirect, # 307
|
||||
codes.permanent_redirect, # 308
|
||||
)
|
||||
|
||||
DEFAULT_REDIRECT_LIMIT = 30
|
||||
@@ -108,7 +109,6 @@ class RequestEncodingMixin(object):
|
||||
if parameters are supplied as a dict.
|
||||
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
|
||||
or 4-tuples (filename, fileobj, contentype, custom_headers).
|
||||
|
||||
"""
|
||||
if (not files):
|
||||
raise ValueError("Files must be provided.")
|
||||
@@ -207,8 +207,8 @@ class Request(RequestHooksMixin):
|
||||
>>> req = requests.Request('GET', 'http://httpbin.org/get')
|
||||
>>> req.prepare()
|
||||
<PreparedRequest [GET]>
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, method=None, url=None, headers=None, files=None,
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||
|
||||
@@ -270,7 +270,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
>>> s = requests.Session()
|
||||
>>> s.send(r)
|
||||
<Response [200]>
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -408,10 +407,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
def prepare_headers(self, headers):
|
||||
"""Prepares the given HTTP headers."""
|
||||
|
||||
self.headers = CaseInsensitiveDict()
|
||||
if headers:
|
||||
self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items())
|
||||
else:
|
||||
self.headers = CaseInsensitiveDict()
|
||||
for header in headers.items():
|
||||
# Raise exception on invalid header value.
|
||||
check_header_validity(header)
|
||||
name, value = header
|
||||
self.headers[to_native_string(name)] = value
|
||||
|
||||
def prepare_body(self, data, files, json=None):
|
||||
"""Prepares the given HTTP body data."""
|
||||
@@ -517,8 +519,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
can only be called once for the life of the
|
||||
:class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
|
||||
to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
|
||||
header is removed beforehand."""
|
||||
|
||||
header is removed beforehand.
|
||||
"""
|
||||
if isinstance(cookies, cookielib.CookieJar):
|
||||
self._cookies = cookies
|
||||
else:
|
||||
@@ -662,6 +664,12 @@ class Response(object):
|
||||
read into memory. This is not necessarily the length of each item
|
||||
returned as decoding can take place.
|
||||
|
||||
chunk_size must be of type int or None. A value of None will
|
||||
function differently depending on the value of `stream`.
|
||||
stream=True will read data as it arrives in whatever size the
|
||||
chunks are received. If stream=False, data is returned as
|
||||
a single chunk.
|
||||
|
||||
If decode_unicode is True, content will be decoded using the best
|
||||
available encoding based on the response.
|
||||
"""
|
||||
@@ -690,6 +698,8 @@ class Response(object):
|
||||
|
||||
if self._content_consumed and isinstance(self._content, bool):
|
||||
raise StreamConsumedError()
|
||||
elif chunk_size is not None and not isinstance(chunk_size, int):
|
||||
raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size))
|
||||
# simulate reading small chunks of the content
|
||||
reused_chunks = iter_slices(self._content, chunk_size)
|
||||
|
||||
@@ -842,12 +852,23 @@ class Response(object):
|
||||
"""Raises stored :class:`HTTPError`, if one occurred."""
|
||||
|
||||
http_error_msg = ''
|
||||
if isinstance(self.reason, bytes):
|
||||
# We attempt to decode utf-8 first because some servers
|
||||
# choose to localize their reason strings. If the string
|
||||
# isn't utf-8, we fall back to iso-8859-1 for all other
|
||||
# encodings. (See PR #3538)
|
||||
try:
|
||||
reason = self.reason.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
reason = self.reason.decode('iso-8859-1')
|
||||
else:
|
||||
reason = self.reason
|
||||
|
||||
if 400 <= self.status_code < 500:
|
||||
http_error_msg = '%s Client Error: %s for url: %s' % (self.status_code, self.reason, self.url)
|
||||
http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url)
|
||||
|
||||
elif 500 <= self.status_code < 600:
|
||||
http_error_msg = '%s Server Error: %s for url: %s' % (self.status_code, self.reason, self.url)
|
||||
http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url)
|
||||
|
||||
if http_error_msg:
|
||||
raise HTTPError(http_error_msg, response=self)
|
||||
@@ -859,6 +880,8 @@ class Response(object):
|
||||
*Note: Should not normally need to be called explicitly.*
|
||||
"""
|
||||
if not self._content_consumed:
|
||||
return self.raw.close()
|
||||
self.raw.close()
|
||||
|
||||
return self.raw.release_conn()
|
||||
release_conn = getattr(self.raw, 'release_conn', None)
|
||||
if release_conn is not None:
|
||||
release_conn()
|
||||
|
||||
@@ -32,7 +32,7 @@ except ImportError:
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = '1.15.1'
|
||||
__version__ = '1.16'
|
||||
|
||||
__all__ = (
|
||||
'HTTPConnectionPool',
|
||||
|
||||
@@ -90,7 +90,7 @@ class ConnectionPool(object):
|
||||
# Return False to re-raise any potential exceptions
|
||||
return False
|
||||
|
||||
def close():
|
||||
def close(self):
|
||||
"""
|
||||
Close all pooled connections and disable the pool.
|
||||
"""
|
||||
@@ -163,6 +163,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
scheme = 'http'
|
||||
ConnectionCls = HTTPConnection
|
||||
ResponseCls = HTTPResponse
|
||||
|
||||
def __init__(self, host, port=None, strict=False,
|
||||
timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False,
|
||||
@@ -383,8 +384,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
try:
|
||||
try: # Python 2.7, use buffering of HTTP responses
|
||||
httplib_response = conn.getresponse(buffering=True)
|
||||
except TypeError: # Python 2.6 and older
|
||||
httplib_response = conn.getresponse()
|
||||
except TypeError: # Python 2.6 and older, Python 3
|
||||
try:
|
||||
httplib_response = conn.getresponse()
|
||||
except Exception as e:
|
||||
# Remove the TypeError from the exception chain in Python 3;
|
||||
# otherwise it looks like a programming error was the cause.
|
||||
six.raise_from(e, None)
|
||||
except (SocketTimeout, BaseSSLError, SocketError) as e:
|
||||
self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
|
||||
raise
|
||||
@@ -545,6 +551,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
conn = None
|
||||
|
||||
# Track whether `conn` needs to be released before
|
||||
# returning/raising/recursing. Update this variable if necessary, and
|
||||
# leave `release_conn` constant throughout the function. That way, if
|
||||
# the function recurses, the original value of `release_conn` will be
|
||||
# passed down into the recursive call, and its value will be respected.
|
||||
#
|
||||
# See issue #651 [1] for details.
|
||||
#
|
||||
# [1] <https://github.com/shazow/urllib3/issues/651>
|
||||
release_this_conn = release_conn
|
||||
|
||||
# Merge the proxy headers. Only do this in HTTP. We have to copy the
|
||||
# headers dict so we can safely change it without those changes being
|
||||
# reflected in anyone else's copy.
|
||||
@@ -584,10 +601,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
response_conn = conn if not release_conn else None
|
||||
|
||||
# Import httplib's response into our own wrapper object
|
||||
response = HTTPResponse.from_httplib(httplib_response,
|
||||
pool=self,
|
||||
connection=response_conn,
|
||||
**response_kw)
|
||||
response = self.ResponseCls.from_httplib(httplib_response,
|
||||
pool=self,
|
||||
connection=response_conn,
|
||||
**response_kw)
|
||||
|
||||
# Everything went great!
|
||||
clean_exit = True
|
||||
@@ -633,9 +650,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
# Close the connection, set the variable to None, and make sure
|
||||
# we put the None back in the pool to avoid leaking it.
|
||||
conn = conn and conn.close()
|
||||
release_conn = True
|
||||
release_this_conn = True
|
||||
|
||||
if release_conn:
|
||||
if release_this_conn:
|
||||
# Put the connection back to be reused. If the connection is
|
||||
# expired then it will be None, which will get replaced with a
|
||||
# fresh connection during _get_conn.
|
||||
@@ -817,7 +834,7 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
warnings.warn((
|
||||
'Unverified HTTPS request is being made. '
|
||||
'Adding certificate verification is strongly advised. See: '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'),
|
||||
'https://urllib3.readthedocs.io/en/latest/security.html'),
|
||||
InsecureRequestWarning)
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class AppEngineManager(RequestMethods):
|
||||
warnings.warn(
|
||||
"urllib3 is using URLFetch on Google App Engine sandbox instead "
|
||||
"of sockets. To use sockets directly instead of URLFetch see "
|
||||
"https://urllib3.readthedocs.org/en/latest/contrib.html.",
|
||||
"https://urllib3.readthedocs.io/en/latest/contrib.html.",
|
||||
AppEnginePlatformWarning)
|
||||
|
||||
RequestMethods.__init__(self, headers)
|
||||
|
||||
@@ -26,7 +26,7 @@ except ImportError:
|
||||
warnings.warn((
|
||||
'SOCKS support in urllib3 requires the installation of optional '
|
||||
'dependencies: specifically, PySocks. For more information, see '
|
||||
'https://urllib3.readthedocs.org/en/latest/contrib.html#socks-proxies'
|
||||
'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies'
|
||||
),
|
||||
DependencyWarning
|
||||
)
|
||||
|
||||
@@ -1,34 +1,41 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
#Copyright (c) 2010-2011 Benjamin Peterson
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
#this software and associated documentation files (the "Software"), to deal in
|
||||
#the Software without restriction, including without limitation the rights to
|
||||
#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
#the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
#subject to the following conditions:
|
||||
|
||||
#The above copyright notice and this permission notice shall be included in all
|
||||
#copies or substantial portions of the Software.
|
||||
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.2.0" # Revision 41c74fef2ded
|
||||
__version__ = "1.10.0"
|
||||
|
||||
|
||||
# True if we are running on Python 3.
|
||||
# Useful for very coarse version differentiation.
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY34 = sys.version_info[0:2] >= (3, 4)
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
@@ -51,6 +58,7 @@ else:
|
||||
else:
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
@@ -61,7 +69,7 @@ else:
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
@@ -82,9 +90,13 @@ class _LazyDescr(object):
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result)
|
||||
# This is a bit ugly, but it avoids running this again.
|
||||
delattr(tp, self.name)
|
||||
setattr(obj, self.name, result) # Invokes __set__.
|
||||
try:
|
||||
# This is a bit ugly, but it avoids running this again by
|
||||
# removing this descriptor.
|
||||
delattr(obj.__class__, self.name)
|
||||
except AttributeError:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
@@ -102,6 +114,27 @@ class MovedModule(_LazyDescr):
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
_module = self._resolve()
|
||||
value = getattr(_module, attr)
|
||||
setattr(self, attr, value)
|
||||
return value
|
||||
|
||||
|
||||
class _LazyModule(types.ModuleType):
|
||||
|
||||
def __init__(self, name):
|
||||
super(_LazyModule, self).__init__(name)
|
||||
self.__doc__ = self.__class__.__doc__
|
||||
|
||||
def __dir__(self):
|
||||
attrs = ["__doc__", "__name__"]
|
||||
attrs += [attr.name for attr in self._moved_attributes]
|
||||
return attrs
|
||||
|
||||
# Subclasses should override this
|
||||
_moved_attributes = []
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
@@ -128,30 +161,111 @@ class MovedAttribute(_LazyDescr):
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
class _SixMetaPathImporter(object):
|
||||
|
||||
"""
|
||||
A meta path importer to import six.moves and its submodules.
|
||||
|
||||
This class implements a PEP302 finder and loader. It should be compatible
|
||||
with Python 2.5 and all existing versions of Python3
|
||||
"""
|
||||
|
||||
def __init__(self, six_module_name):
|
||||
self.name = six_module_name
|
||||
self.known_modules = {}
|
||||
|
||||
def _add_module(self, mod, *fullnames):
|
||||
for fullname in fullnames:
|
||||
self.known_modules[self.name + "." + fullname] = mod
|
||||
|
||||
def _get_module(self, fullname):
|
||||
return self.known_modules[self.name + "." + fullname]
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname in self.known_modules:
|
||||
return self
|
||||
return None
|
||||
|
||||
def __get_module(self, fullname):
|
||||
try:
|
||||
return self.known_modules[fullname]
|
||||
except KeyError:
|
||||
raise ImportError("This loader does not know module " + fullname)
|
||||
|
||||
def load_module(self, fullname):
|
||||
try:
|
||||
# in case of a reload
|
||||
return sys.modules[fullname]
|
||||
except KeyError:
|
||||
pass
|
||||
mod = self.__get_module(fullname)
|
||||
if isinstance(mod, MovedModule):
|
||||
mod = mod._resolve()
|
||||
else:
|
||||
mod.__loader__ = self
|
||||
sys.modules[fullname] = mod
|
||||
return mod
|
||||
|
||||
def is_package(self, fullname):
|
||||
"""
|
||||
Return true, if the named module is a package.
|
||||
|
||||
We need this method to get correct spec objects with
|
||||
Python 3.4 (see PEP451)
|
||||
"""
|
||||
return hasattr(self.__get_module(fullname), "__path__")
|
||||
|
||||
def get_code(self, fullname):
|
||||
"""Return None
|
||||
|
||||
Required, if is_package is implemented"""
|
||||
self.__get_module(fullname) # eventually raises ImportError
|
||||
return None
|
||||
get_source = get_code # same as get_code
|
||||
|
||||
_importer = _SixMetaPathImporter(__name__)
|
||||
|
||||
|
||||
class _MovedItems(_LazyModule):
|
||||
|
||||
class _MovedItems(types.ModuleType):
|
||||
"""Lazy loading of moved objects"""
|
||||
__path__ = [] # mark as package
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
|
||||
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
|
||||
MovedAttribute("intern", "__builtin__", "sys"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
|
||||
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
||||
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("UserDict", "UserDict", "collections"),
|
||||
MovedAttribute("UserList", "UserList", "collections"),
|
||||
MovedAttribute("UserString", "UserString", "collections"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
|
||||
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
|
||||
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
@@ -159,12 +273,14 @@ _moved_attributes = [
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("_thread", "thread", "_thread"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
@@ -176,14 +292,195 @@ _moved_attributes = [
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
|
||||
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
|
||||
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("winreg", "_winreg"),
|
||||
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
||||
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
|
||||
]
|
||||
# Add windows specific modules.
|
||||
if sys.platform == "win32":
|
||||
_moved_attributes += [
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
if isinstance(attr, MovedModule):
|
||||
_importer._add_module(attr, "moves." + attr.name)
|
||||
del attr
|
||||
|
||||
moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
|
||||
_MovedItems._moved_attributes = _moved_attributes
|
||||
|
||||
moves = _MovedItems(__name__ + ".moves")
|
||||
_importer._add_module(moves, "moves")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_parse(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_parse"""
|
||||
|
||||
|
||||
_urllib_parse_moved_attributes = [
|
||||
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("quote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
||||
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
|
||||
]
|
||||
for attr in _urllib_parse_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
|
||||
"moves.urllib_parse", "moves.urllib.parse")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_error(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_error"""
|
||||
|
||||
|
||||
_urllib_error_moved_attributes = [
|
||||
MovedAttribute("URLError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
|
||||
]
|
||||
for attr in _urllib_error_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_error, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
|
||||
"moves.urllib_error", "moves.urllib.error")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_request(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_request"""
|
||||
|
||||
|
||||
_urllib_request_moved_attributes = [
|
||||
MovedAttribute("urlopen", "urllib2", "urllib.request"),
|
||||
MovedAttribute("install_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("build_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("pathname2url", "urllib", "urllib.request"),
|
||||
MovedAttribute("url2pathname", "urllib", "urllib.request"),
|
||||
MovedAttribute("getproxies", "urllib", "urllib.request"),
|
||||
MovedAttribute("Request", "urllib2", "urllib.request"),
|
||||
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
|
||||
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
|
||||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
|
||||
"moves.urllib_request", "moves.urllib.request")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_response(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_response"""
|
||||
|
||||
|
||||
_urllib_response_moved_attributes = [
|
||||
MovedAttribute("addbase", "urllib", "urllib.response"),
|
||||
MovedAttribute("addclosehook", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfo", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfourl", "urllib", "urllib.response"),
|
||||
]
|
||||
for attr in _urllib_response_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_response, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
|
||||
"moves.urllib_response", "moves.urllib.response")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_robotparser(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
|
||||
|
||||
|
||||
_urllib_robotparser_moved_attributes = [
|
||||
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
|
||||
]
|
||||
for attr in _urllib_robotparser_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
|
||||
"moves.urllib_robotparser", "moves.urllib.robotparser")
|
||||
|
||||
|
||||
class Module_six_moves_urllib(types.ModuleType):
|
||||
|
||||
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
|
||||
__path__ = [] # mark as package
|
||||
parse = _importer._get_module("moves.urllib_parse")
|
||||
error = _importer._get_module("moves.urllib_error")
|
||||
request = _importer._get_module("moves.urllib_request")
|
||||
response = _importer._get_module("moves.urllib_response")
|
||||
robotparser = _importer._get_module("moves.urllib_robotparser")
|
||||
|
||||
def __dir__(self):
|
||||
return ['parse', 'error', 'request', 'response', 'robotparser']
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
|
||||
"moves.urllib")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
@@ -206,22 +503,18 @@ if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_closure = "__closure__"
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
|
||||
_iterkeys = "keys"
|
||||
_itervalues = "values"
|
||||
_iteritems = "items"
|
||||
_func_globals = "__globals__"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_closure = "func_closure"
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
|
||||
_iterkeys = "iterkeys"
|
||||
_itervalues = "itervalues"
|
||||
_iteritems = "iteritems"
|
||||
_func_globals = "func_globals"
|
||||
|
||||
|
||||
try:
|
||||
@@ -232,18 +525,33 @@ except NameError:
|
||||
next = advance_iterator
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
Iterator = object
|
||||
create_bound_method = types.MethodType
|
||||
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
def create_unbound_method(func, cls):
|
||||
return func
|
||||
|
||||
Iterator = object
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
def create_bound_method(func, obj):
|
||||
return types.MethodType(func, obj, obj.__class__)
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return types.MethodType(func, None, cls)
|
||||
|
||||
class Iterator(object):
|
||||
|
||||
def next(self):
|
||||
@@ -256,90 +564,179 @@ _add_doc(get_unbound_function,
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_closure = operator.attrgetter(_func_closure)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
get_function_globals = operator.attrgetter(_func_globals)
|
||||
|
||||
|
||||
def iterkeys(d):
|
||||
"""Return an iterator over the keys of a dictionary."""
|
||||
return iter(getattr(d, _iterkeys)())
|
||||
if PY3:
|
||||
def iterkeys(d, **kw):
|
||||
return iter(d.keys(**kw))
|
||||
|
||||
def itervalues(d):
|
||||
"""Return an iterator over the values of a dictionary."""
|
||||
return iter(getattr(d, _itervalues)())
|
||||
def itervalues(d, **kw):
|
||||
return iter(d.values(**kw))
|
||||
|
||||
def iteritems(d):
|
||||
"""Return an iterator over the (key, value) pairs of a dictionary."""
|
||||
return iter(getattr(d, _iteritems)())
|
||||
def iteritems(d, **kw):
|
||||
return iter(d.items(**kw))
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return iter(d.lists(**kw))
|
||||
|
||||
viewkeys = operator.methodcaller("keys")
|
||||
|
||||
viewvalues = operator.methodcaller("values")
|
||||
|
||||
viewitems = operator.methodcaller("items")
|
||||
else:
|
||||
def iterkeys(d, **kw):
|
||||
return d.iterkeys(**kw)
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return d.itervalues(**kw)
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return d.iteritems(**kw)
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return d.iterlists(**kw)
|
||||
|
||||
viewkeys = operator.methodcaller("viewkeys")
|
||||
|
||||
viewvalues = operator.methodcaller("viewvalues")
|
||||
|
||||
viewitems = operator.methodcaller("viewitems")
|
||||
|
||||
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
|
||||
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
|
||||
_add_doc(iteritems,
|
||||
"Return an iterator over the (key, value) pairs of a dictionary.")
|
||||
_add_doc(iterlists,
|
||||
"Return an iterator over the (key, [values]) pairs of a dictionary.")
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
if sys.version_info[1] <= 1:
|
||||
def int2byte(i):
|
||||
return bytes((i,))
|
||||
else:
|
||||
# This is about 2x faster than the implementation above on 3.2+
|
||||
int2byte = operator.methodcaller("to_bytes", 1, "big")
|
||||
unichr = chr
|
||||
import struct
|
||||
int2byte = struct.Struct(">B").pack
|
||||
del struct
|
||||
byte2int = operator.itemgetter(0)
|
||||
indexbytes = operator.getitem
|
||||
iterbytes = iter
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
_assertCountEqual = "assertCountEqual"
|
||||
if sys.version_info[1] <= 1:
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
else:
|
||||
_assertRaisesRegex = "assertRaisesRegex"
|
||||
_assertRegex = "assertRegex"
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
# Workaround for standalone backslash
|
||||
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
|
||||
unichr = unichr
|
||||
int2byte = chr
|
||||
|
||||
def byte2int(bs):
|
||||
return ord(bs[0])
|
||||
|
||||
def indexbytes(buf, i):
|
||||
return ord(buf[i])
|
||||
iterbytes = functools.partial(itertools.imap, ord)
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_assertCountEqual = "assertItemsEqual"
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
if PY3:
|
||||
import builtins
|
||||
exec_ = getattr(builtins, "exec")
|
||||
def assertCountEqual(self, *args, **kwargs):
|
||||
return getattr(self, _assertCountEqual)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRaisesRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
if PY3:
|
||||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(builtins, "print")
|
||||
del builtins
|
||||
|
||||
else:
|
||||
def exec_(code, globs=None, locs=None):
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
"""Execute code in a namespace."""
|
||||
if globs is None:
|
||||
if _globs_ is None:
|
||||
frame = sys._getframe(1)
|
||||
globs = frame.f_globals
|
||||
if locs is None:
|
||||
locs = frame.f_locals
|
||||
_globs_ = frame.f_globals
|
||||
if _locs_ is None:
|
||||
_locs_ = frame.f_locals
|
||||
del frame
|
||||
elif locs is None:
|
||||
locs = globs
|
||||
exec("""exec code in globs, locs""")
|
||||
|
||||
elif _locs_ is None:
|
||||
_locs_ = _globs_
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
if sys.version_info[:2] == (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
if from_value is None:
|
||||
raise value
|
||||
raise value from from_value
|
||||
""")
|
||||
elif sys.version_info[:2] > (3, 2):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
raise value from from_value
|
||||
""")
|
||||
else:
|
||||
def raise_from(value, from_value):
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(moves.builtins, "print", None)
|
||||
if print_ is None:
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function."""
|
||||
"""The new-style print function for Python 2.4 and 2.5."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
# If the file has an encoding, encode unicode with it.
|
||||
if (isinstance(fp, file) and
|
||||
isinstance(data, unicode) and
|
||||
fp.encoding is not None):
|
||||
errors = getattr(fp, "errors", None)
|
||||
if errors is None:
|
||||
errors = "strict"
|
||||
data = data.encode(fp.encoding, errors)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
@@ -376,10 +773,96 @@ else:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
if sys.version_info[:2] < (3, 3):
|
||||
_print = print_
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
fp = kwargs.get("file", sys.stdout)
|
||||
flush = kwargs.pop("flush", False)
|
||||
_print(*args, **kwargs)
|
||||
if flush and fp is not None:
|
||||
fp.flush()
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
if sys.version_info[0:2] < (3, 4):
|
||||
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
|
||||
updated=functools.WRAPPER_UPDATES):
|
||||
def wrapper(f):
|
||||
f = functools.wraps(wrapped, assigned, updated)(f)
|
||||
f.__wrapped__ = wrapped
|
||||
return f
|
||||
return wrapper
|
||||
else:
|
||||
wraps = functools.wraps
|
||||
|
||||
def with_metaclass(meta, base=object):
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""Create a base class with a metaclass."""
|
||||
return meta("NewBase", (base,), {})
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta):
|
||||
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
def add_metaclass(metaclass):
|
||||
"""Class decorator for creating a class with a metaclass."""
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
||||
|
||||
def python_2_unicode_compatible(klass):
|
||||
"""
|
||||
A decorator that defines __unicode__ and __str__ methods under Python 2.
|
||||
Under Python 3 it does nothing.
|
||||
|
||||
To support Python 2 and 3 with a single code base, define a __str__ method
|
||||
returning text and apply this decorator to the class.
|
||||
"""
|
||||
if PY2:
|
||||
if '__str__' not in klass.__dict__:
|
||||
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
||||
"to %s because it doesn't define __str__()." %
|
||||
klass.__name__)
|
||||
klass.__unicode__ = klass.__str__
|
||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
||||
return klass
|
||||
|
||||
|
||||
# Complete the moves implementation.
|
||||
# This code is at the end of this module to speed up module loading.
|
||||
# Turn this module into a package.
|
||||
__path__ = [] # required for PEP 302 and PEP 451
|
||||
__package__ = __name__ # see PEP 366 @ReservedAssignment
|
||||
if globals().get("__spec__") is not None:
|
||||
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
|
||||
# Remove other six meta path importers, since they cause problems. This can
|
||||
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
|
||||
# this for some reason.)
|
||||
if sys.meta_path:
|
||||
for i, importer in enumerate(sys.meta_path):
|
||||
# Here's some real nastiness: Another "instance" of the six module might
|
||||
# be floating around. Therefore, we can't use isinstance() to check for
|
||||
# the six meta path importer, since the other six instance will have
|
||||
# inserted an importer with different class.
|
||||
if (type(importer).__name__ == "_SixMetaPathImporter" and
|
||||
importer.name == __name__):
|
||||
del sys.meta_path[i]
|
||||
break
|
||||
del i, importer
|
||||
# Finally, add the importer to the meta path import hook.
|
||||
sys.meta_path.append(_importer)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
env
|
||||
@@ -1,4 +1,6 @@
|
||||
from __future__ import absolute_import
|
||||
import collections
|
||||
import functools
|
||||
import logging
|
||||
|
||||
try: # Python 3
|
||||
@@ -23,6 +25,59 @@ log = logging.getLogger(__name__)
|
||||
SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs',
|
||||
'ssl_version', 'ca_cert_dir')
|
||||
|
||||
# The base fields to use when determining what pool to get a connection from;
|
||||
# these do not rely on the ``connection_pool_kw`` and can be determined by the
|
||||
# URL and potentially the ``urllib3.connection.port_by_scheme`` dictionary.
|
||||
#
|
||||
# All custom key schemes should include the fields in this key at a minimum.
|
||||
BasePoolKey = collections.namedtuple('BasePoolKey', ('scheme', 'host', 'port'))
|
||||
|
||||
# The fields to use when determining what pool to get a HTTP and HTTPS
|
||||
# connection from. All additional fields must be present in the PoolManager's
|
||||
# ``connection_pool_kw`` instance variable.
|
||||
HTTPPoolKey = collections.namedtuple(
|
||||
'HTTPPoolKey', BasePoolKey._fields + ('timeout', 'retries', 'strict',
|
||||
'block', 'source_address')
|
||||
)
|
||||
HTTPSPoolKey = collections.namedtuple(
|
||||
'HTTPSPoolKey', HTTPPoolKey._fields + SSL_KEYWORDS
|
||||
)
|
||||
|
||||
|
||||
def _default_key_normalizer(key_class, request_context):
|
||||
"""
|
||||
Create a pool key of type ``key_class`` for a request.
|
||||
|
||||
According to RFC 3986, both the scheme and host are case-insensitive.
|
||||
Therefore, this function normalizes both before constructing the pool
|
||||
key for an HTTPS request. If you wish to change this behaviour, provide
|
||||
alternate callables to ``key_fn_by_scheme``.
|
||||
|
||||
:param key_class:
|
||||
The class to use when constructing the key. This should be a namedtuple
|
||||
with the ``scheme`` and ``host`` keys at a minimum.
|
||||
|
||||
:param request_context:
|
||||
A dictionary-like object that contain the context for a request.
|
||||
It should contain a key for each field in the :class:`HTTPPoolKey`
|
||||
"""
|
||||
context = {}
|
||||
for key in key_class._fields:
|
||||
context[key] = request_context.get(key)
|
||||
context['scheme'] = context['scheme'].lower()
|
||||
context['host'] = context['host'].lower()
|
||||
return key_class(**context)
|
||||
|
||||
|
||||
# A dictionary that maps a scheme to a callable that creates a pool key.
|
||||
# This can be used to alter the way pool keys are constructed, if desired.
|
||||
# Each PoolManager makes a copy of this dictionary so they can be configured
|
||||
# globally here, or individually on the instance.
|
||||
key_fn_by_scheme = {
|
||||
'http': functools.partial(_default_key_normalizer, HTTPPoolKey),
|
||||
'https': functools.partial(_default_key_normalizer, HTTPSPoolKey),
|
||||
}
|
||||
|
||||
pool_classes_by_scheme = {
|
||||
'http': HTTPConnectionPool,
|
||||
'https': HTTPSConnectionPool,
|
||||
@@ -65,8 +120,10 @@ class PoolManager(RequestMethods):
|
||||
self.pools = RecentlyUsedContainer(num_pools,
|
||||
dispose_func=lambda p: p.close())
|
||||
|
||||
# Locally set the pool classes so other PoolManagers can override them.
|
||||
# Locally set the pool classes and keys so other PoolManagers can
|
||||
# override them.
|
||||
self.pool_classes_by_scheme = pool_classes_by_scheme
|
||||
self.key_fn_by_scheme = key_fn_by_scheme.copy()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -113,10 +170,36 @@ class PoolManager(RequestMethods):
|
||||
if not host:
|
||||
raise LocationValueError("No host specified.")
|
||||
|
||||
scheme = scheme or 'http'
|
||||
port = port or port_by_scheme.get(scheme, 80)
|
||||
pool_key = (scheme, host, port)
|
||||
request_context = self.connection_pool_kw.copy()
|
||||
request_context['scheme'] = scheme or 'http'
|
||||
if not port:
|
||||
port = port_by_scheme.get(request_context['scheme'].lower(), 80)
|
||||
request_context['port'] = port
|
||||
request_context['host'] = host
|
||||
|
||||
return self.connection_from_context(request_context)
|
||||
|
||||
def connection_from_context(self, request_context):
|
||||
"""
|
||||
Get a :class:`ConnectionPool` based on the request context.
|
||||
|
||||
``request_context`` must at least contain the ``scheme`` key and its
|
||||
value must be a key in ``key_fn_by_scheme`` instance variable.
|
||||
"""
|
||||
scheme = request_context['scheme'].lower()
|
||||
pool_key_constructor = self.key_fn_by_scheme[scheme]
|
||||
pool_key = pool_key_constructor(request_context)
|
||||
|
||||
return self.connection_from_pool_key(pool_key)
|
||||
|
||||
def connection_from_pool_key(self, pool_key):
|
||||
"""
|
||||
Get a :class:`ConnectionPool` based on the provided pool key.
|
||||
|
||||
``pool_key`` should be a namedtuple that only contains immutable
|
||||
objects. At a minimum it must have the ``scheme``, ``host``, and
|
||||
``port`` fields.
|
||||
"""
|
||||
with self.pools.lock:
|
||||
# If the scheme, host, or port doesn't match existing open
|
||||
# connections, open a new ConnectionPool.
|
||||
@@ -125,7 +208,7 @@ class PoolManager(RequestMethods):
|
||||
return pool
|
||||
|
||||
# Make a fresh ConnectionPool of the desired type
|
||||
pool = self._new_pool(scheme, host, port)
|
||||
pool = self._new_pool(pool_key.scheme, pool_key.host, pool_key.port)
|
||||
self.pools[pool_key] = pool
|
||||
|
||||
return pool
|
||||
|
||||
@@ -165,6 +165,10 @@ class HTTPResponse(io.IOBase):
|
||||
if self._fp:
|
||||
return self.read(cache_content=True)
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
return self._connection
|
||||
|
||||
def tell(self):
|
||||
"""
|
||||
Obtain the number of bytes pulled over the wire so far. May differ from
|
||||
|
||||
@@ -46,6 +46,8 @@ def is_connection_dropped(conn): # Platform-specific
|
||||
|
||||
# This function is copied from socket.py in the Python 2.7 standard
|
||||
# library test suite. Added to its signature is only `socket_options`.
|
||||
# One additional modification is that we avoid binding to IPv6 servers
|
||||
# discovered in DNS if the system doesn't have IPv6 functionality.
|
||||
def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None, socket_options=None):
|
||||
"""Connect to *address* and return the socket object.
|
||||
@@ -64,14 +66,19 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
if host.startswith('['):
|
||||
host = host.strip('[]')
|
||||
err = None
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
|
||||
# Using the value from allowed_gai_family() in the context of getaddrinfo lets
|
||||
# us select whether to work with IPv4 DNS records, IPv6 records, or both.
|
||||
# The original create_connection function always returns all records.
|
||||
family = allowed_gai_family()
|
||||
|
||||
for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
|
||||
# If provided, set socket level options before connecting.
|
||||
# This is the only addition urllib3 makes to this function.
|
||||
_set_socket_options(sock, socket_options)
|
||||
|
||||
if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
|
||||
@@ -99,3 +106,39 @@ def _set_socket_options(sock, options):
|
||||
|
||||
for opt in options:
|
||||
sock.setsockopt(*opt)
|
||||
|
||||
|
||||
def allowed_gai_family():
|
||||
"""This function is designed to work in the context of
|
||||
getaddrinfo, where family=socket.AF_UNSPEC is the default and
|
||||
will perform a DNS search for both IPv6 and IPv4 records."""
|
||||
|
||||
family = socket.AF_INET
|
||||
if HAS_IPV6:
|
||||
family = socket.AF_UNSPEC
|
||||
return family
|
||||
|
||||
|
||||
def _has_ipv6(host):
|
||||
""" Returns True if the system can bind an IPv6 address. """
|
||||
sock = None
|
||||
has_ipv6 = False
|
||||
|
||||
if socket.has_ipv6:
|
||||
# has_ipv6 returns true if cPython was compiled with IPv6 support.
|
||||
# It does not tell us if the system has IPv6 support enabled. To
|
||||
# determine that we must bind to an IPv6 address.
|
||||
# https://github.com/shazow/urllib3/pull/611
|
||||
# https://bugs.python.org/issue658327
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET6)
|
||||
sock.bind((host, 0))
|
||||
has_ipv6 = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if sock:
|
||||
sock.close()
|
||||
return has_ipv6
|
||||
|
||||
HAS_IPV6 = _has_ipv6('::1')
|
||||
|
||||
@@ -80,21 +80,27 @@ class Retry(object):
|
||||
Set of uppercased HTTP method verbs that we should retry on.
|
||||
|
||||
By default, we only retry on methods which are considered to be
|
||||
indempotent (multiple requests with the same parameters end with the
|
||||
idempotent (multiple requests with the same parameters end with the
|
||||
same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`.
|
||||
|
||||
Set to a ``False`` value to retry on any verb.
|
||||
|
||||
:param iterable status_forcelist:
|
||||
A set of HTTP status codes that we should force a retry on.
|
||||
A set of integer HTTP status codes that we should force a retry on.
|
||||
A retry is initiated if the request method is in ``method_whitelist``
|
||||
and the response status code is in ``status_forcelist``.
|
||||
|
||||
By default, this is disabled with ``None``.
|
||||
|
||||
:param float backoff_factor:
|
||||
A backoff factor to apply between attempts. urllib3 will sleep for::
|
||||
A backoff factor to apply between attempts after the second try
|
||||
(most errors are resolved immediately by a second try without a
|
||||
delay). urllib3 will sleep for::
|
||||
|
||||
{backoff factor} * (2 ^ ({number of total retries} - 1))
|
||||
|
||||
seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
|
||||
for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer
|
||||
for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer
|
||||
than :attr:`Retry.BACKOFF_MAX`.
|
||||
|
||||
By default, backoff is disabled (set to 0).
|
||||
|
||||
@@ -117,7 +117,7 @@ except ImportError:
|
||||
'urllib3 from configuring SSL appropriately and may cause '
|
||||
'certain SSL connections to fail. You can upgrade to a newer '
|
||||
'version of Python to solve this. For more information, see '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||
'https://urllib3.readthedocs.io/en/latest/security.html'
|
||||
'#insecureplatformwarning.',
|
||||
InsecurePlatformWarning
|
||||
)
|
||||
@@ -313,7 +313,7 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
'This may cause the server to present an incorrect TLS '
|
||||
'certificate, which can cause validation failures. You can upgrade to '
|
||||
'a newer version of Python to solve this. For more information, see '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||
'https://urllib3.readthedocs.io/en/latest/security.html'
|
||||
'#snimissingwarning.',
|
||||
SNIMissingWarning
|
||||
)
|
||||
|
||||
+44
-18
@@ -6,7 +6,6 @@ requests.session
|
||||
|
||||
This module provides a Session object to manage and persist settings across
|
||||
requests (cookies, auth, proxies).
|
||||
|
||||
"""
|
||||
import os
|
||||
from collections import Mapping
|
||||
@@ -160,10 +159,12 @@ class SessionRedirectMixin(object):
|
||||
self.rebuild_method(prepared_request, response)
|
||||
|
||||
# https://github.com/kennethreitz/requests/issues/1084
|
||||
if response.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
|
||||
if 'Content-Length' in prepared_request.headers:
|
||||
del prepared_request.headers['Content-Length']
|
||||
|
||||
if response.status_code not in (codes.temporary_redirect,
|
||||
codes.permanent_redirect):
|
||||
# https://github.com/kennethreitz/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
|
||||
@@ -203,8 +204,9 @@ class SessionRedirectMixin(object):
|
||||
yield response
|
||||
|
||||
def rebuild_auth(self, prepared_request, response):
|
||||
"""When being redirected, we may want to strip authentication from the
|
||||
request to avoid leaking credentials. This method intelligently removes
|
||||
"""When being redirected we may want to strip authentication from the
|
||||
request to avoid leaking credentials. This method intelligently
|
||||
removes
|
||||
and reapplies authentication where possible to avoid credential loss.
|
||||
"""
|
||||
headers = prepared_request.headers
|
||||
@@ -212,7 +214,7 @@ class SessionRedirectMixin(object):
|
||||
|
||||
if 'Authorization' in headers:
|
||||
# If we get redirected to a new host, we should strip out any
|
||||
# authentication headers.
|
||||
# authentication headers.
|
||||
original_parsed = urlparse(response.request.url)
|
||||
redirect_parsed = urlparse(url)
|
||||
|
||||
@@ -235,6 +237,8 @@ class SessionRedirectMixin(object):
|
||||
|
||||
This method also replaces the Proxy-Authorization header where
|
||||
necessary.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
headers = prepared_request.headers
|
||||
url = prepared_request.url
|
||||
@@ -244,7 +248,7 @@ class SessionRedirectMixin(object):
|
||||
if self.trust_env and not should_bypass_proxies(url):
|
||||
environ_proxies = get_environ_proxies(url)
|
||||
|
||||
proxy = environ_proxies.get('all', environ_proxies.get(scheme))
|
||||
proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
|
||||
|
||||
if proxy:
|
||||
new_proxies.setdefault(scheme, proxy)
|
||||
@@ -340,7 +344,7 @@ class Session(SessionRedirectMixin):
|
||||
#: SSL Verification default.
|
||||
self.verify = True
|
||||
|
||||
#: SSL certificate default.
|
||||
#: SSL client certificate default.
|
||||
self.cert = None
|
||||
|
||||
#: Maximum number of redirects allowed. If the request exceeds this
|
||||
@@ -381,6 +385,7 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:param request: :class:`Request` instance to prepare with this
|
||||
Session's settings.
|
||||
:rtype: requests.PreparedRequest
|
||||
"""
|
||||
cookies = request.cookies or {}
|
||||
|
||||
@@ -392,7 +397,6 @@ class Session(SessionRedirectMixin):
|
||||
merged_cookies = merge_cookies(
|
||||
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
|
||||
|
||||
|
||||
# Set environment's basic authentication if not explicitly set.
|
||||
auth = request.auth
|
||||
if self.trust_env and not auth and not self.auth:
|
||||
@@ -499,6 +503,7 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -509,6 +514,7 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -519,6 +525,7 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
@@ -531,6 +538,7 @@ class Session(SessionRedirectMixin):
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('POST', url, data=data, json=json, **kwargs)
|
||||
@@ -541,6 +549,7 @@ class Session(SessionRedirectMixin):
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('PUT', url, data=data, **kwargs)
|
||||
@@ -551,6 +560,7 @@ class Session(SessionRedirectMixin):
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('PATCH', url, data=data, **kwargs)
|
||||
@@ -560,12 +570,17 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('DELETE', url, **kwargs)
|
||||
|
||||
def send(self, request, **kwargs):
|
||||
"""Send a given PreparedRequest."""
|
||||
"""
|
||||
Send a given PreparedRequest.
|
||||
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
# Set defaults that the hooks can utilize to ensure they always have
|
||||
# the correct parameters to reproduce the previous request.
|
||||
kwargs.setdefault('stream', self.stream)
|
||||
@@ -642,12 +657,15 @@ class Session(SessionRedirectMixin):
|
||||
return r
|
||||
|
||||
def merge_environment_settings(self, url, proxies, stream, verify, cert):
|
||||
"""Check the environment and merge it with some settings."""
|
||||
"""
|
||||
Check the environment and merge it with some settings.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
# Merge all the kwargs except for proxies.
|
||||
stream = merge_setting(stream, self.stream)
|
||||
verify = merge_setting(verify, self.verify)
|
||||
cert = merge_setting(cert, self.cert)
|
||||
|
||||
# Gather clues from the surrounding environment.
|
||||
# We do this after merging the Session values to make sure we don't
|
||||
# accidentally exclude them.
|
||||
@@ -675,7 +693,11 @@ class Session(SessionRedirectMixin):
|
||||
'cert': cert}
|
||||
|
||||
def get_adapter(self, url):
|
||||
"""Returns the appropriate connection adapter for the given URL."""
|
||||
"""
|
||||
Returns the appropriate connection adapter for the given URL.
|
||||
|
||||
:rtype: requests.adapters.BaseAdapter
|
||||
"""
|
||||
for (prefix, adapter) in self.adapters.items():
|
||||
|
||||
if url.lower().startswith(prefix):
|
||||
@@ -692,8 +714,8 @@ class Session(SessionRedirectMixin):
|
||||
def mount(self, prefix, adapter):
|
||||
"""Registers a connection adapter to a prefix.
|
||||
|
||||
Adapters are sorted in descending order by key length."""
|
||||
|
||||
Adapters are sorted in descending order by key length.
|
||||
"""
|
||||
self.adapters[prefix] = adapter
|
||||
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
|
||||
|
||||
@@ -716,6 +738,10 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
|
||||
def session():
|
||||
"""Returns a :class:`Session` for context-management."""
|
||||
"""
|
||||
Returns a :class:`Session` for context-management.
|
||||
|
||||
:rtype: Session
|
||||
"""
|
||||
|
||||
return Session()
|
||||
|
||||
@@ -31,7 +31,7 @@ _codes = {
|
||||
306: ('switch_proxy',),
|
||||
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
|
||||
308: ('permanent_redirect',
|
||||
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
|
||||
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
|
||||
|
||||
# Client Error.
|
||||
400: ('bad_request', 'bad'),
|
||||
|
||||
@@ -5,7 +5,6 @@ requests.structures
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Data structures that power Requests.
|
||||
|
||||
"""
|
||||
|
||||
import collections
|
||||
@@ -14,8 +13,7 @@ from .compat import OrderedDict
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
"""
|
||||
A case-insensitive ``dict``-like object.
|
||||
"""A case-insensitive ``dict``-like object.
|
||||
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
@@ -39,8 +37,8 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
If the constructor, ``.update``, or equality comparison
|
||||
operations are given keys that have equal ``.lower()``s, the
|
||||
behavior is undefined.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, **kwargs):
|
||||
self._store = OrderedDict()
|
||||
if data is None:
|
||||
@@ -87,6 +85,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
def __repr__(self):
|
||||
return str(dict(self.items()))
|
||||
|
||||
|
||||
class LookupDict(dict):
|
||||
"""Dictionary lookup object."""
|
||||
|
||||
|
||||
+95
-19
@@ -6,7 +6,6 @@ requests.utils
|
||||
|
||||
This module provides utility functions that are used within Requests
|
||||
that are also useful for external consumption.
|
||||
|
||||
"""
|
||||
|
||||
import cgi
|
||||
@@ -27,7 +26,7 @@ from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
|
||||
basestring, is_py3)
|
||||
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .exceptions import InvalidURL, FileModeWarning
|
||||
from .exceptions import InvalidURL, InvalidHeader, FileModeWarning
|
||||
|
||||
_hush_pyflakes = (RequestsCookieJar,)
|
||||
|
||||
@@ -165,6 +164,8 @@ def from_key_val_list(value):
|
||||
ValueError: need more than 1 value to unpack
|
||||
>>> from_key_val_list({'key': 'val'})
|
||||
OrderedDict([('key', 'val')])
|
||||
|
||||
:rtype: OrderedDict
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
@@ -187,6 +188,8 @@ def to_key_val_list(value):
|
||||
[('key', 'val')]
|
||||
>>> to_key_val_list('string')
|
||||
ValueError: cannot encode objects that are not 2-tuples.
|
||||
|
||||
:rtype: list
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
@@ -222,6 +225,7 @@ def parse_list_header(value):
|
||||
|
||||
:param value: a string with a list header.
|
||||
:return: :class:`list`
|
||||
:rtype: list
|
||||
"""
|
||||
result = []
|
||||
for item in _parse_list_header(value):
|
||||
@@ -252,6 +256,7 @@ def parse_dict_header(value):
|
||||
|
||||
:param value: a string with a dict header.
|
||||
:return: :class:`dict`
|
||||
:rtype: dict
|
||||
"""
|
||||
result = {}
|
||||
for item in _parse_list_header(value):
|
||||
@@ -272,6 +277,7 @@ def unquote_header_value(value, is_filename=False):
|
||||
using for quoting.
|
||||
|
||||
:param value: the header value to unquote.
|
||||
:rtype: str
|
||||
"""
|
||||
if value and value[0] == value[-1] == '"':
|
||||
# this is not the real unquoting, but fixing this so that the
|
||||
@@ -294,6 +300,7 @@ def dict_from_cookiejar(cj):
|
||||
"""Returns a key/value dictionary from a CookieJar.
|
||||
|
||||
:param cj: CookieJar object to extract cookies from.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
cookie_dict = {}
|
||||
@@ -309,6 +316,7 @@ def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
|
||||
:param cj: CookieJar to insert cookies into.
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
:rtype: CookieJar
|
||||
"""
|
||||
|
||||
cj2 = cookiejar_from_dict(cookie_dict)
|
||||
@@ -340,6 +348,7 @@ def get_encoding_from_headers(headers):
|
||||
"""Returns encodings from given HTTP Header Dict.
|
||||
|
||||
:param headers: dictionary to extract encoding from.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
content_type = headers.get('content-type')
|
||||
@@ -377,6 +386,8 @@ def stream_decode_response_unicode(iterator, r):
|
||||
def iter_slices(string, slice_length):
|
||||
"""Iterate over slices of a string."""
|
||||
pos = 0
|
||||
if slice_length is None or slice_length <= 0:
|
||||
slice_length = len(string)
|
||||
while pos < len(string):
|
||||
yield string[pos:pos + slice_length]
|
||||
pos += slice_length
|
||||
@@ -392,6 +403,7 @@ def get_unicode_from_response(r):
|
||||
1. charset from content-type
|
||||
2. fall back and replace all unicode characters
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
warnings.warn((
|
||||
'In requests 3.0, get_unicode_from_response will be removed. For '
|
||||
@@ -426,6 +438,8 @@ UNRESERVED_SET = frozenset(
|
||||
def unquote_unreserved(uri):
|
||||
"""Un-escape any percent-escape sequences in a URI that are unreserved
|
||||
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
# This convert function is used to optionally convert the output of `chr`.
|
||||
# In Python 3, `chr` returns a unicode string, while in Python 2 it returns
|
||||
@@ -469,6 +483,8 @@ def requote_uri(uri):
|
||||
|
||||
This function passes the given URI through an unquote/quote cycle to
|
||||
ensure that it is fully and consistently quoted.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
safe_with_percent = "!#$%&'()*+,/:;=?@[]~"
|
||||
safe_without_percent = "!#$&'()*+,/:;=?@[]~"
|
||||
@@ -485,10 +501,12 @@ def requote_uri(uri):
|
||||
|
||||
|
||||
def address_in_network(ip, net):
|
||||
"""
|
||||
This function allows you to check if on IP belongs to a network subnet
|
||||
"""This function allows you to check if on IP belongs to a network subnet
|
||||
|
||||
Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24
|
||||
returns False if ip = 192.168.1.1 and net = 192.168.100.0/24
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0]
|
||||
netaddr, bits = net.split('/')
|
||||
@@ -498,15 +516,20 @@ def address_in_network(ip, net):
|
||||
|
||||
|
||||
def dotted_netmask(mask):
|
||||
"""
|
||||
Converts mask from /xx format to xxx.xxx.xxx.xxx
|
||||
"""Converts mask from /xx format to xxx.xxx.xxx.xxx
|
||||
|
||||
Example: if mask is 24 function returns 255.255.255.0
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
bits = 0xffffffff ^ (1 << 32 - mask) - 1
|
||||
return socket.inet_ntoa(struct.pack('>I', bits))
|
||||
|
||||
|
||||
def is_ipv4_address(string_ip):
|
||||
"""
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
socket.inet_aton(string_ip)
|
||||
except socket.error:
|
||||
@@ -515,7 +538,11 @@ def is_ipv4_address(string_ip):
|
||||
|
||||
|
||||
def is_valid_cidr(string_network):
|
||||
"""Very simple check of the cidr format in no_proxy variable"""
|
||||
"""
|
||||
Very simple check of the cidr format in no_proxy variable.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
if string_network.count('/') == 1:
|
||||
try:
|
||||
mask = int(string_network.split('/')[1])
|
||||
@@ -537,6 +564,8 @@ def is_valid_cidr(string_network):
|
||||
def should_bypass_proxies(url):
|
||||
"""
|
||||
Returns whether we should bypass proxies or not.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
|
||||
|
||||
@@ -587,7 +616,11 @@ def should_bypass_proxies(url):
|
||||
|
||||
|
||||
def get_environ_proxies(url):
|
||||
"""Return a dict of environment proxies."""
|
||||
"""
|
||||
Return a dict of environment proxies.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
if should_bypass_proxies(url):
|
||||
return {}
|
||||
else:
|
||||
@@ -603,13 +636,13 @@ def select_proxy(url, proxies):
|
||||
proxies = proxies or {}
|
||||
urlparts = urlparse(url)
|
||||
if urlparts.hostname is None:
|
||||
return proxies.get('all', proxies.get(urlparts.scheme))
|
||||
return proxies.get(urlparts.scheme, proxies.get('all'))
|
||||
|
||||
proxy_keys = [
|
||||
'all://' + urlparts.hostname,
|
||||
'all',
|
||||
urlparts.scheme + '://' + urlparts.hostname,
|
||||
urlparts.scheme,
|
||||
'all://' + urlparts.hostname,
|
||||
'all',
|
||||
]
|
||||
proxy = None
|
||||
for proxy_key in proxy_keys:
|
||||
@@ -621,11 +654,18 @@ def select_proxy(url, proxies):
|
||||
|
||||
|
||||
def default_user_agent(name="python-requests"):
|
||||
"""Return a string representing the default user agent."""
|
||||
"""
|
||||
Return a string representing the default user agent.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return '%s/%s' % (name, __version__)
|
||||
|
||||
|
||||
def default_headers():
|
||||
"""
|
||||
:rtype: requests.structures.CaseInsensitiveDict
|
||||
"""
|
||||
return CaseInsensitiveDict({
|
||||
'User-Agent': default_user_agent(),
|
||||
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
|
||||
@@ -639,6 +679,7 @@ def parse_header_links(value):
|
||||
|
||||
i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"
|
||||
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
links = []
|
||||
@@ -684,6 +725,9 @@ _null3 = _null * 3
|
||||
|
||||
|
||||
def guess_json_utf(data):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
# JSON always starts with two ASCII characters, so detection is as
|
||||
# easy as counting the nulls and from their location and count
|
||||
# determine the encoding. Also detect a BOM, if present.
|
||||
@@ -714,7 +758,10 @@ def guess_json_utf(data):
|
||||
|
||||
def prepend_scheme_if_needed(url, new_scheme):
|
||||
"""Given a URL that may or may not have a scheme, prepend the given scheme.
|
||||
Does not replace a present scheme with the one provided as an argument."""
|
||||
Does not replace a present scheme with the one provided as an argument.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
|
||||
|
||||
# urlparse is a finicky beast, and sometimes decides that there isn't a
|
||||
@@ -728,7 +775,10 @@ def prepend_scheme_if_needed(url, new_scheme):
|
||||
|
||||
def get_auth_from_url(url):
|
||||
"""Given a url with authentication components, extract them into a tuple of
|
||||
username,password."""
|
||||
username,password.
|
||||
|
||||
:rtype: (str,str)
|
||||
"""
|
||||
parsed = urlparse(url)
|
||||
|
||||
try:
|
||||
@@ -740,10 +790,9 @@ def get_auth_from_url(url):
|
||||
|
||||
|
||||
def to_native_string(string, encoding='ascii'):
|
||||
"""
|
||||
Given a string object, regardless of type, returns a representation of that
|
||||
string in the native string type, encoding and decoding where necessary.
|
||||
This assumes ASCII unless told otherwise.
|
||||
"""Given a string object, regardless of type, returns a representation of
|
||||
that string in the native string type, encoding and decoding where
|
||||
necessary. This assumes ASCII unless told otherwise.
|
||||
"""
|
||||
if isinstance(string, builtin_str):
|
||||
out = string
|
||||
@@ -756,9 +805,36 @@ def to_native_string(string, encoding='ascii'):
|
||||
return out
|
||||
|
||||
|
||||
# Moved outside of function to avoid recompile every call
|
||||
_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$')
|
||||
_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$')
|
||||
|
||||
def check_header_validity(header):
|
||||
"""Verifies that header value is a string which doesn't contain
|
||||
leading whitespace or return characters. This prevents unintended
|
||||
header injection.
|
||||
|
||||
:param header: tuple, in the format (name, value).
|
||||
"""
|
||||
name, value = header
|
||||
|
||||
if isinstance(value, bytes):
|
||||
pat = _CLEAN_HEADER_REGEX_BYTE
|
||||
else:
|
||||
pat = _CLEAN_HEADER_REGEX_STR
|
||||
try:
|
||||
if not pat.match(value):
|
||||
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
|
||||
except TypeError:
|
||||
raise InvalidHeader("Header value %s must be of type str or bytes, "
|
||||
"not %s" % (value, type(value)))
|
||||
|
||||
|
||||
def urldefragauth(url):
|
||||
"""
|
||||
Given a url remove the fragment and the authentication part
|
||||
Given a url remove the fragment and the authentication part.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
||||
|
||||
|
||||
@@ -93,6 +93,6 @@ setup(
|
||||
tests_require=test_requirements,
|
||||
extras_require={
|
||||
'security': ['pyOpenSSL>=0.13', 'ndg-httpsclient', 'pyasn1'],
|
||||
'socks': ['PySocks>=1.5.6'],
|
||||
'socks': ['PySocks>=1.5.6, !=1.5.7'],
|
||||
},
|
||||
)
|
||||
|
||||
+22
-1
@@ -1 +1,22 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Requests test package initialisation."""
|
||||
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import urllib3 as urllib3_package
|
||||
except ImportError:
|
||||
urllib3_package = False
|
||||
|
||||
from requests.packages import urllib3 as urllib3_bundle
|
||||
|
||||
if urllib3_package is urllib3_bundle:
|
||||
from urllib3.exceptions import SNIMissingWarning
|
||||
else:
|
||||
from requests.packages.urllib3.exceptions import SNIMissingWarning
|
||||
|
||||
# urllib3 sets SNIMissingWarning to only go off once,
|
||||
# while this test suite requires it to always fire
|
||||
# so that it occurs during test_requests.test_https_warnings
|
||||
warnings.simplefilter('always', SNIMissingWarning)
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from requests.compat import is_py3
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from requests.compat import urljoin
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
from requests import hooks
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import threading
|
||||
@@ -17,7 +19,7 @@ def test_chunked_upload():
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.post(url, data=data, stream=True)
|
||||
close_server.set() # release server block
|
||||
close_server.set() # release server block
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.request.headers['Transfer-Encoding'] == 'chunked'
|
||||
|
||||
+193
-13
@@ -9,6 +9,7 @@ import os
|
||||
import pickle
|
||||
import collections
|
||||
import contextlib
|
||||
import warnings
|
||||
|
||||
import io
|
||||
import requests
|
||||
@@ -51,6 +52,19 @@ class SendRecordingAdapter(HTTPAdapter):
|
||||
# listening on that port)
|
||||
TARPIT = 'http://10.255.255.1'
|
||||
|
||||
try:
|
||||
from ssl import SSLContext
|
||||
del SSLContext
|
||||
HAS_MODERN_SSL = True
|
||||
except ImportError:
|
||||
HAS_MODERN_SSL = False
|
||||
|
||||
try:
|
||||
requests.pyopenssl
|
||||
HAS_PYOPENSSL = True
|
||||
except AttributeError:
|
||||
HAS_PYOPENSSL = False
|
||||
|
||||
|
||||
class TestRequests:
|
||||
|
||||
@@ -236,9 +250,45 @@ class TestRequests:
|
||||
# next triggers yield on generator
|
||||
next(ses.resolve_redirects(resp, prep))
|
||||
|
||||
# def test_HTTP_302_ALLOW_REDIRECT_POST(self):
|
||||
# r = requests.post(httpbin('status', '302'), data={'some': 'data'})
|
||||
# self.assertEqual(r.status_code, 200)
|
||||
def test_header_and_body_removal_on_redirect(self, httpbin):
|
||||
purged_headers = ('Content-Length', 'Content-Type')
|
||||
ses = requests.Session()
|
||||
req = requests.Request('POST', httpbin('post'), data={'test': 'data'})
|
||||
prep = ses.prepare_request(req)
|
||||
resp = ses.send(prep)
|
||||
|
||||
# Mimic a redirect response
|
||||
resp.status_code = 302
|
||||
resp.headers['location'] = 'get'
|
||||
|
||||
# Run request through resolve_redirects
|
||||
next_resp = next(ses.resolve_redirects(resp, prep))
|
||||
assert next_resp.request.body is None
|
||||
for header in purged_headers:
|
||||
assert header not in next_resp.request.headers
|
||||
|
||||
def test_transfer_enc_removal_on_redirect(self, httpbin):
|
||||
purged_headers = ('Transfer-Encoding', 'Content-Type')
|
||||
ses = requests.Session()
|
||||
req = requests.Request('POST', httpbin('post'), data=(b'x' for x in range(1)))
|
||||
prep = ses.prepare_request(req)
|
||||
assert 'Transfer-Encoding' in prep.headers
|
||||
|
||||
# Create Response to avoid https://github.com/kevin1024/pytest-httpbin/issues/33
|
||||
resp = requests.Response()
|
||||
resp.raw = io.BytesIO(b'the content')
|
||||
resp.request = prep
|
||||
setattr(resp.raw, 'release_conn', lambda *args: args)
|
||||
|
||||
# Mimic a redirect response
|
||||
resp.status_code = 302
|
||||
resp.headers['location'] = httpbin('get')
|
||||
|
||||
# Run request through resolve_redirect
|
||||
next_resp = next(ses.resolve_redirects(resp, prep))
|
||||
assert next_resp.request.body is None
|
||||
for header in purged_headers:
|
||||
assert header not in next_resp.request.headers
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self, httpbin):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
@@ -583,8 +633,7 @@ class TestRequests:
|
||||
assert post1.status_code == 200
|
||||
|
||||
with open('requirements.txt') as f:
|
||||
post2 = requests.post(url,
|
||||
data={'some': 'data'}, files={'some': f})
|
||||
post2 = requests.post(url, data={'some': 'data'}, files={'some': f})
|
||||
assert post2.status_code == 200
|
||||
|
||||
post4 = requests.post(url, data='[{"some": "json"}]')
|
||||
@@ -635,6 +684,27 @@ class TestRequests:
|
||||
def test_pyopenssl_redirect(self, httpbin_secure, httpbin_ca_bundle):
|
||||
requests.get(httpbin_secure('status', '301'), verify=httpbin_ca_bundle)
|
||||
|
||||
def test_https_warnings(self, httpbin_secure, httpbin_ca_bundle):
|
||||
"""warnings are emitted with requests.get"""
|
||||
if HAS_MODERN_SSL or HAS_PYOPENSSL:
|
||||
warnings_expected = ('SubjectAltNameWarning', )
|
||||
else:
|
||||
warnings_expected = ('SNIMissingWarning',
|
||||
'InsecurePlatformWarning',
|
||||
'SubjectAltNameWarning', )
|
||||
|
||||
with pytest.warns(None) as warning_records:
|
||||
warnings.simplefilter('always')
|
||||
requests.get(httpbin_secure('status', '200'),
|
||||
verify=httpbin_ca_bundle)
|
||||
|
||||
warning_records = [item for item in warning_records
|
||||
if item.category.__name__ != 'ResourceWarning']
|
||||
|
||||
warnings_category = tuple(
|
||||
item.category.__name__ for item in warning_records)
|
||||
assert warnings_category == warnings_expected
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self, httpbin):
|
||||
|
||||
r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz']))
|
||||
@@ -938,8 +1008,7 @@ class TestRequests:
|
||||
def test_time_elapsed_blank(self, httpbin):
|
||||
r = requests.get(httpbin('get'))
|
||||
td = r.elapsed
|
||||
total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600)
|
||||
* 10**6) / 10**6)
|
||||
total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6)
|
||||
assert total_seconds > 0.0
|
||||
|
||||
def test_response_is_iterable(self):
|
||||
@@ -955,8 +1024,7 @@ class TestRequests:
|
||||
io.close()
|
||||
|
||||
def test_response_decode_unicode(self):
|
||||
"""
|
||||
When called with decode_unicode, Response.iter_content should always
|
||||
"""When called with decode_unicode, Response.iter_content should always
|
||||
return unicode.
|
||||
"""
|
||||
r = requests.Response()
|
||||
@@ -974,6 +1042,51 @@ class TestRequests:
|
||||
chunks = r.iter_content(decode_unicode=True)
|
||||
assert all(isinstance(chunk, str) for chunk in chunks)
|
||||
|
||||
def test_response_reason_unicode(self):
|
||||
# check for unicode HTTP status
|
||||
r = requests.Response()
|
||||
r.url = u'unicode URL'
|
||||
r.reason = u'Komponenttia ei löydy'.encode('utf-8')
|
||||
r.status_code = 404
|
||||
r.encoding = None
|
||||
assert not r.ok # old behaviour - crashes here
|
||||
|
||||
def test_response_reason_unicode_fallback(self):
|
||||
# check raise_status falls back to ISO-8859-1
|
||||
r = requests.Response()
|
||||
r.url = 'some url'
|
||||
reason = b'Komponenttia ei l\xf6ydy'
|
||||
r.reason = reason
|
||||
r.status_code = 500
|
||||
r.encoding = None
|
||||
str_error = ''
|
||||
try:
|
||||
r.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
str_error = e.args[0]
|
||||
else:
|
||||
raise AssertionError('Expected an HTTPError but it was not raised')
|
||||
assert reason.decode('latin-1') in str_error
|
||||
|
||||
def test_response_chunk_size_type(self):
|
||||
"""Ensure that chunk_size is passed as None or an integer, otherwise
|
||||
raise a TypeError.
|
||||
"""
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
chunks = r.iter_content(1)
|
||||
assert all(len(chunk) == 1 for chunk in chunks)
|
||||
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
chunks = r.iter_content(None)
|
||||
assert list(chunks) == [b'the content']
|
||||
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
with pytest.raises(TypeError):
|
||||
chunks = r.iter_content("1024")
|
||||
|
||||
def test_request_and_response_are_pickleable(self, httpbin):
|
||||
r = requests.get(httpbin('get'))
|
||||
|
||||
@@ -1012,9 +1125,7 @@ class TestRequests:
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_fixes_1329(self, httpbin):
|
||||
"""
|
||||
Ensure that header updates are done case-insensitively.
|
||||
"""
|
||||
"""Ensure that header updates are done case-insensitively."""
|
||||
s = requests.Session()
|
||||
s.headers.update({'ACCEPT': 'BOGUS'})
|
||||
s.headers.update({'accept': 'application/json'})
|
||||
@@ -1101,6 +1212,65 @@ class TestRequests:
|
||||
assert 'unicode' in p.headers.keys()
|
||||
assert 'byte' in p.headers.keys()
|
||||
|
||||
def test_header_validation(self, httpbin):
|
||||
"""Ensure prepare_headers regex isn't flagging valid header contents."""
|
||||
headers_ok = {'foo': 'bar baz qux',
|
||||
'bar': u'fbbq'.encode('utf8'),
|
||||
'baz': '',
|
||||
'qux': '1'}
|
||||
r = requests.get(httpbin('get'), headers=headers_ok)
|
||||
assert r.request.headers['foo'] == headers_ok['foo']
|
||||
|
||||
def test_header_value_not_str(self, httpbin):
|
||||
"""Ensure the header value is of type string or bytes as
|
||||
per discussion in GH issue #3386
|
||||
"""
|
||||
headers_int = {'foo': 3}
|
||||
headers_dict = {'bar': {'foo': 'bar'}}
|
||||
headers_list = {'baz': ['foo', 'bar']}
|
||||
|
||||
# Test for int
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_int)
|
||||
# Test for dict
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_dict)
|
||||
# Test for list
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_list)
|
||||
|
||||
def test_header_no_return_chars(self, httpbin):
|
||||
"""Ensure that a header containing return character sequences raise an
|
||||
exception. Otherwise, multiple headers are created from single string.
|
||||
"""
|
||||
headers_ret = {'foo': 'bar\r\nbaz: qux'}
|
||||
headers_lf = {'foo': 'bar\nbaz: qux'}
|
||||
headers_cr = {'foo': 'bar\rbaz: qux'}
|
||||
|
||||
# Test for newline
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_ret)
|
||||
# Test for line feed
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_lf)
|
||||
# Test for carriage return
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_cr)
|
||||
|
||||
def test_header_no_leading_space(self, httpbin):
|
||||
"""Ensure headers containing leading whitespace raise
|
||||
InvalidHeader Error before sending.
|
||||
"""
|
||||
headers_space = {'foo': ' bar'}
|
||||
headers_tab = {'foo': ' bar'}
|
||||
|
||||
# Test for whitespace
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_space)
|
||||
# Test for tab
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.get(httpbin('get'), headers=headers_tab)
|
||||
|
||||
@pytest.mark.parametrize('files', ('foo', b'foo', bytearray(b'foo')))
|
||||
def test_can_send_objects_with_files(self, httpbin, files):
|
||||
data = {'a': 'this is a string'}
|
||||
@@ -1136,6 +1306,7 @@ class TestRequests:
|
||||
preq = req.prepare()
|
||||
assert test_url == preq.url
|
||||
|
||||
@pytest.mark.xfail(raises=ConnectionError)
|
||||
def test_auth_is_stripped_on_redirect_off_host(self, httpbin):
|
||||
r = requests.get(
|
||||
httpbin('redirect-to'),
|
||||
@@ -1306,6 +1477,15 @@ class TestRequests:
|
||||
with pytest.raises(ValueError):
|
||||
r.json()
|
||||
|
||||
def test_response_without_release_conn(self):
|
||||
"""Test `close` call for non-urllib3-like raw objects.
|
||||
Should work when `release_conn` attr doesn't exist on `response.raw`.
|
||||
"""
|
||||
resp = requests.Response()
|
||||
resp.raw = StringIO.StringIO('test')
|
||||
assert not resp.raw.closed
|
||||
resp.close()
|
||||
assert resp.raw.closed
|
||||
|
||||
class TestCaseInsensitiveDict:
|
||||
|
||||
@@ -1533,7 +1713,7 @@ class TestTimeout:
|
||||
assert error_text in str(e)
|
||||
|
||||
def test_none_timeout(self, httpbin):
|
||||
""" Check that you can set None as a valid timeout value.
|
||||
"""Check that you can set None as a valid timeout value.
|
||||
|
||||
To actually test this behavior, we'd want to check that setting the
|
||||
timeout to None actually lets the request block past the system default
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
from requests.structures import CaseInsensitiveDict, LookupDict
|
||||
@@ -8,9 +9,7 @@ class TestCaseInsensitiveDict:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
"""
|
||||
CaseInsensitiveDict instance with "Accept" header.
|
||||
"""
|
||||
"""CaseInsensitiveDict instance with "Accept" header."""
|
||||
self.case_insensitive_dict = CaseInsensitiveDict()
|
||||
self.case_insensitive_dict['Accept'] = 'application/json'
|
||||
|
||||
@@ -54,9 +53,7 @@ class TestLookupDict:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
"""
|
||||
LookupDict instance with "bad_gateway" attribute.
|
||||
"""
|
||||
"""LookupDict instance with "bad_gateway" attribute."""
|
||||
self.lookup_dict = LookupDict('test')
|
||||
self.lookup_dict.bad_gateway = 502
|
||||
|
||||
|
||||
+16
-12
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import threading
|
||||
import socket
|
||||
import time
|
||||
@@ -6,16 +8,19 @@ import pytest
|
||||
import requests
|
||||
from tests.testserver.server import Server
|
||||
|
||||
|
||||
class TestTestServer:
|
||||
|
||||
def test_basic(self):
|
||||
"""messages are sent and received properly"""
|
||||
question = b"sucess?"
|
||||
question = b"success?"
|
||||
answer = b"yeah, success"
|
||||
|
||||
def handler(sock):
|
||||
text = sock.recv(1000)
|
||||
assert text == question
|
||||
assert text == question
|
||||
sock.sendall(answer)
|
||||
|
||||
|
||||
with Server(handler) as (host, port):
|
||||
sock = socket.socket()
|
||||
sock.connect((host, port))
|
||||
@@ -39,7 +44,7 @@ class TestTestServer:
|
||||
def test_text_response(self):
|
||||
"""the text_response_server sends the given text"""
|
||||
server = Server.text_response_server(
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 6\r\n" +
|
||||
"\r\nroflol"
|
||||
)
|
||||
@@ -49,8 +54,8 @@ class TestTestServer:
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.text == u'roflol'
|
||||
assert r.headers['Content-Length'] == '6'
|
||||
|
||||
assert r.headers['Content-Length'] == '6'
|
||||
|
||||
def test_basic_response(self):
|
||||
"""the basic response server returns an empty http response"""
|
||||
with Server.basic_response_server() as (host, port):
|
||||
@@ -69,12 +74,12 @@ class TestTestServer:
|
||||
sock.sendall(b'send something')
|
||||
time.sleep(2.5)
|
||||
sock.sendall(b'still alive')
|
||||
block_server.set() # release server block
|
||||
block_server.set() # release server block
|
||||
|
||||
def test_multiple_requests(self):
|
||||
"""multiple requests can be served"""
|
||||
requests_to_handle = 5
|
||||
|
||||
|
||||
server = Server.basic_response_server(requests_to_handle=requests_to_handle)
|
||||
|
||||
with server as (host, port):
|
||||
@@ -96,7 +101,7 @@ class TestTestServer:
|
||||
with server as address:
|
||||
sock1 = socket.socket()
|
||||
sock2 = socket.socket()
|
||||
|
||||
|
||||
sock1.connect(address)
|
||||
sock1.sendall(first_request)
|
||||
sock1.close()
|
||||
@@ -121,19 +126,18 @@ class TestTestServer:
|
||||
|
||||
assert server.handler_results[0] == b''
|
||||
|
||||
|
||||
def test_request_recovery_with_bigger_timeout(self):
|
||||
"""a biggest timeout can be specified"""
|
||||
server = Server.basic_response_server(request_timeout=3)
|
||||
data = b'bananadine'
|
||||
|
||||
with server as address:
|
||||
sock = socket.socket()
|
||||
sock = socket.socket()
|
||||
sock.connect(address)
|
||||
time.sleep(1.5)
|
||||
sock.sendall(data)
|
||||
sock.close()
|
||||
|
||||
|
||||
assert server.handler_results[0] == data
|
||||
|
||||
def test_server_finishes_on_error(self):
|
||||
|
||||
+24
-10
@@ -1,4 +1,5 @@
|
||||
# coding: utf-8
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
@@ -40,9 +41,7 @@ class TestSuperLen:
|
||||
|
||||
@pytest.mark.parametrize('error', [IOError, OSError])
|
||||
def test_super_len_handles_files_raising_weird_errors_in_tell(self, error):
|
||||
"""
|
||||
If tell() raises errors, assume the cursor is at position zero.
|
||||
"""
|
||||
"""If tell() raises errors, assume the cursor is at position zero."""
|
||||
class BoomFile(object):
|
||||
def __len__(self):
|
||||
return 5
|
||||
@@ -104,7 +103,8 @@ class TestUnquoteHeaderValue:
|
||||
|
||||
class TestGetEnvironProxies:
|
||||
"""Ensures that IP addresses are correctly matches with ranges
|
||||
in no_proxy variable."""
|
||||
in no_proxy variable.
|
||||
"""
|
||||
|
||||
@pytest.fixture(autouse=True, params=['no_proxy', 'NO_PROXY'])
|
||||
def no_proxy(self, request, monkeypatch):
|
||||
@@ -288,8 +288,7 @@ def test_get_auth_from_url(url, auth):
|
||||
),
|
||||
))
|
||||
def test_requote_uri_with_unquoted_percents(uri, expected):
|
||||
"""See: https://github.com/kennethreitz/requests/issues/2356
|
||||
"""
|
||||
"""See: https://github.com/kennethreitz/requests/issues/2356"""
|
||||
assert requote_uri(uri) == expected
|
||||
|
||||
|
||||
@@ -324,6 +323,9 @@ http_proxies = {'http': 'http://http.proxy',
|
||||
'http://some.host': 'http://some.host.proxy'}
|
||||
all_proxies = {'all': 'socks5://http.proxy',
|
||||
'all://some.host': 'socks5://some.host.proxy'}
|
||||
mixed_proxies = {'http': 'http://http.proxy',
|
||||
'http://some.host': 'http://some.host.proxy',
|
||||
'all': 'socks5://http.proxy'}
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected, proxies', (
|
||||
('hTTp://u:p@Some.Host/path', 'http://some.host.proxy', http_proxies),
|
||||
@@ -337,6 +339,11 @@ all_proxies = {'all': 'socks5://http.proxy',
|
||||
('hTTp:///path', 'socks5://http.proxy', all_proxies),
|
||||
('hTTps://Other.Host', 'socks5://http.proxy', all_proxies),
|
||||
|
||||
('http://u:p@other.host/path', 'http://http.proxy', mixed_proxies),
|
||||
('http://u:p@some.host/path', 'http://some.host.proxy', mixed_proxies),
|
||||
('https://u:p@other.host/path', 'socks5://http.proxy', mixed_proxies),
|
||||
('https://u:p@some.host/path', 'socks5://http.proxy', mixed_proxies),
|
||||
('https://', 'socks5://http.proxy', mixed_proxies),
|
||||
# XXX: unsure whether this is reasonable behavior
|
||||
('file:///etc/motd', 'socks5://http.proxy', all_proxies),
|
||||
))
|
||||
@@ -378,9 +385,16 @@ def test_get_encoding_from_headers(value, expected):
|
||||
('', 0),
|
||||
('T', 1),
|
||||
('Test', 4),
|
||||
('Cont', 0),
|
||||
('Other', -5),
|
||||
('Content', None),
|
||||
))
|
||||
def test_iter_slices(value, length):
|
||||
assert len(list(iter_slices(value, 1))) == length
|
||||
if length is None or (length <= 0 and len(value) > 0):
|
||||
# Reads all content at once
|
||||
assert len(list(iter_slices(value, length))) == 1
|
||||
else:
|
||||
assert len(list(iter_slices(value, 1))) == length
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -453,8 +467,8 @@ def test_urldefragauth(url, expected):
|
||||
('http://google.com:5000/v1.0/', False),
|
||||
))
|
||||
def test_should_bypass_proxies(url, expected, monkeypatch):
|
||||
"""
|
||||
Tests for function should_bypass_proxies to check if proxy can be bypassed or not
|
||||
"""Tests for function should_bypass_proxies to check if proxy
|
||||
can be bypassed or not
|
||||
"""
|
||||
monkeypatch.setenv('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
|
||||
monkeypatch.setenv('NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import threading
|
||||
import socket
|
||||
import select
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
|
||||
|
||||
Reference in New Issue
Block a user