Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1285f576ae | ||
|
|
9726f5314e | ||
|
|
1da1213561 | ||
|
|
4683f16990 | ||
|
|
fae6701478 | ||
|
|
449d74842d | ||
|
|
576b447a37 | ||
|
|
11a86a5651 | ||
|
|
13716728e7 | ||
|
|
14d71fd41a | ||
|
|
2d2c831d07 | ||
|
|
c398ab0e7d | ||
|
|
7d2dfa8684 | ||
|
|
6f659a4179 | ||
|
|
402a55b647 | ||
|
|
ca15d48087 | ||
|
|
5c4549493b | ||
|
|
36c9c8c3de | ||
|
|
f897be58bf | ||
|
|
dfad00a6e8 | ||
|
|
d5e050da85 | ||
|
|
1ec08bedd4 | ||
|
|
724ae1274e | ||
|
|
4f428228d7 | ||
|
|
ccabcf1fca | ||
|
|
4f9d0e0455 | ||
|
|
b9f1c448f7 | ||
|
|
22ec73d71a | ||
|
|
005f93fd0f | ||
|
|
34af72c87d | ||
|
|
a83685ce00 | ||
|
|
264f5bd5e1 | ||
|
|
d52e9b8c80 | ||
|
|
268672ab33 | ||
|
|
3b956495c1 | ||
|
|
3b84be3c99 | ||
|
|
e15280e49d | ||
|
|
55dea233ce | ||
|
|
4f0efdb77e | ||
|
|
7c4821231f | ||
|
|
bf3e960e15 | ||
|
|
53d4a1feee | ||
|
|
ed3ba3dbb8 | ||
|
|
37bde4b638 | ||
|
|
5562c0f097 | ||
|
|
2185fac626 | ||
|
|
329a5cfdd4 | ||
|
|
5b66b364a7 | ||
|
|
5f4d0d08de | ||
|
|
7e138f945e | ||
|
|
1e198f9115 | ||
|
|
be8b13e89b | ||
|
|
0d7de50dbd | ||
|
|
248d3e9f0f | ||
|
|
e514920e19 |
@@ -172,3 +172,5 @@ Patches and Suggestions
|
||||
- Brian Bamsch <bbamsch32@gmail.com> (`@bbamsch <https://github.com/bbamsch>`_)
|
||||
- Om Prakash Kumar <omprakash070@gmail.com> (`@iamprakashom <https://github.com/iamprakashom>`_)
|
||||
- Philipp Konrad <gardiac2002@gmail.com> (`@gardiac2002 <https://github.com/gardiac2002>`_)
|
||||
- Hussain Tamboli <hussaintamboli18@gmail.com> (`@hussaintamboli <https://github.com/hussaintamboli>`_)
|
||||
- Casey Davidson (`@davidsoncasey <https://github.com/davidsoncasey>`_)
|
||||
|
||||
41
HISTORY.rst
41
HISTORY.rst
@@ -3,6 +3,47 @@
|
||||
Release History
|
||||
---------------
|
||||
|
||||
2.12.4 (2016-12-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed regression from 2.12.2 where non-string types were rejected in the
|
||||
basic auth parameters. While support for this behaviour has been readded,
|
||||
the behaviour is deprecated and will be removed in the future.
|
||||
|
||||
2.12.3 (2016-12-01)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed regression from v2.12.1 for URLs with schemes that begin with "http".
|
||||
These URLs have historically been processed as though they were HTTP-schemed
|
||||
URLs, and so have had parameters added. This was removed in v2.12.2 in an
|
||||
overzealous attempt to resolve problems with IDNA-encoding those URLs. This
|
||||
change was reverted: the other fixes for IDNA-encoding have been judged to
|
||||
be sufficient to return to the behaviour Requests had before v2.12.0.
|
||||
|
||||
2.12.2 (2016-11-30)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed several issues with IDNA-encoding URLs that are technically invalid but
|
||||
which are widely accepted. Requests will now attempt to IDNA-encode a URL if
|
||||
it can but, if it fails, and the host contains only ASCII characters, it will
|
||||
be passed through optimistically. This will allow users to opt-in to using
|
||||
IDNA2003 themselves if they want to, and will also allow technically invalid
|
||||
but still common hostnames.
|
||||
- Fixed an issue where URLs with leading whitespace would raise
|
||||
``InvalidSchema`` errors.
|
||||
- Fixed an issue where some URLs without the HTTP or HTTPS schemes would still
|
||||
have HTTP URL preparation applied to them.
|
||||
- Fixed an issue where Unicode strings could not be used in basic auth.
|
||||
- Fixed an issue encountered by some Requests plugins where constructing a
|
||||
Response object would cause ``Response.content`` to raise an
|
||||
``AttributeError``.
|
||||
|
||||
2.12.1 (2016-11-16)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Requests: HTTP for Humans
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
|
||||
Requests is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||
@@ -29,6 +29,10 @@ Behold, the power of Requests:
|
||||
|
||||
See `the similar code, sans Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
.. image:: http://docs.python-requests.org/en/master/_static/requests-sidebar.png
|
||||
:target: http://docs.python-requests.org/
|
||||
|
||||
|
||||
Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, without the
|
||||
need for manual labor. There's no need to manually add query strings to your
|
||||
URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling
|
||||
|
||||
BIN
docs/_static/requests-sidebar.png
vendored
BIN
docs/_static/requests-sidebar.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 299 KiB |
4
docs/_templates/sidebarintro.html
vendored
4
docs/_templates/sidebarintro.html
vendored
@@ -1,6 +1,6 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" title="Rezzy the Requests Sea Turtle"/>
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" title="https://kennethreitz.org/tattoos"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
|
||||
|
||||
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow @kennethreitz</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
|
||||
|
||||
<p><a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>
|
||||
<p><a href="http://tinyletter.com/kennethreitz">Join Mailing List</a>.</p>
|
||||
|
||||
<h3>Other Projects</h3>
|
||||
|
||||
6
docs/_templates/sidebarlogo.html
vendored
6
docs/_templates/sidebarlogo.html
vendored
@@ -1,6 +1,6 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" title="Rezzy the Requests Sea Turtle"/>
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" title="https://kennethreitz.org/tattoos"/>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
@@ -19,6 +19,10 @@
|
||||
|
||||
<p><a href="http://tinyletter.com/kennethreitz">Join Mailing List</a>.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<p>If you enjoy using this project, <a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>
|
||||
|
||||
<p><iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=false"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
|
||||
|
||||
|
||||
@@ -178,13 +178,14 @@ In general, however, you should use a pattern like this to save what is being
|
||||
streamed to a file::
|
||||
|
||||
with open(filename, 'wb') as fd:
|
||||
for chunk in r.iter_content(chunk_size):
|
||||
for chunk in r.iter_content(chunk_size=128):
|
||||
fd.write(chunk)
|
||||
|
||||
Using ``Response.iter_content`` will handle a lot of what you would otherwise
|
||||
have to handle when using ``Response.raw`` directly. When streaming a
|
||||
download, the above is the preferred and recommended way to retrieve the
|
||||
content.
|
||||
content. Note that ``chunk_size`` can be freely adjusted to a number that
|
||||
may better fit your use cases.
|
||||
|
||||
|
||||
Custom Headers
|
||||
|
||||
9793
ext/requests-logo.ai
9793
ext/requests-logo.ai
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 863 KiB |
@@ -41,8 +41,8 @@ is at <http://python-requests.org>.
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '2.12.1'
|
||||
__build__ = 0x021201
|
||||
__version__ = '2.12.4'
|
||||
__build__ = 0x021204
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2016 Kenneth Reitz'
|
||||
|
||||
@@ -8,7 +8,7 @@ Provides utility functions that are consumed internally by Requests
|
||||
which depend on extremely few external helpers (such as compat)
|
||||
"""
|
||||
|
||||
from .compat import is_py2, builtin_str
|
||||
from .compat import is_py2, builtin_str, str
|
||||
|
||||
|
||||
def to_native_string(string, encoding='ascii'):
|
||||
@@ -25,3 +25,18 @@ def to_native_string(string, encoding='ascii'):
|
||||
out = string.decode(encoding)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def unicode_is_ascii(u_string):
|
||||
"""Determine if unicode string only contains ASCII characters.
|
||||
|
||||
:param str u_string: unicode string to check. Must be unicode
|
||||
and not Python 2 `str`.
|
||||
:rtype: bool
|
||||
"""
|
||||
assert isinstance(u_string, str)
|
||||
try:
|
||||
u_string.encode('ascii')
|
||||
return True
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
|
||||
@@ -12,10 +12,11 @@ import re
|
||||
import time
|
||||
import hashlib
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
from .compat import urlparse, str
|
||||
from .compat import urlparse, str, basestring
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from ._internal_utils import to_native_string
|
||||
from .utils import parse_dict_header
|
||||
@@ -28,8 +29,42 @@ CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
|
||||
def _basic_auth_str(username, password):
|
||||
"""Returns a Basic Auth string."""
|
||||
|
||||
# "I want us to put a big-ol' comment on top of it that
|
||||
# says that this behaviour is dumb but we need to preserve
|
||||
# it because people are relying on it."
|
||||
# - Lukasa
|
||||
#
|
||||
# These are here solely to maintain backwards compatibility
|
||||
# for things like ints. This will be removed in 3.0.0.
|
||||
if not isinstance(username, basestring):
|
||||
warnings.warn(
|
||||
"Non-string usernames will no longer be supported in Requests "
|
||||
"3.0.0. Please convert the object you've passed in ({!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(username),
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
username = str(username)
|
||||
|
||||
if not isinstance(password, basestring):
|
||||
warnings.warn(
|
||||
"Non-string passwords will no longer be supported in Requests "
|
||||
"3.0.0. Please convert the object you've passed in ({!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(password),
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
password = str(password)
|
||||
# -- End Removal --
|
||||
|
||||
if isinstance(username, str):
|
||||
username = username.encode('latin1')
|
||||
|
||||
if isinstance(password, str):
|
||||
password = password.encode('latin1')
|
||||
|
||||
authstr = 'Basic ' + to_native_string(
|
||||
b64encode(('%s:%s' % (username, password)).encode('latin1')).strip()
|
||||
b64encode(b':'.join((username, password))).strip()
|
||||
)
|
||||
|
||||
return authstr
|
||||
|
||||
@@ -30,7 +30,7 @@ from .packages.urllib3.exceptions import (
|
||||
from .exceptions import (
|
||||
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
|
||||
ContentDecodingError, ConnectionError, StreamConsumedError)
|
||||
from ._internal_utils import to_native_string
|
||||
from ._internal_utils import to_native_string, unicode_is_ascii
|
||||
from .utils import (
|
||||
guess_filename, get_auth_from_url, requote_uri,
|
||||
stream_decode_response_unicode, to_key_val_list, parse_header_links,
|
||||
@@ -343,6 +343,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
url = unicode(url) if is_py2 else str(url)
|
||||
|
||||
# Remove leading whitespaces from url
|
||||
url = url.lstrip()
|
||||
|
||||
# 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.
|
||||
@@ -365,11 +368,17 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if not host:
|
||||
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
||||
|
||||
# Only want to apply IDNA to the hostname
|
||||
# In general, we want to try IDNA encoding every hostname, as that
|
||||
# allows users to automatically get the correct behaviour. However,
|
||||
# we’re quite strict about IDNA encoding, so certain valid hostnames
|
||||
# may fail to encode. On failure, we verify the hostname meets a
|
||||
# minimum standard of only containing ASCII characters, and not starting
|
||||
# with a wildcard (*), before allowing the unencoded hostname through.
|
||||
try:
|
||||
host = idna.encode(host, uts46=True).decode('utf-8')
|
||||
except (UnicodeError, idna.IDNAError):
|
||||
raise InvalidURL('URL has an invalid label.')
|
||||
if not unicode_is_ascii(host) or host.startswith(u'*'):
|
||||
raise InvalidURL('URL has an invalid label.')
|
||||
|
||||
# Carefully reconstruct the network location
|
||||
netloc = auth or ''
|
||||
@@ -766,7 +775,7 @@ class Response(object):
|
||||
raise RuntimeError(
|
||||
'The content for this response was already consumed')
|
||||
|
||||
if self.status_code == 0:
|
||||
if self.status_code == 0 or self.raw is None:
|
||||
self._content = None
|
||||
else:
|
||||
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
import threading
|
||||
import requests
|
||||
|
||||
from tests.testserver.server import Server
|
||||
from tests.testserver.server import Server, consume_socket_content
|
||||
|
||||
from .utils import override_environ
|
||||
|
||||
@@ -25,6 +25,117 @@ def test_chunked_upload():
|
||||
assert r.request.headers['Transfer-Encoding'] == 'chunked'
|
||||
|
||||
|
||||
def test_digestauth_401_count_reset_on_redirect():
|
||||
"""Ensure we correctly reset num_401_calls after a successful digest auth,
|
||||
followed by a 302 redirect to another digest auth prompt.
|
||||
|
||||
See https://github.com/kennethreitz/requests/issues/1979.
|
||||
"""
|
||||
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
|
||||
|
||||
text_302 = (b'HTTP/1.1 302 FOUND\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'Location: /\r\n\r\n')
|
||||
|
||||
text_200 = (b'HTTP/1.1 200 OK\r\n'
|
||||
b'Content-Length: 0\r\n\r\n')
|
||||
|
||||
expected_digest = (b'Authorization: Digest username="user", '
|
||||
b'realm="me@kennethreitz.com", '
|
||||
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
|
||||
|
||||
auth = requests.auth.HTTPDigestAuth('user', 'pass')
|
||||
|
||||
def digest_response_handler(sock):
|
||||
# Respond to initial GET with a challenge.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content.startswith(b"GET / HTTP/1.1")
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify we receive an Authorization header in response, then redirect.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert expected_digest in request_content
|
||||
sock.send(text_302)
|
||||
|
||||
# Verify Authorization isn't sent to the redirected host,
|
||||
# then send another challenge.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert b'Authorization:' not in request_content
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify Authorization is sent correctly again, and return 200 OK.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert expected_digest in request_content
|
||||
sock.send(text_200)
|
||||
|
||||
return request_content
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(digest_response_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.get(url, auth=auth)
|
||||
# Verify server succeeded in authenticating.
|
||||
assert r.status_code == 200
|
||||
# Verify Authorization was sent in final request.
|
||||
assert 'Authorization' in r.request.headers
|
||||
assert r.request.headers['Authorization'].startswith('Digest ')
|
||||
# Verify redirect happened as we expected.
|
||||
assert r.history[0].status_code == 302
|
||||
close_server.set()
|
||||
|
||||
|
||||
def test_digestauth_401_only_sent_once():
|
||||
"""Ensure we correctly respond to a 401 challenge once, and then
|
||||
stop responding if challenged again.
|
||||
"""
|
||||
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
|
||||
|
||||
expected_digest = (b'Authorization: Digest username="user", '
|
||||
b'realm="me@kennethreitz.com", '
|
||||
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
|
||||
|
||||
auth = requests.auth.HTTPDigestAuth('user', 'pass')
|
||||
|
||||
def digest_failed_response_handler(sock):
|
||||
# Respond to initial GET with a challenge.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content.startswith(b"GET / HTTP/1.1")
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify we receive an Authorization header in response, then
|
||||
# challenge again.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert expected_digest in request_content
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify the client didn't respond to second challenge.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content == b''
|
||||
|
||||
return request_content
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(digest_failed_response_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.get(url, auth=auth)
|
||||
# Verify server didn't authenticate us.
|
||||
assert r.status_code == 401
|
||||
assert r.history[0].status_code == 401
|
||||
close_server.set()
|
||||
|
||||
|
||||
_schemes_by_var_prefix = [
|
||||
('http', ['http']),
|
||||
('https', ['https']),
|
||||
|
||||
@@ -140,6 +140,11 @@ class TestRequests:
|
||||
data=u"ööö".encode("utf-8")).prepare()
|
||||
assert isinstance(request.body, bytes)
|
||||
|
||||
def test_whitespaces_are_removed_from_url(self):
|
||||
# Test for issue #3696
|
||||
request = requests.Request('GET', ' http://example.com').prepare()
|
||||
assert request.url == 'http://example.com/'
|
||||
|
||||
@pytest.mark.parametrize('scheme', ('http://', 'HTTP://', 'hTTp://', 'HttP://'))
|
||||
def test_mixed_case_scheme_acceptable(self, httpbin, scheme):
|
||||
s = requests.Session()
|
||||
@@ -475,6 +480,32 @@ class TestRequests:
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'username, password', (
|
||||
('user', 'pass'),
|
||||
(u'имя'.encode('utf-8'), u'пароль'.encode('utf-8')),
|
||||
(42, 42),
|
||||
(None, None),
|
||||
))
|
||||
def test_set_basicauth(self, httpbin, username, password):
|
||||
auth = (username, password)
|
||||
url = httpbin('get')
|
||||
|
||||
r = requests.Request('GET', url, auth=auth)
|
||||
p = r.prepare()
|
||||
|
||||
assert p.headers['Authorization'] == _basic_auth_str(username, password)
|
||||
|
||||
def test_basicauth_encodes_byte_strings(self):
|
||||
"""Ensure b'test' formats as the byte string "test" rather
|
||||
than the unicode string "b'test'" in Python 3.
|
||||
"""
|
||||
auth = (b'\xc5\xafsername', b'test\xc6\xb6')
|
||||
r = requests.Request('GET', 'http://localhost', auth=auth)
|
||||
p = r.prepare()
|
||||
|
||||
assert p.headers['Authorization'] == 'Basic xa9zZXJuYW1lOnRlc3TGtg=='
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, exception', (
|
||||
# Connecting to an unknown domain should raise a ConnectionError
|
||||
@@ -1075,6 +1106,10 @@ class TestRequests:
|
||||
total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6)
|
||||
assert total_seconds > 0.0
|
||||
|
||||
def test_empty_response_has_content_none(self):
|
||||
r = requests.Response()
|
||||
assert r.content is None
|
||||
|
||||
def test_response_is_iterable(self):
|
||||
r = requests.Response()
|
||||
io = StringIO.StringIO('abc')
|
||||
@@ -1568,10 +1603,15 @@ class TestRequests:
|
||||
self._patch_adapter_gzipped_redirect(s, url)
|
||||
s.get(url)
|
||||
|
||||
def test_basic_auth_str_is_always_native(self):
|
||||
s = _basic_auth_str("test", "test")
|
||||
@pytest.mark.parametrize(
|
||||
'username, password, auth_str', (
|
||||
('test', 'test', 'Basic dGVzdDp0ZXN0'),
|
||||
(u'имя'.encode('utf-8'), u'пароль'.encode('utf-8'), 'Basic 0LjQvNGPOtC/0LDRgNC+0LvRjA=='),
|
||||
))
|
||||
def test_basic_auth_str_is_always_native(self, username, password, auth_str):
|
||||
s = _basic_auth_str(username, password)
|
||||
assert isinstance(s, builtin_str)
|
||||
assert s == "Basic dGVzdDp0ZXN0"
|
||||
assert s == auth_str
|
||||
|
||||
def test_requests_history_is_saved(self, httpbin):
|
||||
r = requests.get(httpbin('redirect/5'))
|
||||
@@ -1665,6 +1705,42 @@ class TestRequests:
|
||||
resp.close()
|
||||
assert resp.raw.closed
|
||||
|
||||
def test_empty_stream_with_auth_does_not_set_content_length_header(self, httpbin):
|
||||
"""Ensure that a byte stream with size 0 will not set both a Content-Length
|
||||
and Transfer-Encoding header.
|
||||
"""
|
||||
auth = ('user', 'pass')
|
||||
url = httpbin('post')
|
||||
file_obj = io.BytesIO(b'')
|
||||
r = requests.Request('POST', url, auth=auth, data=file_obj)
|
||||
prepared_request = r.prepare()
|
||||
assert 'Transfer-Encoding' in prepared_request.headers
|
||||
assert 'Content-Length' not in prepared_request.headers
|
||||
|
||||
def test_stream_with_auth_does_not_set_transfer_encoding_header(self, httpbin):
|
||||
"""Ensure that a byte stream with size > 0 will not set both a Content-Length
|
||||
and Transfer-Encoding header.
|
||||
"""
|
||||
auth = ('user', 'pass')
|
||||
url = httpbin('post')
|
||||
file_obj = io.BytesIO(b'test data')
|
||||
r = requests.Request('POST', url, auth=auth, data=file_obj)
|
||||
prepared_request = r.prepare()
|
||||
assert 'Transfer-Encoding' not in prepared_request.headers
|
||||
assert 'Content-Length' in prepared_request.headers
|
||||
|
||||
def test_chunked_upload_does_not_set_content_length_header(self, httpbin):
|
||||
"""Ensure that requests with a generator body stream using
|
||||
Transfer-Encoding: chunked, not a Content-Length header.
|
||||
"""
|
||||
data = (i for i in [b'a', b'b', b'c'])
|
||||
url = httpbin('post')
|
||||
r = requests.Request('POST', url, data=data)
|
||||
prepared_request = r.prepare()
|
||||
assert 'Transfer-Encoding' in prepared_request.headers
|
||||
assert 'Content-Length' not in prepared_request.headers
|
||||
|
||||
|
||||
class TestCaseInsensitiveDict:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -2093,6 +2169,7 @@ class TestPreparingURLs(object):
|
||||
(
|
||||
('http://google.com', 'http://google.com/'),
|
||||
(u'http://ジェーピーニック.jp', u'http://xn--hckqz9bzb1cyrb.jp/'),
|
||||
(u'http://xn--n3h.net/', u'http://xn--n3h.net/'),
|
||||
(
|
||||
u'http://ジェーピーニック.jp'.encode('utf-8'),
|
||||
u'http://xn--hckqz9bzb1cyrb.jp/'
|
||||
@@ -2113,6 +2190,18 @@ class TestPreparingURLs(object):
|
||||
u'http://Königsgäßchen.de/straße'.encode('utf-8'),
|
||||
u'http://xn--knigsgchen-b4a3dun.de/stra%C3%9Fe'
|
||||
),
|
||||
(
|
||||
b'http://xn--n3h.net/',
|
||||
u'http://xn--n3h.net/'
|
||||
),
|
||||
(
|
||||
b'http://[1200:0000:ab00:1234:0000:2552:7777:1313]:12345/',
|
||||
u'http://[1200:0000:ab00:1234:0000:2552:7777:1313]:12345/'
|
||||
),
|
||||
(
|
||||
u'http://[1200:0000:ab00:1234:0000:2552:7777:1313]:12345/',
|
||||
u'http://[1200:0000:ab00:1234:0000:2552:7777:1313]:12345/'
|
||||
)
|
||||
)
|
||||
)
|
||||
def test_preparing_url(self, url, expected):
|
||||
@@ -2127,9 +2216,81 @@ class TestPreparingURLs(object):
|
||||
b"http://*",
|
||||
u"http://*.google.com",
|
||||
u"http://*",
|
||||
u"http://☃.net/"
|
||||
)
|
||||
)
|
||||
def test_preparing_bad_url(self, url):
|
||||
r = requests.Request('GET', url=url)
|
||||
with pytest.raises(requests.exceptions.InvalidURL):
|
||||
r.prepare()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'input, expected',
|
||||
(
|
||||
(
|
||||
b"http+unix://%2Fvar%2Frun%2Fsocket/path",
|
||||
u"http+unix://%2fvar%2frun%2fsocket/path",
|
||||
),
|
||||
(
|
||||
u"http+unix://%2Fvar%2Frun%2Fsocket/path",
|
||||
u"http+unix://%2fvar%2frun%2fsocket/path",
|
||||
),
|
||||
(
|
||||
b"mailto:user@example.org",
|
||||
u"mailto:user@example.org",
|
||||
),
|
||||
(
|
||||
u"mailto:user@example.org",
|
||||
u"mailto:user@example.org",
|
||||
),
|
||||
(
|
||||
b"data:SSDimaUgUHl0aG9uIQ==",
|
||||
u"data:SSDimaUgUHl0aG9uIQ==",
|
||||
)
|
||||
)
|
||||
)
|
||||
def test_url_mutation(self, input, expected):
|
||||
"""
|
||||
This test validates that we correctly exclude some URLs from
|
||||
preparation, and that we handle others. Specifically, it tests that
|
||||
any URL whose scheme doesn't begin with "http" is left alone, and
|
||||
those whose scheme *does* begin with "http" are mutated.
|
||||
"""
|
||||
r = requests.Request('GET', url=input)
|
||||
p = r.prepare()
|
||||
assert p.url == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'input, params, expected',
|
||||
(
|
||||
(
|
||||
b"http+unix://%2Fvar%2Frun%2Fsocket/path",
|
||||
{"key": "value"},
|
||||
u"http+unix://%2fvar%2frun%2fsocket/path?key=value",
|
||||
),
|
||||
(
|
||||
u"http+unix://%2Fvar%2Frun%2Fsocket/path",
|
||||
{"key": "value"},
|
||||
u"http+unix://%2fvar%2frun%2fsocket/path?key=value",
|
||||
),
|
||||
(
|
||||
b"mailto:user@example.org",
|
||||
{"key": "value"},
|
||||
u"mailto:user@example.org",
|
||||
),
|
||||
(
|
||||
u"mailto:user@example.org",
|
||||
{"key": "value"},
|
||||
u"mailto:user@example.org",
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_parameters_for_nonstandard_schemes(self, input, params, expected):
|
||||
"""
|
||||
Setting paramters for nonstandard schemes is allowed if those schemes
|
||||
begin with "http", and is forbidden otherwise.
|
||||
"""
|
||||
r = requests.Request('GET', url=input, params=params)
|
||||
p = r.prepare()
|
||||
assert p.url == expected
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from requests.utils import (
|
||||
to_key_val_list, to_native_string,
|
||||
unquote_header_value, unquote_unreserved,
|
||||
urldefragauth, add_dict_to_cookiejar)
|
||||
from requests._internal_utils import unicode_is_ascii
|
||||
|
||||
from .compat import StringIO, cStringIO
|
||||
|
||||
@@ -515,6 +516,7 @@ def test_should_bypass_proxies(url, expected, monkeypatch):
|
||||
monkeypatch.setenv('NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
|
||||
assert should_bypass_proxies(url) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'cookiejar', (
|
||||
compat.cookielib.CookieJar(),
|
||||
@@ -529,3 +531,14 @@ def test_add_dict_to_cookiejar(cookiejar):
|
||||
cj = add_dict_to_cookiejar(cookiejar, cookiedict)
|
||||
cookies = dict((cookie.name, cookie.value) for cookie in cj)
|
||||
assert cookiedict == cookies
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
(u'test', True),
|
||||
(u'æíöû', False),
|
||||
(u'ジェーピーニック', False),
|
||||
)
|
||||
)
|
||||
def test_unicode_is_ascii(value, expected):
|
||||
assert unicode_is_ascii(value) is expected
|
||||
|
||||
Reference in New Issue
Block a user