Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46d646064c | ||
|
|
78d12bbbe9 | ||
|
|
6da438dabd | ||
|
|
4ec26da2f7 | ||
|
|
0e4efab270 | ||
|
|
ca2ebc5b68 | ||
|
|
5850b1f53b | ||
|
|
779c001e44 | ||
|
|
79f56e2e7e | ||
|
|
90dc467fc0 | ||
|
|
65cdccb70f | ||
|
|
1e79cf6a47 | ||
|
|
20ca9feeb3 | ||
|
|
b1f3a2dd66 | ||
|
|
e74791d054 | ||
|
|
8ed941fa69 | ||
|
|
a68d1b4517 | ||
|
|
cd5eb62961 | ||
|
|
6e1db21733 | ||
|
|
a718a81d27 | ||
|
|
3635b1501d | ||
|
|
de76fbeefb | ||
|
|
e0d88c2814 | ||
|
|
671ba85d41 | ||
|
|
ac44b48297 | ||
|
|
64ba451049 | ||
|
|
6e5f7bb9d3 | ||
|
|
58e0a6f4a0 | ||
|
|
d6470870d0 | ||
|
|
fdf932c61c | ||
|
|
d28d45af91 | ||
|
|
d243668d16 | ||
|
|
466a70f7c1 | ||
|
|
082c8d0db8 | ||
|
|
0924069b68 | ||
|
|
4707d11004 | ||
|
|
9b41308b9f | ||
|
|
bff8b91638 | ||
|
|
1a44a99f17 | ||
|
|
d3566ee1b0 | ||
|
|
9e8702d3d5 | ||
|
|
27b6bfc213 | ||
|
|
dff383ce8f | ||
|
|
5e94c81e98 | ||
|
|
2416e2343a | ||
|
|
bf8f791a93 | ||
|
|
27c83f7177 | ||
|
|
359659cf4b | ||
|
|
9c6ae1dc0b | ||
|
|
0df32de02d | ||
|
|
a77054f90f | ||
|
|
1324ca1a0f | ||
|
|
209a871b63 | ||
|
|
07bbe214aa | ||
|
|
958845ae35 | ||
|
|
bc2560480e | ||
|
|
9354855648 | ||
|
|
226b5c7b0a | ||
|
|
ce7957c085 | ||
|
|
c6a1167e8b | ||
|
|
c809867035 | ||
|
|
2c76122a48 | ||
|
|
da122231e4 | ||
|
|
34ea836d77 | ||
|
|
59f5a1089a | ||
|
|
81d6b3e435 | ||
|
|
dfa41afd43 | ||
|
|
0713e09526 | ||
|
|
402f3b4993 | ||
|
|
b34a496649 | ||
|
|
8f17741849 |
@@ -154,3 +154,5 @@ Patches and Suggestions
|
||||
- Константин Подшумок (`@podshumok <https://github.com/podshumok>`_)
|
||||
- Ben Bass (`@codedstructure <https://github.com/codedstructure>`_)
|
||||
- Jonathan Wong <evolutionace@gmail.com> (`@ContinuousFunction <https://github.com/ContinuousFunction>`_)
|
||||
- Martin Jul (`@mjul <https://github.com/mjul>`_)
|
||||
- Joe Alcorn (`@buttscicles <https://github.com/buttscicles>`_)
|
||||
|
||||
40
HISTORY.rst
40
HISTORY.rst
@@ -3,6 +3,46 @@
|
||||
Release History
|
||||
---------------
|
||||
|
||||
2.4.3 (2014-10-06)
|
||||
++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Unicode URL improvements for Python 2.
|
||||
- Re-order JSON param for backwards compat.
|
||||
- Automatically defrag authentication schemes from host/pass URIs. (`#2249 <https://github.com/kennethreitz/requests/issues/2249>`_)
|
||||
|
||||
|
||||
2.4.2 (2014-10-05)
|
||||
++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- FINALLY! Add json parameter for uploads! (`#2258 <https://github.com/kennethreitz/requests/pull/2258>`_)
|
||||
- Support for bytestring URLs on Python 3.x (`#2238 <https://github.com/kennethreitz/requests/pull/2238>`_)
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Avoid getting stuck in a loop (`#2244 <https://github.com/kennethreitz/requests/pull/2244>`_)
|
||||
- Multiple calls to iter* fail with unhelpful error. (`#2240 <https://github.com/kennethreitz/requests/issues/2240>`_, `#2241 <https://github.com/kennethreitz/requests/issues/2241>`_)
|
||||
|
||||
**Documentation**
|
||||
|
||||
- Correct redirection introduction (`#2245 <https://github.com/kennethreitz/requests/pull/2245/>`_)
|
||||
- Added example of how to send multiple files in one request. (`#2227 <https://github.com/kennethreitz/requests/pull/2227/>`_)
|
||||
- Clarify how to pass a custom set of CAs (`#2248 <https://github.com/kennethreitz/requests/pull/2248/>`_)
|
||||
|
||||
|
||||
|
||||
2.4.1 (2014-09-09)
|
||||
++++++++++++++++++
|
||||
|
||||
- Now has a "security" package extras set, ``$ pip install requests[security]``
|
||||
- Requests will now use Certifi if it is available.
|
||||
- Capture and re-raise urllib3 ProtocolError
|
||||
- Bugfix for responses that attempt to redirect to themselves forever (wtf?).
|
||||
|
||||
|
||||
2.4.0 (2014-08-29)
|
||||
++++++++++++++++++
|
||||
|
||||
|
||||
24
docs/api.rst
24
docs/api.rst
@@ -60,6 +60,8 @@ Exceptions
|
||||
.. autoexception:: requests.exceptions.HTTPError
|
||||
.. autoexception:: requests.exceptions.URLRequired
|
||||
.. autoexception:: requests.exceptions.TooManyRedirects
|
||||
.. autoexception:: requests.exceptions.ConnectTimeout
|
||||
.. autoexception:: requests.exceptions.ReadTimeout
|
||||
.. autoexception:: requests.exceptions.Timeout
|
||||
|
||||
|
||||
@@ -247,29 +249,11 @@ API Changes
|
||||
requests.get("http://example.org", proxies=proxies)
|
||||
|
||||
|
||||
Behavioral Changes
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
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)
|
||||
they will be converted to the native string type assuming UTF-8 encoding.
|
||||
|
||||
* Timeouts behave slightly differently. On streaming requests, the timeout
|
||||
only applies to the connection attempt. On regular requests, the timeout
|
||||
is applied to the connection process and on to all attempts to read data from
|
||||
the underlying socket. It does *not* apply to the total download time for the
|
||||
request.
|
||||
|
||||
::
|
||||
|
||||
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
|
||||
|
||||
# One second timeout for the connection attempt
|
||||
# Unlimited time to download the tarball
|
||||
r = requests.get(tarball_url, stream=True, timeout=1)
|
||||
|
||||
# One second timeout for the connection attempt
|
||||
# Another full second timeout to download the tarball
|
||||
r = requests.get(tarball_url, timeout=1)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ If your question is less than 140 characters, feel free to send a tweet to
|
||||
File an Issue
|
||||
-------------
|
||||
|
||||
If you notice some unexpected behavior in Requests, or want to see support
|
||||
If you notice some unexpected behaviour in Requests, or want to see support
|
||||
for a new feature,
|
||||
`file an issue on GitHub <https://github.com/kennethreitz/requests/issues>`_.
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ Development Dependencies
|
||||
You'll need to install py.test in order to run the Requests' test suite::
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
$ invoke test
|
||||
py.test
|
||||
$ py.test
|
||||
platform darwin -- Python 2.7.3 -- pytest-2.3.4
|
||||
collected 25 items
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ Testimonials
|
||||
|
||||
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 US Institutions
|
||||
use Requests internally. It has been downloaded over 21,000,000 times from PyPI.
|
||||
Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed
|
||||
use Requests internally. It has been downloaded over 23,000,000 times from PyPI.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
|
||||
@@ -166,7 +166,7 @@ I don't have SSL setup on this domain, so it fails. Excellent. GitHub does thoug
|
||||
>>> requests.get('https://github.com', verify=True)
|
||||
<Response [200]>
|
||||
|
||||
You can also pass ``verify`` the path to a CA_BUNDLE file for private certs. You can also set the ``REQUESTS_CA_BUNDLE`` environment variable.
|
||||
You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs. 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.
|
||||
|
||||
@@ -194,7 +194,7 @@ Body Content Workflow
|
||||
---------------------
|
||||
|
||||
By default, when you make a request, the body of the response is downloaded
|
||||
immediately. You can override this behavior and defer downloading the response
|
||||
immediately. You can override this behaviour and defer downloading the response
|
||||
body until you access the :class:`Response.content <requests.Response.content>`
|
||||
attribute with the ``stream`` parameter::
|
||||
|
||||
@@ -267,6 +267,30 @@ a length) for your body::
|
||||
requests.post('http://some.url/chunked', data=gen())
|
||||
|
||||
|
||||
|
||||
POST Multiple Multipart-Encoded Files
|
||||
-------------------------------------
|
||||
|
||||
You can send multiple files in one request. For example, suppose you want to
|
||||
upload image files to an HTML form with a multiple file field 'images':
|
||||
|
||||
<input type="file" name="images" multiple="true" required="true"/>
|
||||
|
||||
To do that, just set files to a list of tuples of (form_field_name, file_info):
|
||||
|
||||
>>> url = 'http://httpbin.org/post'
|
||||
>>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
|
||||
('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
|
||||
>>> r = requests.post(url, files=multiple_files)
|
||||
>>> r.text
|
||||
{
|
||||
...
|
||||
'files': {'images': 'data:image/png;base64,iVBORw ....'}
|
||||
'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
Event Hooks
|
||||
-----------
|
||||
|
||||
@@ -741,3 +765,21 @@ coffee.
|
||||
r = requests.get('https://github.com', timeout=None)
|
||||
|
||||
.. _`connect()`: http://linux.die.net/man/2/connect
|
||||
|
||||
CA Certificates
|
||||
---------------
|
||||
|
||||
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.
|
||||
|
||||
From Requests version 2.4.0 onwards, Requests will attempt to use certificates
|
||||
from `certifi`_ if it is present on the system. This allows for users to update
|
||||
their trusted certificates without having to change the code that runs on their
|
||||
system.
|
||||
|
||||
For the sake of security we recommend upgrading certifi frequently!
|
||||
|
||||
.. _certifi: http://certifi.io/
|
||||
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
|
||||
@@ -31,7 +31,7 @@ timeline ::
|
||||
|
||||
>>> r = requests.get('https://api.github.com/events')
|
||||
|
||||
Now, we have a :class:`Request <requests.Request>` object called ``r``. We can
|
||||
Now, we have a :class:`Response <requests.Response>` object called ``r``. We can
|
||||
get all the information we need from this object.
|
||||
|
||||
Requests' simple API means that all forms of HTTP request are as obvious. For
|
||||
@@ -81,7 +81,7 @@ We can read the content of the server's response. Consider the GitHub timeline
|
||||
again::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://github.com/timeline.json')
|
||||
>>> r = requests.get('https://api.github.com/events')
|
||||
>>> r.text
|
||||
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
@@ -134,7 +134,7 @@ JSON Response Content
|
||||
There's also a builtin JSON decoder, in case you're dealing with JSON data::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://github.com/timeline.json')
|
||||
>>> r = requests.get('https://api.github.com/events')
|
||||
>>> r.json()
|
||||
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
|
||||
|
||||
@@ -150,7 +150,7 @@ In the rare case that you'd like to get the raw socket response from the
|
||||
server, you can access ``r.raw``. If you want to do this, make sure you set
|
||||
``stream=True`` in your initial request. Once you do, you can do this::
|
||||
|
||||
>>> r = requests.get('https://github.com/timeline.json', stream=True)
|
||||
>>> r = requests.get('https://api.github.com/events', stream=True)
|
||||
>>> r.raw
|
||||
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
|
||||
>>> r.raw.read(10)
|
||||
@@ -270,6 +270,9 @@ support this, but there is a separate package which does -
|
||||
``requests-toolbelt``. You should read `the toolbelt's documentation
|
||||
<https://toolbelt.rtfd.org>`_ for more details about how to use it.
|
||||
|
||||
For sending multiple files in one request refer to the :ref:`advanced <advanced>`
|
||||
section.
|
||||
|
||||
|
||||
Response Status Codes
|
||||
---------------------
|
||||
@@ -369,9 +372,9 @@ HEAD.
|
||||
We can use the ``history`` property of the Response object to track redirection.
|
||||
|
||||
The :meth:`Response.history <requests.Response.history>` list contains the
|
||||
:class:`Request <requests.Request>` objects that were created in order to
|
||||
: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
|
||||
request.
|
||||
response.
|
||||
|
||||
For example, GitHub redirects all HTTP requests to HTTPS::
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Requests is an HTTP library, written in Python, for human beings. Basic GET
|
||||
usage:
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('http://python.org')
|
||||
>>> r = requests.get('https://www.python.org')
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> 'Python is a programming language' in r.content
|
||||
@@ -22,7 +22,7 @@ usage:
|
||||
... or POST:
|
||||
|
||||
>>> payload = dict(key1='value1', key2='value2')
|
||||
>>> r = requests.post("http://httpbin.org/post", data=payload)
|
||||
>>> r = requests.post('http://httpbin.org/post', data=payload)
|
||||
>>> print(r.text)
|
||||
{
|
||||
...
|
||||
@@ -42,8 +42,8 @@ is at <http://python-requests.org>.
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '2.4.0'
|
||||
__build__ = 0x020400
|
||||
__version__ = '2.4.3'
|
||||
__build__ = 0x020403
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2014 Kenneth Reitz'
|
||||
|
||||
@@ -15,14 +15,15 @@ from .packages.urllib3 import Retry
|
||||
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .packages.urllib3.util import Timeout as TimeoutSauce
|
||||
from .compat import urlparse, basestring, urldefrag
|
||||
from .compat import urlparse, basestring
|
||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||
prepend_scheme_if_needed, get_auth_from_url)
|
||||
prepend_scheme_if_needed, get_auth_from_url, urldefragauth)
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .packages.urllib3.exceptions import ConnectTimeoutError
|
||||
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
||||
from .packages.urllib3.exceptions import MaxRetryError
|
||||
from .packages.urllib3.exceptions import ProxyError as _ProxyError
|
||||
from .packages.urllib3.exceptions import ProtocolError
|
||||
from .packages.urllib3.exceptions import ReadTimeoutError
|
||||
from .packages.urllib3.exceptions import SSLError as _SSLError
|
||||
from .cookies import extract_cookies_to_jar
|
||||
@@ -269,7 +270,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
proxy = proxies.get(scheme)
|
||||
|
||||
if proxy and scheme != 'https':
|
||||
url, _ = urldefrag(request.url)
|
||||
url = urldefragauth(request.url)
|
||||
else:
|
||||
url = request.path_url
|
||||
|
||||
@@ -316,8 +317,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
:param stream: (optional) Whether to stream the request content.
|
||||
:param timeout: (optional) The timeout on the request.
|
||||
:type timeout: float or tuple (connect timeout, read timeout), eg (3.1, 20)
|
||||
:param timeout: (optional) How long to wait for the server to send
|
||||
data before giving up, as a float, or a (`connect timeout, read
|
||||
timeout <user/advanced.html#timeouts>`_) tuple.
|
||||
:type timeout: float or tuple
|
||||
: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.
|
||||
@@ -400,8 +403,8 @@ class HTTPAdapter(BaseAdapter):
|
||||
# All is well, return the connection to the pool.
|
||||
conn._put_conn(low_conn)
|
||||
|
||||
except socket.error as sockerr:
|
||||
raise ConnectionError(sockerr, request=request)
|
||||
except (ProtocolError, socket.error) as err:
|
||||
raise ConnectionError(err, request=request)
|
||||
|
||||
except MaxRetryError as e:
|
||||
if isinstance(e.reason, ConnectTimeoutError):
|
||||
|
||||
@@ -22,12 +22,17 @@ def request(method, url, **kwargs):
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload.
|
||||
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload.
|
||||
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request in seconds.
|
||||
:param timeout: (optional) How long to wait for the server to send data
|
||||
before giving up, as a float, or a (`connect timeout, read timeout
|
||||
<user/advanced.html#timeouts>`_) tuple.
|
||||
:type timeout: float or tuple
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:type allow_redirects: bool
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
|
||||
@@ -77,15 +82,16 @@ def head(url, **kwargs):
|
||||
return request('head', url, **kwargs)
|
||||
|
||||
|
||||
def post(url, data=None, **kwargs):
|
||||
def post(url, data=None, json=None, **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
: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 json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, **kwargs)
|
||||
return request('post', url, data=data, json=json, **kwargs)
|
||||
|
||||
|
||||
def put(url, data=None, **kwargs):
|
||||
|
||||
@@ -46,15 +46,16 @@ class SSLError(ConnectionError):
|
||||
class Timeout(RequestException):
|
||||
"""The request timed out.
|
||||
|
||||
Catching this error will catch both :exc:`ConnectTimeout` and
|
||||
:exc:`ReadTimeout` errors.
|
||||
Catching this error will catch both
|
||||
:exc:`~requests.exceptions.ConnectTimeout` and
|
||||
:exc:`~requests.exceptions.ReadTimeout` errors.
|
||||
"""
|
||||
|
||||
|
||||
class ConnectTimeout(ConnectionError, Timeout):
|
||||
"""The request timed out while trying to connect to the server.
|
||||
"""The request timed out while trying to connect to the remote server.
|
||||
|
||||
Requests that produce this error are safe to retry
|
||||
Requests that produced this error are safe to retry.
|
||||
"""
|
||||
|
||||
|
||||
@@ -88,3 +89,6 @@ class ChunkedEncodingError(RequestException):
|
||||
|
||||
class ContentDecodingError(RequestException, BaseHTTPError):
|
||||
"""Failed to decode response content"""
|
||||
|
||||
class StreamConsumedError(RequestException, TypeError):
|
||||
"""The content for this response was already consumed"""
|
||||
|
||||
@@ -22,8 +22,9 @@ from .packages.urllib3.util import parse_url
|
||||
from .packages.urllib3.exceptions import (
|
||||
DecodeError, ReadTimeoutError, ProtocolError)
|
||||
from .exceptions import (
|
||||
HTTPError, RequestException, MissingSchema, InvalidURL,
|
||||
ChunkedEncodingError, ContentDecodingError, ConnectionError)
|
||||
HTTPError, RequestException, MissingSchema, InvalidURL,
|
||||
ChunkedEncodingError, ContentDecodingError, ConnectionError,
|
||||
StreamConsumedError)
|
||||
from .utils import (
|
||||
guess_filename, get_auth_from_url, requote_uri,
|
||||
stream_decode_response_unicode, to_key_val_list, parse_header_links,
|
||||
@@ -46,6 +47,8 @@ DEFAULT_REDIRECT_LIMIT = 30
|
||||
CONTENT_CHUNK_SIZE = 10 * 1024
|
||||
ITER_CHUNK_SIZE = 512
|
||||
|
||||
json_dumps = json.dumps
|
||||
|
||||
|
||||
class RequestEncodingMixin(object):
|
||||
@property
|
||||
@@ -189,7 +192,8 @@ class Request(RequestHooksMixin):
|
||||
:param url: URL to send.
|
||||
:param headers: dictionary of headers to send.
|
||||
:param files: dictionary of {filename: fileobject} files to multipart upload.
|
||||
:param data: the body to attach the request. If a dictionary is provided, form-encoding will take place.
|
||||
:param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place.
|
||||
:param json: json for the body to attach to the request (if data is not specified).
|
||||
:param params: dictionary of URL parameters to append to the URL.
|
||||
:param auth: Auth handler or (user, pass) tuple.
|
||||
:param cookies: dictionary or CookieJar of cookies to attach to this request.
|
||||
@@ -212,7 +216,8 @@ class Request(RequestHooksMixin):
|
||||
params=None,
|
||||
auth=None,
|
||||
cookies=None,
|
||||
hooks=None):
|
||||
hooks=None,
|
||||
json=None):
|
||||
|
||||
# Default empty dicts for dict params.
|
||||
data = [] if data is None else data
|
||||
@@ -230,6 +235,7 @@ class Request(RequestHooksMixin):
|
||||
self.headers = headers
|
||||
self.files = files
|
||||
self.data = data
|
||||
self.json = json
|
||||
self.params = params
|
||||
self.auth = auth
|
||||
self.cookies = cookies
|
||||
@@ -246,6 +252,7 @@ class Request(RequestHooksMixin):
|
||||
headers=self.headers,
|
||||
files=self.files,
|
||||
data=self.data,
|
||||
json=self.json,
|
||||
params=self.params,
|
||||
auth=self.auth,
|
||||
cookies=self.cookies,
|
||||
@@ -289,14 +296,15 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
self.hooks = default_hooks()
|
||||
|
||||
def prepare(self, method=None, url=None, headers=None, files=None,
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None):
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None,
|
||||
json=None):
|
||||
"""Prepares the entire request with the given parameters."""
|
||||
|
||||
self.prepare_method(method)
|
||||
self.prepare_url(url, params)
|
||||
self.prepare_headers(headers)
|
||||
self.prepare_cookies(cookies)
|
||||
self.prepare_body(data, files)
|
||||
self.prepare_body(data, files, json)
|
||||
self.prepare_auth(auth, url)
|
||||
# Note that prepare_auth must be last to enable authentication schemes
|
||||
# such as OAuth to work on a fully prepared request.
|
||||
@@ -326,15 +334,18 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
def prepare_url(self, url, params):
|
||||
"""Prepares the given HTTP URL."""
|
||||
#: Accept objects that have string representations.
|
||||
try:
|
||||
url = unicode(url)
|
||||
except NameError:
|
||||
# We're on Python 3.
|
||||
url = str(url)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
#: We're unable to blindy call unicode/str functions
|
||||
#: as this will include the bytestring indicator (b'')
|
||||
#: on python 3.x.
|
||||
#: https://github.com/kennethreitz/requests/pull/2238
|
||||
if isinstance(url, bytes):
|
||||
url = url.decode('utf8')
|
||||
else:
|
||||
url = unicode(url) if is_py2 else str(url)
|
||||
|
||||
# Don't do any URL preparation for oddball schemes
|
||||
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
|
||||
# `data` etc to work around exceptions from `url_parse`, which
|
||||
# handles RFC 3986 only.
|
||||
if ':' in url and not url.lower().startswith('http'):
|
||||
self.url = url
|
||||
return
|
||||
@@ -397,7 +408,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
self.headers = CaseInsensitiveDict()
|
||||
|
||||
def prepare_body(self, data, files):
|
||||
def prepare_body(self, data, files, json=None):
|
||||
"""Prepares the given HTTP body data."""
|
||||
|
||||
# Check if file, fo, generator, iterator.
|
||||
@@ -408,6 +419,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
content_type = None
|
||||
length = None
|
||||
|
||||
if json is not None:
|
||||
content_type = 'application/json'
|
||||
body = json_dumps(json)
|
||||
|
||||
is_stream = all([
|
||||
hasattr(data, '__iter__'),
|
||||
not isinstance(data, (basestring, list, tuple, dict))
|
||||
@@ -433,7 +448,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if files:
|
||||
(body, content_type) = self._encode_files(files, data)
|
||||
else:
|
||||
if data:
|
||||
if data and json is None:
|
||||
body = self._encode_params(data)
|
||||
if isinstance(data, basestring) or hasattr(data, 'read'):
|
||||
content_type = None
|
||||
@@ -443,7 +458,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
self.prepare_content_length(body)
|
||||
|
||||
# Add content-type if it wasn't explicitly provided.
|
||||
if (content_type) and (not 'content-type' in self.headers):
|
||||
if content_type and ('content-type' not in self.headers):
|
||||
self.headers['Content-Type'] = content_type
|
||||
|
||||
self.body = body
|
||||
@@ -653,6 +668,8 @@ class Response(object):
|
||||
|
||||
self._content_consumed = True
|
||||
|
||||
if self._content_consumed and isinstance(self._content, bool):
|
||||
raise StreamConsumedError()
|
||||
# simulate reading small chunks of the content
|
||||
reused_chunks = iter_slices(self._content, chunk_size)
|
||||
|
||||
|
||||
@@ -134,8 +134,8 @@ class SessionRedirectMixin(object):
|
||||
url = requote_uri(url)
|
||||
|
||||
prepared_request.url = to_native_string(url)
|
||||
# cache the url
|
||||
if resp.is_permanent_redirect:
|
||||
# Cache the url, unless it redirects to itself.
|
||||
if resp.is_permanent_redirect and req.url != prepared_request.url:
|
||||
self.redirect_cache[req.url] = prepared_request.url
|
||||
|
||||
# http://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||
@@ -271,9 +271,10 @@ class Session(SessionRedirectMixin):
|
||||
"""
|
||||
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
||||
'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream',
|
||||
'trust_env', 'max_redirects', 'redirect_cache']
|
||||
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
|
||||
'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
|
||||
'max_redirects', 'redirect_cache'
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -365,6 +366,7 @@ class Session(SessionRedirectMixin):
|
||||
url=request.url,
|
||||
files=request.files,
|
||||
data=request.data,
|
||||
json=request.json,
|
||||
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
|
||||
params=merge_setting(request.params, self.params),
|
||||
auth=merge_setting(auth, self.auth),
|
||||
@@ -386,7 +388,8 @@ class Session(SessionRedirectMixin):
|
||||
hooks=None,
|
||||
stream=None,
|
||||
verify=None,
|
||||
cert=None):
|
||||
cert=None,
|
||||
json=None):
|
||||
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
@@ -396,17 +399,22 @@ class Session(SessionRedirectMixin):
|
||||
string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the
|
||||
:class:`Request`.
|
||||
:param json: (optional) json to send in the body of the
|
||||
:class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the
|
||||
:class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the
|
||||
:class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects
|
||||
:param files: (optional) Dictionary of ``'filename': file-like-objects``
|
||||
for multipart encoding upload.
|
||||
:param auth: (optional) Auth tuple or callable to enable
|
||||
Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the
|
||||
request in seconds.
|
||||
:param allow_redirects: (optional) Boolean. Set to True by default.
|
||||
:param timeout: (optional) How long to wait for the server to send
|
||||
data before giving up, as a float, or a (`connect timeout, read
|
||||
timeout <user/advanced.html#timeouts>`_) tuple.
|
||||
:type timeout: float or tuple
|
||||
:param allow_redirects: (optional) Set to True by default.
|
||||
:type allow_redirects: bool
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of
|
||||
the proxy.
|
||||
:param stream: (optional) whether to immediately download the response
|
||||
@@ -426,6 +434,7 @@ class Session(SessionRedirectMixin):
|
||||
headers = headers,
|
||||
files = files,
|
||||
data = data or {},
|
||||
json = json,
|
||||
params = params or {},
|
||||
auth = auth,
|
||||
cookies = cookies,
|
||||
@@ -479,15 +488,16 @@ class Session(SessionRedirectMixin):
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return self.request('HEAD', url, **kwargs)
|
||||
|
||||
def post(self, url, data=None, **kwargs):
|
||||
def post(self, url, data=None, json=None, **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
: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 json: (optional) json to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('POST', url, data=data, **kwargs)
|
||||
return self.request('POST', url, data=data, json=json, **kwargs)
|
||||
|
||||
def put(self, url, data=None, **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
@@ -532,8 +542,13 @@ class Session(SessionRedirectMixin):
|
||||
if not isinstance(request, PreparedRequest):
|
||||
raise ValueError('You can only send PreparedRequests.')
|
||||
|
||||
checked_urls = set()
|
||||
while request.url in self.redirect_cache:
|
||||
request.url = self.redirect_cache.get(request.url)
|
||||
checked_urls.add(request.url)
|
||||
new_url = self.redirect_cache.get(request.url)
|
||||
if new_url in checked_urls:
|
||||
break
|
||||
request.url = new_url
|
||||
|
||||
# Set up variables needed for resolve_redirects and dispatching of hooks
|
||||
allow_redirects = kwargs.pop('allow_redirects', True)
|
||||
|
||||
@@ -351,10 +351,7 @@ def get_unicode_from_response(r):
|
||||
Tried:
|
||||
|
||||
1. charset from content-type
|
||||
|
||||
2. every encodings from ``<meta ... charset=XXX>``
|
||||
|
||||
3. fall back and replace all unicode characters
|
||||
2. fall back and replace all unicode characters
|
||||
|
||||
"""
|
||||
|
||||
@@ -555,7 +552,7 @@ def default_headers():
|
||||
'User-Agent': default_user_agent(),
|
||||
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
|
||||
'Accept': '*/*',
|
||||
'Connection': 'keep-alive'
|
||||
'Connection': 'keep-alive',
|
||||
})
|
||||
|
||||
|
||||
@@ -672,3 +669,18 @@ def to_native_string(string, encoding='ascii'):
|
||||
out = string.decode(encoding)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def urldefragauth(url):
|
||||
"""
|
||||
Given a url remove the fragment and the authentication part
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
||||
|
||||
# see func:`prepend_scheme_if_needed`
|
||||
if not netloc:
|
||||
netloc, path = path, netloc
|
||||
|
||||
netloc = netloc.rsplit('@', 1)[-1]
|
||||
|
||||
return urlunparse((scheme, netloc, path, params, query, ''))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
py==1.4.12
|
||||
pytest==2.3.4
|
||||
pytest-cov==1.6
|
||||
invoke==0.2.0
|
||||
wheel
|
||||
|
||||
11
setup.py
11
setup.py
@@ -5,6 +5,8 @@ import sys
|
||||
|
||||
import requests
|
||||
|
||||
from codecs import open
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
@@ -25,11 +27,11 @@ packages = [
|
||||
'requests.packages.urllib3.packages.ssl_match_hostname',
|
||||
]
|
||||
|
||||
requires = ['certifi']
|
||||
requires = []
|
||||
|
||||
with open('README.rst') as f:
|
||||
with open('README.rst', 'r', 'utf-8') as f:
|
||||
readme = f.read()
|
||||
with open('HISTORY.rst') as f:
|
||||
with open('HISTORY.rst', 'r', 'utf-8') as f:
|
||||
history = f.read()
|
||||
|
||||
setup(
|
||||
@@ -60,4 +62,7 @@ setup(
|
||||
'Programming Language :: Python :: 3.4'
|
||||
|
||||
),
|
||||
extras_require={
|
||||
'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -18,8 +18,9 @@ from requests.auth import HTTPDigestAuth, _basic_auth_str
|
||||
from requests.compat import (
|
||||
Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str)
|
||||
from requests.cookies import cookiejar_from_dict, morsel_to_cookie
|
||||
from requests.exceptions import (InvalidURL, MissingSchema, ConnectTimeout,
|
||||
ReadTimeout, ConnectionError, Timeout)
|
||||
from requests.exceptions import (ConnectionError, ConnectTimeout,
|
||||
InvalidSchema, InvalidURL, MissingSchema,
|
||||
ReadTimeout, Timeout)
|
||||
from requests.models import PreparedRequest
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
from requests.sessions import SessionRedirectMixin
|
||||
@@ -78,6 +79,12 @@ class RequestsTestCase(unittest.TestCase):
|
||||
def test_invalid_url(self):
|
||||
with pytest.raises(MissingSchema):
|
||||
requests.get('hiwpefhipowhefopw')
|
||||
with pytest.raises(InvalidSchema):
|
||||
requests.get('localhost:3128')
|
||||
with pytest.raises(InvalidSchema):
|
||||
requests.get('localhost.localdomain:3128/')
|
||||
with pytest.raises(InvalidSchema):
|
||||
requests.get('10.122.1.1:3128/')
|
||||
with pytest.raises(InvalidURL):
|
||||
requests.get('http://')
|
||||
|
||||
@@ -286,6 +293,14 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_connection_error(self):
|
||||
"""Connecting to an unknown domain should raise a ConnectionError"""
|
||||
with pytest.raises(ConnectionError):
|
||||
requests.get("http://fooobarbangbazbing.httpbin.org")
|
||||
|
||||
with pytest.raises(ConnectionError):
|
||||
requests.get("http://httpbin.org:1")
|
||||
|
||||
def test_basicauth_with_netrc(self):
|
||||
auth = ('user', 'pass')
|
||||
wrong_auth = ('wronguser', 'wrongpass')
|
||||
@@ -576,6 +591,12 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert resp.json()['headers'][
|
||||
'Dummy-Auth-Test'] == 'dummy-auth-test-ok'
|
||||
|
||||
def test_prepare_request_with_bytestring_url(self):
|
||||
req = requests.Request('GET', b'https://httpbin.org/')
|
||||
s = requests.Session()
|
||||
prep = s.prepare_request(req)
|
||||
assert prep.url == "https://httpbin.org/"
|
||||
|
||||
def test_links(self):
|
||||
r = requests.Response()
|
||||
r.headers = {
|
||||
@@ -907,7 +928,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
|
||||
assert p.headers['Content-Length'] == length
|
||||
|
||||
def test_oddball_schemes_dont_check_URLs(self):
|
||||
def test_nonhttp_schemes_dont_check_URLs(self):
|
||||
test_urls = (
|
||||
'data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==',
|
||||
'file:///etc/passwd',
|
||||
@@ -986,6 +1007,15 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert item.history == total[0:i]
|
||||
i=i+1
|
||||
|
||||
def test_json_param_post_content_type_works(self):
|
||||
r = requests.post(
|
||||
httpbin('post'),
|
||||
json={'life': 42}
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert 'application/json' in r.request.headers['Content-Type']
|
||||
assert {'life': 42} == r.json()['json']
|
||||
|
||||
|
||||
class TestContentEncodingDetection(unittest.TestCase):
|
||||
|
||||
@@ -1477,5 +1507,13 @@ def test_prepared_request_complete_copy():
|
||||
)
|
||||
assert_copy(p, p.copy())
|
||||
|
||||
def test_prepare_unicode_url():
|
||||
p = PreparedRequest()
|
||||
p.prepare(
|
||||
method='GET',
|
||||
url=u('http://www.example.com/üniçø∂é')
|
||||
)
|
||||
assert_copy(p, p.copy())
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user