Compare commits
272 Commits
v2.16.2
...
update-3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
190a68550a | ||
|
|
548823be83 | ||
|
|
a6f7f5dc62 | ||
|
|
f308ea1a31 | ||
|
|
c3f4a028bf | ||
|
|
56971482af | ||
|
|
98bbf8d170 | ||
|
|
d3a4d06238 | ||
|
|
152faa2a0f | ||
|
|
e41c2c2d96 | ||
|
|
3ca2722802 | ||
|
|
310d7f8a9b | ||
|
|
01f982da50 | ||
|
|
ba509635a3 | ||
|
|
008ab18a77 | ||
|
|
67831abaaf | ||
|
|
0f57faba70 | ||
|
|
35f2e55308 | ||
|
|
77ae596e8b | ||
|
|
84114419a0 | ||
|
|
500fe3ee03 | ||
|
|
d78ec90e7b | ||
|
|
04ed1e5122 | ||
|
|
33619268f9 | ||
|
|
e5894e99b7 | ||
|
|
6495190b00 | ||
|
|
5b363d1a57 | ||
|
|
f6a4ab02da | ||
|
|
5fa4e74d61 | ||
|
|
7fc48b099e | ||
|
|
3fa02a1336 | ||
|
|
e55d3d0658 | ||
|
|
7c3861a322 | ||
|
|
7c2618eb82 | ||
|
|
4e1bbe1288 | ||
|
|
cfc3db06cf | ||
|
|
5091c15bd7 | ||
|
|
dcb8277975 | ||
|
|
412e3a20a9 | ||
|
|
17084cd6d8 | ||
|
|
1278ecdf71 | ||
|
|
2d763c90ae | ||
|
|
db076c098e | ||
|
|
588e8f7f64 | ||
|
|
593cd18202 | ||
|
|
2daf1cbad9 | ||
|
|
71d8f289b0 | ||
|
|
44c9b7e93b | ||
|
|
916d171f2b | ||
|
|
3857431159 | ||
|
|
4ad4670583 | ||
|
|
68076a7f81 | ||
|
|
86f8cb8a35 | ||
|
|
7f14db17c8 | ||
|
|
14c1102e0f | ||
|
|
c77911ca0f | ||
|
|
5a2e5af3a4 | ||
|
|
31fc69ec0d | ||
|
|
88b435e7e8 | ||
|
|
60226cc25c | ||
|
|
8ddba4662d | ||
|
|
cd2beaf04a | ||
|
|
58501d1ec7 | ||
|
|
beccd21146 | ||
|
|
0622b5c54e | ||
|
|
821774cf47 | ||
|
|
78349914c8 | ||
|
|
2ccecf6dbd | ||
|
|
8af1d9331f | ||
|
|
ef34a85378 | ||
|
|
55014bcc48 | ||
|
|
303eca03f3 | ||
|
|
e02eb69fcc | ||
|
|
2936a77678 | ||
|
|
e6e3589c36 | ||
|
|
c3a00771e3 | ||
|
|
16c8241a81 | ||
|
|
1dfa631dae | ||
|
|
1fa168eea4 | ||
|
|
8b5fa606ec | ||
|
|
148ab30d70 | ||
|
|
8d0c2537ee | ||
|
|
458008653c | ||
|
|
679d368e3e | ||
|
|
7384fe71d6 | ||
|
|
94331723d1 | ||
|
|
b565b79858 | ||
|
|
a00d14a61f | ||
|
|
dc70edf17d | ||
|
|
68fda6bcf2 | ||
|
|
141ce93445 | ||
|
|
6abe1ff933 | ||
|
|
3d425ac191 | ||
|
|
0ff28d7421 | ||
|
|
2758b8b0f0 | ||
|
|
6746b2540d | ||
|
|
03944f4cd6 | ||
|
|
4a28ffcfe9 | ||
|
|
b6d51d9f2b | ||
|
|
2edb553036 | ||
|
|
cffa0effe0 | ||
|
|
76a66c41d6 | ||
|
|
19a1379ff5 | ||
|
|
057689bb8a | ||
|
|
88f28325c9 | ||
|
|
62afdabb30 | ||
|
|
2468058fea | ||
|
|
e4d8571080 | ||
|
|
048997daa0 | ||
|
|
c166c4f5d4 | ||
|
|
f4b488b7f8 | ||
|
|
cd4c701186 | ||
|
|
8e93b100da | ||
|
|
276024b170 | ||
|
|
6af910610f | ||
|
|
1d76cb3572 | ||
|
|
03e5f5daf3 | ||
|
|
b9a004a22b | ||
|
|
2e67c86075 | ||
|
|
f0fb5034fc | ||
|
|
b0c19226c6 | ||
|
|
4e06345988 | ||
|
|
892fbe09f6 | ||
|
|
46a4e731da | ||
|
|
46a930941a | ||
|
|
c6827de846 | ||
|
|
5c65f6d01e | ||
|
|
7ce5ca853d | ||
|
|
0ae8524e3b | ||
|
|
bf4a813304 | ||
|
|
6d082ea972 | ||
|
|
f180980b28 | ||
|
|
a44284c045 | ||
|
|
c1f32f68f6 | ||
|
|
4c155de9fd | ||
|
|
03dd91093b | ||
|
|
8e97db2dc5 | ||
|
|
009b80c95a | ||
|
|
a889b62c50 | ||
|
|
084625cb3c | ||
|
|
458df8f4f4 | ||
|
|
567e12bfb8 | ||
|
|
73456b0048 | ||
|
|
d491e9f9b2 | ||
|
|
052595ffbf | ||
|
|
cc2ac23c0d | ||
|
|
5a8bc19384 | ||
|
|
0380ac5893 | ||
|
|
9881be25f3 | ||
|
|
9174925916 | ||
|
|
02031e3e14 | ||
|
|
84dc6b66da | ||
|
|
35fc2b387f | ||
|
|
be2f92b9e3 | ||
|
|
64b66b6409 | ||
|
|
21972c4601 | ||
|
|
5a65a0dab1 | ||
|
|
3046a1eb54 | ||
|
|
a52fe6586c | ||
|
|
f239fe754d | ||
|
|
1003fdf0f2 | ||
|
|
033dfc165d | ||
|
|
bfb202527d | ||
|
|
60339d17ee | ||
|
|
8e07dae0fd | ||
|
|
b22833ce2a | ||
|
|
b8a87e00d7 | ||
|
|
56dd77fc52 | ||
|
|
250b3e5c8c | ||
|
|
efcbe93075 | ||
|
|
b26f4c7f58 | ||
|
|
16bbc7478f | ||
|
|
85a29f1da0 | ||
|
|
241f2b2a6b | ||
|
|
195d628d31 | ||
|
|
7b76bd5866 | ||
|
|
0b4302bb2b | ||
|
|
354ac7ecad | ||
|
|
3bb25a1e0b | ||
|
|
eff932627c | ||
|
|
084fb05d52 | ||
|
|
f554ec7987 | ||
|
|
f1a707d191 | ||
|
|
78fe2f738c | ||
|
|
500dc75c8c | ||
|
|
c7e5c0d2b7 | ||
|
|
4dae8de815 | ||
|
|
ba7c464a03 | ||
|
|
e237a60add | ||
|
|
84d99f01f5 | ||
|
|
973a7b1cd0 | ||
|
|
cfd898fb93 | ||
|
|
7f88aca8f7 | ||
|
|
e70ad25ca1 | ||
|
|
35bdfc78c9 | ||
|
|
5a41febce2 | ||
|
|
233a5da664 | ||
|
|
563d6572f3 | ||
|
|
5f941b3583 | ||
|
|
55e511dd10 | ||
|
|
713f56ea53 | ||
|
|
3d2b337906 | ||
|
|
b2f6173a38 | ||
|
|
2b2a07e3b7 | ||
|
|
715830fe21 | ||
|
|
adb577d5b8 | ||
|
|
569601e233 | ||
|
|
c418c4c4aa | ||
|
|
fd4332916f | ||
|
|
deada71526 | ||
|
|
ed07583f7e | ||
|
|
ecfb85f85e | ||
|
|
9fc765638f | ||
|
|
41fc9ebff7 | ||
|
|
e988e22d0f | ||
|
|
eab12fa029 | ||
|
|
8f33e56c0d | ||
|
|
8000def20c | ||
|
|
c26e82ed87 | ||
|
|
e68dd5dca0 | ||
|
|
a3532632af | ||
|
|
5530091b86 | ||
|
|
d185a40aaf | ||
|
|
aedc0e515d | ||
|
|
b644af0ec7 | ||
|
|
0acbf2b91d | ||
|
|
09ecb718ae | ||
|
|
e668a09490 | ||
|
|
7977cd9808 | ||
|
|
7a2b20cb53 | ||
|
|
4e880b5bbe | ||
|
|
a94b3e4513 | ||
|
|
d9b1cac867 | ||
|
|
14b6f6fad8 | ||
|
|
20ca527751 | ||
|
|
c270845a46 | ||
|
|
d6538d7034 | ||
|
|
0e51e48473 | ||
|
|
789227f5a9 | ||
|
|
be83be2457 | ||
|
|
0d8f38a403 | ||
|
|
66eedec782 | ||
|
|
4dfe7a4885 | ||
|
|
0f930d99f2 | ||
|
|
a0b7bdd187 | ||
|
|
ad4d87437a | ||
|
|
2ce700f9a2 | ||
|
|
d4eb2e9ce1 | ||
|
|
4b60bd4d7b | ||
|
|
c44fb99633 | ||
|
|
5da8a34277 | ||
|
|
6dff2bb71c | ||
|
|
26cf9abfd6 | ||
|
|
d1871fabe9 | ||
|
|
45f110b52d | ||
|
|
1881851e50 | ||
|
|
f8d2fb83a3 | ||
|
|
0c14e84c82 | ||
|
|
ea1cbb3a1d | ||
|
|
aa8c8f3983 | ||
|
|
1024441aee | ||
|
|
9c5ce48ca5 | ||
|
|
f351c1b687 | ||
|
|
91eebb1fb0 | ||
|
|
038b61477c | ||
|
|
4294400733 | ||
|
|
8c4d4f1af3 | ||
|
|
4aa4f82b37 | ||
|
|
ff8153d9c5 | ||
|
|
50a65a7415 | ||
|
|
636b946af5 | ||
|
|
52bc2e57c0 |
28
.github/ISSUE_TEMPLATE.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
Summary.
|
||||
|
||||
## Expected Result
|
||||
|
||||
What you expected.
|
||||
|
||||
## Actual Result
|
||||
|
||||
What happened instead.
|
||||
|
||||
## Reproduction Steps
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
```
|
||||
|
||||
## System Information
|
||||
|
||||
$ python -m requests.help
|
||||
|
||||
```
|
||||
<paste here>
|
||||
```
|
||||
|
||||
This command is only available on Requests v2.16.4 and greater. Otherwise,
|
||||
please provide some basic information about your system (Python version,
|
||||
operating system, &c).
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,7 +5,6 @@ nosetests.xml
|
||||
junit-report.xml
|
||||
pylint.txt
|
||||
toy.py
|
||||
tox.ini
|
||||
cover/
|
||||
build/
|
||||
docs/_build
|
||||
|
||||
18
.travis.yml
18
.travis.yml
@@ -7,7 +7,7 @@ python:
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7-dev"
|
||||
# - "3.7-dev"
|
||||
# - "pypy" -- appears to hang
|
||||
# - "pypy3"
|
||||
# command to install dependencies
|
||||
@@ -16,7 +16,17 @@ install: "make"
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi
|
||||
- make coverage
|
||||
after_success:
|
||||
- pipenv run codecov
|
||||
- make ci
|
||||
cache: pip
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi
|
||||
- make ci
|
||||
- stage: coverage
|
||||
python: 3.6
|
||||
script: codecov
|
||||
|
||||
|
||||
|
||||
68
3.0-HISTORY.rst
Normal file
68
3.0-HISTORY.rst
Normal file
@@ -0,0 +1,68 @@
|
||||
3.0.0 (2017-xx-xx)
|
||||
++++++++++++++++++
|
||||
|
||||
- Simplified logic for determining Content-Length and Transfer-Encoding.
|
||||
Requests will now avoid setting both headers on the same request, and
|
||||
raise an exception if this is done manually by a user.
|
||||
|
||||
- Remove the HTTPProxyAuth class in favor of supporting proxy auth via
|
||||
the proxies parameter.
|
||||
|
||||
- Relax how Requests strips bodies from redirects. 3.0.0 only supports body
|
||||
removal on 301/302 POST redirects and all 303 redirects.
|
||||
|
||||
- Remove support for non-string/bytes parameters in ``_basic_auth_str``.
|
||||
|
||||
- Prevent ``Session.merge_environment`` from erroneously setting the
|
||||
``verify`` parameter to ``None`` instead of ``True``.
|
||||
|
||||
- Streaming responses with ``Response.iter_lines`` or ``Response.iter_content``
|
||||
now requires an encoding to be set if one isn't provided by the server.
|
||||
|
||||
- Raise exception if multiple locations are returned during a redirect.
|
||||
|
||||
- Update ConnectionPool connections when TLS/SSL settings change.
|
||||
|
||||
- Remove simplejson import and only use standard json module.
|
||||
|
||||
- Strip surrounding whitespace from urls.
|
||||
|
||||
- MissingSchema and InvalidSchema renamed to MissingScheme and InvalidScheme
|
||||
respectively.
|
||||
|
||||
- Change merge order for environment settings to avoid excluding Session-level
|
||||
settings.
|
||||
|
||||
- Encode redirect URIs as latin-1 before performing redirects in Python 3 to
|
||||
avoid mangling during the requoting process.
|
||||
|
||||
- Remove the ``__bool__`` and ``__nonzero__`` methods from a ``Response``
|
||||
object.
|
||||
|
||||
This has been a planned feature for over a year. The behaviour is surprising
|
||||
to most people and breaks most of the assumptions that people have about
|
||||
Response objects. This resolves issue `#2002`_
|
||||
|
||||
- Skip over empty chunks in iterators. Empty chunks could prematurely signal
|
||||
the end of a request body's transmission, skipping them allows all of the
|
||||
data through. See `#2631`_ for more details.
|
||||
|
||||
- Rename the ``req`` argument from ``Session.resolve_redirects`` method
|
||||
to ``request``.
|
||||
|
||||
- Rename the ``resp`` argument from ``Session.resolve_redirects`` to
|
||||
``response``.
|
||||
|
||||
- New ``PreparedRequest.send`` method. Now, you can
|
||||
``Request().prepare().send()``.
|
||||
|
||||
- All porcelain API functions (e.g. ``requests.get``, etc) now accept an
|
||||
optional ``session`` parameter. If provided, the session given will be used
|
||||
for the request, in place of one being created for you.
|
||||
|
||||
- URLs are now automatically stripped of leading/trailing whitespace.
|
||||
|
||||
- ``Response.raise_for_status()`` now returns the response object for good responses
|
||||
|
||||
.. _#2002: https://github.com/kennethreitz/requests/issues/2002
|
||||
.. _#2631: https://github.com/kennethreitz/requests/issues/2631
|
||||
@@ -160,16 +160,19 @@ Patches and Suggestions
|
||||
- Muhammad Yasoob Ullah Khalid <yasoob.khld@gmail.com> (`@yasoob <https://github.com/yasoob>`_)
|
||||
- Paul van der Linden (`@pvanderlinden <https://github.com/pvanderlinden>`_)
|
||||
- Colin Dickson (`@colindickson <https://github.com/colindickson>`_)
|
||||
- Sabari Kumar Murugesan (`@neosab <https://github.com/neosab>`_)
|
||||
- Smiley Barry (`@smiley <https://github.com/smiley>`_)
|
||||
- Shagun Sodhani (`@shagunsodhani <https://github.com/shagunsodhani>`_)
|
||||
- Robin Linderborg (`@vienno <https://github.com/vienno>`_)
|
||||
- Brian Samek (`@bsamek <https://github.com/bsamek>`_)
|
||||
- Dmitry Dygalo (`@Stranger6667 <https://github.com/Stranger6667>`_)
|
||||
- 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
|
||||
- Jeremy Cline <jcline@redhat.com> (`@jeremycline <https://github.com/jeremycline>`_)
|
||||
- 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>`_)
|
||||
@@ -181,3 +184,5 @@ Patches and Suggestions
|
||||
- Jonathan Vanasco (`@jvanasco <https://github.com/jvanasco>`_)
|
||||
- David Fontenot (`@davidfontenot <https://github.com/davidfontenot>`_)
|
||||
- Shmuel Amar (`@shmuelamar <https://github.com/shmuelamar>`_)
|
||||
- Gary Wu (`@garywu <https://github.com/garywu>`_)
|
||||
- Ryan Pineo (`@ryanpineo <https://github.com/ryanpineo>`_)
|
||||
|
||||
77
HISTORY.rst
77
HISTORY.rst
@@ -3,6 +3,62 @@
|
||||
Release History
|
||||
---------------
|
||||
|
||||
dev
|
||||
+++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Resolve installation failure if multiprocessing is not available
|
||||
- Resolve tests crash if multiprocessing is not able to determine the number of CPU cores
|
||||
|
||||
|
||||
2.17.3 (2017-05-29)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Improved ``packages`` namespace identity support, for monkeypatching libraries.
|
||||
|
||||
|
||||
2.17.2 (2017-05-29)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Improved ``packages`` namespace identity support, for monkeypatching libraries.
|
||||
|
||||
|
||||
2.17.1 (2017-05-29)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Improved ``packages`` namespace identity support, for monkeypatching libraries.
|
||||
|
||||
|
||||
2.17.0 (2017-05-29)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Removal of the 301 redirect cache. This improves thread-safety.
|
||||
|
||||
|
||||
2.16.5 (2017-05-28)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Improvements to ``$ python -m requests.help``.
|
||||
|
||||
2.16.4 (2017-05-27)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Introduction of the ``$ python -m requests.help`` command, for debugging with maintainers!
|
||||
|
||||
2.16.3 (2017-05-27)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Further restored the ``requests.packages`` namespace for compatibility reasons.
|
||||
|
||||
2.16.2 (2017-05-27)
|
||||
+++++++++++++++++++
|
||||
|
||||
@@ -118,6 +174,11 @@ Or, even better::
|
||||
- Updated bundled idna to v2.5.
|
||||
- Updated bundled certifi to 2017.4.17.
|
||||
|
||||
- Altered how ``SessionRedirectMixin.resolve_redirects`` and ``Session.send``
|
||||
process redirect history. Developers who subclass ``resolve_redirects`` will
|
||||
find a different ``.history`` attribute - the first element now contains the
|
||||
original response, and the last element now contains the active response.
|
||||
|
||||
2.13.0 (2017-01-24)
|
||||
+++++++++++++++++++
|
||||
|
||||
@@ -577,7 +638,7 @@ This is the first release that follows our new release process. For more, see
|
||||
|
||||
- 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>`_)
|
||||
- Automatically defrag authentication schemes from host/pass URIs. (`#2249 <https://github.com/requests/requests/issues/2249>`_)
|
||||
|
||||
|
||||
2.4.2 (2014-10-05)
|
||||
@@ -585,19 +646,19 @@ This is the first release that follows our new release process. For more, see
|
||||
|
||||
**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>`_)
|
||||
- FINALLY! Add json parameter for uploads! (`#2258 <https://github.com/requests/requests/pull/2258>`_)
|
||||
- Support for bytestring URLs on Python 3.x (`#2238 <https://github.com/requests/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>`_)
|
||||
- Avoid getting stuck in a loop (`#2244 <https://github.com/requests/requests/pull/2244>`_)
|
||||
- Multiple calls to iter* fail with unhelpful error. (`#2240 <https://github.com/requests/requests/issues/2240>`_, `#2241 <https://github.com/requests/requests/issues/2241>`_)
|
||||
|
||||
**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/>`_)
|
||||
- Correct redirection introduction (`#2245 <https://github.com/requests/requests/pull/2245/>`_)
|
||||
- Added example of how to send multiple files in one request. (`#2227 <https://github.com/requests/requests/pull/2227/>`_)
|
||||
- Clarify how to pass a custom set of CAs (`#2248 <https://github.com/requests/requests/pull/2248/>`_)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini requests/cacert.pem
|
||||
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini
|
||||
recursive-include tests *.py
|
||||
|
||||
25
Makefile
25
Makefile
@@ -1,23 +1,20 @@
|
||||
.PHONY: docs
|
||||
|
||||
init:
|
||||
pip install pipenv
|
||||
pipenv install --dev
|
||||
pipenv run pip install -e .[socks]
|
||||
|
||||
pip install -r requirements.txt
|
||||
test:
|
||||
# This runs all of the tests. To run an individual test, run py.test with
|
||||
# the -k flag, like "py.test -k test_path_is_not_double_encoded"
|
||||
pipenv run py.test tests
|
||||
# This runs all of the tests, on both Python 2 and Python 3.
|
||||
detox
|
||||
ci:
|
||||
py.test -n 8 --boxed --junitxml=report.xml
|
||||
|
||||
test-readme:
|
||||
pipenv run python setup.py check -r -s
|
||||
python setup.py check -r -s
|
||||
|
||||
flake8:
|
||||
flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
|
||||
|
||||
coverage:
|
||||
pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
|
||||
|
||||
certs:
|
||||
curl http://ci.kennethreitz.org/job/ca-bundle/lastSuccessfulBuild/artifact/cacerts.pem -o requests/cacert.pem
|
||||
py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
|
||||
|
||||
publish:
|
||||
pip install 'twine>=1.5.0'
|
||||
@@ -27,4 +24,4 @@ publish:
|
||||
|
||||
docs:
|
||||
cd docs && make html
|
||||
@echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m"
|
||||
@echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m"
|
||||
14
Pipfile
14
Pipfile
@@ -1,14 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
pytest = ">=2.8.0"
|
||||
codecov = "*"
|
||||
pytest-httpbin = "==0.0.7"
|
||||
pytest-mock = "*"
|
||||
pytest-cov = "*"
|
||||
alabaster = "*"
|
||||
readme_renderer = "*"
|
||||
Sphinx = "<=1.5.5"
|
||||
PySocks = "*"
|
||||
116
Pipfile.lock
generated
116
Pipfile.lock
generated
@@ -1,116 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "a26ef57f42fa3da7d70d1c1decb4e6445a6ff66e47c9094ddc9ab8e1f4c0a5b6"
|
||||
},
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {},
|
||||
"develop": {
|
||||
"alabaster": {
|
||||
"version": "==0.7.10"
|
||||
},
|
||||
"appdirs": {
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"babel": {
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"click": {
|
||||
"version": "==6.7"
|
||||
},
|
||||
"codecov": {
|
||||
"version": "==2.0.9"
|
||||
},
|
||||
"coverage": {
|
||||
"version": "==4.4.1"
|
||||
},
|
||||
"decorator": {
|
||||
"version": "==4.0.11"
|
||||
},
|
||||
"docutils": {
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"flask": {
|
||||
"version": "==0.12.2"
|
||||
},
|
||||
"funcsigs": {
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"httpbin": {
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"imagesize": {
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"version": "==0.24"
|
||||
},
|
||||
"jinja2": {
|
||||
"version": "==2.9.6"
|
||||
},
|
||||
"markupsafe": {
|
||||
"version": "==1.0"
|
||||
},
|
||||
"mock": {
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"packaging": {
|
||||
"version": "==16.8"
|
||||
},
|
||||
"pbr": {
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"py": {
|
||||
"version": "==1.4.33"
|
||||
},
|
||||
"pygments": {
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pysocks": {
|
||||
"version": "==1.6.7"
|
||||
},
|
||||
"pytest": {
|
||||
"version": "==3.0.7"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"version": "==2.5.1"
|
||||
},
|
||||
"pytest-httpbin": {
|
||||
"version": "==0.0.7"
|
||||
},
|
||||
"pytest-mock": {
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"pytz": {
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"requests": {
|
||||
"version": "==2.14.2"
|
||||
},
|
||||
"setuptools": {
|
||||
"version": "==35.0.2"
|
||||
},
|
||||
"six": {
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"sphinx": {
|
||||
"version": "==1.5.5"
|
||||
},
|
||||
"werkzeug": {
|
||||
"version": "==0.12.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
README.rst
23
README.rst
@@ -10,16 +10,13 @@ Requests: HTTP for Humans
|
||||
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/requests.svg?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/requests
|
||||
|
||||
.. image:: https://codecov.io/github/kennethreitz/requests/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/kennethreitz/requests
|
||||
.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/requests/requests
|
||||
:alt: codecov.io
|
||||
|
||||
.. image:: https://img.shields.io/github/contributors/kennethreitz/requests.svg
|
||||
:target: https://github.com/kennethreitz/requests/graphs/contributors
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/github/contributors/requests/requests.svg
|
||||
:target: https://github.com/requests/requests/graphs/contributors
|
||||
|
||||
.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
@@ -50,7 +47,7 @@ Behold, the power of Requests:
|
||||
|
||||
See `the similar code, sans Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/kennethreitz/requests/master/docs/_static/requests-logo-small.png
|
||||
.. image:: https://raw.githubusercontent.com/requests/requests/master/docs/_static/requests-logo-small.png
|
||||
:target: http://docs.python-requests.org/
|
||||
|
||||
|
||||
@@ -112,6 +109,6 @@ How to Contribute
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/requests
|
||||
.. _AUTHORS: https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst
|
||||
.. _Contributor Friendly: https://github.com/kennethreitz/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open
|
||||
.. _`the repository`: http://github.com/requests/requests
|
||||
.. _AUTHORS: https://github.com/requests/requests/blob/master/AUTHORS.rst
|
||||
.. _Contributor Friendly: https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open
|
||||
|
||||
@@ -53,4 +53,4 @@ test_script:
|
||||
- "C:\\MinGW\\bin\\mingw32-make coverage"
|
||||
|
||||
on_success:
|
||||
- "pipenv run codecov -f coverage.xml"
|
||||
- "codecov -f coverage.xml"
|
||||
|
||||
6
docs/_templates/sidebarintro.html
vendored
6
docs/_templates/sidebarintro.html
vendored
@@ -5,7 +5,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
|
||||
</p>
|
||||
|
||||
@@ -58,9 +58,9 @@
|
||||
|
||||
<p></p>
|
||||
|
||||
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="http://github.com/requests/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
|
||||
<li><a href="http://github.com/requests/requests/issues">Issue Tracker</a></li>
|
||||
<li><a href="http://docs.python-requests.org/en/latest/community/updates/#software-updates">Release History</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
2
docs/_templates/sidebarlogo.html
vendored
2
docs/_templates/sidebarlogo.html
vendored
@@ -4,7 +4,7 @@
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ Authentication
|
||||
|
||||
.. autoclass:: requests.auth.AuthBase
|
||||
.. autoclass:: requests.auth.HTTPBasicAuth
|
||||
.. autoclass:: requests.auth.HTTPProxyAuth
|
||||
.. autoclass:: requests.auth.HTTPDigestAuth
|
||||
|
||||
|
||||
@@ -252,7 +251,7 @@ API Changes
|
||||
}
|
||||
|
||||
# In requests 1.x, this was legal, in requests 2.x,
|
||||
# this raises requests.exceptions.MissingSchema
|
||||
# this raises requests.exceptions.MissingScheme
|
||||
requests.get("http://example.org", proxies=proxies)
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ File an Issue
|
||||
|
||||
If you notice some unexpected behaviour in Requests, or want to see support
|
||||
for a new feature,
|
||||
`file an issue on GitHub <https://github.com/kennethreitz/requests/issues>`_.
|
||||
`file an issue on GitHub <https://github.com/requests/requests/issues>`_.
|
||||
|
||||
|
||||
E-mail
|
||||
|
||||
@@ -12,7 +12,7 @@ GitHub
|
||||
------
|
||||
|
||||
The best way to track the development of Requests is through
|
||||
`the GitHub repo <https://github.com/kennethreitz/requests>`_.
|
||||
`the GitHub repo <https://github.com/requests/requests>`_.
|
||||
|
||||
Twitter
|
||||
-------
|
||||
|
||||
@@ -126,7 +126,7 @@ html_theme = 'alabaster'
|
||||
# documentation.
|
||||
html_theme_options = {
|
||||
'show_powered_by': False,
|
||||
'github_user': 'kennethreitz',
|
||||
'github_user': 'requests',
|
||||
'github_repo': 'requests',
|
||||
'github_banner': True,
|
||||
'show_related': False
|
||||
|
||||
@@ -187,7 +187,7 @@ through the `GitHub issues`_, **both open and closed**, to confirm that the bug
|
||||
hasn't been reported before. Duplicate bug reports are a huge drain on the time
|
||||
of other contributors, and should be avoided as much as possible.
|
||||
|
||||
.. _GitHub issues: https://github.com/kennethreitz/requests/issues
|
||||
.. _GitHub issues: https://github.com/requests/requests/issues
|
||||
|
||||
|
||||
Feature Requests
|
||||
|
||||
@@ -7,9 +7,9 @@ Requests is an open but opinionated library, created by an open but opinionated
|
||||
Management Style
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
`Kenneth Reitz <http://kennethreitz.org>`_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests. Only Kenneth may merge code into Requests.
|
||||
`Kenneth Reitz <http://kennethreitz.org>`_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library, as well as its presentation. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests.
|
||||
|
||||
`Ian Cordasco <http://www.coglib.com/~icordasc/>`_ and `Cory Benfield <https://lukasa.co.uk/about/>`_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. They do not have the authority to change code or merge code changes, though they may change documentation. Their word is not final.
|
||||
`Ian Cordasco <http://www.coglib.com/~icordasc/>`_ and `Cory Benfield <https://lukasa.co.uk/about/>`_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. Their word is, in some ways, more final than Kenneth's.
|
||||
|
||||
Values
|
||||
~~~~~~
|
||||
|
||||
@@ -6,11 +6,11 @@ Requests is under active development, and contributions are more than welcome!
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a bug.
|
||||
There is a Contributor Friendly tag for issues that should be ideal for people who are not very
|
||||
familiar with the codebase yet.
|
||||
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ on GitHub and start making your
|
||||
#. Fork `the repository <https://github.com/requests/requests>`_ on GitHub and start making your
|
||||
changes to a new branch.
|
||||
#. Write a test which shows that the bug was fixed.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :)
|
||||
Make sure to add yourself to `AUTHORS <https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst>`_.
|
||||
Make sure to add yourself to `AUTHORS <https://github.com/requests/requests/blob/master/AUTHORS.rst>`_.
|
||||
|
||||
Feature Freeze
|
||||
--------------
|
||||
@@ -23,10 +23,11 @@ Development Dependencies
|
||||
|
||||
You'll need to install py.test in order to run the Requests' test suite::
|
||||
|
||||
$ pip install pipenv
|
||||
$ pipenv lock
|
||||
$ pipenv install --dev
|
||||
$ pipenv run py.test tests
|
||||
$ venv .venv
|
||||
$ source .venv/bin/activate
|
||||
|
||||
$ make
|
||||
$ python setup.py test
|
||||
============================= test session starts ==============================
|
||||
platform darwin -- Python 3.4.4, pytest-3.0.6, py-1.4.32, pluggy-0.4.0
|
||||
...
|
||||
@@ -41,6 +42,8 @@ You'll need to install py.test in order to run the Requests' test suite::
|
||||
|
||||
============== 442 passed, 1 skipped, 2 xpassed in 46.48 seconds ===============
|
||||
|
||||
You can also run ``$ make tests`` to run against all supported Python versions, using tox/detox.
|
||||
|
||||
Runtime Environments
|
||||
--------------------
|
||||
|
||||
@@ -59,13 +62,3 @@ with the `Requests-Toolbelt`_.
|
||||
|
||||
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/
|
||||
|
||||
|
||||
Are you crazy?
|
||||
--------------
|
||||
|
||||
- SPDY support would be awesome. No C extensions.
|
||||
|
||||
Downstream Repackaging
|
||||
----------------------
|
||||
|
||||
If you are repackaging Requests, please note that you must also redistribute the ``cacerts.pem`` file in order to get correct SSL functionality.
|
||||
|
||||
@@ -17,11 +17,8 @@ Release v\ |version|. (:ref:`Installation <install>`)
|
||||
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/requests.svg?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/requests
|
||||
|
||||
.. image:: https://codecov.io/github/kennethreitz/requests/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/kennethreitz/requests
|
||||
.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/requests/requests
|
||||
:alt: codecov.io
|
||||
|
||||
.. image:: https://img.shields.io/badge/Say%20Thanks!-🦉-1EAEDB.svg
|
||||
|
||||
@@ -280,7 +280,7 @@ immediately. You can override this behaviour and defer downloading the response
|
||||
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'
|
||||
tarball_url = 'https://github.com/requests/requests/tarball/master'
|
||||
r = requests.get(tarball_url, stream=True)
|
||||
|
||||
At this point only the response headers have been downloaded and the connection
|
||||
@@ -642,7 +642,7 @@ from GitHub. Suppose we wanted commit ``a050faf`` on Requests. We would get it
|
||||
like so::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
|
||||
>>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
|
||||
|
||||
We should confirm that GitHub responded correctly. If it has, we want to work
|
||||
out what type of content it is. Do this like so::
|
||||
@@ -697,12 +697,12 @@ we should probably avoid making ham-handed POSTS to it. Instead, let's play
|
||||
with the Issues feature of GitHub.
|
||||
|
||||
This documentation was added in response to
|
||||
`Issue #482 <https://github.com/kennethreitz/requests/issues/482>`_. Given that
|
||||
`Issue #482 <https://github.com/requests/requests/issues/482>`_. Given that
|
||||
this issue already exists, we will use it as an example. Let's start by getting it.
|
||||
|
||||
::
|
||||
|
||||
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
|
||||
>>> r = requests.get('https://api.github.com/repos/requests/requests/issues/482')
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
@@ -745,7 +745,7 @@ is to POST to the thread. Let's do it.
|
||||
::
|
||||
|
||||
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
|
||||
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
|
||||
>>> url = u"https://api.github.com/repos/requests/requests/issues/482/comments"
|
||||
|
||||
>>> r = requests.post(url=url, data=body)
|
||||
>>> r.status_code
|
||||
@@ -779,7 +779,7 @@ that.
|
||||
5804413
|
||||
|
||||
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
|
||||
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
|
||||
>>> url = u"https://api.github.com/repos/requests/requests/issues/comments/5804413"
|
||||
|
||||
>>> r = requests.patch(url=url, data=body, auth=auth)
|
||||
>>> r.status_code
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
.. _install:
|
||||
|
||||
Installation
|
||||
============
|
||||
Installation of Requests
|
||||
========================
|
||||
|
||||
This part of the documentation covers the installation of Requests.
|
||||
The first step to using any software package is getting it properly installed.
|
||||
|
||||
|
||||
Pip Install Requests
|
||||
--------------------
|
||||
$ pip install requests
|
||||
----------------------
|
||||
|
||||
To install Requests, simply run this simple command in your terminal of choice::
|
||||
|
||||
@@ -22,18 +22,19 @@ Get the Source Code
|
||||
-------------------
|
||||
|
||||
Requests is actively developed on GitHub, where the code is
|
||||
`always available <https://github.com/kennethreitz/requests>`_.
|
||||
`always available <https://github.com/requests/requests>`_.
|
||||
|
||||
You can either clone the public repository::
|
||||
|
||||
$ git clone git://github.com/kennethreitz/requests.git
|
||||
$ git clone git://github.com/requests/requests.git
|
||||
|
||||
Or, download the `tarball <https://github.com/kennethreitz/requests/tarball/master>`_::
|
||||
Or, download the `tarball <https://github.com/requests/requests/tarball/master>`_::
|
||||
|
||||
$ curl -OL https://github.com/kennethreitz/requests/tarball/master
|
||||
# optionally, zipball is also available (for Windows users).
|
||||
$ curl -OL https://github.com/requests/requests/tarball/master
|
||||
# optionally, zipball is also available (for Windows users).
|
||||
|
||||
Once you have a copy of the source, you can embed it in your own Python
|
||||
package, or install it into your site-packages easily::
|
||||
|
||||
$ python setup.py install
|
||||
$ cd requests
|
||||
$ pip install .
|
||||
|
||||
@@ -234,7 +234,24 @@ dictionary of data will automatically be form-encoded when the request is made::
|
||||
...
|
||||
}
|
||||
|
||||
There are many times that you want to send data that is not form-encoded. If
|
||||
You can also pass a list of tuples to the ``data`` argument. This is particularly
|
||||
useful when the form has multiple elements that use the same key::
|
||||
|
||||
>>> payload = (('key1', 'value1'), ('key1', 'value2'))
|
||||
>>> r = requests.post('http://httpbin.org/post', data=payload)
|
||||
>>> print(r.text)
|
||||
{
|
||||
...
|
||||
"form": {
|
||||
"key1": [
|
||||
"value1",
|
||||
"value2"
|
||||
]
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
There are times that you may want to send data that is not form-encoded. If
|
||||
you pass in a ``string`` instead of a ``dict``, that data will be posted directly.
|
||||
|
||||
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
|
||||
@@ -354,10 +371,14 @@ But, since our ``status_code`` for ``r`` was ``200``, when we call
|
||||
``raise_for_status()`` we get::
|
||||
|
||||
>>> r.raise_for_status()
|
||||
None
|
||||
<Response [200]>
|
||||
|
||||
All is well.
|
||||
|
||||
.. note:: ``raise_for_status`` returns the response object for a successful response. This eases chaining in trivial cases, where we want bad codes to raise an exception, but use the response otherwise:
|
||||
|
||||
>>> value = requests.get('http://httpbin.org/ip').raise_for_status().json()['origin']
|
||||
|
||||
|
||||
Response Headers
|
||||
----------------
|
||||
@@ -485,7 +506,7 @@ Timeouts
|
||||
--------
|
||||
|
||||
You can tell Requests to stop waiting for a response after a given number of
|
||||
seconds with the ``timeout`` parameter. Nearly all production code should use
|
||||
seconds with the ``timeout`` parameter. Nearly all production code should use
|
||||
this parameter in nearly all requests. Failure to do so can cause your program
|
||||
to hang indefinitely::
|
||||
|
||||
|
||||
@@ -40,17 +40,13 @@ is at <http://python-requests.org>.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from .__version__ import __title__, __description__, __url__, __version__
|
||||
from .__version__ import __build__, __author__, __author_email__, __license__
|
||||
from .__version__ import __copyright__, __cake__
|
||||
|
||||
# Check urllib3 for compatibility.
|
||||
import urllib3
|
||||
urllib3_version = urllib3.__version__.split('.')
|
||||
# Sometimes, urllib3 only reports its version as 16.1.
|
||||
if len(urllib3_version) == 2:
|
||||
urllib3_version.append('0')
|
||||
major, minor, patch= urllib3_version
|
||||
major, minor, patch = urllib3_version
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# urllib3 >= 1.21.1, < 1.22
|
||||
try:
|
||||
@@ -86,7 +82,12 @@ import warnings
|
||||
from urllib3.exceptions import DependencyWarning
|
||||
warnings.simplefilter('ignore', DependencyWarning)
|
||||
|
||||
from .__version__ import __title__, __description__, __url__, __version__
|
||||
from .__version__ import __build__, __author__, __author_email__, __license__
|
||||
from .__version__ import __copyright__, __cake__
|
||||
|
||||
from . import utils
|
||||
from . import packages
|
||||
from .models import Request, Response, PreparedRequest
|
||||
from .api import request, get, head, post, patch, put, delete, options
|
||||
from .sessions import session, Session
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# .-. .-. .-. . . .-. .-. .-. .-.
|
||||
# |( |- |.| | | |- `-. | `-.
|
||||
# ' ' `-' `-`.`-' `-' `-' ' `-'
|
||||
@@ -7,10 +5,10 @@
|
||||
__title__ = 'requests'
|
||||
__description__ = 'Python HTTP for Humans.'
|
||||
__url__ = 'http://python-requests.org'
|
||||
__version__ = '2.16.2'
|
||||
__build__ = 0x021602
|
||||
__version__ = '3.0.0'
|
||||
__build__ = 0x030000
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__author_email__ = 'me@kennethreitz.org'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2017 Kenneth Reitz'
|
||||
__cake__ = u'✨ 🍰 ✨'
|
||||
__cake__ = u'\u2728 \U0001f370 \u2728'
|
||||
|
||||
@@ -10,6 +10,10 @@ and maintain connections.
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
try:
|
||||
from threading import RLock
|
||||
except ImportError: # threading is an optional module and may not be present.
|
||||
from dummy_threading import RLock
|
||||
|
||||
from urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from urllib3.response import HTTPResponse
|
||||
@@ -34,14 +38,14 @@ from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
||||
ProxyError, RetryError, InvalidSchema)
|
||||
ProxyError, RetryError, InvalidScheme)
|
||||
from .auth import _basic_auth_str
|
||||
|
||||
try:
|
||||
from urllib3.contrib.socks import SOCKSProxyManager
|
||||
except ImportError:
|
||||
def SOCKSProxyManager(*args, **kwargs):
|
||||
raise InvalidSchema("Missing dependencies for SOCKS support.")
|
||||
raise InvalidScheme("Missing dependencies for SOCKS support.")
|
||||
|
||||
DEFAULT_POOLBLOCK = False
|
||||
DEFAULT_POOLSIZE = 10
|
||||
@@ -122,6 +126,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
self._pool_connections = pool_connections
|
||||
self._pool_maxsize = pool_maxsize
|
||||
self._pool_block = pool_block
|
||||
self._pool_kw_lock = RLock()
|
||||
|
||||
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
|
||||
|
||||
@@ -134,6 +139,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
# self.poolmanager uses a lambda function, which isn't pickleable.
|
||||
self.proxy_manager = {}
|
||||
self.config = {}
|
||||
self._pool_kw_lock = RLock()
|
||||
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
@@ -198,19 +204,21 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
return manager
|
||||
|
||||
def cert_verify(self, conn, url, verify, cert):
|
||||
"""Verify a SSL certificate. This method should not be called from user
|
||||
code, and is only exposed for use when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
def _update_poolmanager_ssl_kw(self, verify, cert):
|
||||
"""Update the :class:`PoolManager <urllib3.poolmanager.PoolManager>`
|
||||
connection_pool_kw with the necessary SSL configuration. This method
|
||||
should not be called from user code, and is only exposed for use when
|
||||
subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param conn: The urllib3 connection object associated with the cert.
|
||||
:param url: The requested URL.
|
||||
:param verify: Either a boolean, in which case it controls whether we verify
|
||||
the server's TLS certificate, or a string, in which case it must be a path
|
||||
to a CA bundle to use
|
||||
:param cert: The SSL certificate to verify.
|
||||
:param verify: Whether we should actually verify the certificate;
|
||||
optionally a path to a CA certificate bundle or
|
||||
directory of CA certificates.
|
||||
:param cert: The path to the client certificate and key, if any.
|
||||
This can either be the path to the certificate and
|
||||
key concatenated in a single file, or as a tuple of
|
||||
(cert_file, key_file).
|
||||
"""
|
||||
if url.lower().startswith('https') and verify:
|
||||
if verify:
|
||||
|
||||
cert_loc = None
|
||||
|
||||
@@ -225,30 +233,35 @@ class HTTPAdapter(BaseAdapter):
|
||||
raise IOError("Could not find a suitable TLS CA certificate bundle, "
|
||||
"invalid path: {0}".format(cert_loc))
|
||||
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
self.poolmanager.connection_pool_kw['cert_reqs'] = 'CERT_REQUIRED'
|
||||
|
||||
if not os.path.isdir(cert_loc):
|
||||
conn.ca_certs = cert_loc
|
||||
self.poolmanager.connection_pool_kw['ca_certs'] = cert_loc
|
||||
self.poolmanager.connection_pool_kw['ca_cert_dir'] = None
|
||||
else:
|
||||
conn.ca_cert_dir = cert_loc
|
||||
self.poolmanager.connection_pool_kw['ca_cert_dir'] = cert_loc
|
||||
self.poolmanager.connection_pool_kw['ca_certs'] = None
|
||||
else:
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
conn.ca_cert_dir = None
|
||||
self.poolmanager.connection_pool_kw['cert_reqs'] = 'CERT_NONE'
|
||||
self.poolmanager.connection_pool_kw['ca_certs'] = None
|
||||
self.poolmanager.connection_pool_kw['ca_cert_dir'] = None
|
||||
|
||||
if cert:
|
||||
if not isinstance(cert, basestring):
|
||||
conn.cert_file = cert[0]
|
||||
conn.key_file = cert[1]
|
||||
self.poolmanager.connection_pool_kw['cert_file'] = cert[0]
|
||||
self.poolmanager.connection_pool_kw['key_file'] = cert[1]
|
||||
else:
|
||||
conn.cert_file = cert
|
||||
conn.key_file = None
|
||||
if conn.cert_file and not os.path.exists(conn.cert_file):
|
||||
self.poolmanager.connection_pool_kw['cert_file'] = cert
|
||||
self.poolmanager.connection_pool_kw['key_file'] = None
|
||||
|
||||
cert_file = self.poolmanager.connection_pool_kw['cert_file']
|
||||
key_file = self.poolmanager.connection_pool_kw['key_file']
|
||||
if cert_file and not os.path.exists(cert_file):
|
||||
raise IOError("Could not find the TLS certificate file, "
|
||||
"invalid path: {0}".format(conn.cert_file))
|
||||
if conn.key_file and not os.path.exists(conn.key_file):
|
||||
"invalid path: {0}".format(cert_file))
|
||||
if key_file and not os.path.exists(key_file):
|
||||
raise IOError("Could not find the TLS key file, "
|
||||
"invalid path: {0}".format(conn.key_file))
|
||||
"invalid path: {0}".format(key_file))
|
||||
|
||||
def build_response(self, req, resp):
|
||||
"""Builds a :class:`Response <requests.Response>` object from a urllib3
|
||||
@@ -287,7 +300,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
return response
|
||||
|
||||
def get_connection(self, url, proxies=None):
|
||||
def get_connection(self, url, proxies=None, verify=None, cert=None):
|
||||
"""Returns a urllib3 connection for the given URL. This should not be
|
||||
called from user code, and is only exposed for use when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
@@ -296,17 +309,21 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
:rtype: urllib3.ConnectionPool
|
||||
"""
|
||||
proxy = select_proxy(url, proxies)
|
||||
with self._pool_kw_lock:
|
||||
if url.lower().startswith('https'):
|
||||
self._update_poolmanager_ssl_kw(verify, cert)
|
||||
|
||||
if proxy:
|
||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||
proxy_manager = self.proxy_manager_for(proxy)
|
||||
conn = proxy_manager.connection_from_url(url)
|
||||
else:
|
||||
# Only scheme should be lower case
|
||||
parsed = urlparse(url)
|
||||
url = parsed.geturl()
|
||||
conn = self.poolmanager.connection_from_url(url)
|
||||
proxy = select_proxy(url, proxies)
|
||||
|
||||
if proxy:
|
||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||
proxy_manager = self.proxy_manager_for(proxy)
|
||||
conn = proxy_manager.connection_from_url(url)
|
||||
else:
|
||||
# Only scheme should be lower case
|
||||
parsed = urlparse(url)
|
||||
url = parsed.geturl()
|
||||
conn = self.poolmanager.connection_from_url(url)
|
||||
|
||||
return conn
|
||||
|
||||
@@ -401,10 +418,8 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) The proxies dictionary to apply to the request.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
conn = self.get_connection(request.url, proxies, verify, cert)
|
||||
|
||||
conn = self.get_connection(request.url, proxies)
|
||||
|
||||
self.cert_verify(conn, request.url, verify, cert)
|
||||
url = self.request_url(request, proxies)
|
||||
self.add_headers(request)
|
||||
|
||||
@@ -437,7 +452,8 @@ class HTTPAdapter(BaseAdapter):
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=self.max_retries,
|
||||
timeout=timeout
|
||||
timeout=timeout,
|
||||
enforce_content_length=True
|
||||
)
|
||||
|
||||
# Send the request.
|
||||
@@ -458,7 +474,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
low_conn.endheaders()
|
||||
|
||||
for i in request.body:
|
||||
low_conn.send(hex(len(i))[2:].encode('utf-8'))
|
||||
chunk_size = len(i)
|
||||
if chunk_size == 0:
|
||||
continue
|
||||
low_conn.send(hex(chunk_size)[2:].encode('utf-8'))
|
||||
low_conn.send(b'\r\n')
|
||||
low_conn.send(i)
|
||||
low_conn.send(b'\r\n')
|
||||
@@ -478,7 +497,9 @@ class HTTPAdapter(BaseAdapter):
|
||||
pool=conn,
|
||||
connection=low_conn,
|
||||
preload_content=False,
|
||||
decode_content=False
|
||||
decode_content=False,
|
||||
enforce_content_length=True,
|
||||
request_method=request.method
|
||||
)
|
||||
except:
|
||||
# If we hit any problems here, clean up the connection.
|
||||
|
||||
@@ -13,11 +13,12 @@ This module implements the Requests API.
|
||||
from . import sessions
|
||||
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
def request(method, url, session=None, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param session: :class:`Session` object to use for this request. If none is given, one will be provided.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), 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`.
|
||||
@@ -54,7 +55,10 @@ def request(method, url, **kwargs):
|
||||
# By using the 'with' statement we are sure the session is closed, thus we
|
||||
# avoid leaving sockets open which can trigger a ResourceWarning in some
|
||||
# cases, and look like a memory leak in others.
|
||||
with sessions.Session() as session:
|
||||
|
||||
session = sessions.Session() if session is None else session
|
||||
|
||||
with session:
|
||||
return session.request(method=method, url=url, **kwargs)
|
||||
|
||||
|
||||
@@ -137,7 +141,7 @@ def patch(url, data=None, **kwargs):
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
|
||||
@@ -12,7 +12,6 @@ import re
|
||||
import time
|
||||
import hashlib
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
@@ -28,33 +27,13 @@ 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 ({0!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(username),
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
username = str(username)
|
||||
raise TypeError('username must be of type str or bytes, '
|
||||
'instead it was %s' % type(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 ({0!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(password),
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
password = str(password)
|
||||
# -- End Removal --
|
||||
raise TypeError('password must be of type str or bytes, '
|
||||
'instead it was %s' % type(password))
|
||||
|
||||
if isinstance(username, str):
|
||||
username = username.encode('latin1')
|
||||
@@ -97,14 +76,6 @@ class HTTPBasicAuth(AuthBase):
|
||||
return r
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class HTTPDigestAuth(AuthBase):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object."""
|
||||
|
||||
@@ -192,7 +163,7 @@ class HTTPDigestAuth(AuthBase):
|
||||
elif qop == 'auth' or 'auth' in qop.split(','):
|
||||
noncebit = "%s:%s:%s:%s:%s" % (
|
||||
nonce, ncvalue, cnonce, 'auth', HA2
|
||||
)
|
||||
)
|
||||
respdig = KD(HA1, noncebit)
|
||||
else:
|
||||
# XXX handle auth-int.
|
||||
@@ -227,7 +198,7 @@ class HTTPDigestAuth(AuthBase):
|
||||
"""
|
||||
|
||||
# If response is not 4xx, do not auth
|
||||
# See https://github.com/kennethreitz/requests/issues/3772
|
||||
# See https://github.com/requests/requests/issues/3772
|
||||
if not 400 <= r.status_code < 500:
|
||||
self._thread_local.num_401_calls = 1
|
||||
return r
|
||||
|
||||
@@ -25,12 +25,6 @@ is_py2 = (_ver[0] == 2)
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except (ImportError, SyntaxError):
|
||||
# simplejson does not support Python 3.2, it throws a SyntaxError
|
||||
# because of u'...' Unicode literals.
|
||||
import json
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
|
||||
@@ -306,8 +306,10 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""
|
||||
dictionary = {}
|
||||
for cookie in iter(self):
|
||||
if (domain is None or cookie.domain == domain) and (path is None
|
||||
or cookie.path == path):
|
||||
if (
|
||||
(domain is None or cookie.domain == domain) and
|
||||
(path is None or cookie.path == path)
|
||||
):
|
||||
dictionary[cookie.name] = cookie.value
|
||||
return dictionary
|
||||
|
||||
@@ -412,7 +414,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of this RequestsCookieJar."""
|
||||
new_cj = RequestsCookieJar()
|
||||
new_cj = RequestsCookieJar(self._policy)
|
||||
new_cj.update(self)
|
||||
return new_cj
|
||||
|
||||
|
||||
@@ -69,12 +69,12 @@ class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
|
||||
|
||||
class MissingSchema(RequestException, ValueError):
|
||||
"""The URL schema (e.g. http or https) is missing."""
|
||||
class MissingScheme(RequestException, ValueError):
|
||||
"""The URL scheme (e.g. http or https) is missing."""
|
||||
|
||||
|
||||
class InvalidSchema(RequestException, ValueError):
|
||||
"""See defaults.py for valid schemas."""
|
||||
class InvalidScheme(RequestException, ValueError):
|
||||
"""See defaults.py for valid schemes."""
|
||||
|
||||
|
||||
class InvalidURL(RequestException, ValueError):
|
||||
@@ -100,9 +100,14 @@ class StreamConsumedError(RequestException, TypeError):
|
||||
class RetryError(RequestException):
|
||||
"""Custom retries logic failed"""
|
||||
|
||||
|
||||
class UnrewindableBodyError(RequestException):
|
||||
"""Requests encountered an error when trying to rewind a body"""
|
||||
|
||||
|
||||
class InvalidBodyError(RequestException, ValueError):
|
||||
"""An invalid request body was specified"""
|
||||
|
||||
# Warnings
|
||||
|
||||
|
||||
|
||||
111
requests/help.py
Normal file
111
requests/help.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""Module containing bug report helper(s)."""
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
import platform
|
||||
import sys
|
||||
import ssl
|
||||
|
||||
import urllib3
|
||||
import chardet
|
||||
|
||||
from . import __version__ as requests_version
|
||||
|
||||
try:
|
||||
from .packages.urllib3.contrib import pyopenssl
|
||||
except ImportError:
|
||||
pyopenssl = None
|
||||
OpenSSL = None
|
||||
cryptography = None
|
||||
else:
|
||||
import OpenSSL
|
||||
import cryptography
|
||||
|
||||
|
||||
def _implementation():
|
||||
"""Return a dict with the Python implementation and verison.
|
||||
|
||||
Provide both the name and the version of the Python implementation
|
||||
currently running. For example, on CPython 2.7.5 it will return
|
||||
{'name': 'CPython', 'version': '2.7.5'}.
|
||||
|
||||
This function works best on CPython and PyPy: in particular, it probably
|
||||
doesn't work for Jython or IronPython. Future investigation should be done
|
||||
to work out the correct shape of the code for those platforms.
|
||||
"""
|
||||
implementation = platform.python_implementation()
|
||||
|
||||
if implementation == 'CPython':
|
||||
implementation_version = platform.python_version()
|
||||
elif implementation == 'PyPy':
|
||||
implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
|
||||
sys.pypy_version_info.minor,
|
||||
sys.pypy_version_info.micro)
|
||||
if sys.pypy_version_info.releaselevel != 'final':
|
||||
implementation_version = ''.join([
|
||||
implementation_version, sys.pypy_version_info.releaselevel
|
||||
])
|
||||
elif implementation == 'Jython':
|
||||
implementation_version = platform.python_version() # Complete Guess
|
||||
elif implementation == 'IronPython':
|
||||
implementation_version = platform.python_version() # Complete Guess
|
||||
else:
|
||||
implementation_version = 'Unknown'
|
||||
|
||||
return {'name': implementation, 'version': implementation_version}
|
||||
|
||||
|
||||
def info():
|
||||
"""Generate information for a bug report."""
|
||||
try:
|
||||
platform_info = {
|
||||
'system': platform.system(),
|
||||
'release': platform.release(),
|
||||
}
|
||||
except IOError:
|
||||
platform_info = {
|
||||
'system': 'Unknown',
|
||||
'release': 'Unknown',
|
||||
}
|
||||
|
||||
implementation_info = _implementation()
|
||||
urllib3_info = {'version': urllib3.__version__}
|
||||
chardet_info = {'version': chardet.__version__}
|
||||
|
||||
pyopenssl_info = {
|
||||
'version': None,
|
||||
'openssl_version': '',
|
||||
}
|
||||
if OpenSSL:
|
||||
pyopenssl_info = {
|
||||
'version': OpenSSL.__version__,
|
||||
'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER,
|
||||
}
|
||||
cryptography_info = {
|
||||
'version': getattr(cryptography, '__version__', ''),
|
||||
}
|
||||
|
||||
return {
|
||||
'platform': platform_info,
|
||||
'implementation': implementation_info,
|
||||
'system_ssl': {
|
||||
'version': '%x' % ssl.OPENSSL_VERSION_NUMBER,
|
||||
},
|
||||
'using_pyopenssl': pyopenssl is not None,
|
||||
'pyOpenSSL': pyopenssl_info,
|
||||
'urllib3': urllib3_info,
|
||||
'chardet': chardet_info,
|
||||
'cryptography': cryptography_info,
|
||||
'requests': {
|
||||
'version': requests_version,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Pretty-print the bug information as JSON."""
|
||||
print(json.dumps(info(), sort_keys=True, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -9,11 +9,12 @@ This module contains the primary objects that power Requests.
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import codecs
|
||||
import sys
|
||||
|
||||
# Import encoding now, to avoid implicit import later.
|
||||
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
|
||||
# such as in Embedded Python. See https://github.com/kennethreitz/requests/issues/3578.
|
||||
# such as in Embedded Python. See https://github.com/requests/requests/issues/3578.
|
||||
import encodings.idna
|
||||
|
||||
from urllib3.fields import RequestField
|
||||
@@ -26,20 +27,23 @@ from io import UnsupportedOperation
|
||||
from .hooks import default_hooks
|
||||
from .structures import CaseInsensitiveDict
|
||||
|
||||
import requests
|
||||
from .auth import HTTPBasicAuth
|
||||
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
|
||||
from .exceptions import (
|
||||
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
|
||||
ContentDecodingError, ConnectionError, StreamConsumedError)
|
||||
HTTPError, MissingScheme, InvalidURL, ChunkedEncodingError,
|
||||
ContentDecodingError, ConnectionError, StreamConsumedError,
|
||||
InvalidHeader, InvalidBodyError)
|
||||
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,
|
||||
iter_slices, guess_json_utf, super_len, check_header_validity)
|
||||
iter_slices, guess_json_utf, super_len, check_header_validity,
|
||||
is_stream)
|
||||
from .compat import (
|
||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
|
||||
is_py2, chardet, builtin_str, basestring)
|
||||
from .compat import json as complexjson
|
||||
import json as complexjson
|
||||
from .status_codes import codes
|
||||
|
||||
#: The set of HTTP status codes that indicate an automatically
|
||||
@@ -215,8 +219,9 @@ class Request(RequestHooksMixin):
|
||||
<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):
|
||||
def __init__(self,
|
||||
method=None, url=None, headers=None, files=None, data=None,
|
||||
params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||
|
||||
# Default empty dicts for dict params.
|
||||
data = [] if data is None else data
|
||||
@@ -295,8 +300,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
#: integer denoting starting position of a readable file-like body.
|
||||
self._body_position = None
|
||||
|
||||
def prepare(self, method=None, url=None, headers=None, files=None,
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||
def prepare(self,
|
||||
method=None, url=None, headers=None, files=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)
|
||||
@@ -329,8 +335,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
def prepare_method(self, method):
|
||||
"""Prepares the given HTTP method."""
|
||||
self.method = method
|
||||
if self.method is not None:
|
||||
self.method = to_native_string(self.method.upper())
|
||||
if self.method is None:
|
||||
raise ValueError('Request method cannot be "None"')
|
||||
self.method = to_native_string(self.method.upper())
|
||||
|
||||
@staticmethod
|
||||
def _get_idna_encoded_host(host):
|
||||
@@ -348,14 +355,14 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
#: We're unable to blindly call unicode/str functions
|
||||
#: as this will include the bytestring indicator (b'')
|
||||
#: on python 3.x.
|
||||
#: https://github.com/kennethreitz/requests/pull/2238
|
||||
#: https://github.com/requests/requests/pull/2238
|
||||
if isinstance(url, bytes):
|
||||
url = url.decode('utf8')
|
||||
else:
|
||||
url = unicode(url) if is_py2 else str(url)
|
||||
|
||||
# Remove leading whitespaces from url
|
||||
url = url.lstrip()
|
||||
# Ignore any leading and trailing whitespace characters.
|
||||
url = url.strip()
|
||||
|
||||
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
|
||||
# `data` etc to work around exceptions from `url_parse`, which
|
||||
@@ -371,10 +378,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
raise InvalidURL(*e.args)
|
||||
|
||||
if not scheme:
|
||||
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
|
||||
error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?")
|
||||
error = error.format(to_native_string(url, 'utf8'))
|
||||
|
||||
raise MissingSchema(error)
|
||||
raise MissingScheme(error)
|
||||
|
||||
if not host:
|
||||
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
||||
@@ -457,17 +464,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if not isinstance(body, bytes):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
is_stream = all([
|
||||
hasattr(data, '__iter__'),
|
||||
not isinstance(data, (basestring, list, tuple, collections.Mapping))
|
||||
])
|
||||
|
||||
try:
|
||||
length = super_len(data)
|
||||
except (TypeError, AttributeError, UnsupportedOperation):
|
||||
length = None
|
||||
|
||||
if is_stream:
|
||||
if is_stream(data):
|
||||
body = data
|
||||
|
||||
if getattr(body, 'tell', None) is not None:
|
||||
@@ -484,10 +481,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if files:
|
||||
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
|
||||
|
||||
if length:
|
||||
self.headers['Content-Length'] = builtin_str(length)
|
||||
else:
|
||||
self.headers['Transfer-Encoding'] = 'chunked'
|
||||
else:
|
||||
# Multi-part file uploads.
|
||||
if files:
|
||||
@@ -500,27 +493,40 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
|
||||
self.prepare_content_length(body)
|
||||
|
||||
# Add content-type if it wasn't explicitly provided.
|
||||
if content_type and ('content-type' not in self.headers):
|
||||
self.headers['Content-Type'] = content_type
|
||||
|
||||
self.prepare_content_length(body)
|
||||
self.body = body
|
||||
|
||||
def prepare_content_length(self, body):
|
||||
"""Prepare Content-Length header based on request method and body"""
|
||||
"""Prepares Content-Length header.
|
||||
|
||||
If the length of the body of the request can be computed, Content-Length
|
||||
is set using ``super_len``. If user has manually set either a
|
||||
Transfer-Encoding or Content-Length header when it should not be set
|
||||
(they should be mutually exclusive) an InvalidHeader
|
||||
error will be raised.
|
||||
"""
|
||||
if body is not None:
|
||||
length = super_len(body)
|
||||
|
||||
if length:
|
||||
# If length exists, set it. Otherwise, we fallback
|
||||
# to Transfer-Encoding: chunked.
|
||||
self.headers['Content-Length'] = builtin_str(length)
|
||||
elif is_stream(body):
|
||||
self.headers['Transfer-Encoding'] = 'chunked'
|
||||
else:
|
||||
raise InvalidBodyError('Non-null body must have length or be streamable.')
|
||||
elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None:
|
||||
# Set Content-Length to 0 for methods that can have a body
|
||||
# but don't provide one. (i.e. not GET or HEAD)
|
||||
self.headers['Content-Length'] = '0'
|
||||
|
||||
if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers:
|
||||
raise InvalidHeader('Conflicting Headers: Both Transfer-Encoding and '
|
||||
'Content-Length are set.')
|
||||
|
||||
def prepare_auth(self, auth, url=''):
|
||||
"""Prepares the given HTTP auth data."""
|
||||
|
||||
@@ -572,6 +578,14 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
for event in hooks:
|
||||
self.register_hook(event, hooks[event])
|
||||
|
||||
def send(self, session=None, **send_kwargs):
|
||||
"""Sends the PreparedRequest to the given Session.
|
||||
If none is provided, one is created for you."""
|
||||
session = requests.Session() if session is None else session
|
||||
|
||||
with session:
|
||||
return session.send(self, **send_kwargs)
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""The :class:`Response <Response>` object, which contains a
|
||||
@@ -606,7 +620,8 @@ class Response(object):
|
||||
#: Final URL location of Response.
|
||||
self.url = None
|
||||
|
||||
#: Encoding to decode with when accessing r.text.
|
||||
#: Encoding to decode with when accessing r.text or
|
||||
#: r.iter_content(decode_unicode=True)
|
||||
self.encoding = None
|
||||
|
||||
#: A list of :class:`Response <Response>` objects from
|
||||
@@ -654,26 +669,6 @@ class Response(object):
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
return self.ok
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
return self.ok
|
||||
|
||||
def __iter__(self):
|
||||
"""Allows you to use a response as an iterator."""
|
||||
return self.iter_content(128)
|
||||
@@ -728,8 +723,8 @@ class Response(object):
|
||||
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.
|
||||
If using decode_unicode, the encoding must be set to a valid encoding
|
||||
enumeration before invoking iter_content.
|
||||
"""
|
||||
|
||||
def generate():
|
||||
@@ -739,7 +734,10 @@ class Response(object):
|
||||
for chunk in self.raw.stream(chunk_size, decode_content=True):
|
||||
yield chunk
|
||||
except ProtocolError as e:
|
||||
raise ChunkedEncodingError(e)
|
||||
if self.headers.get('Transfer-Encoding') == 'chunked':
|
||||
raise ChunkedEncodingError(e)
|
||||
else:
|
||||
raise ConnectionError(e)
|
||||
except DecodeError as e:
|
||||
raise ContentDecodingError(e)
|
||||
except ReadTimeoutError as e:
|
||||
@@ -766,6 +764,16 @@ class Response(object):
|
||||
chunks = reused_chunks if self._content_consumed else stream_chunks
|
||||
|
||||
if decode_unicode:
|
||||
if self.encoding is None:
|
||||
raise TypeError(
|
||||
'encoding must be set before consuming streaming '
|
||||
'responses'
|
||||
)
|
||||
|
||||
# check encoding value here, don't wait for the generator to be
|
||||
# consumed before raising an exception
|
||||
codecs.lookup(self.encoding)
|
||||
|
||||
chunks = stream_decode_response_unicode(chunks, self)
|
||||
|
||||
return chunks
|
||||
@@ -777,23 +785,67 @@ class Response(object):
|
||||
|
||||
.. note:: This method is not reentrant safe.
|
||||
"""
|
||||
carriage_return = u'\r' if decode_unicode else b'\r'
|
||||
line_feed = u'\n' if decode_unicode else b'\n'
|
||||
|
||||
pending = None
|
||||
last_chunk_ends_with_cr = False
|
||||
|
||||
for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
|
||||
for chunk in self.iter_content(chunk_size=chunk_size,
|
||||
decode_unicode=decode_unicode):
|
||||
# Skip any null responses: if there is pending data it is necessarily an
|
||||
# incomplete chunk, so if we don't have more data we don't want to bother
|
||||
# trying to get it. Unconsumed pending data will be yielded anyway in the
|
||||
# end of the loop if the stream ends.
|
||||
if not chunk:
|
||||
continue
|
||||
|
||||
# Consume any pending data
|
||||
if pending is not None:
|
||||
chunk = pending + chunk
|
||||
pending = None
|
||||
|
||||
# Either split on a line, or split on a specified delimiter
|
||||
if delimiter:
|
||||
lines = chunk.split(delimiter)
|
||||
else:
|
||||
# Python splitlines() supports the universal newline (PEP 278).
|
||||
# That means, '\r', '\n', and '\r\n' are all treated as end of
|
||||
# line. If the last chunk ends with '\r', and the current chunk
|
||||
# starts with '\n', they should be merged and treated as only
|
||||
# *one* new line separator '\r\n' by splitlines().
|
||||
# This rule only applies when splitlines() is used.
|
||||
|
||||
# The last chunk ends with '\r', so the '\n' at chunk[0]
|
||||
# is just the second half of a '\r\n' pair rather than a
|
||||
# new line break. Just skip it.
|
||||
skip_first_char = last_chunk_ends_with_cr and chunk.startswith(line_feed)
|
||||
last_chunk_ends_with_cr = chunk.endswith(carriage_return)
|
||||
if skip_first_char:
|
||||
chunk = chunk[1:]
|
||||
# it's possible that after stripping the '\n' then chunk becomes empty
|
||||
if not chunk:
|
||||
continue
|
||||
lines = chunk.splitlines()
|
||||
|
||||
if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
|
||||
# Calling `.split(delimiter)` will always end with whatever text
|
||||
# remains beyond the delimiter, or '' if the delimiter is the end
|
||||
# of the text. On the other hand, `.splitlines()` doesn't include
|
||||
# a '' if the text ends in a line delimiter.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# 'abc\ndef\n'.split('\n') ~> ['abc', 'def', '']
|
||||
# 'abc\ndef\n'.splitlines() ~> ['abc', 'def']
|
||||
#
|
||||
# So if we have a specified delimiter, we always pop the final
|
||||
# item and prepend it to the next chunk.
|
||||
#
|
||||
# If we're using `splitlines()`, we only do this if the chunk
|
||||
# ended midway through a line.
|
||||
incomplete_line = lines[-1] and lines[-1][-1] == chunk[-1]
|
||||
if delimiter or incomplete_line:
|
||||
pending = lines.pop()
|
||||
else:
|
||||
pending = None
|
||||
|
||||
for line in lines:
|
||||
yield line
|
||||
@@ -904,7 +956,8 @@ class Response(object):
|
||||
return l
|
||||
|
||||
def raise_for_status(self):
|
||||
"""Raises stored :class:`HTTPError`, if one occurred."""
|
||||
"""Raises stored :class:`HTTPError`, if one occurred.
|
||||
Otherwise, returns the response object (self)."""
|
||||
|
||||
http_error_msg = ''
|
||||
if isinstance(self.reason, bytes):
|
||||
@@ -928,6 +981,8 @@ class Response(object):
|
||||
if http_error_msg:
|
||||
raise HTTPError(http_error_msg, response=self)
|
||||
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
"""Releases the connection back to the pool. Once this method has been
|
||||
called the underlying ``raw`` object must not be accessed again.
|
||||
|
||||
@@ -3,13 +3,12 @@ import sys
|
||||
# This code exists for backwards compatibility reasons.
|
||||
# I don't like it either. Just look the other way. :)
|
||||
|
||||
import urllib3
|
||||
sys.modules['requests.packages.urllib3'] = urllib3
|
||||
for package in ('urllib3', 'idna', 'chardet'):
|
||||
locals()[package] = __import__(package)
|
||||
# This traversal is apparently necessary such that the identities are
|
||||
# preserved (requests.packages.urllib3.* is urllib3.*)
|
||||
for mod in list(sys.modules):
|
||||
if mod == package or mod.startswith(package + '.'):
|
||||
sys.modules['requests.packages.' + mod] = sys.modules[mod]
|
||||
|
||||
import idna
|
||||
sys.modules['requests.packages.idna'] = idna
|
||||
|
||||
import chardet
|
||||
sys.modules['requests.packages.chardet'] = chardet
|
||||
|
||||
# Kinda cool, though, right?
|
||||
# Kinda cool, though, right?
|
||||
|
||||
@@ -13,25 +13,25 @@ import time
|
||||
from collections import Mapping
|
||||
from datetime import timedelta
|
||||
|
||||
from urllib3._collections import RecentlyUsedContainer
|
||||
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse
|
||||
from .compat import cookielib, OrderedDict, urljoin, urlparse, is_py3, str
|
||||
from .cookies import (
|
||||
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
|
||||
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar,
|
||||
merge_cookies, _copy_cookie_jar)
|
||||
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
|
||||
from .hooks import default_hooks, dispatch_hook
|
||||
from ._internal_utils import to_native_string
|
||||
from .utils import to_key_val_list, default_headers
|
||||
from .exceptions import (
|
||||
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
|
||||
TooManyRedirects, InvalidScheme, ChunkedEncodingError,
|
||||
ConnectionError, ContentDecodingError, InvalidHeader)
|
||||
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .adapters import HTTPAdapter
|
||||
|
||||
from .utils import (
|
||||
requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
|
||||
get_auth_from_url, rewind_body
|
||||
get_auth_from_url, is_valid_location, rewind_body
|
||||
)
|
||||
|
||||
from .status_codes import codes
|
||||
@@ -39,8 +39,6 @@ from .status_codes import codes
|
||||
# formerly defined here, reexposed here for backward compatibility
|
||||
from .models import REDIRECT_STATI
|
||||
|
||||
REDIRECT_CACHE_SIZE = 1000
|
||||
|
||||
# Preferred clock, based on which one is more accurate on a given system.
|
||||
if platform.system() == 'Windows':
|
||||
try: # Python 3.3+
|
||||
@@ -54,7 +52,7 @@ else:
|
||||
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||
"""Determines appropriate setting for a given request, taking into account
|
||||
the explicit setting on that request, and the setting in the session. If a
|
||||
setting is a dictionary, they will be merged together using `dict_class`
|
||||
setting is a dictionary, they will be merged together using `dict_class`.
|
||||
"""
|
||||
|
||||
if session_setting is None:
|
||||
@@ -99,10 +97,14 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
|
||||
|
||||
class SessionRedirectMixin(object):
|
||||
|
||||
def get_redirect_target(self, resp):
|
||||
def get_redirect_target(self, response):
|
||||
"""Receives a Response. Returns a redirect URI or ``None``"""
|
||||
if resp.is_redirect:
|
||||
location = resp.headers['location']
|
||||
if response.is_redirect:
|
||||
if not is_valid_location(response):
|
||||
raise InvalidHeader('Response contains multiple Location headers. '
|
||||
'Unable to perform redirect.')
|
||||
|
||||
location = response.headers['location']
|
||||
# Currently the underlying http module on py3 decode headers
|
||||
# in latin1, but empirical evidence suggests that latin1 is very
|
||||
# rarely used with non-ASCII characters in HTTP headers.
|
||||
@@ -114,59 +116,57 @@ class SessionRedirectMixin(object):
|
||||
return to_native_string(location, 'utf8')
|
||||
return None
|
||||
|
||||
def resolve_redirects(self, resp, req, stream=False, timeout=None,
|
||||
verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):
|
||||
"""Receives a Response. Returns a generator of Responses or Requests."""
|
||||
def resolve_redirects(self, response, request, stream=False, timeout=None,
|
||||
verify=True, cert=None, proxies=None,
|
||||
yield_requests=False, **adapter_kwargs):
|
||||
"""Given a Response, yields Responses until 'Location' header-based
|
||||
redirection ceases, or the Session.max_redirects limit has been
|
||||
reached.
|
||||
"""
|
||||
|
||||
hist = [] # keep track of history
|
||||
history = [response] # keep track of history; seed it with the original response
|
||||
|
||||
url = self.get_redirect_target(resp)
|
||||
while url:
|
||||
prepared_request = req.copy()
|
||||
location_url = self.get_redirect_target(response)
|
||||
|
||||
# Update history and keep track of redirects.
|
||||
# resp.history must ignore the original request in this loop
|
||||
hist.append(resp)
|
||||
resp.history = hist[1:]
|
||||
while location_url:
|
||||
prepared_request = request.copy()
|
||||
|
||||
try:
|
||||
resp.content # Consume socket so it can be released
|
||||
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
|
||||
resp.raw.read(decode_content=False)
|
||||
response.content # Consume socket so it can be released
|
||||
except (ChunkedEncodingError, ConnectionError, ContentDecodingError, RuntimeError):
|
||||
response.raw.read(decode_content=False)
|
||||
|
||||
if len(resp.history) >= self.max_redirects:
|
||||
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
|
||||
if len(response.history) >= self.max_redirects:
|
||||
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=response)
|
||||
|
||||
# Release the connection back into the pool.
|
||||
resp.close()
|
||||
response.close()
|
||||
|
||||
# Handle redirection without scheme (see: RFC 1808 Section 4)
|
||||
if url.startswith('//'):
|
||||
parsed_rurl = urlparse(resp.url)
|
||||
url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
|
||||
if location_url.startswith('//'):
|
||||
parsed_rurl = urlparse(response.url)
|
||||
location_url = '%s:%s' % (to_native_string(parsed_rurl.scheme), location_url)
|
||||
|
||||
# The scheme should be lower case...
|
||||
parsed = urlparse(url)
|
||||
url = parsed.geturl()
|
||||
parsed = urlparse(location_url)
|
||||
location_url = parsed.geturl()
|
||||
|
||||
# Facilitate relative 'location' headers, as allowed by RFC 7231.
|
||||
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
|
||||
# Compliant with RFC3986, we percent encode the url.
|
||||
if not parsed.netloc:
|
||||
url = urljoin(resp.url, requote_uri(url))
|
||||
location_url = urljoin(response.url, requote_uri(location_url))
|
||||
else:
|
||||
url = requote_uri(url)
|
||||
location_url = requote_uri(location_url)
|
||||
|
||||
prepared_request.url = to_native_string(url)
|
||||
# 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
|
||||
prepared_request.url = to_native_string(location_url)
|
||||
|
||||
self.rebuild_method(prepared_request, resp)
|
||||
method_changed = self.rebuild_method(prepared_request, response)
|
||||
|
||||
# https://github.com/kennethreitz/requests/issues/1084
|
||||
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
|
||||
# https://github.com/kennethreitz/requests/issues/3490
|
||||
# https://github.com/kennethreitz/requests/issues/2590
|
||||
# If method is changed to GET we need to remove body and associated headers.
|
||||
if method_changed and prepared_request.method == 'GET':
|
||||
# https://github.com/requests/requests/issues/3490
|
||||
purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')
|
||||
for header in purged_headers:
|
||||
prepared_request.headers.pop(header, None)
|
||||
@@ -181,13 +181,13 @@ class SessionRedirectMixin(object):
|
||||
# Extract any cookies sent on the response to the cookiejar
|
||||
# in the new request. Because we've mutated our copied prepared
|
||||
# request, use the old one that we haven't yet touched.
|
||||
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
|
||||
extract_cookies_to_jar(prepared_request._cookies, request, response.raw)
|
||||
merge_cookies(prepared_request._cookies, self.cookies)
|
||||
prepared_request.prepare_cookies(prepared_request._cookies)
|
||||
|
||||
# Rebuild auth and proxy information.
|
||||
proxies = self.rebuild_proxies(prepared_request, proxies)
|
||||
self.rebuild_auth(prepared_request, resp)
|
||||
self.rebuild_auth(prepared_request, response)
|
||||
|
||||
# A failed tell() sets `_body_position` to `object()`. This non-None
|
||||
# value ensures `rewindable` will be True, allowing us to raise an
|
||||
@@ -202,14 +202,14 @@ class SessionRedirectMixin(object):
|
||||
rewind_body(prepared_request)
|
||||
|
||||
# Override the original request.
|
||||
req = prepared_request
|
||||
request = prepared_request
|
||||
|
||||
if yield_requests:
|
||||
yield req
|
||||
yield request
|
||||
else:
|
||||
|
||||
resp = self.send(
|
||||
req,
|
||||
response = self.send(
|
||||
request,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
verify=verify,
|
||||
@@ -218,16 +218,21 @@ class SessionRedirectMixin(object):
|
||||
allow_redirects=False,
|
||||
**adapter_kwargs
|
||||
)
|
||||
# copy our history tracker into the response
|
||||
response.history = history[:]
|
||||
# append the new response to the history tracker for the next iteration
|
||||
history.append(response)
|
||||
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, response.raw)
|
||||
|
||||
# extract redirect url, if any, for the next loop
|
||||
url = self.get_redirect_target(resp)
|
||||
yield resp
|
||||
location_url = self.get_redirect_target(response)
|
||||
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
|
||||
request to avoid leaking credentials. This method intelligently
|
||||
removes
|
||||
and reapplies authentication where possible to avoid credential loss.
|
||||
"""
|
||||
headers = prepared_request.headers
|
||||
@@ -250,11 +255,11 @@ class SessionRedirectMixin(object):
|
||||
return
|
||||
|
||||
def rebuild_proxies(self, prepared_request, proxies):
|
||||
"""This method re-evaluates the proxy configuration by considering the
|
||||
environment variables. If we are redirected to a URL covered by
|
||||
NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
|
||||
proxy keys for this URL (in case they were stripped by a previous
|
||||
redirect).
|
||||
"""This method re-evaluates the proxy configuration by
|
||||
considering the environment variables. If we are redirected to a
|
||||
URL covered by NO_PROXY, we strip the proxy configuration.
|
||||
Otherwise, we set missing proxy keys for this URL (in case they
|
||||
were stripped by a previous redirect).
|
||||
|
||||
This method also replaces the Proxy-Authorization header where
|
||||
necessary.
|
||||
@@ -293,24 +298,26 @@ class SessionRedirectMixin(object):
|
||||
def rebuild_method(self, prepared_request, response):
|
||||
"""When being redirected we may want to change the method of the request
|
||||
based on certain specs or browser behavior.
|
||||
|
||||
:rtype bool:
|
||||
:return: boolean expressing if the method changed during rebuild.
|
||||
"""
|
||||
method = prepared_request.method
|
||||
method = original_method = prepared_request.method
|
||||
|
||||
# http://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||
if response.status_code == codes.see_other and method != 'HEAD':
|
||||
method = 'GET'
|
||||
|
||||
# Do what the browsers do, despite standards...
|
||||
# First, turn 302s into GETs.
|
||||
if response.status_code == codes.found and method != 'HEAD':
|
||||
method = 'GET'
|
||||
|
||||
# Second, if a POST is responded to with a 301, turn it into a GET.
|
||||
# This bizarre behaviour is explained in Issue 1704.
|
||||
if response.status_code == codes.moved and method == 'POST':
|
||||
# If a POST is responded to with a 301 or 302, turn it into a GET. This has
|
||||
# become a common pattern in browsers and was introduced into later versions
|
||||
# of HTTP RFCs. While some browsers transform other methods to GET, little of
|
||||
# that has been standardized. For that reason, we're using curl as a model
|
||||
# which only supports POST->GET.
|
||||
if response.status_code in (codes.found, codes.moved) and method == 'POST':
|
||||
method = 'GET'
|
||||
|
||||
prepared_request.method = method
|
||||
return method != original_method
|
||||
|
||||
|
||||
class Session(SessionRedirectMixin):
|
||||
@@ -393,9 +400,6 @@ class Session(SessionRedirectMixin):
|
||||
self.mount('https://', HTTPAdapter())
|
||||
self.mount('http://', HTTPAdapter())
|
||||
|
||||
# Only store 1000 redirects to prevent using infinite memory
|
||||
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
@@ -409,7 +413,7 @@ class Session(SessionRedirectMixin):
|
||||
:class:`Session`.
|
||||
|
||||
:param request: :class:`Request` instance to prepare with this
|
||||
session's settings.
|
||||
Session's settings.
|
||||
:rtype: requests.PreparedRequest
|
||||
"""
|
||||
cookies = request.cookies or {}
|
||||
@@ -419,8 +423,8 @@ class Session(SessionRedirectMixin):
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Merge with session cookies
|
||||
merged_cookies = merge_cookies(
|
||||
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
|
||||
session_cookies = _copy_cookie_jar(self.cookies)
|
||||
merged_cookies = merge_cookies(session_cookies, cookies)
|
||||
|
||||
# Set environment's basic authentication if not explicitly set.
|
||||
auth = request.auth
|
||||
@@ -443,21 +447,10 @@ class Session(SessionRedirectMixin):
|
||||
return p
|
||||
|
||||
def request(self, method, url,
|
||||
params=None,
|
||||
data=None,
|
||||
headers=None,
|
||||
cookies=None,
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=True,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
stream=None,
|
||||
verify=None,
|
||||
cert=None,
|
||||
json=None):
|
||||
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
|
||||
params=None, data=None, headers=None, cookies=None, files=None,
|
||||
auth=None, timeout=None, allow_redirects=True, proxies=None,
|
||||
hooks=None, stream=None, verify=None, cert=None, json=None):
|
||||
"""Constructs a :class:`Request <Request>`, prepares it, and sends it.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
@@ -495,16 +488,16 @@ class Session(SessionRedirectMixin):
|
||||
"""
|
||||
# Create the Request.
|
||||
req = Request(
|
||||
method = method.upper(),
|
||||
url = url,
|
||||
headers = headers,
|
||||
files = files,
|
||||
data = data or {},
|
||||
json = json,
|
||||
params = params or {},
|
||||
auth = auth,
|
||||
cookies = cookies,
|
||||
hooks = hooks,
|
||||
method=method.upper(),
|
||||
url=url,
|
||||
headers=headers,
|
||||
files=files,
|
||||
data=data or {},
|
||||
json=json,
|
||||
params=params or {},
|
||||
auth=auth,
|
||||
cookies=cookies,
|
||||
hooks=hooks,
|
||||
)
|
||||
prep = self.prepare_request(req)
|
||||
|
||||
@@ -589,7 +582,7 @@ class Session(SessionRedirectMixin):
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('PATCH', url, data=data, **kwargs)
|
||||
return self.request('PATCH', url, data=data, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
r"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
@@ -618,21 +611,12 @@ class Session(SessionRedirectMixin):
|
||||
if isinstance(request, Request):
|
||||
raise ValueError('You can only send PreparedRequests.')
|
||||
|
||||
# Set up variables needed for resolve_redirects and dispatching of hooks
|
||||
# Set up variables needed for resolve_redirects and dispatching of
|
||||
# hooks
|
||||
allow_redirects = kwargs.pop('allow_redirects', True)
|
||||
stream = kwargs.get('stream')
|
||||
hooks = request.hooks
|
||||
|
||||
# Resolve URL in redirect cache, if available.
|
||||
if allow_redirects:
|
||||
checked_urls = set()
|
||||
while request.url in self.redirect_cache:
|
||||
checked_urls.add(request.url)
|
||||
new_url = self.redirect_cache.get(request.url)
|
||||
if new_url in checked_urls:
|
||||
break
|
||||
request.url = new_url
|
||||
|
||||
# Get the appropriate adapter to use
|
||||
adapter = self.get_adapter(url=request.url)
|
||||
|
||||
@@ -646,7 +630,7 @@ class Session(SessionRedirectMixin):
|
||||
elapsed = preferred_clock() - start
|
||||
r.elapsed = timedelta(seconds=elapsed)
|
||||
|
||||
# Response manipulation hooks
|
||||
# Response manipulation hooks.
|
||||
r = dispatch_hook('response', hooks, r, **kwargs)
|
||||
|
||||
# Persist cookies
|
||||
@@ -661,16 +645,12 @@ class Session(SessionRedirectMixin):
|
||||
# Redirect resolving generator.
|
||||
gen = self.resolve_redirects(r, request, **kwargs)
|
||||
|
||||
# Resolve redirects if allowed.
|
||||
# Resolve redirects, if allowed.
|
||||
history = [resp for resp in gen] if allow_redirects else []
|
||||
|
||||
# Shuffle things around if there's history.
|
||||
# If there is a history, replace ``r`` with the last response
|
||||
if history:
|
||||
# Insert the first (original) request at the start
|
||||
history.insert(0, r)
|
||||
# Get the last request made
|
||||
r = history.pop()
|
||||
r.history = history
|
||||
|
||||
# If redirects aren't being followed, store the response on the Request for Response.next().
|
||||
if not allow_redirects:
|
||||
@@ -690,25 +670,37 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
: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.
|
||||
if self.trust_env:
|
||||
# Set environment's proxies.
|
||||
no_proxy = proxies.get('no_proxy') if proxies is not None else None
|
||||
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
|
||||
for (k, v) in env_proxies.items():
|
||||
proxies.setdefault(k, v)
|
||||
|
||||
# Look for requests environment configuration and be compatible
|
||||
# with cURL.
|
||||
if verify is True or verify is None:
|
||||
verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
|
||||
os.environ.get('CURL_CA_BUNDLE'))
|
||||
os.environ.get('CURL_CA_BUNDLE') or
|
||||
verify)
|
||||
|
||||
# Merge all the kwargs.
|
||||
proxies = merge_setting(proxies, self.proxies)
|
||||
stream = merge_setting(stream, self.stream)
|
||||
verify = merge_setting(verify, self.verify)
|
||||
cert = merge_setting(cert, self.cert)
|
||||
# Now we handle proxies.
|
||||
# Proxies need to be built up backwards. This is because None values
|
||||
# can delete proxy information, which can then be re-added by a more
|
||||
# specific layer. So we begin by getting the environment's proxies,
|
||||
# then add the Session, then add the request.
|
||||
no_proxy = proxies.get('no_proxy') if proxies is not None else None
|
||||
if no_proxy is None:
|
||||
no_proxy = self.proxies.get('no_proxy')
|
||||
|
||||
env_proxies = {}
|
||||
|
||||
if self.trust_env:
|
||||
env_proxies = get_environ_proxies(url, no_proxy=no_proxy) or {}
|
||||
|
||||
new_proxies = merge_setting(self.proxies, env_proxies)
|
||||
proxies = merge_setting(proxies, new_proxies)
|
||||
|
||||
return {'verify': verify, 'proxies': proxies, 'stream': stream,
|
||||
'cert': cert}
|
||||
@@ -725,10 +717,10 @@ class Session(SessionRedirectMixin):
|
||||
return adapter
|
||||
|
||||
# Nothing matches :-/
|
||||
raise InvalidSchema("No connection adapters were found for '%s'" % url)
|
||||
raise InvalidScheme("No connection adapters were found for '%s'" % url)
|
||||
|
||||
def close(self):
|
||||
"""Closes all adapters and as such the session"""
|
||||
"""Closes all adapters and, as such, the Session."""
|
||||
for v in self.adapters.values():
|
||||
v.close()
|
||||
|
||||
@@ -745,18 +737,12 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
def __getstate__(self):
|
||||
state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
state['redirect_cache'] = dict(self.redirect_cache)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
redirect_cache = state.pop('redirect_cache', {})
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
|
||||
for redirect, to in redirect_cache.items():
|
||||
self.redirect_cache[redirect] = to
|
||||
|
||||
|
||||
def session():
|
||||
"""
|
||||
|
||||
@@ -27,7 +27,7 @@ from ._internal_utils import to_native_string
|
||||
from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import (
|
||||
quote, urlparse, bytes, str, OrderedDict, unquote, getproxies,
|
||||
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
|
||||
proxy_bypass, urlunparse, basestring, integer_types, is_py2, is_py3,
|
||||
proxy_bypass_environment, getproxies_environment)
|
||||
from .cookies import cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
@@ -171,7 +171,7 @@ def get_netrc_auth(url, raise_errors=False):
|
||||
except KeyError:
|
||||
# os.path.expanduser can fail when $HOME is undefined and
|
||||
# getpwuid fails. See http://bugs.python.org/issue20164 &
|
||||
# https://github.com/kennethreitz/requests/issues/1846
|
||||
# https://github.com/requests/requests/issues/1846
|
||||
return
|
||||
|
||||
if os.path.exists(loc):
|
||||
@@ -431,11 +431,6 @@ def get_encoding_from_headers(headers):
|
||||
def stream_decode_response_unicode(iterator, r):
|
||||
"""Stream decodes a iterator."""
|
||||
|
||||
if r.encoding is None:
|
||||
for item in iterator:
|
||||
yield item
|
||||
return
|
||||
|
||||
decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace')
|
||||
for chunk in iterator:
|
||||
rv = decoder.decode(chunk)
|
||||
@@ -494,8 +489,7 @@ def get_unicode_from_response(r):
|
||||
|
||||
# The unreserved URI characters (RFC 3986)
|
||||
UNRESERVED_SET = frozenset(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
+ "0123456789-._~")
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~")
|
||||
|
||||
|
||||
def unquote_unreserved(uri):
|
||||
@@ -504,7 +498,26 @@ def unquote_unreserved(uri):
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
parts = uri.split('%')
|
||||
# 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
|
||||
# a bytestring. Here we deal with that by optionally converting.
|
||||
def convert(is_bytes, c):
|
||||
if is_py2 and not is_bytes:
|
||||
return c.decode('ascii')
|
||||
elif is_py3 and is_bytes:
|
||||
return c.encode('ascii')
|
||||
else:
|
||||
return c
|
||||
|
||||
# Handle both bytestrings and unicode strings.
|
||||
is_bytes = isinstance(uri, bytes)
|
||||
splitchar = u'%'
|
||||
base = u''
|
||||
if is_bytes:
|
||||
splitchar = splitchar.encode('ascii')
|
||||
base = base.encode('ascii')
|
||||
|
||||
parts = uri.split(splitchar)
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2 and h.isalnum():
|
||||
@@ -514,12 +527,12 @@ def unquote_unreserved(uri):
|
||||
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
|
||||
|
||||
if c in UNRESERVED_SET:
|
||||
parts[i] = c + parts[i][2:]
|
||||
parts[i] = convert(is_bytes, c) + parts[i][2:]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
parts[i] = splitchar + parts[i]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
return ''.join(parts)
|
||||
parts[i] = splitchar + parts[i]
|
||||
return base.join(parts)
|
||||
|
||||
|
||||
def requote_uri(uri):
|
||||
@@ -776,6 +789,19 @@ def parse_header_links(value):
|
||||
return links
|
||||
|
||||
|
||||
def is_valid_location(response):
|
||||
"""Verify that multiple Location headers weren't
|
||||
returned from the last response.
|
||||
"""
|
||||
headers = getattr(response.raw, 'headers', None)
|
||||
if headers is not None:
|
||||
getlist = getattr(headers, 'getlist', None)
|
||||
if getlist is not None:
|
||||
return len(getlist('location')) <= 1
|
||||
# If response.raw isn't urllib3-like we can't reliably check this
|
||||
return True
|
||||
|
||||
|
||||
# Null bytes; no need to recreate these on each call to guess_json_utf
|
||||
_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3
|
||||
_null2 = _null * 2
|
||||
@@ -851,6 +877,7 @@ def get_auth_from_url(url):
|
||||
_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
|
||||
@@ -888,6 +915,7 @@ def urldefragauth(url):
|
||||
|
||||
return urlunparse((scheme, netloc, path, params, query, ''))
|
||||
|
||||
|
||||
def rewind_body(prepared_request):
|
||||
"""Move file pointer back to its recorded starting position
|
||||
so it can be read again on redirect.
|
||||
@@ -901,3 +929,10 @@ def rewind_body(prepared_request):
|
||||
"body for redirect.")
|
||||
else:
|
||||
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
|
||||
|
||||
|
||||
def is_stream(data):
|
||||
"""Given data, determines if it should be sent as a stream."""
|
||||
is_iterable = getattr(data, '__iter__', False)
|
||||
is_io_type = not isinstance(data, (basestring, list, tuple, collections.Mapping))
|
||||
return is_iterable and is_io_type
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
-e .
|
||||
-e .[socks]
|
||||
pytest>=2.8.0
|
||||
codecov
|
||||
pytest-httpbin==0.0.7
|
||||
pytest-mock
|
||||
pytest-cov
|
||||
pytest-xdist
|
||||
alabaster
|
||||
readme_renderer
|
||||
Sphinx<=1.5.5
|
||||
PySocks
|
||||
setuptools>=18.5
|
||||
docutils
|
||||
flake8
|
||||
tox
|
||||
detox
|
||||
|
||||
10
setup.py
10
setup.py
@@ -16,7 +16,11 @@ class PyTest(TestCommand):
|
||||
|
||||
def initialize_options(self):
|
||||
TestCommand.initialize_options(self)
|
||||
self.pytest_args = []
|
||||
try:
|
||||
from multiprocessing import cpu_count
|
||||
self.pytest_args = ['-n', str(cpu_count()), '--boxed']
|
||||
except (ImportError, NotImplementedError):
|
||||
self.pytest_args = ['-n', '1', '--boxed']
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
@@ -44,10 +48,10 @@ requires = [
|
||||
'certifi>=2017.4.17'
|
||||
|
||||
]
|
||||
test_requirements = ['pytest>=2.8.0', 'pytest-httpbin==0.0.7', 'pytest-cov', 'pytest-mock']
|
||||
test_requirements = ['pytest-httpbin==0.0.7', 'pytest-cov', 'pytest-mock', 'pytest-xdist', 'PySocks>=1.5.6, !=1.5.7', 'pytest>=2.8.0']
|
||||
|
||||
about = {}
|
||||
with open(os.path.join(here, 'requests', '__version__.py')) as f:
|
||||
with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
with open('README.rst', 'r', 'utf-8') as f:
|
||||
|
||||
@@ -23,12 +23,29 @@ def test_chunked_upload():
|
||||
assert r.status_code == 200
|
||||
assert r.request.headers['Transfer-Encoding'] == 'chunked'
|
||||
|
||||
def test_incorrect_content_length():
|
||||
"""Test ConnectionError raised for incomplete responses"""
|
||||
close_server = threading.Event()
|
||||
server = Server.text_response_server(
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 50\r\n\r\n" +
|
||||
"Hello World."
|
||||
)
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.Request('GET', url).prepare()
|
||||
s = requests.Session()
|
||||
with pytest.raises(requests.exceptions.ConnectionError) as e:
|
||||
resp = s.send(r)
|
||||
assert "12 bytes read, 38 more expected" in str(e)
|
||||
close_server.set() # release server block
|
||||
|
||||
|
||||
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.
|
||||
See https://github.com/requests/requests/issues/1979.
|
||||
"""
|
||||
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
@@ -138,7 +155,7 @@ def test_digestauth_401_only_sent_once():
|
||||
def test_digestauth_only_on_4xx():
|
||||
"""Ensure we only send digestauth on 4xx challenges.
|
||||
|
||||
See https://github.com/kennethreitz/requests/issues/3772.
|
||||
See https://github.com/requests/requests/issues/3772.
|
||||
"""
|
||||
text_200_chal = (b'HTTP/1.1 200 OK\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
|
||||
13
tests/test_packages.py
Normal file
13
tests/test_packages.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import requests
|
||||
|
||||
|
||||
def test_can_access_urllib3_attribute():
|
||||
requests.packages.urllib3
|
||||
|
||||
|
||||
def test_can_access_idna_attribute():
|
||||
requests.packages.idna
|
||||
|
||||
|
||||
def test_can_access_chardet_attribute():
|
||||
requests.packages.chardet
|
||||
@@ -14,6 +14,7 @@ import warnings
|
||||
import io
|
||||
import requests
|
||||
import pytest
|
||||
import pytest_httpbin
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import HTTPDigestAuth, _basic_auth_str
|
||||
from requests.compat import (
|
||||
@@ -22,9 +23,9 @@ from requests.compat import (
|
||||
from requests.cookies import (
|
||||
cookiejar_from_dict, morsel_to_cookie)
|
||||
from requests.exceptions import (
|
||||
ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL,
|
||||
MissingSchema, ReadTimeout, Timeout, RetryError, TooManyRedirects,
|
||||
ProxyError, InvalidHeader, UnrewindableBodyError)
|
||||
ConnectionError, ConnectTimeout, InvalidScheme, InvalidURL,
|
||||
MissingScheme, ReadTimeout, Timeout, RetryError, TooManyRedirects,
|
||||
ProxyError, InvalidHeader, UnrewindableBodyError, InvalidBodyError)
|
||||
from requests.models import PreparedRequest
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
from requests.sessions import SessionRedirectMixin
|
||||
@@ -35,6 +36,21 @@ from .compat import StringIO, u
|
||||
from .utils import override_environ
|
||||
from urllib3.util import Timeout as Urllib3Timeout
|
||||
|
||||
class SendRecordingAdapter(HTTPAdapter):
|
||||
"""
|
||||
A basic subclass of the HTTPAdapter that records the arguments used to
|
||||
``send``.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SendRecordingAdapter, self).__init__(*args, **kwargs)
|
||||
|
||||
self.send_calls = []
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
self.send_calls.append((args, kwargs))
|
||||
return super(SendRecordingAdapter, self).send(*args, **kwargs)
|
||||
|
||||
|
||||
# Requests to this URL should always fail with a connection timeout (nothing
|
||||
# listening on that port)
|
||||
TARPIT = 'http://10.255.255.1'
|
||||
@@ -65,13 +81,15 @@ class TestRequests:
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
# Not really an entry point, but people rely on it.
|
||||
from requests.packages.urllib3.poolmanager import PoolManager
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, url', (
|
||||
(MissingSchema, 'hiwpefhipowhefopw'),
|
||||
(InvalidSchema, 'localhost:3128'),
|
||||
(InvalidSchema, 'localhost.localdomain:3128/'),
|
||||
(InvalidSchema, '10.122.1.1:3128/'),
|
||||
(MissingScheme, 'hiwpefhipowhefopw'),
|
||||
(InvalidScheme, 'localhost:3128'),
|
||||
(InvalidScheme, 'localhost.localdomain:3128/'),
|
||||
(InvalidScheme, '10.122.1.1:3128/'),
|
||||
(InvalidURL, 'http://'),
|
||||
))
|
||||
def test_invalid_url(self, exception, url):
|
||||
@@ -79,7 +97,7 @@ class TestRequests:
|
||||
requests.get(url)
|
||||
|
||||
def test_basic_building(self):
|
||||
req = requests.Request()
|
||||
req = requests.Request(method='GET')
|
||||
req.url = 'http://kennethreitz.org/'
|
||||
req.data = {'life': '42'}
|
||||
|
||||
@@ -210,48 +228,106 @@ class TestRequests:
|
||||
else:
|
||||
pytest.fail('Expected custom max number of redirects to be respected but was not')
|
||||
|
||||
def test_http_301_changes_post_to_get(self, httpbin):
|
||||
r = requests.post(httpbin('status', '301'))
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'GET'
|
||||
@pytest.mark.parametrize(
|
||||
'method, body, expected', (
|
||||
('GET', None, 'GET'),
|
||||
('HEAD', None, 'HEAD'),
|
||||
('POST', 'test', 'GET'),
|
||||
('PUT', 'put test', 'PUT'),
|
||||
('PATCH', 'patch test', 'PATCH'),
|
||||
('DELETE', '', 'DELETE')
|
||||
)
|
||||
)
|
||||
def test_http_301_for_redirectable_methods(self, httpbin, method, body, expected):
|
||||
"""Tests all methods except OPTIONS for expected redirect behaviour.
|
||||
|
||||
OPTIONS responses can behave differently depending on the server, so
|
||||
we don't have anything uniform to test except how httpbin responds
|
||||
to them. For that reason they aren't included here.
|
||||
"""
|
||||
params = {'url': '/%s' % expected.lower(), 'status_code': '301'}
|
||||
r = requests.request(method, httpbin('redirect-to'), data=body, params=params)
|
||||
|
||||
assert r.request.url == httpbin(expected.lower())
|
||||
assert r.request.method == expected
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
def test_http_301_doesnt_change_head_to_get(self, httpbin):
|
||||
r = requests.head(httpbin('status', '301'), allow_redirects=True)
|
||||
print(r.content)
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'HEAD'
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.history[0].is_redirect
|
||||
if expected in ('GET', 'HEAD'):
|
||||
assert r.request.body is None
|
||||
else:
|
||||
assert r.json()['data'] == body
|
||||
|
||||
def test_http_302_changes_post_to_get(self, httpbin):
|
||||
r = requests.post(httpbin('status', '302'))
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'GET'
|
||||
@pytest.mark.parametrize(
|
||||
'method, body, expected', (
|
||||
('GET', None, 'GET'),
|
||||
('HEAD', None, 'HEAD'),
|
||||
('POST', 'test', 'GET'),
|
||||
('PUT', 'put test', 'PUT'),
|
||||
('PATCH', 'patch test', 'PATCH'),
|
||||
('DELETE', '', 'DELETE')
|
||||
)
|
||||
)
|
||||
def test_http_302_for_redirectable_methods(self, httpbin, method, body, expected):
|
||||
"""Tests all methods except OPTIONS for expected redirect behaviour.
|
||||
|
||||
OPTIONS responses can behave differently depending on the server, so
|
||||
we don't have anything uniform to test except how httpbin responds
|
||||
to them. For that reason they aren't included here.
|
||||
"""
|
||||
params = {'url': '/%s' % expected.lower()}
|
||||
r = requests.request(method, httpbin('redirect-to'), data=body, params=params)
|
||||
|
||||
assert r.request.url == httpbin(expected.lower())
|
||||
assert r.request.method == expected
|
||||
assert r.history[0].status_code == 302
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
def test_http_302_doesnt_change_head_to_get(self, httpbin):
|
||||
r = requests.head(httpbin('status', '302'), allow_redirects=True)
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'HEAD'
|
||||
assert r.history[0].status_code == 302
|
||||
assert r.history[0].is_redirect
|
||||
if expected in ('GET', 'HEAD'):
|
||||
assert r.request.body is None
|
||||
else:
|
||||
assert r.json()['data'] == body
|
||||
|
||||
def test_http_303_changes_post_to_get(self, httpbin):
|
||||
r = requests.post(httpbin('status', '303'))
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'GET'
|
||||
@pytest.mark.parametrize(
|
||||
'method, body, expected', (
|
||||
('GET', None, 'GET'),
|
||||
('HEAD', None, 'HEAD'),
|
||||
('POST', 'test', 'GET'),
|
||||
('PUT', 'put test', 'GET'),
|
||||
('PATCH', 'patch test', 'GET'),
|
||||
('DELETE', '', 'GET')
|
||||
)
|
||||
)
|
||||
def test_http_303_for_redirectable_methods(self, httpbin, method, body, expected):
|
||||
"""Tests all methods except OPTIONS for expected redirect behaviour.
|
||||
|
||||
OPTIONS responses can behave differently depending on the server, so
|
||||
we don't have anything uniform to test except how httpbin responds
|
||||
to them. For that reason they aren't included here.
|
||||
"""
|
||||
params = {'url': '/%s' % expected.lower(), 'status_code': '303'}
|
||||
r = requests.request(method, httpbin('redirect-to'), data=body, params=params)
|
||||
|
||||
assert r.request.url == httpbin(expected.lower())
|
||||
assert r.request.method == expected
|
||||
assert r.history[0].status_code == 303
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
def test_http_303_doesnt_change_head_to_get(self, httpbin):
|
||||
r = requests.head(httpbin('status', '303'), allow_redirects=True)
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'HEAD'
|
||||
assert r.history[0].status_code == 303
|
||||
assert r.history[0].is_redirect
|
||||
assert r.request.body is None
|
||||
|
||||
def test_multiple_location_headers(self, httpbin):
|
||||
headers = [('Location', 'http://example.com'),
|
||||
('Location', 'https://example.com/1')]
|
||||
params = '&'.join(['%s=%s' % (k, v) for k, v in headers])
|
||||
ses = requests.Session()
|
||||
req = requests.Request('GET', httpbin('response-headers?%s' % params))
|
||||
prep = ses.prepare_request(req)
|
||||
resp = ses.send(prep)
|
||||
# change response to redirect
|
||||
resp.status_code = 302
|
||||
with pytest.raises(InvalidHeader):
|
||||
# next triggers yield on generator
|
||||
next(ses.resolve_redirects(resp, prep))
|
||||
|
||||
def test_header_and_body_removal_on_redirect(self, httpbin):
|
||||
purged_headers = ('Content-Length', 'Content-Type')
|
||||
@@ -408,6 +484,35 @@ class TestRequests:
|
||||
assert cookies['foo'] == 'bar'
|
||||
assert cookies['cookie'] == 'tasty'
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'jar', (
|
||||
requests.cookies.RequestsCookieJar(),
|
||||
cookielib.CookieJar()
|
||||
))
|
||||
def test_custom_cookie_policy_persistence(self, httpbin, jar):
|
||||
"""Verify a custom CookiePolicy is propagated on each session request."""
|
||||
|
||||
class TestCookiePolicy(cookielib.DefaultCookiePolicy):
|
||||
"""Policy to restrict all cookies from localhost (127.0.0.1)."""
|
||||
def __init__(self):
|
||||
cookielib.DefaultCookiePolicy.__init__(self, blocked_domains=['127.0.0.1'])
|
||||
|
||||
# Establish session with jar and set some cookies.
|
||||
s = requests.Session()
|
||||
s.cookies = jar
|
||||
s.get(httpbin('cookies/set?k1=v1&k2=v2'))
|
||||
assert len(s.cookies) == 2
|
||||
|
||||
# Set different policy.
|
||||
s.cookies.set_policy(TestCookiePolicy())
|
||||
assert isinstance(s.cookies._policy, TestCookiePolicy)
|
||||
|
||||
# No cookies were sent to our blocked domain and none were set.
|
||||
resp = s.get(httpbin('cookies/set?k3=v3'))
|
||||
assert 'Cookie' not in resp.request.headers
|
||||
assert len(s.cookies) == 2
|
||||
assert 'k3' not in s.cookies
|
||||
|
||||
def test_requests_in_history_are_not_overridden(self, httpbin):
|
||||
resp = requests.get(httpbin('redirect/3'))
|
||||
urls = [r.url for r in resp.history]
|
||||
@@ -453,7 +558,7 @@ class TestRequests:
|
||||
@pytest.mark.parametrize('key', ('User-agent', 'user-agent'))
|
||||
def test_user_agent_transfers(self, httpbin, key):
|
||||
|
||||
heads = {key: 'Mozilla/5.0 (github.com/kennethreitz/requests)'}
|
||||
heads = {key: 'Mozilla/5.0 (github.com/requests/requests)'}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
assert heads[key] in r.text
|
||||
@@ -485,8 +590,6 @@ class TestRequests:
|
||||
'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)
|
||||
@@ -497,6 +600,18 @@ class TestRequests:
|
||||
|
||||
assert p.headers['Authorization'] == _basic_auth_str(username, password)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'username, password', (
|
||||
('user', 1234),
|
||||
(None, 'test'),
|
||||
))
|
||||
def test_non_str_basicauth(self, username, password):
|
||||
"""Ensure we only allow string or bytes values for basicauth"""
|
||||
with pytest.raises(TypeError) as e:
|
||||
requests.auth._basic_auth_str(username, password)
|
||||
|
||||
assert 'must be of type str or bytes' in str(e)
|
||||
|
||||
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.
|
||||
@@ -633,7 +748,7 @@ class TestRequests:
|
||||
post1 = requests.post(url, data={'some': 'data'})
|
||||
assert post1.status_code == 200
|
||||
|
||||
with open('Pipfile') as f:
|
||||
with open('requirements.txt') as f:
|
||||
post2 = requests.post(url, files={'some': f})
|
||||
assert post2.status_code == 200
|
||||
|
||||
@@ -693,7 +808,7 @@ class TestRequests:
|
||||
post1 = requests.post(url, data={'some': 'data'})
|
||||
assert post1.status_code == 200
|
||||
|
||||
with open('Pipfile') as f:
|
||||
with open('requirements.txt') as f:
|
||||
post2 = requests.post(url, data={'some': 'data'}, files={'some': f})
|
||||
assert post2.status_code == 200
|
||||
|
||||
@@ -730,7 +845,7 @@ class TestRequests:
|
||||
|
||||
def test_conflicting_post_params(self, httpbin):
|
||||
url = httpbin('post')
|
||||
with open('Pipfile') as f:
|
||||
with open('requirements.txt') as f:
|
||||
pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})")
|
||||
pytest.raises(ValueError, "requests.post(url, data=u('[{\"some\": \"data\"}]'), files={'some': f})")
|
||||
|
||||
@@ -746,6 +861,10 @@ class TestRequests:
|
||||
r = requests.get(httpbin('status', '500'))
|
||||
assert not r.ok
|
||||
|
||||
def test_raise_for_status_returns_self(self, httpbin):
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
assert r.raise_for_status() is r
|
||||
|
||||
def test_decompress_gzip(self, httpbin):
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
@@ -824,8 +943,8 @@ class TestRequests:
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
assert r.status_code == 200
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'data', (
|
||||
@pytest.mark.parametrize('data',
|
||||
(
|
||||
{'stuff': u('ëlïxr')},
|
||||
{'stuff': u('ëlïxr').encode('utf-8')},
|
||||
{'stuff': 'elixr'},
|
||||
@@ -1159,9 +1278,24 @@ class TestRequests:
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
r.encoding = 'ascii'
|
||||
|
||||
chunks = r.iter_content(decode_unicode=True)
|
||||
assert all(isinstance(chunk, str) for chunk in chunks)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'encoding, exception', (
|
||||
(None, TypeError),
|
||||
('invalid encoding', LookupError),
|
||||
))
|
||||
def test_decode_unicode_encoding(self, encoding, exception):
|
||||
# raise an exception if encoding isn't set
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
r.encoding = encoding
|
||||
|
||||
with pytest.raises(exception):
|
||||
chunks = r.iter_content(decode_unicode=True)
|
||||
|
||||
def test_response_reason_unicode(self):
|
||||
# check for unicode HTTP status
|
||||
r = requests.Response()
|
||||
@@ -1214,6 +1348,109 @@ class TestRequests:
|
||||
assert r.request.url == pr.request.url
|
||||
assert r.request.headers == pr.request.headers
|
||||
|
||||
|
||||
def test_response_lines(self):
|
||||
"""
|
||||
iter_lines should be able to handle data dribbling in which delimiters
|
||||
might not be lined up ideally.
|
||||
"""
|
||||
mock_chunks = [
|
||||
b'This \r\n',
|
||||
b'',
|
||||
b'is\r',
|
||||
b'\n',
|
||||
b'a',
|
||||
b' ',
|
||||
b'',
|
||||
b'',
|
||||
b'test.',
|
||||
b'\r',
|
||||
b'\n',
|
||||
b'end.',
|
||||
]
|
||||
mock_data = b''.join(mock_chunks)
|
||||
unicode_mock_data = mock_data.decode('utf-8')
|
||||
|
||||
def mock_iter_content(*args, **kwargs):
|
||||
if kwargs.get("decode_unicode"):
|
||||
return (e.decode('utf-8') for e in mock_chunks)
|
||||
return (e for e in mock_chunks)
|
||||
|
||||
r = requests.Response()
|
||||
r._content_consumed = True
|
||||
r.iter_content = mock_iter_content
|
||||
|
||||
# decode_unicode=None, output raw bytes
|
||||
assert list(r.iter_lines(delimiter=b'\r\n')) == mock_data.split(b'\r\n')
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
assert list(r.iter_lines(decode_unicode=True, delimiter=u'\r\n')) == unicode_mock_data.split(u'\r\n')
|
||||
|
||||
# When delimiter is None, we should yield the same result as splitlines()
|
||||
# which supports the universal newline.
|
||||
# '\r', '\n', and '\r\n' are all treated as one line break.
|
||||
|
||||
# decode_unicode=None, output raw bytes
|
||||
result = list(r.iter_lines())
|
||||
assert result == mock_data.splitlines()
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
result = list(r.iter_lines(decode_unicode=True))
|
||||
assert result == unicode_mock_data.splitlines()
|
||||
|
||||
# If we change all the line breaks to `\r`, we should be okay.
|
||||
# decode_unicode=None, output raw bytes
|
||||
mock_chunks = [chunk.replace(b'\n', b'\r') for chunk in mock_chunks]
|
||||
mock_data = b''.join(mock_chunks)
|
||||
assert list(r.iter_lines()) == mock_data.splitlines()
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
unicode_mock_data = mock_data.decode('utf-8')
|
||||
assert list(r.iter_lines(decode_unicode=True)) == unicode_mock_data.splitlines()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'content, expected_no_delimiter, expected_delimiter', (
|
||||
([b''], [], []),
|
||||
([b'line\n'], [u'line'], [u'line\n']),
|
||||
([b'line', b'\n'], [u'line'], [u'line\n']),
|
||||
([b'line\r\n'], [u'line'], [u'line', u'']),
|
||||
# Empty chunk in the end of stream, same behavior as the previous
|
||||
([b'line\r\n', b''], [u'line'], [u'line', u'']),
|
||||
([b'line', b'\r\n'], [u'line'], [u'line', u'']),
|
||||
([b'a\r', b'\nb\r'], [u'a', u'b'], [u'a', u'b\r']),
|
||||
([b'a\r', b'\n', b'\nb'], [u'a', u'', u'b'], [u'a', u'\nb']),
|
||||
([b'a\n', b'\nb'], [u'a', u'', u'b'], [u'a\n\nb']),
|
||||
([b'a\r\n', b'\rb\n'], [u'a', u'', u'b'], [u'a', u'\rb\n']),
|
||||
([b'a\nb', b'c'], [u'a', u'bc'], [u'a\nbc']),
|
||||
([b'a\n', b'\rb', b'\r\nc'], [u'a', u'', u'b', u'c'], [u'a\n\rb', u'c']),
|
||||
([b'a\r\nb', b'', b'c'], [u'a', u'bc'], [u'a', u'bc']) # Empty chunk with pending data
|
||||
))
|
||||
def test_response_lines_parametrized(self, content, expected_no_delimiter, expected_delimiter):
|
||||
"""
|
||||
Test a lot of potential chunk splits to ensure consistency of
|
||||
iter_lines(delimiter=x), as well as the legacy behavior of
|
||||
iter_lines() without delimiter
|
||||
https://github.com/kennethreitz/requests/pull/2431#issuecomment-72333964
|
||||
"""
|
||||
mock_chunks = content
|
||||
def mock_iter_content(*args, **kwargs):
|
||||
if kwargs.get("decode_unicode"):
|
||||
return (e.decode('utf-8') for e in mock_chunks)
|
||||
return (e for e in mock_chunks)
|
||||
|
||||
r = requests.Response()
|
||||
r._content_consumed = True
|
||||
r.iter_content = mock_iter_content
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
assert list(r.iter_lines(decode_unicode=True)) == expected_no_delimiter
|
||||
assert list(r.iter_lines(decode_unicode=True, delimiter='\r\n')) == expected_delimiter
|
||||
|
||||
# decode_unicode=None, output raw bytes
|
||||
assert list(r.iter_lines()) == [line.encode('utf-8') for line in expected_no_delimiter]
|
||||
assert list(r.iter_lines(delimiter=b'\r\n')) == [line.encode('utf-8') for line in expected_delimiter]
|
||||
|
||||
def test_prepared_request_is_pickleable(self, httpbin):
|
||||
p = requests.Request('GET', httpbin('get')).prepare()
|
||||
|
||||
@@ -1486,9 +1723,10 @@ class TestRequests:
|
||||
|
||||
def test_manual_redirect_with_partial_body_read(self, httpbin):
|
||||
s = requests.Session()
|
||||
r1 = s.get(httpbin('redirect/2'), allow_redirects=False, stream=True)
|
||||
req = requests.Request('GET', httpbin('redirect/2')).prepare()
|
||||
r1 = s.send(req, allow_redirects=False, stream=True)
|
||||
assert r1.is_redirect
|
||||
rg = s.resolve_redirects(r1, r1.request, stream=True)
|
||||
rg = s.resolve_redirects(r1, req, stream=True)
|
||||
|
||||
# read only the first eight bytes of the response body,
|
||||
# then follow the redirect
|
||||
@@ -1658,11 +1896,12 @@ class TestRequests:
|
||||
prep = r.prepare()
|
||||
assert 'stuff=elixr' == prep.body
|
||||
|
||||
def test_response_iter_lines(self, httpbin):
|
||||
@pytest.mark.parametrize('decode_unicode', (True, False))
|
||||
def test_response_iter_lines(self, httpbin, decode_unicode):
|
||||
r = requests.get(httpbin('stream/4'), stream=True)
|
||||
assert r.status_code == 200
|
||||
|
||||
it = r.iter_lines()
|
||||
r.encoding = 'utf-8'
|
||||
it = r.iter_lines(decode_unicode=decode_unicode)
|
||||
next(it)
|
||||
assert len(list(it)) == 3
|
||||
|
||||
@@ -1684,6 +1923,59 @@ class TestRequests:
|
||||
next(r.iter_lines())
|
||||
assert len(list(r.iter_lines())) == 3
|
||||
|
||||
def test_environment_comes_after_session(self, httpbin):
|
||||
"""The Session arguments should come before environment arguments."""
|
||||
# We get proxies from the environment and verify from the argument.
|
||||
s = requests.Session()
|
||||
a = SendRecordingAdapter()
|
||||
s.mount('http://', a)
|
||||
|
||||
# Both of these arguments are safe fallbacks that we can easily
|
||||
# detect, but which will allow the request to succeed.
|
||||
s.verify = False
|
||||
s.proxies = {'http': None}
|
||||
|
||||
old_proxy = os.environ.get('HTTP_PROXY')
|
||||
old_bundle = os.environ.get('REQUESTS_CA_BUNDLE')
|
||||
|
||||
try:
|
||||
os.environ['HTTP_PROXY'] = '10.10.10.10:3128'
|
||||
os.environ['REQUESTS_CA_BUNDLE'] = '/path/to/nowhere'
|
||||
|
||||
s.get(httpbin('get'), timeout=5)
|
||||
finally:
|
||||
if old_proxy is not None:
|
||||
os.environ['HTTP_PROXY'] = old_proxy
|
||||
else:
|
||||
del os.environ['HTTP_PROXY']
|
||||
|
||||
if old_bundle is not None:
|
||||
os.environ['REQUESTS_CA_BUNDLE'] = old_bundle
|
||||
else:
|
||||
del os.environ['REQUESTS_CA_BUNDLE']
|
||||
|
||||
call = a.send_calls[0]
|
||||
assert call[1]['verify'] == False
|
||||
|
||||
proxies = call[1]['proxies']
|
||||
with pytest.raises(KeyError):
|
||||
proxies['http']
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def test_merge_environment_settings_verify(self, monkeypatch):
|
||||
"""Assert CA environment settings are merged as expected when missing"""
|
||||
session = requests.Session()
|
||||
monkeypatch.delenv('CURL_CA_BUNDLE', raising=False)
|
||||
monkeypatch.delenv('REQUESTS_CA_BUNDLE', raising=False)
|
||||
|
||||
assert session.trust_env is True
|
||||
assert session.verify is True
|
||||
assert 'REQUESTS_CA_BUNDLE' not in os.environ
|
||||
assert 'CURL_CA_BUNDLE' not in os.environ
|
||||
merged_settings = session.merge_environment_settings(
|
||||
'http://example.com', {}, False, True, None)
|
||||
assert merged_settings['verify'] is True
|
||||
|
||||
def test_session_close_proxy_clear(self, mocker):
|
||||
proxies = {
|
||||
'one': mocker.Mock(),
|
||||
@@ -1726,6 +2018,33 @@ class TestRequests:
|
||||
resp.close()
|
||||
assert resp.raw.closed
|
||||
|
||||
def test_updating_ca_cert(self, httpbin_secure):
|
||||
"""Assert that requests use the latest configured CA certificates."""
|
||||
session = requests.session()
|
||||
session.verify = pytest_httpbin.certs.where()
|
||||
session.get(httpbin_secure('/'))
|
||||
session.verify = True
|
||||
with pytest.raises(requests.exceptions.SSLError) as e:
|
||||
session.get(httpbin_secure('/'))
|
||||
assert 'certificate verify failed' in str(e)
|
||||
|
||||
def test_updating_client_cert(self, httpbin_secure):
|
||||
"""Assert that requests use the latest configured client certificates."""
|
||||
ca_file = pytest_httpbin.certs.where()
|
||||
cert_dir = os.path.dirname(ca_file)
|
||||
# All we need is a valid certificate and key to make a request. httpbin_secure
|
||||
# won't check the signature or subject name, so it's okay that these happen to
|
||||
# be the server's certificate and key.
|
||||
cert = os.path.join(cert_dir, 'cert.pem')
|
||||
key = os.path.join(cert_dir, 'key.pem')
|
||||
session = requests.session()
|
||||
session.verify = ca_file
|
||||
resp = session.get(httpbin_secure('/'))
|
||||
resp_with_cert = session.get(httpbin_secure('/'), cert=(cert, key))
|
||||
assert resp_with_cert.raw._pool.cert_file == cert
|
||||
assert resp_with_cert.raw._pool.key_file == key
|
||||
assert resp.raw._pool is not resp_with_cert.raw._pool
|
||||
|
||||
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.
|
||||
@@ -1761,6 +2080,61 @@ class TestRequests:
|
||||
assert 'Transfer-Encoding' in prepared_request.headers
|
||||
assert 'Content-Length' not in prepared_request.headers
|
||||
|
||||
def test_chunked_upload_with_manually_set_content_length_header_raises_error(self, httpbin):
|
||||
"""Ensure that if a user manually sets a content length header, when
|
||||
the data is chunked, that an InvalidHeader error is raised.
|
||||
"""
|
||||
data = (i for i in [b'a', b'b', b'c'])
|
||||
url = httpbin('post')
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.post(url, data=data, headers={'Content-Length': 'foo'})
|
||||
|
||||
def test_content_length_with_manually_set_transfer_encoding_raises_error(self, httpbin):
|
||||
"""Ensure that if a user manually sets a Transfer-Encoding header when
|
||||
data is not chunked that an InvalidHeader error is raised.
|
||||
"""
|
||||
data = 'test data'
|
||||
url = httpbin('post')
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.post(url, data=data, headers={'Transfer-Encoding': 'chunked'})
|
||||
|
||||
def test_null_body_does_not_raise_error(self, httpbin):
|
||||
url = httpbin('post')
|
||||
try:
|
||||
requests.post(url, data=None)
|
||||
except InvalidHeader:
|
||||
pytest.fail('InvalidHeader error raised unexpectedly.')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'body, expected', (
|
||||
(None, ('Content-Length', '0')),
|
||||
('test_data', ('Content-Length', '9')),
|
||||
(io.BytesIO(b'test_data'), ('Content-Length', '9')),
|
||||
(StringIO.StringIO(''), ('Transfer-Encoding', 'chunked'))
|
||||
))
|
||||
def test_prepare_content_length(self, httpbin, body, expected):
|
||||
"""Test prepare_content_length creates expected header."""
|
||||
prep = requests.PreparedRequest()
|
||||
prep.headers = {}
|
||||
prep.method = 'POST'
|
||||
|
||||
# Ensure Content-Length is set appropriately.
|
||||
key, value = expected
|
||||
prep.prepare_content_length(body)
|
||||
assert prep.headers[key] == value
|
||||
|
||||
def test_prepare_content_length_with_bad_body(self, httpbin):
|
||||
"""Test prepare_content_length raises exception with unsendable body."""
|
||||
# Initialize minimum required PreparedRequest.
|
||||
prep = requests.PreparedRequest()
|
||||
prep.headers = {}
|
||||
prep.method = 'POST'
|
||||
|
||||
with pytest.raises(InvalidBodyError) as e:
|
||||
# Send object that isn't iterable and has no accessible content.
|
||||
prep.prepare_content_length(object())
|
||||
assert "Non-null body must have length or be streamable." in str(e)
|
||||
|
||||
def test_custom_redirect_mixin(self, httpbin):
|
||||
"""Tests a custom mixin to overwrite ``get_redirect_target``.
|
||||
|
||||
@@ -1946,6 +2320,18 @@ class TestCaseInsensitiveDict:
|
||||
cid['changed'] = True
|
||||
assert cid != cid_copy
|
||||
|
||||
def test_url_surrounding_whitespace(self, httpbin):
|
||||
"""Test case with URLs surrounded by whitespace characters."""
|
||||
get_url = httpbin('get')
|
||||
# All surrounding whitespaces are supposed to be ignored:
|
||||
assert requests.get(get_url + ' ').status_code == 200
|
||||
assert requests.get(' ' + get_url).status_code == 200
|
||||
assert requests.get(get_url + ' \t ').status_code == 200
|
||||
assert requests.get(' \t' + get_url).status_code == 200
|
||||
assert requests.get(get_url + '\n').status_code == 200
|
||||
# The whitespaces can't be in the middle of the URL though:
|
||||
assert requests.get(get_url + ' abc').status_code == 404
|
||||
|
||||
|
||||
class TestMorselToCookieExpires:
|
||||
"""Tests for morsel_to_cookie when morsel contains expires."""
|
||||
@@ -2073,7 +2459,7 @@ class TestTimeout:
|
||||
pass
|
||||
|
||||
def test_encoded_methods(self, httpbin):
|
||||
"""See: https://github.com/kennethreitz/requests/issues/2316"""
|
||||
"""See: https://github.com/requests/requests/issues/2316"""
|
||||
r = requests.request(b'GET', httpbin('get'))
|
||||
assert r.ok
|
||||
|
||||
@@ -2088,6 +2474,7 @@ class RedirectSession(SessionRedirectMixin):
|
||||
self.max_redirects = 30
|
||||
self.cookies = {}
|
||||
self.trust_env = False
|
||||
self.location = '/'
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
self.calls.append(SendCall(args, kwargs))
|
||||
@@ -2102,7 +2489,7 @@ class RedirectSession(SessionRedirectMixin):
|
||||
except IndexError:
|
||||
r.status_code = 200
|
||||
|
||||
r.headers = CaseInsensitiveDict({'Location': '/'})
|
||||
r.headers = CaseInsensitiveDict({'Location': self.location})
|
||||
r.raw = self._build_raw()
|
||||
r.request = request
|
||||
return r
|
||||
@@ -2217,6 +2604,16 @@ def test_prepared_copy(kwargs):
|
||||
assert getattr(p, attr) == getattr(copy, attr)
|
||||
|
||||
|
||||
def test_prepare_requires_a_request_method():
|
||||
req = requests.Request()
|
||||
with pytest.raises(ValueError):
|
||||
req.prepare()
|
||||
|
||||
prepped = PreparedRequest()
|
||||
with pytest.raises(ValueError):
|
||||
prepped.prepare()
|
||||
|
||||
|
||||
def test_urllib3_retries(httpbin):
|
||||
from urllib3.util import Retry
|
||||
s = requests.Session()
|
||||
|
||||
@@ -92,8 +92,10 @@ class TestTestServer:
|
||||
with pytest.raises(requests.exceptions.ConnectionError):
|
||||
r = requests.get(server_url)
|
||||
|
||||
@pytest.mark.skip(reason="this fails non-deterministically under pytest-xdist")
|
||||
def test_request_recovery(self):
|
||||
"""can check the requests content"""
|
||||
# TODO: figure out why this sometimes fails when using pytest-xdist.
|
||||
server = Server.basic_response_server(requests_to_handle=2)
|
||||
first_request = b'put your hands up in the air'
|
||||
second_request = b'put your hand down in the floor'
|
||||
|
||||
@@ -367,7 +367,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/requests/requests/issues/2356"""
|
||||
assert requote_uri(uri) == expected
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user