Compare commits
331 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd4762d5a3 | ||
|
|
2bbe38fd8b | ||
|
|
8f3f022c54 | ||
|
|
6497cbee7e | ||
|
|
3951232f8c | ||
|
|
86ab1ce916 | ||
|
|
d956dc1d70 | ||
|
|
2aa9d23a91 | ||
|
|
c4a2e88436 | ||
|
|
e407afc494 | ||
|
|
f132bfbc6a | ||
|
|
eb37c39c66 | ||
|
|
b115fb7525 | ||
|
|
cac7a2843d | ||
|
|
b6bc1d06aa | ||
|
|
d61f6b65d3 | ||
|
|
f64b27bfed | ||
|
|
e16fc666af | ||
|
|
c761385c37 | ||
|
|
5808cbfa7f | ||
|
|
f46005d771 | ||
|
|
68f10d0d1a | ||
|
|
e8ccdd6343 | ||
|
|
aa7314e97a | ||
|
|
0c722d9476 | ||
|
|
5574076b05 | ||
|
|
631076d600 | ||
|
|
8a6bcfbe1a | ||
|
|
64d85b7ebf | ||
|
|
6a27949e5f | ||
|
|
b2e99359fc | ||
|
|
fa33697514 | ||
|
|
8348ba8b6b | ||
|
|
8a1baaf47b | ||
|
|
4bd079f871 | ||
|
|
808b520724 | ||
|
|
0b96f5282a | ||
|
|
9cea8ce09d | ||
|
|
d763e8e99e | ||
|
|
38b29446b0 | ||
|
|
2bf628be3c | ||
|
|
9af75f01a9 | ||
|
|
12671d4819 | ||
|
|
272ea7db4e | ||
|
|
f7a36c5866 | ||
|
|
530db6c4b9 | ||
|
|
c7d0f3ef01 | ||
|
|
db2aecbac8 | ||
|
|
fc4637032a | ||
|
|
82c3c42193 | ||
|
|
b72d5379ea | ||
|
|
edf844a2db | ||
|
|
440dffe6e9 | ||
|
|
3ac6ee9ce4 | ||
|
|
ccffc02547 | ||
|
|
77e2ca1a85 | ||
|
|
20641583e3 | ||
|
|
582ce4d247 | ||
|
|
4d902fdbaa | ||
|
|
9d1d3d165b | ||
|
|
dba39c5285 | ||
|
|
4239856a4a | ||
|
|
f531629a9b | ||
|
|
220c368e2c | ||
|
|
8ba99d246b | ||
|
|
6be73f90b1 | ||
|
|
315a5661e3 | ||
|
|
9c6bd54b44 | ||
|
|
4c6d9e1954 | ||
|
|
a58b1e66cb | ||
|
|
8f00695d40 | ||
|
|
5d6eeb5a7f | ||
|
|
78bf3ee6e6 | ||
|
|
9146b62d31 | ||
|
|
946f136715 | ||
|
|
19a294a2a1 | ||
|
|
006aa3de4f | ||
|
|
37f1d23c39 | ||
|
|
e86df6e0eb | ||
|
|
ec5804c706 | ||
|
|
4316af7ae8 | ||
|
|
8c4f78448a | ||
|
|
15054aa390 | ||
|
|
9713289e74 | ||
|
|
e6afc37be5 | ||
|
|
5eb0811ef8 | ||
|
|
40c8bcf70c | ||
|
|
0eb9a4c161 | ||
|
|
cda429aa5d | ||
|
|
28ae74dbad | ||
|
|
a9d05f06cd | ||
|
|
d552132464 | ||
|
|
c3ecb825c4 | ||
|
|
70a4ae744c | ||
|
|
4c300c2067 | ||
|
|
4f0b496d2b | ||
|
|
a36247e148 | ||
|
|
6dc3003f16 | ||
|
|
5aef6e7583 | ||
|
|
95b127714b | ||
|
|
5980d5df3e | ||
|
|
fdb9b753b4 | ||
|
|
4bda7b66e7 | ||
|
|
068d8863d1 | ||
|
|
c49f3643ae | ||
|
|
328cff252d | ||
|
|
c57bd07a34 | ||
|
|
0d82afa0c3 | ||
|
|
18e37c9368 | ||
|
|
389da34fc9 | ||
|
|
8ef20d5c36 | ||
|
|
e63d481a7a | ||
|
|
f3eeb38542 | ||
|
|
afaaae185c | ||
|
|
8af65a1eca | ||
|
|
c04b044b30 | ||
|
|
530198e0c0 | ||
|
|
8c57515696 | ||
|
|
175452a6b6 | ||
|
|
e35c829b59 | ||
|
|
1ad51b0a41 | ||
|
|
8be8105c42 | ||
|
|
49ec082a77 | ||
|
|
0488c640e2 | ||
|
|
8371b56d0f | ||
|
|
830c18e4f9 | ||
|
|
2c438cacd1 | ||
|
|
9c8ae76928 | ||
|
|
d5a05469f1 | ||
|
|
e22e3a5059 | ||
|
|
054ad4aa67 | ||
|
|
b9638ec7be | ||
|
|
940c1246e0 | ||
|
|
e0e5b2e3aa | ||
|
|
96b9a3f0fc | ||
|
|
657e44ad9a | ||
|
|
ac9a3e5320 | ||
|
|
6b21682712 | ||
|
|
785c733415 | ||
|
|
369be8afd0 | ||
|
|
572d2f4534 | ||
|
|
7e20b9193a | ||
|
|
ad1aff52a0 | ||
|
|
3a6dec9827 | ||
|
|
3cceee3459 | ||
|
|
dd57439fa5 | ||
|
|
f09e462165 | ||
|
|
3a2ea9deba | ||
|
|
5dcbdb50fc | ||
|
|
8ad09b68ea | ||
|
|
88dc33522d | ||
|
|
e5666b93cf | ||
|
|
554a210f88 | ||
|
|
8e411d0f99 | ||
|
|
7d190ef010 | ||
|
|
965ada6f66 | ||
|
|
af9dd61fcb | ||
|
|
a3d7cf3f27 | ||
|
|
3c5ecfc9f5 | ||
|
|
d528106876 | ||
|
|
cbc7c2d1c9 | ||
|
|
e36083b89c | ||
|
|
308343508b | ||
|
|
ba21d14976 | ||
|
|
cee8fae65e | ||
|
|
1c2022cf86 | ||
|
|
8480cf937a | ||
|
|
f6952a9b8d | ||
|
|
d758f0cf97 | ||
|
|
c31a450c55 | ||
|
|
0a2dc74ecb | ||
|
|
f78a57936b | ||
|
|
218978601f | ||
|
|
cb9620780f | ||
|
|
963c78853f | ||
|
|
b670b61d54 | ||
|
|
9ec2005d9f | ||
|
|
f4ddf00c36 | ||
|
|
248f122970 | ||
|
|
be8ce365ab | ||
|
|
3393dc8221 | ||
|
|
c7266a5ff4 | ||
|
|
9766870807 | ||
|
|
e1b6fb7444 | ||
|
|
af8ea2e809 | ||
|
|
ed068ea0af | ||
|
|
fb9abdda5e | ||
|
|
66f5aebd35 | ||
|
|
f58d473d4e | ||
|
|
14a0bf2912 | ||
|
|
bacd043256 | ||
|
|
f3cdbcb86d | ||
|
|
190a68550a | ||
|
|
548823be83 | ||
|
|
a6f7f5dc62 | ||
|
|
f308ea1a31 | ||
|
|
c3f4a028bf | ||
|
|
8e97db2dc5 | ||
|
|
009b80c95a | ||
|
|
a889b62c50 | ||
|
|
084625cb3c | ||
|
|
458df8f4f4 | ||
|
|
73456b0048 | ||
|
|
d491e9f9b2 | ||
|
|
052595ffbf | ||
|
|
cc2ac23c0d | ||
|
|
5a8bc19384 | ||
|
|
0380ac5893 | ||
|
|
9881be25f3 | ||
|
|
9174925916 | ||
|
|
02031e3e14 | ||
|
|
84dc6b66da | ||
|
|
35fc2b387f | ||
|
|
be2f92b9e3 | ||
|
|
64b66b6409 | ||
|
|
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 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -21,3 +21,9 @@ t.py
|
||||
|
||||
t2.py
|
||||
dist
|
||||
|
||||
/.mypy_cache/
|
||||
|
||||
/.pytest_cache/
|
||||
|
||||
/.tox/
|
||||
|
||||
16
.travis.yml
16
.travis.yml
@@ -1,29 +1,27 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
# - "3.4"
|
||||
# - "3.5"
|
||||
- "3.6"
|
||||
# - "3.7-dev"
|
||||
# - "pypy" -- appears to hang
|
||||
# - "pypy3"
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: 3.7-dev
|
||||
# command to install dependencies
|
||||
install: "make"
|
||||
# command to run tests
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi
|
||||
- make test-readme
|
||||
- make ci
|
||||
cache: pip
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script:
|
||||
- |
|
||||
if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi
|
||||
- make test-readme
|
||||
- make ci
|
||||
- stage: coverage
|
||||
python: 3.6
|
||||
|
||||
81
3.0-HISTORY.rst
Normal file
81
3.0-HISTORY.rst
Normal file
@@ -0,0 +1,81 @@
|
||||
3.0.0 (2017-xx-xx)
|
||||
++++++++++++++++++
|
||||
|
||||
- Support for Python 2.6 has been dropped.
|
||||
|
||||
- The ``OrderedDict`` import no longer exists in compat.py because it is part
|
||||
of ``collections`` in Python 2.7 and newer.
|
||||
|
||||
- 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.
|
||||
|
||||
- Exception raised during read timeout for ``Response.iter_content`` and
|
||||
``Response.iter_lines`` changed from ``ConnectionError`` to more
|
||||
specific ``ReadTimeout``.
|
||||
|
||||
- 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
|
||||
|
||||
- Use ``HTTPHeaderDict`` for response headers, allowing easier access to
|
||||
individual values when multiple response headers are sent using the same
|
||||
header name.
|
||||
|
||||
.. _#2002: https://github.com/kennethreitz/requests/issues/2002
|
||||
.. _#2631: https://github.com/kennethreitz/requests/issues/2631
|
||||
18
AUTHORS.rst
18
AUTHORS.rst
@@ -10,13 +10,6 @@ Keepers of the Four Crystals
|
||||
- Nate Prewitt <nate.prewitt@gmail.com> `@nateprewitt <https://github.com/nateprewitt>`_
|
||||
|
||||
|
||||
|
||||
Urllib3
|
||||
```````
|
||||
|
||||
- Andrey Petrov <andrey.petrov@shazow.net>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
@@ -128,7 +121,7 @@ Patches and Suggestions
|
||||
- Bryce Boe <bbzbryce@gmail.com> (`@bboe <https://github.com/bboe>`_)
|
||||
- Colin Dunklau <colin.dunklau@gmail.com> (`@cdunklau <https://github.com/cdunklau>`_)
|
||||
- Bob Carroll <bob.carroll@alum.rit.edu> (`@rcarz <https://github.com/rcarz>`_)
|
||||
- Hugo Osvaldo Barrera <hugo@osvaldobarrera.com.ar> (`@hobarrera <https://github.com/hobarrera>`_)
|
||||
- Hugo Osvaldo Barrera <hugo@barrera.io> (`@hobarrera <https://github.com/hobarrera>`_)
|
||||
- Łukasz Langa <lukasz@langa.pl>
|
||||
- Dave Shawley <daveshawley@gmail.com>
|
||||
- James Clarke (`@jam <https://github.com/jam>`_)
|
||||
@@ -160,16 +153,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>`_)
|
||||
@@ -183,5 +179,11 @@ Patches and Suggestions
|
||||
- Shmuel Amar (`@shmuelamar <https://github.com/shmuelamar>`_)
|
||||
- Gary Wu (`@garywu <https://github.com/garywu>`_)
|
||||
- Ryan Pineo (`@ryanpineo <https://github.com/ryanpineo>`_)
|
||||
<<<<<<< HEAD
|
||||
- Ed Morley (`@edmorley <https://github.com/edmorley>`_)
|
||||
- Matt Liu <liumatt@gmail.com> (`@mlcrazy <https://github.com/mlcrazy>`_)
|
||||
- Taylor Hoff <primdevs@protonmail.com> (`@PrimordialHelios <https://github.com/PrimordialHelios>`_)
|
||||
- Hugo van Kemenade (`@hugovk <https://github.com/hugovk>`_)
|
||||
=======
|
||||
- Allan Crooks (`@the-allanc <https://github.com/the-allanc>`_)
|
||||
>>>>>>> 4f0b496d2b3c91d72a951bebdd6be9cec33c17d6
|
||||
|
||||
35
HISTORY.rst
35
HISTORY.rst
@@ -3,6 +3,30 @@
|
||||
Release History
|
||||
---------------
|
||||
|
||||
dev
|
||||
+++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Warn user about possible slowdown when using cryptography version < 1.3.4
|
||||
- Use rfc3986 for URL parsing.
|
||||
- Use __slots__ for often–called classes.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Parsing empty ``Link`` headers with ``parse_header_links()`` no longer return one bogus entry
|
||||
|
||||
2.18.4 (2017-08-15)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Error messages for invalid headers now include the header name for easier debugging
|
||||
|
||||
**Dependencies**
|
||||
|
||||
- We now support idna v2.6.
|
||||
|
||||
2.18.3 (2017-08-02)
|
||||
+++++++++++++++++++
|
||||
|
||||
@@ -13,7 +37,7 @@ Release History
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed issue where Requests would raise ``ConnectionError`` instead of
|
||||
``SSLError`` when encoutering SSL problems when using urllib3 v1.22.
|
||||
``SSLError`` when encountering SSL problems when using urllib3 v1.22.
|
||||
|
||||
2.18.2 (2017-07-25)
|
||||
+++++++++++++++++++
|
||||
@@ -32,8 +56,8 @@ Release History
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fix an error in the packaging whereby the *.whl contained incorrect data that
|
||||
regressed the fix in v2.17.3.
|
||||
- Fix an error in the packaging whereby the ``*.whl`` contained incorrect data
|
||||
that regressed the fix in v2.17.3.
|
||||
|
||||
2.18.0 (2017-06-14)
|
||||
+++++++++++++++++++
|
||||
@@ -212,6 +236,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)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
15
Makefile
15
Makefile
@@ -1,20 +1,23 @@
|
||||
.PHONY: docs
|
||||
init:
|
||||
pip install -r requirements.txt
|
||||
pip install pipenv --upgrade
|
||||
pipenv install --dev --skip-lock
|
||||
test:
|
||||
# This runs all of the tests, on both Python 2 and Python 3.
|
||||
detox
|
||||
python setup.py test
|
||||
mypy:
|
||||
python setup.py mypy
|
||||
ci:
|
||||
py.test -n 8 --boxed --junitxml=report.xml
|
||||
pipenv run py.test -n 8 --boxed --junitxml=report.xml
|
||||
|
||||
test-readme:
|
||||
@python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
|
||||
@pipenv run python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
|
||||
|
||||
flake8:
|
||||
flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
|
||||
pipenv run flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
|
||||
|
||||
coverage:
|
||||
py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
|
||||
pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
|
||||
|
||||
publish:
|
||||
pip install 'twine>=1.5.0'
|
||||
|
||||
32
Pipfile
Normal file
32
Pipfile
Normal file
@@ -0,0 +1,32 @@
|
||||
[[source]]
|
||||
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
|
||||
[dev-packages]
|
||||
|
||||
pytest = ">=2.8.0"
|
||||
codecov = "*"
|
||||
pytest-httpbin = "*"
|
||||
pytest-mock = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-xdist = "*"
|
||||
alabaster = "*"
|
||||
readme-renderer = "*"
|
||||
sphinx = "<=1.5.5"
|
||||
pysocks = "*"
|
||||
docutils = "*"
|
||||
"flake8" = "*"
|
||||
tox = "*"
|
||||
detox = "*"
|
||||
httpbin = "==0.5.0"
|
||||
pytest-mypy = {git = "https://github.com/petr-muller/pytest-mypy", editable = true, ref = "flush-errors"}
|
||||
white = {version="*"}
|
||||
"e1839a8" = {path = ".", editable = true, extras=["socks"]}
|
||||
mypy = "*"
|
||||
"rfc3986" = "*"
|
||||
|
||||
|
||||
[packages]
|
||||
|
||||
12
README.rst
12
README.rst
@@ -20,14 +20,10 @@ Requests: HTTP for Humans
|
||||
.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
|
||||
|
||||
Requests is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||
**Warning:** Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
|
||||
including: security vulnerabilities, verbose code, reinventing the wheel,
|
||||
constantly reading documentation, depression, headaches, or even death.
|
||||
.. image:: https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg
|
||||
|
||||
Behold, the power of Requests:
|
||||
|
||||
@@ -81,16 +77,16 @@ Requests is ready for today's web.
|
||||
- ``.netrc`` Support
|
||||
- Chunked Requests
|
||||
|
||||
Requests officially supports Python 2.6–2.7 & 3.3–3.7, and runs great on PyPy.
|
||||
Requests officially supports Python 2.7 & 3.4–3.7, and runs great on PyPy.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To install Requests, simply:
|
||||
To install Requests, simply use `pipenv <http://pipenv.org/>`_ (or pip, of course):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install requests
|
||||
$ pipenv install requests
|
||||
✨🍰✨
|
||||
|
||||
Satisfaction guaranteed.
|
||||
|
||||
22
appveyor.yml
22
appveyor.yml
@@ -5,26 +5,6 @@ build: off
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
PYTHON_VERSION: "2.7.x"
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py27"
|
||||
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
PYTHON_VERSION: "3.3.x"
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py33"
|
||||
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
PYTHON_VERSION: "3.4.x"
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py34"
|
||||
|
||||
- PYTHON: "C:\\Python35-x64"
|
||||
PYTHON_VERSION: "3.5.x"
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py35"
|
||||
|
||||
- PYTHON: "C:\\Python36-x64"
|
||||
PYTHON_VERSION: "3.6.x"
|
||||
PYTHON_ARCH: "64"
|
||||
@@ -53,4 +33,4 @@ test_script:
|
||||
- "C:\\MinGW\\bin\\mingw32-make coverage"
|
||||
|
||||
on_success:
|
||||
- "codecov -f coverage.xml"
|
||||
- "pipenv run codecov -f coverage.xml"
|
||||
|
||||
32
docs/_templates/hacks.html
vendored
32
docs/_templates/hacks.html
vendored
@@ -1,4 +1,34 @@
|
||||
<!-- Alabaster (krTheme++) Hacks -->
|
||||
<aside id="python27">
|
||||
Python 3, the new best practice, is here to stay.
|
||||
Python 2 will retire in only <time></time> months!
|
||||
</aside>
|
||||
|
||||
<!-- Python 2 Death Clock™ -->
|
||||
<style type="text/css">
|
||||
body { margin-top: 4em; }
|
||||
#python27 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0; right: 0;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background-color: black;
|
||||
font-size: larger;
|
||||
line-height: 3;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
|
||||
<script>
|
||||
var death = new Date('2020-04-12');
|
||||
|
||||
var diff = moment.duration(death - moment());
|
||||
|
||||
document.querySelector('#python27 time').innerText = (diff.years() * 12) + diff.months();
|
||||
</script>
|
||||
|
||||
|
||||
<!-- CSS Adjustments (I'm very picky.) -->
|
||||
<style type="text/css">
|
||||
@@ -26,6 +56,7 @@
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<!-- Analytics tracking for Kenneth. -->
|
||||
<script type="text/javascript">
|
||||
var _gauges = _gauges || [];
|
||||
@@ -42,6 +73,7 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<!-- There are no more hacks. -->
|
||||
<!-- இڿڰۣ-ڰۣ— -->
|
||||
<!-- Love, Kenneth Reitz -->
|
||||
|
||||
3
docs/_templates/sidebarintro.html
vendored
3
docs/_templates/sidebarintro.html
vendored
@@ -16,6 +16,7 @@
|
||||
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg" id="_carbonads_js"></script>
|
||||
|
||||
<h3><a href="http://www.unixstickers.com/stickers/coding_stickers/requests-shaped-sticker">Stickers!</a></h3>
|
||||
<h3>Stay Informed</h3>
|
||||
<p>Receive updates on new releases and upcoming projects.</p>
|
||||
|
||||
@@ -25,8 +26,6 @@
|
||||
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow @kennethreitz</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
|
||||
<p><a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>
|
||||
<p><a href="http://tinyletter.com/kennethreitz">Join Mailing List</a>.</p>
|
||||
|
||||
|
||||
<h3>Other Projects</h3>
|
||||
|
||||
<p>More <a href="http://kennethreitz.org/">Kenneth Reitz</a> projects:</p>
|
||||
|
||||
2
docs/_templates/sidebarlogo.html
vendored
2
docs/_templates/sidebarlogo.html
vendored
@@ -13,7 +13,7 @@
|
||||
human beings. You are currently looking at the documentation of the
|
||||
development release.
|
||||
</p>
|
||||
|
||||
<h3><a href="http://www.unixstickers.com/stickers/coding_stickers/requests-shaped-sticker">Stickers!</a></h3>
|
||||
<h3>Stay Informed</h3>
|
||||
<p>Receive updates on new releases and upcoming projects.</p>
|
||||
|
||||
|
||||
134
docs/_themes/flask_theme_support.py
vendored
134
docs/_themes/flask_theme_support.py
vendored
@@ -1,86 +1,76 @@
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
Punctuation: "bold #000000",
|
||||
# class: 'p'
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
Number: "#990000", # class: 'm'
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4290/35294660055_42c02b2316_k_d.jpg
|
||||
|
||||
This part of the documentation answers common questions about Requests.
|
||||
|
||||
Encoded Data?
|
||||
@@ -54,9 +56,7 @@ Python 3 Support?
|
||||
Yes! Here's a list of Python platforms that are officially
|
||||
supported:
|
||||
|
||||
* Python 2.6
|
||||
* Python 2.7
|
||||
* Python 3.3
|
||||
* Python 3.4
|
||||
* Python 3.5
|
||||
* Python 3.6
|
||||
@@ -69,7 +69,7 @@ These errors occur when :ref:`SSL certificate verification <verification>`
|
||||
fails to match the certificate the server responds with to the hostname
|
||||
Requests thinks it's contacting. If you're certain the server's SSL setup is
|
||||
correct (for example, because you can visit the site with your browser) and
|
||||
you're using Python 2.6 or 2.7, a possible explanation is that you need
|
||||
you're using Python 2.7, a possible explanation is that you need
|
||||
Server-Name-Indication.
|
||||
|
||||
`Server-Name-Indication`_, or SNI, is an official extension to SSL where the
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Integrations
|
||||
============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4239/34450900674_15863ddea0_k_d.jpg
|
||||
|
||||
Python for iOS
|
||||
--------------
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Recommended Packages and Extensions
|
||||
===================================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4218/35224319272_cfc0e621fb_k_d.jpg
|
||||
|
||||
Requests has a great variety of powerful and useful third-party extensions.
|
||||
This page provides an overview of some of the best of them.
|
||||
|
||||
@@ -34,6 +36,14 @@ requested by users within the community.
|
||||
|
||||
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/en/latest/index.html
|
||||
|
||||
|
||||
Requests-Threads
|
||||
----------------
|
||||
|
||||
`Requests-Threads` is a Requests session that returns the amazing Twisted's awaitable Deferreds instead of Response objects. This allows the use of ``async``/``await`` keyword usage on Python 3, or Twisted's style of programming, if desired.
|
||||
|
||||
.. _Requests-Threads: https://github.com/requests/requests-threads
|
||||
|
||||
Requests-OAuthlib
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Release Process and Rules
|
||||
=========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4215/34450901614_b74ae720db_k_d.jpg
|
||||
|
||||
.. versionadded:: v2.6.2
|
||||
|
||||
Starting with the version to be released after ``v2.6.2``, the following rules
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Support
|
||||
=======
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4198/34080352913_5c13ffb336_k_d.jpg
|
||||
|
||||
If you have questions or issues about Requests, there are several options:
|
||||
|
||||
StackOverflow
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
Community Updates
|
||||
=================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4244/34080354873_516c283ad0_k_d.jpg
|
||||
|
||||
If you'd like to stay up to date on the community and development of Requests,
|
||||
there are several options:
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Vulnerability Disclosure
|
||||
========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4211/34709353644_b041e9e1c2_k_d.jpg
|
||||
|
||||
If you think you have found a potential security vulnerability in requests,
|
||||
please email `sigmavirus24 <mailto:graffatcolmingov@gmail.com>`_ and
|
||||
`Lukasa <mailto:cory@lukasa.co.uk>`_ directly. **Do not file a public issue.**
|
||||
|
||||
@@ -129,7 +129,8 @@ html_theme_options = {
|
||||
'github_user': 'requests',
|
||||
'github_repo': 'requests',
|
||||
'github_banner': True,
|
||||
'show_related': False
|
||||
'show_related': False,
|
||||
'note_bg': '#FFF59C'
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Authors
|
||||
=======
|
||||
|
||||
.. image:: https://static1.squarespace.com/static/533ad9bde4b098d084a846b1/t/534f6e1ce4b09b70f38ee6c1/1432265542589/DSCF3147.jpg?format=2500w
|
||||
|
||||
.. include:: ../../AUTHORS.rst
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Contributor's Guide
|
||||
===================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4237/35550408335_7671fde302_k_d.jpg
|
||||
|
||||
If you're reading this, you're probably interested in contributing to Requests.
|
||||
Thank you very much! Open source projects live-and-die based on the support
|
||||
they receive from others, and the fact that you're even considering
|
||||
@@ -203,4 +205,4 @@ while keeping an open ear and mind.
|
||||
|
||||
If you believe there is a feature missing, feel free to raise a feature
|
||||
request, but please do be aware that the overwhelming likelihood is that your
|
||||
feature request will not be accepted.
|
||||
feature request will not be accepted.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Development Philosophy
|
||||
======================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4231/34484831073_636008a23d_k_d.jpg
|
||||
|
||||
Requests is an open but opinionated library, created by an open but opinionated developer.
|
||||
|
||||
|
||||
@@ -31,6 +33,10 @@ Standard Library?
|
||||
|
||||
Requests has no *active* plans to be included in the standard library. This decision has been discussed at length with Guido as well as numerous core developers.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script async class="speakerdeck-embed" data-id="68f22f0841734d848315c618111b13ea" data-ratio="1.33333333333333" src="//speakerdeck.com/assets/embed.js"></script>
|
||||
|
||||
Essentially, the standard library is where a library goes to die. It is appropriate for a module to be included when active development is no longer necessary.
|
||||
|
||||
Linux Distro Packages
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
How to Help
|
||||
===========
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4290/34450900104_bc1d424213_k_d.jpg
|
||||
|
||||
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.
|
||||
@@ -49,9 +51,7 @@ Runtime Environments
|
||||
|
||||
Requests currently supports the following versions of Python:
|
||||
|
||||
- Python 2.6
|
||||
- Python 2.7
|
||||
- Python 3.3
|
||||
- Python 3.4
|
||||
- Python 3.5
|
||||
- Python 3.6
|
||||
|
||||
@@ -28,10 +28,10 @@ Release v\ |version|. (:ref:`Installation <install>`)
|
||||
**Requests** is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||
*Warning: Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
|
||||
including: security vulnerabilities, verbose code, reinventing the wheel,
|
||||
constantly reading documentation, depression, headaches, or even death.*
|
||||
|
||||
.. note:: The use of **Python 3** is *highly* preferred over Python 2. Consider upgrading your applications and infrastructure if you find yourself *still* using Python 2 in production today. If you are using Python 3, congratulations — you are indeed a person of excellent taste.
|
||||
—*Kenneth Reitz*
|
||||
|
||||
|
||||
-------------------
|
||||
|
||||
**Behold, the power of Requests**::
|
||||
@@ -81,7 +81,7 @@ Institutions that prefer to be unnamed claim to use Requests internally.
|
||||
simple, Pythonic.*
|
||||
|
||||
Requests is one of the most downloaded Python packages of all time, pulling in
|
||||
over 11,000,000 downloads every month. All the cool kids are doing it!
|
||||
over 13,000,000 downloads every month. All the cool kids are doing it!
|
||||
|
||||
Beloved Features
|
||||
----------------
|
||||
@@ -104,7 +104,7 @@ Requests is ready for today's web.
|
||||
- Chunked Requests
|
||||
- ``.netrc`` Support
|
||||
|
||||
Requests officially supports Python 2.6–2.7 & 3.3–3.7, and runs great on PyPy.
|
||||
Requests officially supports Python 2.7 & 3.4–3.7, and runs great on PyPy.
|
||||
|
||||
|
||||
The User Guide
|
||||
@@ -131,10 +131,10 @@ This part of the documentation, which is mostly prose, details the
|
||||
Requests ecosystem and community.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:maxdepth: 2
|
||||
|
||||
community/faq
|
||||
community/recommended
|
||||
community/faq
|
||||
community/out-there
|
||||
community/support
|
||||
community/vulnerabilities
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Advanced Usage
|
||||
==============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4263/35163665790_d182d84f5e_k_d.jpg
|
||||
|
||||
This document covers some of Requests more advanced features.
|
||||
|
||||
.. _session-objects:
|
||||
@@ -187,6 +189,25 @@ applied, replace the call to :meth:`Request.prepare()
|
||||
|
||||
print(resp.status_code)
|
||||
|
||||
When you are using the prepared request flow, keep in mind that it does not take into account the environment.
|
||||
This can cause problems if you are using environment variables to change the behaviour of requests.
|
||||
For example: Self-signed SSL certificates specified in ``REQUESTS_CA_BUNDLE`` will not be taken into account.
|
||||
As a result an ``SSL: CERTIFICATE_VERIFY_FAILED`` is thrown.
|
||||
You can get around this behaviour by explicity merging the environment settings into your session::
|
||||
|
||||
from requests import Request, Session
|
||||
|
||||
s = Session()
|
||||
req = Request('GET', url)
|
||||
|
||||
prepped = s.prepare_request(req)
|
||||
|
||||
# Merge environment settings into session
|
||||
settings = s.merge_environment_settings(prepped.url, None, None, None, None)
|
||||
resp = s.send(prepped, **settings)
|
||||
|
||||
print(resp.status_code)
|
||||
|
||||
.. _verification:
|
||||
|
||||
SSL Cert Verification
|
||||
@@ -593,6 +614,8 @@ as using a HTTP one::
|
||||
'https': 'socks5://user:pass@host:port'
|
||||
}
|
||||
|
||||
Using the scheme ``socks5`` causes the DNS resolution to happen on the client, rather than on the proxy server. This is in line with curl, which uses the scheme to decide whether to do the DNS resolution on the client or proxy. If you want to resolve the domains on the proxy server, use ``socks5h`` as the scheme.
|
||||
|
||||
.. _compliance:
|
||||
|
||||
Compliance
|
||||
@@ -934,8 +957,9 @@ response at a time. However, these calls will still block.
|
||||
|
||||
If you are concerned about the use of blocking IO, there are lots of projects
|
||||
out there that combine Requests with one of Python's asynchronicity frameworks.
|
||||
Two excellent examples are `grequests`_ and `requests-futures`_.
|
||||
Some excellent examples are `requests-threads`_, `grequests`_, and `requests-futures`_.
|
||||
|
||||
.. _`requests-threads`: https://github.com/requests/requests-threads
|
||||
.. _`grequests`: https://github.com/kennethreitz/grequests
|
||||
.. _`requests-futures`: https://github.com/ross/requests-futures
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Authentication
|
||||
==============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4258/35550409215_3b08d49d22_k_d.jpg
|
||||
|
||||
This document discusses using various kinds of authentication with Requests.
|
||||
|
||||
Many web services require authentication, and there are many different types.
|
||||
|
||||
@@ -3,18 +3,21 @@
|
||||
Installation of Requests
|
||||
========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4230/35550376215_da1bf77a8c_k_d.jpg
|
||||
|
||||
|
||||
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
|
||||
----------------------
|
||||
$ pipenv install requests
|
||||
-------------------------
|
||||
|
||||
To install Requests, simply run this simple command in your terminal of choice::
|
||||
|
||||
$ pip install requests
|
||||
$ pipenv install requests
|
||||
|
||||
If you don't have `pip <https://pip.pypa.io>`_ installed (tisk tisk!),
|
||||
If you don't have `pipenv <http://pipenv.org/>`_ installed (tisk tisk!), head over to the Pipenv website for installation instructions. Or, if you prefer to just use pip and don't have it installed,
|
||||
`this Python installation guide <http://docs.python-guide.org/en/latest/starting/installation/>`_
|
||||
can guide you through the process.
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg
|
||||
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4259/35163667010_8bfcaef274_k_d.jpg
|
||||
|
||||
.. module:: requests.models
|
||||
|
||||
Eager to get started? This page gives a good introduction in how to get started
|
||||
@@ -371,10 +373,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
|
||||
----------------
|
||||
@@ -414,6 +420,10 @@ represented in the dictionary within a single mapping, as per
|
||||
of the message, by appending each subsequent field value to the combined
|
||||
field value in order, separated by a comma.
|
||||
|
||||
If you do need to access each individual value sent with the same header, then
|
||||
you can use the ``getlist`` method to get a sequence of all the values returned
|
||||
for a particular header.
|
||||
|
||||
Cookies
|
||||
-------
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# __
|
||||
# /__) _ _ _ _ _/ _
|
||||
# / ( (- (/ (/ (- _) / _)
|
||||
# /
|
||||
|
||||
"""
|
||||
Requests HTTP Library
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -44,78 +42,99 @@ import urllib3
|
||||
import chardet
|
||||
import warnings
|
||||
from .exceptions import RequestsDependencyWarning
|
||||
from .import types
|
||||
|
||||
|
||||
def check_compatibility(urllib3_version, chardet_version):
|
||||
urllib3_version = urllib3_version.split('.')
|
||||
assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.
|
||||
|
||||
def check_compatibility(urllib3_version: types.String, chardet_version: types.String) -> None:
|
||||
urllib3_version = urllib3_version.split('.') # type: ignore
|
||||
assert urllib3_version != [
|
||||
'dev'
|
||||
] # Verify urllib3 isn't installed from git.
|
||||
# Sometimes, urllib3 only reports its version as 16.1.
|
||||
if len(urllib3_version) == 2:
|
||||
urllib3_version.append('0')
|
||||
|
||||
urllib3_version.append('0') # type: ignore
|
||||
# Check urllib3 for compatibility.
|
||||
major, minor, patch = urllib3_version # noqa: F811
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
major, minor, patch = int(major), int(minor), int(patch) # type: ignore
|
||||
# urllib3 >= 1.21.1, <= 1.22
|
||||
assert major == 1
|
||||
assert minor >= 21
|
||||
assert minor <= 22
|
||||
|
||||
assert major == 1 # type: ignore
|
||||
assert minor >= 21 # type: ignore
|
||||
assert minor <= 22 # type: ignore
|
||||
# Check chardet for compatibility.
|
||||
major, minor, patch = chardet_version.split('.')[:3]
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
major, minor, patch = int(major), int(minor), int(patch) # type: ignore
|
||||
# chardet >= 3.0.2, < 3.1.0
|
||||
assert major == 3
|
||||
assert minor < 1
|
||||
assert patch >= 2
|
||||
assert major == 3 # type: ignore
|
||||
assert minor < 1 # type: ignore
|
||||
assert patch >= 2 # type: ignore
|
||||
|
||||
|
||||
def _check_cryptography(cryptography_version: types.String) -> None:
|
||||
# cryptography < 1.3.4
|
||||
try:
|
||||
cryptography_version = list(
|
||||
map(int, cryptography_version.split('.'))
|
||||
) # type: ignore
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
if cryptography_version < [1, 3, 4]: # type: ignore
|
||||
warning = 'Old version of cryptography ({0}) may cause slowdown.'.format(
|
||||
cryptography_version
|
||||
)
|
||||
warnings.warn(warning, RequestsDependencyWarning)
|
||||
|
||||
|
||||
# Check imported dependencies for compatibility.
|
||||
try:
|
||||
check_compatibility(urllib3.__version__, chardet.__version__)
|
||||
except (AssertionError, ValueError):
|
||||
warnings.warn("urllib3 ({0}) or chardet ({1}) doesn't match a supported "
|
||||
"version!".format(urllib3.__version__, chardet.__version__),
|
||||
RequestsDependencyWarning)
|
||||
|
||||
warnings.warn(
|
||||
"urllib3 ({0}) or chardet ({1}) doesn't match a supported "
|
||||
"version!".format(urllib3.__version__, chardet.__version__),
|
||||
RequestsDependencyWarning,
|
||||
)
|
||||
# Attempt to enable urllib3's SNI support, if possible
|
||||
try:
|
||||
from urllib3.contrib import pyopenssl
|
||||
|
||||
pyopenssl.inject_into_urllib3()
|
||||
# Check cryptography version
|
||||
from cryptography import __version__ as cryptography_version
|
||||
|
||||
_check_cryptography(cryptography_version)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# urllib3's DependencyWarnings should be silenced.
|
||||
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 .import utils
|
||||
from .models import Request, Response, PreparedRequest
|
||||
from .api import request, get, head, post, patch, put, delete, options
|
||||
from .sessions import session, Session
|
||||
from .sessions import Session
|
||||
from .status_codes import codes
|
||||
from .exceptions import (
|
||||
RequestException, Timeout, URLRequired,
|
||||
TooManyRedirects, HTTPError, ConnectionError,
|
||||
FileModeWarning, ConnectTimeout, ReadTimeout
|
||||
RequestException,
|
||||
Timeout,
|
||||
URLRequired,
|
||||
TooManyRedirects,
|
||||
HTTPError,
|
||||
ConnectionError,
|
||||
FileModeWarning,
|
||||
ConnectTimeout,
|
||||
ReadTimeout,
|
||||
)
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
import logging
|
||||
try: # Python 2.7+
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
from logging import NullHandler
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
# FileModeWarnings go off per the default.
|
||||
warnings.simplefilter('default', FileModeWarning, append=True)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# .-. .-. .-. . . .-. .-. .-. .-.
|
||||
# |( |- |.| | | |- `-. | `-.
|
||||
# ' ' `-' `-`.`-' `-' `-' ' `-'
|
||||
|
||||
__title__ = 'requests'
|
||||
__description__ = 'Python HTTP for Humans.'
|
||||
__url__ = 'http://python-requests.org'
|
||||
__version__ = '2.18.3'
|
||||
__build__ = 0x021803
|
||||
__version__ = '3.0.0'
|
||||
__build__ = 0x030000
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__author_email__ = 'me@kennethreitz.org'
|
||||
__license__ = 'Apache 2.0'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests._internal_utils
|
||||
~~~~~~~~~~~~~~
|
||||
@@ -8,7 +7,7 @@ Provides utility functions that are consumed internally by Requests
|
||||
which depend on extremely few external helpers (such as compat)
|
||||
"""
|
||||
|
||||
from .compat import is_py2, builtin_str, str
|
||||
from .basics import builtin_str, str
|
||||
|
||||
|
||||
def to_native_string(string, encoding='ascii'):
|
||||
@@ -19,11 +18,7 @@ def to_native_string(string, encoding='ascii'):
|
||||
if isinstance(string, builtin_str):
|
||||
out = string
|
||||
else:
|
||||
if is_py2:
|
||||
out = string.encode(encoding)
|
||||
else:
|
||||
out = string.decode(encoding)
|
||||
|
||||
out = string.decode(encoding)
|
||||
return out
|
||||
|
||||
|
||||
@@ -34,9 +29,12 @@ def unicode_is_ascii(u_string):
|
||||
and not Python 2 `str`.
|
||||
:rtype: bool
|
||||
"""
|
||||
assert isinstance(u_string, str)
|
||||
if not isinstance(u_string, str):
|
||||
return None
|
||||
|
||||
try:
|
||||
u_string.encode('ascii')
|
||||
return True
|
||||
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.adapters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -27,21 +26,35 @@ from urllib3.exceptions import SSLError as _SSLError
|
||||
from urllib3.exceptions import ResponseError
|
||||
|
||||
from .models import Response
|
||||
from .compat import urlparse, basestring
|
||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
|
||||
select_proxy)
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .basics import urlparse, basestring
|
||||
from .utils import (
|
||||
DEFAULT_CA_BUNDLE_PATH,
|
||||
get_encoding_from_headers,
|
||||
prepend_scheme_if_needed,
|
||||
get_auth_from_url,
|
||||
urldefragauth,
|
||||
select_proxy,
|
||||
)
|
||||
from .structures import HTTPHeaderDict
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
||||
ProxyError, RetryError, InvalidSchema)
|
||||
from .exceptions import (
|
||||
ConnectionError,
|
||||
ConnectTimeout,
|
||||
ReadTimeout,
|
||||
SSLError,
|
||||
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
|
||||
@@ -49,14 +62,83 @@ DEFAULT_RETRIES = 0
|
||||
DEFAULT_POOL_TIMEOUT = None
|
||||
|
||||
|
||||
def _pool_kwargs(verify, cert):
|
||||
"""Create a dictionary of keyword arguments to pass to a
|
||||
:class:`PoolManager <urllib3.poolmanager.PoolManager>` with the
|
||||
necessary SSL configuration.
|
||||
|
||||
: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).
|
||||
"""
|
||||
pool_kwargs = {}
|
||||
if verify:
|
||||
cert_loc = None
|
||||
# Allow self-specified cert location.
|
||||
if verify is not True:
|
||||
cert_loc = verify
|
||||
if not cert_loc:
|
||||
cert_loc = DEFAULT_CA_BUNDLE_PATH
|
||||
if not cert_loc or not os.path.exists(cert_loc):
|
||||
raise IOError(
|
||||
"Could not find a suitable TLS CA certificate bundle, "
|
||||
"invalid path: {0}".format(cert_loc)
|
||||
)
|
||||
|
||||
pool_kwargs['cert_reqs'] = 'CERT_REQUIRED'
|
||||
if not os.path.isdir(cert_loc):
|
||||
pool_kwargs['ca_certs'] = cert_loc
|
||||
pool_kwargs['ca_cert_dir'] = None
|
||||
else:
|
||||
pool_kwargs['ca_cert_dir'] = cert_loc
|
||||
pool_kwargs['ca_certs'] = None
|
||||
else:
|
||||
pool_kwargs['cert_reqs'] = 'CERT_NONE'
|
||||
pool_kwargs['ca_certs'] = None
|
||||
pool_kwargs['ca_cert_dir'] = None
|
||||
if cert:
|
||||
if not isinstance(cert, basestring):
|
||||
pool_kwargs['cert_file'] = cert[0]
|
||||
pool_kwargs['key_file'] = cert[1]
|
||||
else:
|
||||
pool_kwargs['cert_file'] = cert
|
||||
pool_kwargs['key_file'] = None
|
||||
cert_file = pool_kwargs['cert_file']
|
||||
key_file = pool_kwargs['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(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(key_file)
|
||||
)
|
||||
|
||||
return pool_kwargs
|
||||
|
||||
|
||||
class BaseAdapter(object):
|
||||
"""The Base Transport Adapter"""
|
||||
|
||||
def __init__(self):
|
||||
super(BaseAdapter, self).__init__()
|
||||
|
||||
def send(self, request, stream=False, timeout=None, verify=True,
|
||||
cert=None, proxies=None):
|
||||
def send(
|
||||
self,
|
||||
request,
|
||||
stream=False,
|
||||
timeout=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
proxies=None,
|
||||
):
|
||||
"""Sends PreparedRequest object. Returns Response object.
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
@@ -104,44 +186,50 @@ class HTTPAdapter(BaseAdapter):
|
||||
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
|
||||
>>> s.mount('http://', a)
|
||||
"""
|
||||
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
|
||||
'_pool_block']
|
||||
__attrs__ = [
|
||||
'max_retries',
|
||||
'config',
|
||||
'_pool_connections',
|
||||
'_pool_maxsize',
|
||||
'_pool_block',
|
||||
]
|
||||
|
||||
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
|
||||
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
|
||||
pool_block=DEFAULT_POOLBLOCK):
|
||||
def __init__(
|
||||
self,
|
||||
pool_connections=DEFAULT_POOLSIZE,
|
||||
pool_maxsize=DEFAULT_POOLSIZE,
|
||||
max_retries=DEFAULT_RETRIES,
|
||||
pool_block=DEFAULT_POOLBLOCK,
|
||||
):
|
||||
if max_retries == DEFAULT_RETRIES:
|
||||
self.max_retries = Retry(0, read=False)
|
||||
else:
|
||||
self.max_retries = Retry.from_int(max_retries)
|
||||
self.config = {}
|
||||
self.proxy_manager = {}
|
||||
|
||||
super(HTTPAdapter, self).__init__()
|
||||
|
||||
self._pool_connections = pool_connections
|
||||
self._pool_maxsize = pool_maxsize
|
||||
self._pool_block = pool_block
|
||||
|
||||
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict((attr, getattr(self, attr, None)) for attr in
|
||||
self.__attrs__)
|
||||
return {attr: getattr(self, attr, None) for attr in self.__attrs__}
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
|
||||
# self.poolmanager uses a lambda function, which isn't pickleable.
|
||||
self.proxy_manager = {}
|
||||
self.config = {}
|
||||
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
self.init_poolmanager(
|
||||
self._pool_connections, self._pool_maxsize, block=self._pool_block
|
||||
)
|
||||
|
||||
self.init_poolmanager(self._pool_connections, self._pool_maxsize,
|
||||
block=self._pool_block)
|
||||
|
||||
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
|
||||
def init_poolmanager(
|
||||
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
|
||||
):
|
||||
"""Initializes a urllib3 PoolManager.
|
||||
|
||||
This method should not be called from user code, and is only
|
||||
@@ -157,9 +245,13 @@ class HTTPAdapter(BaseAdapter):
|
||||
self._pool_connections = connections
|
||||
self._pool_maxsize = maxsize
|
||||
self._pool_block = block
|
||||
|
||||
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
|
||||
block=block, strict=True, **pool_kwargs)
|
||||
self.poolmanager = PoolManager(
|
||||
num_pools=connections,
|
||||
maxsize=maxsize,
|
||||
block=block,
|
||||
strict=True,
|
||||
**pool_kwargs,
|
||||
)
|
||||
|
||||
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
||||
"""Return urllib3 ProxyManager for the given proxy.
|
||||
@@ -184,7 +276,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
num_pools=self._pool_connections,
|
||||
maxsize=self._pool_maxsize,
|
||||
block=self._pool_block,
|
||||
**proxy_kwargs
|
||||
**proxy_kwargs,
|
||||
)
|
||||
else:
|
||||
proxy_headers = self.proxy_headers(proxy)
|
||||
@@ -194,62 +286,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
num_pools=self._pool_connections,
|
||||
maxsize=self._pool_maxsize,
|
||||
block=self._pool_block,
|
||||
**proxy_kwargs)
|
||||
|
||||
**proxy_kwargs,
|
||||
)
|
||||
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>`.
|
||||
|
||||
: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.
|
||||
"""
|
||||
if url.lower().startswith('https') and verify:
|
||||
|
||||
cert_loc = None
|
||||
|
||||
# Allow self-specified cert location.
|
||||
if verify is not True:
|
||||
cert_loc = verify
|
||||
|
||||
if not cert_loc:
|
||||
cert_loc = DEFAULT_CA_BUNDLE_PATH
|
||||
|
||||
if not cert_loc or not os.path.exists(cert_loc):
|
||||
raise IOError("Could not find a suitable TLS CA certificate bundle, "
|
||||
"invalid path: {0}".format(cert_loc))
|
||||
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
|
||||
if not os.path.isdir(cert_loc):
|
||||
conn.ca_certs = cert_loc
|
||||
else:
|
||||
conn.ca_cert_dir = cert_loc
|
||||
else:
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
conn.ca_cert_dir = None
|
||||
|
||||
if cert:
|
||||
if not isinstance(cert, basestring):
|
||||
conn.cert_file = cert[0]
|
||||
conn.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):
|
||||
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):
|
||||
raise IOError("Could not find the TLS key file, "
|
||||
"invalid path: {0}".format(conn.key_file))
|
||||
|
||||
def build_response(self, req, resp):
|
||||
"""Builds a :class:`Response <requests.Response>` object from a urllib3
|
||||
response. This should not be called from user code, and is only exposed
|
||||
@@ -261,33 +301,26 @@ class HTTPAdapter(BaseAdapter):
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
response = Response()
|
||||
|
||||
# Fallback to None if there's no status_code, for whatever reason.
|
||||
response.status_code = getattr(resp, 'status', None)
|
||||
|
||||
# Make headers case-insensitive.
|
||||
response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {}))
|
||||
|
||||
response.headers = HTTPHeaderDict(getattr(resp, 'headers', {}))
|
||||
# Set encoding.
|
||||
response.encoding = get_encoding_from_headers(response.headers)
|
||||
response.raw = resp
|
||||
response.reason = response.raw.reason
|
||||
|
||||
if isinstance(req.url, bytes):
|
||||
response.url = req.url.decode('utf-8')
|
||||
else:
|
||||
response.url = req.url
|
||||
|
||||
# Add new cookies from the server.
|
||||
extract_cookies_to_jar(response.cookies, req, resp)
|
||||
|
||||
# Give the Response some context.
|
||||
response.request = req
|
||||
response.connection = self
|
||||
|
||||
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,18 +329,21 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
:rtype: urllib3.ConnectionPool
|
||||
"""
|
||||
pool_kwargs = _pool_kwargs(verify, cert)
|
||||
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)
|
||||
conn = proxy_manager.connection_from_url(
|
||||
url, pool_kwargs=pool_kwargs
|
||||
)
|
||||
else:
|
||||
# Only scheme should be lower case
|
||||
parsed = urlparse(url)
|
||||
url = parsed.geturl()
|
||||
conn = self.poolmanager.connection_from_url(url)
|
||||
|
||||
conn = self.poolmanager.connection_from_url(
|
||||
url, pool_kwargs=pool_kwargs
|
||||
)
|
||||
return conn
|
||||
|
||||
def close(self):
|
||||
@@ -336,17 +372,14 @@ class HTTPAdapter(BaseAdapter):
|
||||
"""
|
||||
proxy = select_proxy(request.url, proxies)
|
||||
scheme = urlparse(request.url).scheme
|
||||
|
||||
is_proxied_http_request = (proxy and scheme != 'https')
|
||||
using_socks_proxy = False
|
||||
if proxy:
|
||||
proxy_scheme = urlparse(proxy).scheme.lower()
|
||||
using_socks_proxy = proxy_scheme.startswith('socks')
|
||||
|
||||
url = request.path_url
|
||||
if is_proxied_http_request and not using_socks_proxy:
|
||||
url = urldefragauth(request.url)
|
||||
|
||||
return url
|
||||
|
||||
def add_headers(self, request, **kwargs):
|
||||
@@ -378,14 +411,21 @@ class HTTPAdapter(BaseAdapter):
|
||||
"""
|
||||
headers = {}
|
||||
username, password = get_auth_from_url(proxy)
|
||||
|
||||
if username:
|
||||
headers['Proxy-Authorization'] = _basic_auth_str(username,
|
||||
password)
|
||||
|
||||
headers['Proxy-Authorization'] = _basic_auth_str(
|
||||
username, password
|
||||
)
|
||||
return headers
|
||||
|
||||
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
|
||||
def send(
|
||||
self,
|
||||
request,
|
||||
stream=False,
|
||||
timeout=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
proxies=None,
|
||||
):
|
||||
"""Sends PreparedRequest object. Returns Response object.
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
@@ -401,30 +441,29 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) The proxies dictionary to apply to the request.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
conn = self.get_connection(request.url, proxies)
|
||||
|
||||
self.cert_verify(conn, request.url, verify, cert)
|
||||
conn = self.get_connection(request.url, proxies, verify, cert)
|
||||
url = self.request_url(request, proxies)
|
||||
self.add_headers(request)
|
||||
|
||||
chunked = not (request.body is None or 'Content-Length' in request.headers)
|
||||
|
||||
chunked = not (
|
||||
request.body is None or 'Content-Length' in request.headers
|
||||
)
|
||||
if isinstance(timeout, tuple):
|
||||
try:
|
||||
connect, read = timeout
|
||||
timeout = TimeoutSauce(connect=connect, read=read)
|
||||
except ValueError as e:
|
||||
# this may raise a string formatting error.
|
||||
err = ("Invalid timeout {0}. Pass a (connect, read) "
|
||||
"timeout tuple, or a single float to set "
|
||||
"both timeouts to the same value".format(timeout))
|
||||
err = (
|
||||
"Invalid timeout {0}. Pass a (connect, read) "
|
||||
"timeout tuple, or a single float to set "
|
||||
"both timeouts to the same value".format(timeout)
|
||||
)
|
||||
raise ValueError(err)
|
||||
|
||||
elif isinstance(timeout, TimeoutSauce):
|
||||
pass
|
||||
else:
|
||||
timeout = TimeoutSauce(connect=timeout, read=timeout)
|
||||
|
||||
try:
|
||||
if not chunked:
|
||||
resp = conn.urlopen(
|
||||
@@ -437,48 +476,46 @@ class HTTPAdapter(BaseAdapter):
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=self.max_retries,
|
||||
timeout=timeout
|
||||
timeout=timeout,
|
||||
enforce_content_length=True,
|
||||
)
|
||||
|
||||
# Send the request.
|
||||
else:
|
||||
if hasattr(conn, 'proxy_pool'):
|
||||
conn = conn.proxy_pool
|
||||
|
||||
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
|
||||
|
||||
try:
|
||||
low_conn.putrequest(request.method,
|
||||
url,
|
||||
skip_accept_encoding=True)
|
||||
|
||||
low_conn.putrequest(
|
||||
request.method, url, skip_accept_encoding=True
|
||||
)
|
||||
for header, value in request.headers.items():
|
||||
low_conn.putheader(header, value)
|
||||
|
||||
low_conn.endheaders()
|
||||
|
||||
for i in request.body:
|
||||
low_conn.send(hex(len(i))[2:].encode('utf-8'))
|
||||
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')
|
||||
low_conn.send(b'0\r\n\r\n')
|
||||
|
||||
# Receive the response from the server
|
||||
try:
|
||||
# For Python 2.7+ versions, use buffering of HTTP
|
||||
# responses
|
||||
# For Python 2.7, use buffering of HTTP responses
|
||||
r = low_conn.getresponse(buffering=True)
|
||||
except TypeError:
|
||||
# For compatibility with Python 2.6 versions and back
|
||||
# For Python 3.3+ versions, this is the default
|
||||
r = low_conn.getresponse()
|
||||
|
||||
resp = HTTPResponse.from_httplib(
|
||||
r,
|
||||
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.
|
||||
@@ -517,8 +554,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
if isinstance(e, _SSLError):
|
||||
# This branch is for urllib3 versions earlier than v1.22
|
||||
raise SSLError(e, request=request)
|
||||
|
||||
elif isinstance(e, ReadTimeoutError):
|
||||
raise ReadTimeout(e, request=request)
|
||||
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.api
|
||||
~~~~~~~~~~~~
|
||||
@@ -10,14 +9,22 @@ This module implements the Requests API.
|
||||
:license: Apache2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from . import sessions
|
||||
from .import sessions
|
||||
from .import types
|
||||
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
def request(
|
||||
method: types.Method,
|
||||
url: types.URL,
|
||||
*,
|
||||
session: types.Session = None,
|
||||
**kwargs,
|
||||
) -> types.Response:
|
||||
"""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`.
|
||||
@@ -50,15 +57,17 @@ def request(method, url, **kwargs):
|
||||
>>> req = requests.request('GET', 'http://httpbin.org/get')
|
||||
<Response [200]>
|
||||
"""
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
def get(url, params=None, **kwargs):
|
||||
def get(
|
||||
url: types.URL, *, params: types.Params = None, **kwargs
|
||||
) -> types.Response:
|
||||
r"""Sends a GET request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -67,12 +76,11 @@ def get(url, params=None, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('get', url, params=params, **kwargs)
|
||||
|
||||
|
||||
def options(url, **kwargs):
|
||||
def options(url: types.URL, **kwargs) -> types.Response:
|
||||
r"""Sends an OPTIONS request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -80,12 +88,11 @@ def options(url, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('options', url, **kwargs)
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
def head(url: types.URL, **kwargs) -> types.Response:
|
||||
r"""Sends a HEAD request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -93,12 +100,17 @@ def head(url, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return request('head', url, **kwargs)
|
||||
|
||||
|
||||
def post(url, data=None, json=None, **kwargs):
|
||||
def post(
|
||||
url: types.URL,
|
||||
*,
|
||||
data: types.Data = None,
|
||||
json: types.JSON = None,
|
||||
**kwargs,
|
||||
) -> types.Response:
|
||||
r"""Sends a POST request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -108,11 +120,12 @@ def post(url, data=None, json=None, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, json=json, **kwargs)
|
||||
|
||||
|
||||
def put(url, data=None, **kwargs):
|
||||
def put(
|
||||
url: types.URL, *, data: types.Data = None, **kwargs
|
||||
) -> types.Response:
|
||||
r"""Sends a PUT request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -122,11 +135,12 @@ def put(url, data=None, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(url, data=None, **kwargs):
|
||||
def patch(
|
||||
url: types.URL, *, data: types.Data = None, **kwargs
|
||||
) -> types.Response:
|
||||
r"""Sends a PATCH request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -136,11 +150,10 @@ def patch(url, data=None, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
def delete(url: types.URL, **kwargs) -> types.Response:
|
||||
r"""Sends a DELETE request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -148,5 +161,4 @@ def delete(url, **kwargs):
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('delete', url, **kwargs)
|
||||
|
||||
112
requests/auth.py
112
requests/auth.py
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.auth
|
||||
~~~~~~~~~~~~~
|
||||
@@ -12,11 +11,10 @@ import re
|
||||
import time
|
||||
import hashlib
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
from .compat import urlparse, str, basestring
|
||||
from .basics import urlparse, str, basestring
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from ._internal_utils import to_native_string
|
||||
from .utils import parse_dict_header
|
||||
@@ -27,45 +25,25 @@ 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,
|
||||
raise TypeError(
|
||||
'username must be of type str or bytes, '
|
||||
'instead it was %s' % type(username)
|
||||
)
|
||||
username = str(username)
|
||||
|
||||
if not isinstance(password, basestring):
|
||||
warnings.warn(
|
||||
"Non-string passwords will no longer be supported in Requests "
|
||||
"3.0.0. Please convert the object you've passed in ({0!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(password),
|
||||
category=DeprecationWarning,
|
||||
raise TypeError(
|
||||
'password must be of type str or bytes, '
|
||||
'instead it was %s' % type(password)
|
||||
)
|
||||
password = str(password)
|
||||
# -- End Removal --
|
||||
|
||||
if isinstance(username, str):
|
||||
username = username.encode('latin1')
|
||||
|
||||
if isinstance(password, str):
|
||||
password = password.encode('latin1')
|
||||
|
||||
authstr = 'Basic ' + to_native_string(
|
||||
b64encode(b':'.join((username, password))).strip()
|
||||
)
|
||||
|
||||
return authstr
|
||||
|
||||
|
||||
@@ -84,24 +62,20 @@ class HTTPBasicAuth(AuthBase):
|
||||
self.password = password
|
||||
|
||||
def __eq__(self, other):
|
||||
return all([
|
||||
self.username == getattr(other, 'username', None),
|
||||
self.password == getattr(other, 'password', None)
|
||||
])
|
||||
return all(
|
||||
[
|
||||
self.username == getattr(other, 'username', None),
|
||||
self.password == getattr(other, 'password', None),
|
||||
]
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __call__(self, r):
|
||||
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
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)
|
||||
r.headers['Authorization'] = _basic_auth_str(
|
||||
self.username, self.password
|
||||
)
|
||||
return r
|
||||
|
||||
|
||||
@@ -128,51 +102,48 @@ class HTTPDigestAuth(AuthBase):
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
realm = self._thread_local.chal['realm']
|
||||
nonce = self._thread_local.chal['nonce']
|
||||
qop = self._thread_local.chal.get('qop')
|
||||
algorithm = self._thread_local.chal.get('algorithm')
|
||||
opaque = self._thread_local.chal.get('opaque')
|
||||
hash_utf8 = None
|
||||
|
||||
if algorithm is None:
|
||||
_algorithm = 'MD5'
|
||||
else:
|
||||
_algorithm = algorithm.upper()
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
|
||||
|
||||
def md5_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.md5(x).hexdigest()
|
||||
|
||||
hash_utf8 = md5_utf8
|
||||
elif _algorithm == 'SHA':
|
||||
|
||||
def sha_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
|
||||
hash_utf8 = sha_utf8
|
||||
|
||||
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
|
||||
|
||||
if hash_utf8 is None:
|
||||
return None
|
||||
|
||||
# XXX not implemented yet
|
||||
entdig = None
|
||||
p_parsed = urlparse(url)
|
||||
#: path is request-uri defined in RFC 2616 which should not be empty
|
||||
# : path is request-uri defined in RFC 2616 which should not be empty
|
||||
path = p_parsed.path or "/"
|
||||
if p_parsed.query:
|
||||
path += '?' + p_parsed.query
|
||||
|
||||
A1 = '%s:%s:%s' % (self.username, realm, self.password)
|
||||
A2 = '%s:%s' % (method, path)
|
||||
|
||||
HA1 = hash_utf8(A1)
|
||||
HA2 = hash_utf8(A2)
|
||||
|
||||
if nonce == self._thread_local.last_nonce:
|
||||
self._thread_local.nonce_count += 1
|
||||
else:
|
||||
@@ -182,27 +153,23 @@ class HTTPDigestAuth(AuthBase):
|
||||
s += nonce.encode('utf-8')
|
||||
s += time.ctime().encode('utf-8')
|
||||
s += os.urandom(8)
|
||||
|
||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||
if _algorithm == 'MD5-SESS':
|
||||
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
|
||||
|
||||
if not qop:
|
||||
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
|
||||
elif qop == 'auth' or 'auth' in qop.split(','):
|
||||
noncebit = "%s:%s:%s:%s:%s" % (
|
||||
nonce, ncvalue, cnonce, 'auth', HA2
|
||||
)
|
||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, 'auth', HA2)
|
||||
respdig = KD(HA1, noncebit)
|
||||
else:
|
||||
# XXX handle auth-int.
|
||||
return None
|
||||
|
||||
self._thread_local.last_nonce = nonce
|
||||
|
||||
# XXX should the partial digests be encoded too?
|
||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||
'response="%s"' % (self.username, realm, nonce, path, respdig)
|
||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' 'response="%s"' % (
|
||||
self.username, realm, nonce, path, respdig
|
||||
)
|
||||
if opaque:
|
||||
base += ', opaque="%s"' % opaque
|
||||
if algorithm:
|
||||
@@ -211,7 +178,6 @@ class HTTPDigestAuth(AuthBase):
|
||||
base += ', digest="%s"' % entdig
|
||||
if qop:
|
||||
base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
|
||||
|
||||
return 'Digest %s' % (base)
|
||||
|
||||
def handle_redirect(self, r, **kwargs):
|
||||
@@ -225,7 +191,6 @@ class HTTPDigestAuth(AuthBase):
|
||||
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
# If response is not 4xx, do not auth
|
||||
# See https://github.com/requests/requests/issues/3772
|
||||
if not 400 <= r.status_code < 500:
|
||||
@@ -237,13 +202,12 @@ class HTTPDigestAuth(AuthBase):
|
||||
# it was to resend the request.
|
||||
r.request.body.seek(self._thread_local.pos)
|
||||
s_auth = r.headers.get('www-authenticate', '')
|
||||
|
||||
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
|
||||
|
||||
self._thread_local.num_401_calls += 1
|
||||
pat = re.compile(r'digest ', flags=re.IGNORECASE)
|
||||
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
||||
|
||||
self._thread_local.chal = parse_dict_header(
|
||||
pat.sub('', s_auth, count=1)
|
||||
)
|
||||
# Consume content and release the original connection
|
||||
# to allow our new request to reuse the same one.
|
||||
r.content
|
||||
@@ -251,13 +215,12 @@ class HTTPDigestAuth(AuthBase):
|
||||
prep = r.request.copy()
|
||||
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
|
||||
prep.prepare_cookies(prep._cookies)
|
||||
|
||||
prep.headers['Authorization'] = self.build_digest_header(
|
||||
prep.method, prep.url)
|
||||
prep.method, prep.url
|
||||
)
|
||||
_r = r.connection.send(prep, **kwargs)
|
||||
_r.history.append(r)
|
||||
_r.request = prep
|
||||
|
||||
return _r
|
||||
|
||||
self._thread_local.num_401_calls = 1
|
||||
@@ -268,7 +231,9 @@ class HTTPDigestAuth(AuthBase):
|
||||
self.init_per_thread_state()
|
||||
# If we have a saved nonce, skip the 401
|
||||
if self._thread_local.last_nonce:
|
||||
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
|
||||
r.headers['Authorization'] = self.build_digest_header(
|
||||
r.method, r.url
|
||||
)
|
||||
try:
|
||||
self._thread_local.pos = r.body.tell()
|
||||
except AttributeError:
|
||||
@@ -280,14 +245,15 @@ class HTTPDigestAuth(AuthBase):
|
||||
r.register_hook('response', self.handle_401)
|
||||
r.register_hook('response', self.handle_redirect)
|
||||
self._thread_local.num_401_calls = 1
|
||||
|
||||
return r
|
||||
|
||||
def __eq__(self, other):
|
||||
return all([
|
||||
self.username == getattr(other, 'username', None),
|
||||
self.password == getattr(other, 'password', None)
|
||||
])
|
||||
return all(
|
||||
[
|
||||
self.username == getattr(other, 'username', None),
|
||||
self.password == getattr(other, 'password', None),
|
||||
]
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
45
requests/basics.py
Normal file
45
requests/basics.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
requests.basics
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This modules covers the basics.
|
||||
"""
|
||||
|
||||
import chardet
|
||||
|
||||
import sys
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
from urllib.parse import (
|
||||
urlparse,
|
||||
urlunparse,
|
||||
urljoin,
|
||||
urlsplit,
|
||||
urlencode,
|
||||
quote,
|
||||
unquote,
|
||||
quote_plus,
|
||||
unquote_plus,
|
||||
urldefrag,
|
||||
)
|
||||
from urllib.request import (
|
||||
parse_http_list,
|
||||
getproxies,
|
||||
proxy_bypass,
|
||||
proxy_bypass_environment,
|
||||
getproxies_environment,
|
||||
)
|
||||
from http import cookiejar as cookielib
|
||||
from http.cookies import Morsel
|
||||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
|
||||
builtin_str = str # type: ignore
|
||||
str = str # type: ignore
|
||||
bytes = bytes # type: ignore
|
||||
basestring = (str, bytes)
|
||||
numeric_types = (int, float)
|
||||
integer_types = (int,)
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.certs
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.compat
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This module handles import compatibility issues between Python 2 and
|
||||
Python 3.
|
||||
"""
|
||||
|
||||
import chardet
|
||||
|
||||
import sys
|
||||
|
||||
# -------
|
||||
# Pythons
|
||||
# -------
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version_info
|
||||
|
||||
#: Python 2.x?
|
||||
is_py2 = (_ver[0] == 2)
|
||||
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
if is_py2:
|
||||
from urllib import (
|
||||
quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
|
||||
proxy_bypass, proxy_bypass_environment, getproxies_environment)
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
|
||||
from urllib2 import parse_http_list
|
||||
import cookielib
|
||||
from Cookie import Morsel
|
||||
from StringIO import StringIO
|
||||
|
||||
from urllib3.packages.ordered_dict import OrderedDict
|
||||
|
||||
builtin_str = str
|
||||
bytes = str
|
||||
str = unicode
|
||||
basestring = basestring
|
||||
numeric_types = (int, long, float)
|
||||
integer_types = (int, long)
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
|
||||
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
|
||||
from http import cookiejar as cookielib
|
||||
from http.cookies import Morsel
|
||||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
|
||||
builtin_str = str
|
||||
str = str
|
||||
bytes = bytes
|
||||
basestring = (str, bytes)
|
||||
numeric_types = (int, float)
|
||||
integer_types = (int,)
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.cookies
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -15,12 +14,12 @@ import calendar
|
||||
import collections
|
||||
|
||||
from ._internal_utils import to_native_string
|
||||
from .compat import cookielib, urlparse, urlunparse, Morsel
|
||||
from .basics import cookielib, urlparse, urlunparse, Morsel
|
||||
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
import dummy_threading as threading
|
||||
import dummy_threading as threading # type: ignore
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
@@ -54,14 +53,21 @@ class MockRequest(object):
|
||||
# header
|
||||
if not self._r.headers.get('Host'):
|
||||
return self._r.url
|
||||
|
||||
# If they did set it, retrieve it and reconstruct the expected domain
|
||||
host = to_native_string(self._r.headers['Host'], encoding='utf-8')
|
||||
parsed = urlparse(self._r.url)
|
||||
# Reconstruct the URL as we expect it
|
||||
return urlunparse([
|
||||
parsed.scheme, host, parsed.path, parsed.params, parsed.query,
|
||||
parsed.fragment
|
||||
])
|
||||
return urlunparse(
|
||||
[
|
||||
parsed.scheme,
|
||||
host,
|
||||
parsed.path,
|
||||
parsed.params,
|
||||
parsed.query,
|
||||
parsed.fragment,
|
||||
]
|
||||
)
|
||||
|
||||
def is_unverifiable(self):
|
||||
return True
|
||||
@@ -74,7 +80,9 @@ class MockRequest(object):
|
||||
|
||||
def add_header(self, key, val):
|
||||
"""cookielib has no legitimate use for this method; add it back if you find one."""
|
||||
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
|
||||
raise NotImplementedError(
|
||||
"Cookie headers should be added with add_unredirected_header()"
|
||||
)
|
||||
|
||||
def add_unredirected_header(self, name, value):
|
||||
self._new_headers[name] = value
|
||||
@@ -123,9 +131,11 @@ def extract_cookies_to_jar(jar, request, response):
|
||||
:param request: our own requests.Request object
|
||||
:param response: urllib3.HTTPResponse object
|
||||
"""
|
||||
if not (hasattr(response, '_original_response') and
|
||||
response._original_response):
|
||||
if not (
|
||||
hasattr(response, '_original_response') and response._original_response
|
||||
):
|
||||
return
|
||||
|
||||
# the _original_response field is the wrapped httplib.HTTPResponse object,
|
||||
req = MockRequest(request)
|
||||
# pull out the HTTPMessage with the headers and put it in the mock:
|
||||
@@ -153,12 +163,14 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
||||
for cookie in cookiejar:
|
||||
if cookie.name != name:
|
||||
continue
|
||||
|
||||
if domain is not None and domain != cookie.domain:
|
||||
continue
|
||||
|
||||
if path is not None and path != cookie.path:
|
||||
continue
|
||||
clearables.append((cookie.domain, cookie.path, cookie.name))
|
||||
|
||||
clearables.append((cookie.domain, cookie.path, cookie.name))
|
||||
for domain, path, name in clearables:
|
||||
cookiejar.clear(domain, path, name)
|
||||
|
||||
@@ -196,6 +208,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""
|
||||
try:
|
||||
return self._find_no_duplicates(name, domain, path)
|
||||
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
@@ -206,7 +219,12 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""
|
||||
# support client code that unsets cookies by assignment of a None value:
|
||||
if value is None:
|
||||
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
|
||||
remove_cookie_by_name(
|
||||
self,
|
||||
name,
|
||||
domain=kwargs.get('domain'),
|
||||
path=kwargs.get('path'),
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(value, Morsel):
|
||||
@@ -294,6 +312,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
for cookie in iter(self):
|
||||
if cookie.domain is not None and cookie.domain in domains:
|
||||
return True
|
||||
|
||||
domains.append(cookie.domain)
|
||||
return False # there is only one domain in jar
|
||||
|
||||
@@ -316,6 +335,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
def __contains__(self, name):
|
||||
try:
|
||||
return super(RequestsCookieJar, self).__contains__(name)
|
||||
|
||||
except CookieConflictError:
|
||||
return True
|
||||
|
||||
@@ -342,9 +362,15 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
remove_cookie_by_name(self, name)
|
||||
|
||||
def set_cookie(self, cookie, *args, **kwargs):
|
||||
if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
|
||||
if hasattr(cookie.value, 'startswith') and cookie.value.startswith(
|
||||
'"'
|
||||
) and cookie.value.endswith(
|
||||
'"'
|
||||
):
|
||||
cookie.value = cookie.value.replace('\\"', '')
|
||||
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
|
||||
return super(RequestsCookieJar, self).set_cookie(
|
||||
cookie, *args, **kwargs
|
||||
)
|
||||
|
||||
def update(self, other):
|
||||
"""Updates this jar with cookies from another CookieJar or dict-like"""
|
||||
@@ -392,11 +418,15 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
if domain is None or cookie.domain == domain:
|
||||
if path is None or cookie.path == path:
|
||||
if toReturn is not None: # if there are multiple cookies that meet passed in criteria
|
||||
raise CookieConflictError('There are multiple cookies with name, %r' % (name))
|
||||
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
|
||||
raise CookieConflictError(
|
||||
'There are multiple cookies with name, %r' %
|
||||
(name)
|
||||
)
|
||||
|
||||
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
|
||||
if toReturn:
|
||||
return toReturn
|
||||
|
||||
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -414,7 +444,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
|
||||
|
||||
@@ -426,6 +456,7 @@ def _copy_cookie_jar(jar):
|
||||
if hasattr(jar, 'copy'):
|
||||
# We're dealing with an instance of RequestsCookieJar
|
||||
return jar.copy()
|
||||
|
||||
# We're dealing with a generic CookieJar instance
|
||||
new_jar = copy.copy(jar)
|
||||
new_jar.clear()
|
||||
@@ -440,21 +471,21 @@ def create_cookie(name, value, **kwargs):
|
||||
By default, the pair of `name` and `value` will be set for the domain ''
|
||||
and sent on every request (this is sometimes called a "supercookie").
|
||||
"""
|
||||
result = dict(
|
||||
version=0,
|
||||
name=name,
|
||||
value=value,
|
||||
port=None,
|
||||
domain='',
|
||||
path='/',
|
||||
secure=False,
|
||||
expires=None,
|
||||
discard=True,
|
||||
comment=None,
|
||||
comment_url=None,
|
||||
rest={'HttpOnly': None},
|
||||
rfc2109=False,)
|
||||
|
||||
result = {
|
||||
'version': 0,
|
||||
'name': name,
|
||||
'value': value,
|
||||
'port': None,
|
||||
'domain': '',
|
||||
'path': '/',
|
||||
'secure': False,
|
||||
'expires': None,
|
||||
'discard': True,
|
||||
'comment': None,
|
||||
'comment_url': None,
|
||||
'rest': {'HttpOnly': None},
|
||||
'rfc2109': False,
|
||||
}
|
||||
badargs = set(kwargs) - set(result)
|
||||
if badargs:
|
||||
err = 'create_cookie() got unexpected keyword arguments: %s'
|
||||
@@ -465,19 +496,18 @@ def create_cookie(name, value, **kwargs):
|
||||
result['domain_specified'] = bool(result['domain'])
|
||||
result['domain_initial_dot'] = result['domain'].startswith('.')
|
||||
result['path_specified'] = bool(result['path'])
|
||||
|
||||
return cookielib.Cookie(**result)
|
||||
|
||||
|
||||
def morsel_to_cookie(morsel):
|
||||
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
|
||||
|
||||
expires = None
|
||||
if morsel['max-age']:
|
||||
try:
|
||||
expires = int(time.time() + int(morsel['max-age']))
|
||||
except ValueError:
|
||||
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
|
||||
|
||||
elif morsel['expires']:
|
||||
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
|
||||
expires = calendar.timegm(
|
||||
@@ -510,13 +540,11 @@ def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
|
||||
"""
|
||||
if cookiejar is None:
|
||||
cookiejar = RequestsCookieJar()
|
||||
|
||||
if cookie_dict is not None:
|
||||
names_from_jar = [cookie.name for cookie in cookiejar]
|
||||
for name in cookie_dict:
|
||||
if overwrite or (name not in names_from_jar):
|
||||
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
|
||||
|
||||
return cookiejar
|
||||
|
||||
|
||||
@@ -531,12 +559,12 @@ def merge_cookies(cookiejar, cookies):
|
||||
|
||||
if isinstance(cookies, dict):
|
||||
cookiejar = cookiejar_from_dict(
|
||||
cookies, cookiejar=cookiejar, overwrite=False)
|
||||
cookies, cookiejar=cookiejar, overwrite=False
|
||||
)
|
||||
elif isinstance(cookies, cookielib.CookieJar):
|
||||
try:
|
||||
cookiejar.update(cookies)
|
||||
except AttributeError:
|
||||
for cookie_in_jar in cookies:
|
||||
cookiejar.set_cookie(cookie_in_jar)
|
||||
|
||||
return cookiejar
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.exceptions
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -19,8 +18,11 @@ class RequestException(IOError):
|
||||
response = kwargs.pop('response', None)
|
||||
self.response = response
|
||||
self.request = kwargs.pop('request', None)
|
||||
if (response is not None and not self.request and
|
||||
hasattr(response, 'request')):
|
||||
if (
|
||||
response is not None and
|
||||
not self.request and
|
||||
hasattr(response, 'request')
|
||||
):
|
||||
self.request = self.response.request
|
||||
super(RequestException, self).__init__(*args, **kwargs)
|
||||
|
||||
@@ -69,12 +71,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):
|
||||
@@ -104,9 +106,14 @@ class RetryError(RequestException):
|
||||
class UnrewindableBodyError(RequestException):
|
||||
"""Requests encountered an error when trying to rewind a body"""
|
||||
|
||||
|
||||
class InvalidBodyError(RequestException, ValueError):
|
||||
"""An invalid request body was specified"""
|
||||
|
||||
|
||||
|
||||
|
||||
# Warnings
|
||||
|
||||
|
||||
class RequestsWarning(Warning):
|
||||
"""Base warning for Requests."""
|
||||
pass
|
||||
|
||||
@@ -10,10 +10,12 @@ import idna
|
||||
import urllib3
|
||||
import chardet
|
||||
|
||||
from . import __version__ as requests_version
|
||||
from .import types
|
||||
|
||||
from .import __version__ as requests_version
|
||||
|
||||
try:
|
||||
from .packages.urllib3.contrib import pyopenssl
|
||||
from . packages.urllib3.contrib import pyopenssl
|
||||
except ImportError:
|
||||
pyopenssl = None
|
||||
OpenSSL = None
|
||||
@@ -23,7 +25,7 @@ else:
|
||||
import cryptography
|
||||
|
||||
|
||||
def _implementation():
|
||||
def _implementation() -> types.Help:
|
||||
"""Return a dict with the Python implementation and version.
|
||||
|
||||
Provide both the name and the version of the Python implementation
|
||||
@@ -35,66 +37,51 @@ def _implementation():
|
||||
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)
|
||||
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
|
||||
])
|
||||
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():
|
||||
def info() -> types.Help:
|
||||
"""Generate information for a bug report."""
|
||||
try:
|
||||
platform_info = {
|
||||
'system': platform.system(),
|
||||
'release': platform.release(),
|
||||
'system': platform.system(), 'release': platform.release()
|
||||
}
|
||||
except IOError:
|
||||
platform_info = {
|
||||
'system': 'Unknown',
|
||||
'release': 'Unknown',
|
||||
}
|
||||
|
||||
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': '',
|
||||
}
|
||||
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__', ''),
|
||||
}
|
||||
idna_info = {
|
||||
'version': getattr(idna, '__version__', ''),
|
||||
}
|
||||
|
||||
cryptography_info = {'version': getattr(cryptography, '__version__', '')}
|
||||
idna_info = {'version': getattr(idna, '__version__', '')}
|
||||
# OPENSSL_VERSION_NUMBER doesn't exist in the Python 2.6 ssl module.
|
||||
system_ssl = getattr(ssl, 'OPENSSL_VERSION_NUMBER', None)
|
||||
system_ssl_info = {
|
||||
'version': '%x' % system_ssl if system_ssl is not None else ''
|
||||
}
|
||||
|
||||
return {
|
||||
'platform': platform_info,
|
||||
'implementation': implementation_info,
|
||||
@@ -105,9 +92,7 @@ def info():
|
||||
'chardet': chardet_info,
|
||||
'cryptography': cryptography_info,
|
||||
'idna': idna_info,
|
||||
'requests': {
|
||||
'version': requests_version,
|
||||
},
|
||||
'requests': {'version': requests_version},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.hooks
|
||||
~~~~~~~~~~~~~~
|
||||
@@ -15,14 +14,15 @@ HOOKS = ['response']
|
||||
|
||||
|
||||
def default_hooks():
|
||||
return dict((event, []) for event in HOOKS)
|
||||
return {event: [] for event in HOOKS}
|
||||
|
||||
|
||||
|
||||
|
||||
# TODO: response is the only one
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data, **kwargs):
|
||||
"""Dispatches a hook dictionary on a given piece of data."""
|
||||
hooks = hooks or dict()
|
||||
hooks = hooks or {}
|
||||
hooks = hooks.get(key)
|
||||
if hooks:
|
||||
if hasattr(hooks, '__call__'):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
import sys
|
||||
|
||||
# This code exists for backwards compatibility reasons.
|
||||
# I don't like it either. Just look the other way. :)
|
||||
|
||||
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]
|
||||
|
||||
# Kinda cool, though, right?
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.session
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -10,26 +9,42 @@ requests (cookies, auth, proxies).
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
from collections import Mapping
|
||||
from collections import Mapping, OrderedDict
|
||||
from datetime import timedelta
|
||||
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse
|
||||
from .basics import cookielib, urljoin, urlparse, 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
|
||||
requote_uri,
|
||||
get_environ_proxies,
|
||||
get_netrc_auth,
|
||||
should_bypass_proxies,
|
||||
get_auth_from_url,
|
||||
is_valid_location,
|
||||
rewind_body,
|
||||
)
|
||||
|
||||
from .status_codes import codes
|
||||
@@ -39,7 +54,7 @@ from .models import REDIRECT_STATI
|
||||
|
||||
# Preferred clock, based on which one is more accurate on a given system.
|
||||
if platform.system() == 'Windows':
|
||||
try: # Python 3.3+
|
||||
try: # Python 3.4+
|
||||
preferred_clock = time.perf_counter
|
||||
except AttributeError: # Earlier than Python 3.
|
||||
preferred_clock = time.clock
|
||||
@@ -50,9 +65,8 @@ 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:
|
||||
return request_setting
|
||||
|
||||
@@ -61,20 +75,18 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||
|
||||
# Bypass if not a dictionary (e.g. verify)
|
||||
if not (
|
||||
isinstance(session_setting, Mapping) and
|
||||
isinstance(request_setting, Mapping)
|
||||
isinstance(session_setting, Mapping) and
|
||||
isinstance(request_setting, Mapping)
|
||||
):
|
||||
return request_setting
|
||||
|
||||
merged_setting = dict_class(to_key_val_list(session_setting))
|
||||
merged_setting.update(to_key_val_list(request_setting))
|
||||
|
||||
# Remove keys that are set to None. Extract keys first to avoid altering
|
||||
# the dictionary during iteration.
|
||||
none_keys = [k for (k, v) in merged_setting.items() if v is None]
|
||||
for key in none_keys:
|
||||
del merged_setting[key]
|
||||
|
||||
return merged_setting
|
||||
|
||||
|
||||
@@ -95,7 +107,7 @@ 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``"""
|
||||
# Due to the nature of how requests processes redirects this method will
|
||||
# be called at least once upon the original response and at least twice
|
||||
@@ -103,91 +115,108 @@ class SessionRedirectMixin(object):
|
||||
# If a custom mixin is used to handle this logic, it may be advantageous
|
||||
# to cache the redirect location onto the response object as a private
|
||||
# attribute.
|
||||
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.
|
||||
# It is more likely to get UTF8 header rather than latin1.
|
||||
# This causes incorrect handling of UTF8 encoded location headers.
|
||||
# To solve this, we re-encode the location in latin1.
|
||||
if is_py3:
|
||||
location = location.encode('latin1')
|
||||
location = location.encode('latin1')
|
||||
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."""
|
||||
|
||||
hist = [] # keep track of history
|
||||
|
||||
url = self.get_redirect_target(resp)
|
||||
while url:
|
||||
prepared_request = req.copy()
|
||||
|
||||
# Update history and keep track of redirects.
|
||||
# resp.history must ignore the original request in this loop
|
||||
hist.append(resp)
|
||||
resp.history = hist[1:]
|
||||
|
||||
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.
|
||||
"""
|
||||
history = [
|
||||
response
|
||||
] # keep track of history; seed it with the original response
|
||||
location_url = self.get_redirect_target(response)
|
||||
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)
|
||||
|
||||
if len(resp.history) >= self.max_redirects:
|
||||
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
|
||||
response.content # Consume socket so it can be released
|
||||
except (
|
||||
ChunkedEncodingError,
|
||||
ConnectionError,
|
||||
ContentDecodingError,
|
||||
RuntimeError,
|
||||
):
|
||||
response.raw.read(decode_content=False)
|
||||
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)
|
||||
|
||||
prepared_request.url = to_native_string(url)
|
||||
|
||||
self.rebuild_method(prepared_request, resp)
|
||||
|
||||
# https://github.com/requests/requests/issues/1084
|
||||
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
|
||||
location_url = requote_uri(location_url)
|
||||
prepared_request.url = to_native_string(location_url)
|
||||
method_changed = self.rebuild_method(prepared_request, response)
|
||||
# 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')
|
||||
purged_headers = (
|
||||
'Content-Length', 'Content-Type', 'Transfer-Encoding'
|
||||
)
|
||||
for header in purged_headers:
|
||||
prepared_request.headers.pop(header, None)
|
||||
prepared_request.body = None
|
||||
|
||||
headers = prepared_request.headers
|
||||
try:
|
||||
del headers['Cookie']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# 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
|
||||
# UnrewindableBodyError, instead of hanging the connection.
|
||||
@@ -195,65 +224,63 @@ class SessionRedirectMixin(object):
|
||||
prepared_request._body_position is not None and
|
||||
('Content-Length' in headers or 'Transfer-Encoding' in headers)
|
||||
)
|
||||
|
||||
# Attempt to rewind consumed file-like object.
|
||||
if rewindable:
|
||||
rewind_body(prepared_request)
|
||||
|
||||
# Override the original request.
|
||||
req = prepared_request
|
||||
|
||||
request = prepared_request
|
||||
if yield_requests:
|
||||
yield req
|
||||
else:
|
||||
yield request
|
||||
|
||||
resp = self.send(
|
||||
req,
|
||||
else:
|
||||
response = self.send(
|
||||
request,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
proxies=proxies,
|
||||
allow_redirects=False,
|
||||
**adapter_kwargs
|
||||
**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, response.raw
|
||||
)
|
||||
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, resp.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
|
||||
url = prepared_request.url
|
||||
|
||||
if 'Authorization' in headers:
|
||||
# If we get redirected to a new host, we should strip out any
|
||||
# authentication headers.
|
||||
original_parsed = urlparse(response.request.url)
|
||||
redirect_parsed = urlparse(url)
|
||||
|
||||
if (original_parsed.hostname != redirect_parsed.hostname):
|
||||
del headers['Authorization']
|
||||
|
||||
# .netrc might have more auth for us on our new host.
|
||||
new_auth = get_netrc_auth(url) if self.trust_env else None
|
||||
if new_auth is not None:
|
||||
prepared_request.prepare_auth(new_auth)
|
||||
|
||||
return
|
||||
|
||||
def rebuild_proxies(self, prepared_request, proxies):
|
||||
"""This method re-evaluates the proxy configuration by considering the
|
||||
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.
|
||||
@@ -266,50 +293,46 @@ class SessionRedirectMixin(object):
|
||||
scheme = urlparse(url).scheme
|
||||
new_proxies = proxies.copy()
|
||||
no_proxy = proxies.get('no_proxy')
|
||||
|
||||
bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
|
||||
if self.trust_env and not bypass_proxy:
|
||||
environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
|
||||
|
||||
proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
|
||||
|
||||
if proxy:
|
||||
new_proxies.setdefault(scheme, proxy)
|
||||
|
||||
if 'Proxy-Authorization' in headers:
|
||||
del headers['Proxy-Authorization']
|
||||
|
||||
try:
|
||||
username, password = get_auth_from_url(new_proxies[scheme])
|
||||
except KeyError:
|
||||
username, password = None, None
|
||||
|
||||
if username and password:
|
||||
headers['Proxy-Authorization'] = _basic_auth_str(username, password)
|
||||
|
||||
headers['Proxy-Authorization'] = _basic_auth_str(
|
||||
username, password
|
||||
)
|
||||
return new_proxies
|
||||
|
||||
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.
|
||||
"""
|
||||
method = prepared_request.method
|
||||
|
||||
:rtype bool:
|
||||
:return: boolean expressing if the method changed during rebuild.
|
||||
"""
|
||||
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':
|
||||
# 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'
|
||||
|
||||
# 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':
|
||||
method = 'GET'
|
||||
|
||||
prepared_request.method = method
|
||||
return method != original_method
|
||||
|
||||
|
||||
class Session(SessionRedirectMixin):
|
||||
@@ -330,63 +353,62 @@ class Session(SessionRedirectMixin):
|
||||
>>> s.get('http://httpbin.org/get')
|
||||
<Response [200]>
|
||||
"""
|
||||
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
|
||||
'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
|
||||
__slots__ = [
|
||||
'headers',
|
||||
'cookies',
|
||||
'auth',
|
||||
'proxies',
|
||||
'hooks',
|
||||
'params',
|
||||
'verify',
|
||||
'cert',
|
||||
'prefetch',
|
||||
'adapters',
|
||||
'stream',
|
||||
'trust_env',
|
||||
'max_redirects',
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
__slots__
|
||||
|
||||
#: A case-insensitive dictionary of headers to be sent on each
|
||||
def __init__(self):
|
||||
# : A case-insensitive dictionary of headers to be sent on each
|
||||
#: :class:`Request <Request>` sent from this
|
||||
#: :class:`Session <Session>`.
|
||||
self.headers = default_headers()
|
||||
|
||||
#: Default Authentication tuple or object to attach to
|
||||
# : Default Authentication tuple or object to attach to
|
||||
#: :class:`Request <Request>`.
|
||||
self.auth = None
|
||||
|
||||
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
|
||||
# : Dictionary mapping protocol or protocol and host to the URL of the proxy
|
||||
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
|
||||
#: be used on each :class:`Request <Request>`.
|
||||
self.proxies = {}
|
||||
|
||||
#: Event-handling hooks.
|
||||
# : Event-handling hooks.
|
||||
self.hooks = default_hooks()
|
||||
|
||||
#: Dictionary of querystring data to attach to each
|
||||
# : Dictionary of querystring data to attach to each
|
||||
#: :class:`Request <Request>`. The dictionary values may be lists for
|
||||
#: representing multivalued query parameters.
|
||||
self.params = {}
|
||||
|
||||
#: Stream response content default.
|
||||
# : Stream response content default.
|
||||
self.stream = False
|
||||
|
||||
#: SSL Verification default.
|
||||
# : SSL Verification default.
|
||||
self.verify = True
|
||||
|
||||
#: SSL client certificate default, if String, path to ssl client
|
||||
# : SSL client certificate default, if String, path to ssl client
|
||||
#: cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
self.cert = None
|
||||
|
||||
#: Maximum number of redirects allowed. If the request exceeds this
|
||||
# : Maximum number of redirects allowed. If the request exceeds this
|
||||
#: limit, a :class:`TooManyRedirects` exception is raised.
|
||||
#: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
|
||||
#: 30.
|
||||
self.max_redirects = DEFAULT_REDIRECT_LIMIT
|
||||
|
||||
#: Trust environment settings for proxy configuration, default
|
||||
# : Trust environment settings for proxy configuration, default
|
||||
#: authentication and similar.
|
||||
self.trust_env = True
|
||||
|
||||
#: A CookieJar containing all currently outstanding cookies set on this
|
||||
# : A CookieJar containing all currently outstanding cookies set on this
|
||||
#: session. By default it is a
|
||||
#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
|
||||
#: may be any other ``cookielib.CookieJar`` compatible object.
|
||||
self.cookies = cookiejar_from_dict({})
|
||||
|
||||
# Default connection adapters.
|
||||
self.adapters = OrderedDict()
|
||||
self.mount('https://', HTTPAdapter())
|
||||
@@ -405,24 +427,20 @@ 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 {}
|
||||
|
||||
# Bootstrap CookieJar.
|
||||
if not isinstance(cookies, cookielib.CookieJar):
|
||||
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
|
||||
if self.trust_env and not auth and not self.auth:
|
||||
auth = get_netrc_auth(request.url)
|
||||
|
||||
p = PreparedRequest()
|
||||
p.prepare(
|
||||
method=request.method.upper(),
|
||||
@@ -430,7 +448,9 @@ class Session(SessionRedirectMixin):
|
||||
files=request.files,
|
||||
data=request.data,
|
||||
json=request.json,
|
||||
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
|
||||
headers=merge_setting(
|
||||
request.headers, self.headers, dict_class=CaseInsensitiveDict
|
||||
),
|
||||
params=merge_setting(request.params, self.params),
|
||||
auth=merge_setting(auth, self.auth),
|
||||
cookies=merged_cookies,
|
||||
@@ -438,11 +458,26 @@ 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.
|
||||
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.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
@@ -492,21 +527,14 @@ class Session(SessionRedirectMixin):
|
||||
hooks=hooks,
|
||||
)
|
||||
prep = self.prepare_request(req)
|
||||
|
||||
proxies = proxies or {}
|
||||
|
||||
settings = self.merge_environment_settings(
|
||||
prep.url, proxies, stream, verify, cert
|
||||
)
|
||||
|
||||
# Send the request.
|
||||
send_kwargs = {
|
||||
'timeout': timeout,
|
||||
'allow_redirects': allow_redirects,
|
||||
}
|
||||
send_kwargs = {'timeout': timeout, 'allow_redirects': allow_redirects}
|
||||
send_kwargs.update(settings)
|
||||
resp = self.send(prep, **send_kwargs)
|
||||
|
||||
return resp
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
@@ -516,7 +544,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('GET', url, **kwargs)
|
||||
|
||||
@@ -527,7 +554,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('OPTIONS', url, **kwargs)
|
||||
|
||||
@@ -538,7 +564,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return self.request('HEAD', url, **kwargs)
|
||||
|
||||
@@ -551,7 +576,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('POST', url, data=data, json=json, **kwargs)
|
||||
|
||||
def put(self, url, data=None, **kwargs):
|
||||
@@ -562,7 +586,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('PUT', url, data=data, **kwargs)
|
||||
|
||||
def patch(self, url, data=None, **kwargs):
|
||||
@@ -573,7 +596,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('PATCH', url, data=data, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
@@ -583,7 +605,6 @@ class Session(SessionRedirectMixin):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return self.request('DELETE', url, **kwargs)
|
||||
|
||||
def send(self, request, **kwargs):
|
||||
@@ -597,66 +618,52 @@ class Session(SessionRedirectMixin):
|
||||
kwargs.setdefault('verify', self.verify)
|
||||
kwargs.setdefault('cert', self.cert)
|
||||
kwargs.setdefault('proxies', self.proxies)
|
||||
|
||||
# It's possible that users might accidentally send a Request object.
|
||||
# Guard against that specific failure case.
|
||||
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
|
||||
|
||||
# Get the appropriate adapter to use
|
||||
adapter = self.get_adapter(url=request.url)
|
||||
|
||||
# Start time (approximately) of the request
|
||||
start = preferred_clock()
|
||||
|
||||
# Send the request
|
||||
r = adapter.send(request, **kwargs)
|
||||
|
||||
# Total elapsed time of the request (approximately)
|
||||
elapsed = preferred_clock() - start
|
||||
r.elapsed = timedelta(seconds=elapsed)
|
||||
|
||||
# Response manipulation hooks
|
||||
# Response manipulation hooks.
|
||||
r = dispatch_hook('response', hooks, r, **kwargs)
|
||||
|
||||
# Persist cookies
|
||||
if r.history:
|
||||
|
||||
# If the hooks create history then we want those cookies too
|
||||
for resp in r.history:
|
||||
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
|
||||
|
||||
extract_cookies_to_jar(self.cookies, request, r.raw)
|
||||
|
||||
# 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:
|
||||
try:
|
||||
r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
|
||||
r._next = next(
|
||||
self.resolve_redirects(
|
||||
r, request, yield_requests=True, **kwargs
|
||||
)
|
||||
)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
if not stream:
|
||||
r.content
|
||||
|
||||
return r
|
||||
|
||||
def merge_environment_settings(self, url, proxies, stream, verify, cert):
|
||||
@@ -665,28 +672,41 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
# Gather clues from the surrounding environment.
|
||||
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'))
|
||||
|
||||
# Merge all the kwargs.
|
||||
proxies = merge_setting(proxies, self.proxies)
|
||||
# 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)
|
||||
|
||||
return {'verify': verify, 'proxies': proxies, 'stream': stream,
|
||||
'cert': 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:
|
||||
# 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') or
|
||||
verify
|
||||
)
|
||||
# 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,
|
||||
}
|
||||
|
||||
def get_adapter(self, url):
|
||||
"""
|
||||
@@ -695,15 +715,14 @@ class Session(SessionRedirectMixin):
|
||||
:rtype: requests.adapters.BaseAdapter
|
||||
"""
|
||||
for (prefix, adapter) in self.adapters.items():
|
||||
|
||||
if url.lower().startswith(prefix):
|
||||
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()
|
||||
|
||||
@@ -714,24 +733,13 @@ class Session(SessionRedirectMixin):
|
||||
"""
|
||||
self.adapters[prefix] = adapter
|
||||
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
|
||||
|
||||
for key in keys_to_move:
|
||||
self.adapters[key] = self.adapters.pop(key)
|
||||
|
||||
def __getstate__(self):
|
||||
state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
state = {attr: getattr(self, attr, None) for attr in self.__slots__}
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
|
||||
def session():
|
||||
"""
|
||||
Returns a :class:`Session` for context-management.
|
||||
|
||||
:rtype: Session
|
||||
"""
|
||||
|
||||
return Session()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .structures import LookupDict
|
||||
|
||||
_codes = {
|
||||
|
||||
# Informational.
|
||||
100: ('continue',),
|
||||
101: ('switching_protocols',),
|
||||
@@ -20,7 +18,6 @@ _codes = {
|
||||
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
|
||||
208: ('already_reported',),
|
||||
226: ('im_used',),
|
||||
|
||||
# Redirection.
|
||||
300: ('multiple_choices',),
|
||||
301: ('moved_permanently', 'moved', '\\o-'),
|
||||
@@ -30,9 +27,8 @@ _codes = {
|
||||
305: ('use_proxy',),
|
||||
306: ('switch_proxy',),
|
||||
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
|
||||
308: ('permanent_redirect',
|
||||
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
|
||||
|
||||
308: ('permanent_redirect', 'resume_incomplete', 'resume'),
|
||||
# These 2 to be removed in 3.0
|
||||
# Client Error.
|
||||
400: ('bad_request', 'bad'),
|
||||
401: ('unauthorized',),
|
||||
@@ -41,7 +37,9 @@ _codes = {
|
||||
404: ('not_found', '-o-'),
|
||||
405: ('method_not_allowed', 'not_allowed'),
|
||||
406: ('not_acceptable',),
|
||||
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
|
||||
407: (
|
||||
'proxy_authentication_required', 'proxy_auth', 'proxy_authentication'
|
||||
),
|
||||
408: ('request_timeout', 'timeout'),
|
||||
409: ('conflict',),
|
||||
410: ('gone',),
|
||||
@@ -50,7 +48,11 @@ _codes = {
|
||||
413: ('request_entity_too_large',),
|
||||
414: ('request_uri_too_large',),
|
||||
415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
|
||||
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
|
||||
416: (
|
||||
'requested_range_not_satisfiable',
|
||||
'requested_range',
|
||||
'range_not_satisfiable',
|
||||
),
|
||||
417: ('expectation_failed',),
|
||||
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
|
||||
421: ('misdirected_request',),
|
||||
@@ -67,7 +69,6 @@ _codes = {
|
||||
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
|
||||
451: ('unavailable_for_legal_reasons', 'legal_reasons'),
|
||||
499: ('client_closed_request',),
|
||||
|
||||
# Server Error.
|
||||
500: ('internal_server_error', 'server_error', '/o\\', '✗'),
|
||||
501: ('not_implemented',),
|
||||
@@ -79,13 +80,15 @@ _codes = {
|
||||
507: ('insufficient_storage',),
|
||||
509: ('bandwidth_limit_exceeded', 'bandwidth'),
|
||||
510: ('not_extended',),
|
||||
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
|
||||
511: (
|
||||
'network_authentication_required',
|
||||
'network_auth',
|
||||
'network_authentication',
|
||||
),
|
||||
}
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for code, titles in _codes.items():
|
||||
for title in titles:
|
||||
for title in titles: # type: ignore
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith(('\\', '/')):
|
||||
setattr(codes, title.upper(), code)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.structures
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -9,7 +8,7 @@ Data structures that power Requests.
|
||||
|
||||
import collections
|
||||
|
||||
from .compat import OrderedDict
|
||||
from .basics import basestring, OrderedDict
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
@@ -38,9 +37,10 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
operations are given keys that have equal ``.lower()``s, the
|
||||
behavior is undefined.
|
||||
"""
|
||||
__slots__ = ('_store')
|
||||
|
||||
def __init__(self, data=None, **kwargs):
|
||||
self._store = OrderedDict()
|
||||
self._store = collections.OrderedDict()
|
||||
if data is None:
|
||||
data = {}
|
||||
self.update(data, **kwargs)
|
||||
@@ -65,9 +65,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
def lower_items(self):
|
||||
"""Like iteritems(), but with all lowercase keys."""
|
||||
return (
|
||||
(lowerkey, keyval[1])
|
||||
for (lowerkey, keyval)
|
||||
in self._store.items()
|
||||
(lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
@@ -75,9 +73,11 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
# Compare insensitively
|
||||
return dict(self.lower_items()) == dict(other.lower_items())
|
||||
|
||||
|
||||
# Copy is required
|
||||
def copy(self):
|
||||
return CaseInsensitiveDict(self._store.values())
|
||||
@@ -86,6 +86,128 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
return str(dict(self.items()))
|
||||
|
||||
|
||||
class HTTPHeaderDict(CaseInsensitiveDict):
|
||||
"""A case-insensitive ``dict``-like object suitable for HTTP headers that
|
||||
supports multiple values with the same key, via the ``add``, ``extend``,
|
||||
``multiget`` and ``multiset`` methods.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, **kwargs):
|
||||
super(HTTPHeaderDict, self).__init__()
|
||||
self.extend({} if data is None else data, **kwargs)
|
||||
|
||||
|
||||
# We'll store tuples in the internal dictionary, but present them as a
|
||||
# concatenated string when we use item access methods.
|
||||
#
|
||||
def __setitem__(self, key, val):
|
||||
# Special–case null values.
|
||||
if (not isinstance(val, basestring)) and (val is not None):
|
||||
raise ValueError('only string-type values (or None) are allowed')
|
||||
|
||||
super(HTTPHeaderDict, self).__setitem__(key, (val,))
|
||||
|
||||
def __getitem__(self, key):
|
||||
val = super(HTTPHeaderDict, self).__getitem__(key)
|
||||
# Special–case null values.
|
||||
if len(val) == 1 and val[0] is None:
|
||||
return val[0]
|
||||
|
||||
return ', '.join(val)
|
||||
|
||||
def lower_items(self):
|
||||
return (
|
||||
(lk, ', '.join(vals)) for (lk, (k, vals)) in self._store.items()
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
return type(self)(self)
|
||||
|
||||
def getlist(self, key):
|
||||
"""Returns a list of all the values for the named field. Returns an
|
||||
empty list if the key isn't present in the dictionary."""
|
||||
return list(self._store.get(key.lower(), (None, []))[1])
|
||||
|
||||
def setlist(self, key, values):
|
||||
"""Set a sequence of strings to the associated key - this will overwrite
|
||||
any previously stored value."""
|
||||
if not isinstance(values, (list, tuple)):
|
||||
raise ValueError('argument is not sequence')
|
||||
|
||||
if any(not isinstance(v, basestring) for v in values):
|
||||
raise ValueError('non-string items in sequence')
|
||||
|
||||
if not values:
|
||||
self.pop(key, None)
|
||||
return
|
||||
|
||||
super(HTTPHeaderDict, self).__setitem__(key, tuple(values))
|
||||
|
||||
def _extend(self, key, values):
|
||||
new_value_tpl = key, values
|
||||
# Inspired by urllib3's implementation - use one call which should be
|
||||
# suitable for the common case.
|
||||
old_value_tpl = self._store.setdefault(key.lower(), new_value_tpl)
|
||||
if old_value_tpl is not new_value_tpl:
|
||||
old_key, old_values = old_value_tpl
|
||||
self._store[key.lower()] = (old_key, old_values + values)
|
||||
|
||||
def add(self, key, val):
|
||||
"""Adds a key, value pair to this dictionary - if there is already a
|
||||
value for this key, then the value will be appended to those values.
|
||||
"""
|
||||
if not isinstance(val, basestring):
|
||||
raise ValueError('value must be a string-type object')
|
||||
|
||||
self._extend(key, (val,))
|
||||
|
||||
def extend(self, *args, **kwargs):
|
||||
"""Like update, but will add values to existing sequences rather than
|
||||
replacing them. You can pass a mapping object or a sequence of two
|
||||
tuples - values in these objects can be strings or sequence of strings.
|
||||
"""
|
||||
if len(args) > 1:
|
||||
raise TypeError(
|
||||
f"extend() takes at most 1 positional "
|
||||
"arguments ({len(args)} given)"
|
||||
)
|
||||
|
||||
for other in args + (kwargs,):
|
||||
if isinstance(other, collections.Mapping):
|
||||
# See if looks like a HTTPHeaderDict (either urllib3's
|
||||
# implementation or ours). If so, then we have to add values
|
||||
# in one go for each key.
|
||||
multiget = getattr(other, 'getlist', None)
|
||||
if multiget:
|
||||
for key in other:
|
||||
self._extend(key, tuple(multiget(key)))
|
||||
continue
|
||||
|
||||
# Otherwise, just walk over items to get them.
|
||||
item_seq = other.items()
|
||||
else:
|
||||
item_seq = other
|
||||
for ik, iv in item_seq:
|
||||
if isinstance(iv, basestring):
|
||||
self._extend(ik, (iv,))
|
||||
elif any(not isinstance(v, basestring) for v in iv):
|
||||
raise ValueError('non-string items in sequence')
|
||||
|
||||
else:
|
||||
self._extend(ik, tuple(iv))
|
||||
|
||||
@property
|
||||
def _as_dict(self):
|
||||
"""A dictionary representation of the HTTPHeaderDict."""
|
||||
d = {}
|
||||
for k, vals in self._store.values():
|
||||
d[k] = vals[0] if len(vals) == 1 else vals
|
||||
return d
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._as_dict)
|
||||
|
||||
|
||||
class LookupDict(dict):
|
||||
"""Dictionary lookup object."""
|
||||
|
||||
@@ -94,12 +216,14 @@ class LookupDict(dict):
|
||||
super(LookupDict, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return '<lookup \'%s\'>' % (self.name)
|
||||
return f'<lookup \'{self.name}\'>'
|
||||
|
||||
def __getitem__(self, key):
|
||||
# We allow fall-through here, so values default to None
|
||||
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def __iter__(self):
|
||||
return super(LookupDict, self).__dir__()
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__dict__.get(key, default)
|
||||
|
||||
72
requests/types.py
Normal file
72
requests/types.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from typing import (
|
||||
Callable,
|
||||
Optional,
|
||||
Union,
|
||||
Any,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Tuple,
|
||||
IO,
|
||||
Text,
|
||||
Type,
|
||||
Dict,
|
||||
)
|
||||
|
||||
from .import auth
|
||||
from .models import Response, PreparedRequest
|
||||
from .cookies import RequestsCookieJar
|
||||
from .sessions import Session
|
||||
|
||||
_ParamsMappingValueType = Union[
|
||||
str, bytes, int, float, Iterable[Union[str, bytes, int, float]]
|
||||
]
|
||||
Params = Optional[
|
||||
Union[
|
||||
Mapping[Union[str, bytes, int, float], _ParamsMappingValueType],
|
||||
Union[str, bytes],
|
||||
Tuple[Union[str, bytes, int, float], _ParamsMappingValueType],
|
||||
Mapping[str, _ParamsMappingValueType],
|
||||
Mapping[bytes, _ParamsMappingValueType],
|
||||
Mapping[int, _ParamsMappingValueType],
|
||||
Mapping[float, _ParamsMappingValueType],
|
||||
]
|
||||
]
|
||||
Data = Union[
|
||||
None,
|
||||
bytes,
|
||||
MutableMapping[str, str],
|
||||
MutableMapping[str, Text],
|
||||
MutableMapping[Text, str],
|
||||
MutableMapping[Text, Text],
|
||||
Iterable[Tuple[str, str]],
|
||||
IO,
|
||||
]
|
||||
_Hook = Callable[[Response], Any]
|
||||
Method = str
|
||||
URL = str
|
||||
Headers = Optional[Union[None, MutableMapping[Text, Text]]]
|
||||
Cookies = Optional[Union[None, RequestsCookieJar, MutableMapping[Text, Text]]]
|
||||
Files = Optional[MutableMapping[Text, IO]]
|
||||
Auth = Union[
|
||||
None,
|
||||
Tuple[Text, Text],
|
||||
auth.AuthBase,
|
||||
Callable[[PreparedRequest], PreparedRequest],
|
||||
]
|
||||
Timeout = Union[None, float, Tuple[float, float]]
|
||||
AllowRedirects = Optional[bool]
|
||||
Proxies = Optional[MutableMapping[Text, Text]]
|
||||
Hooks = Optional[MutableMapping[Text, Union[Iterable[_Hook], _Hook]]]
|
||||
Stream = Optional[bool]
|
||||
Verify = Union[None, bool, Text]
|
||||
Cert = Union[Text, Tuple[Text, Text]]
|
||||
JSON = Optional[MutableMapping]
|
||||
Help = Dict
|
||||
Host = str
|
||||
Sequence = List
|
||||
Filename = str
|
||||
KeyValueList = List[Tuple[Text, Text]]
|
||||
String = str
|
||||
Boolean = Union[bool, None]
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.utils
|
||||
~~~~~~~~~~~~~~
|
||||
@@ -19,43 +18,57 @@ import re
|
||||
import socket
|
||||
import struct
|
||||
import warnings
|
||||
import typing
|
||||
|
||||
from .__version__ import __version__
|
||||
from . import certs
|
||||
# to_native_string is unused here, but imported here for backwards compatibility
|
||||
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_environment, getproxies_environment)
|
||||
from .import certs
|
||||
|
||||
from .basics import parse_http_list as _parse_list_header
|
||||
from .basics import (
|
||||
quote,
|
||||
urlparse,
|
||||
bytes,
|
||||
str,
|
||||
unquote,
|
||||
getproxies,
|
||||
proxy_bypass,
|
||||
urlunparse,
|
||||
basestring,
|
||||
integer_types,
|
||||
proxy_bypass_environment,
|
||||
getproxies_environment,
|
||||
)
|
||||
from .cookies import cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .structures import HTTPHeaderDict
|
||||
from .cookies import RequestsCookieJar
|
||||
from .exceptions import (
|
||||
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError)
|
||||
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError
|
||||
)
|
||||
|
||||
NETRC_FILES = ('.netrc', '_netrc')
|
||||
|
||||
DEFAULT_CA_BUNDLE_PATH = certs.where()
|
||||
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
# provide a proxy_bypass version on Windows without DNS lookups
|
||||
|
||||
def proxy_bypass_registry(host):
|
||||
if is_py3:
|
||||
import winreg
|
||||
else:
|
||||
import _winreg as winreg
|
||||
# provide a proxy_bypass version on Windows without DNS lookups
|
||||
def proxy_bypass_registry(host: str) -> bool:
|
||||
import winreg # typing: ignore
|
||||
|
||||
try:
|
||||
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
|
||||
proxyEnable = winreg.QueryValueEx(internetSettings,
|
||||
'ProxyEnable')[0]
|
||||
proxyOverride = winreg.QueryValueEx(internetSettings,
|
||||
'ProxyOverride')[0]
|
||||
internetSettings = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings',
|
||||
)
|
||||
proxyEnable = winreg.QueryValueEx(internetSettings, 'ProxyEnable')[
|
||||
0
|
||||
]
|
||||
proxyOverride = winreg.QueryValueEx(
|
||||
internetSettings, 'ProxyOverride'
|
||||
)[
|
||||
0
|
||||
]
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
if not proxyEnable or not proxyOverride:
|
||||
return False
|
||||
|
||||
@@ -68,14 +81,16 @@ if platform.system() == 'Windows':
|
||||
if test == '<local>':
|
||||
if '.' not in host:
|
||||
return True
|
||||
test = test.replace(".", r"\.") # mask dots
|
||||
test = test.replace("*", r".*") # change glob sequence
|
||||
test = test.replace("?", r".") # change glob char
|
||||
|
||||
test = test.replace(".", r"\.") # mask dots
|
||||
test = test.replace("*", r".*") # change glob sequence
|
||||
test = test.replace("?", r".") # change glob char
|
||||
if re.match(test, host, re.I):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def proxy_bypass(host): # noqa
|
||||
def proxy_bypass(host: str) -> bool: # noqa
|
||||
"""Return True, if the host should be bypassed.
|
||||
|
||||
Checks proxy settings gathered from the environment, if specified,
|
||||
@@ -83,29 +98,30 @@ if platform.system() == 'Windows':
|
||||
"""
|
||||
if getproxies_environment():
|
||||
return proxy_bypass_environment(host)
|
||||
|
||||
else:
|
||||
return proxy_bypass_registry(host)
|
||||
|
||||
|
||||
def dict_to_sequence(d):
|
||||
def dict_to_sequence(
|
||||
d: dict
|
||||
) -> typing.Union[
|
||||
typing.Optional[typing.ItemsView[typing.Any, typing.Any]], dict
|
||||
]:
|
||||
"""Returns an internal sequence dictionary update."""
|
||||
|
||||
if hasattr(d, 'items'):
|
||||
d = d.items()
|
||||
return d.items()
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def super_len(o):
|
||||
def super_len(o) -> int:
|
||||
total_length = None
|
||||
current_position = 0
|
||||
|
||||
if hasattr(o, '__len__'):
|
||||
total_length = len(o)
|
||||
|
||||
elif hasattr(o, 'len'):
|
||||
total_length = o.len
|
||||
|
||||
elif hasattr(o, 'fileno'):
|
||||
try:
|
||||
fileno = o.fileno()
|
||||
@@ -113,20 +129,20 @@ def super_len(o):
|
||||
pass
|
||||
else:
|
||||
total_length = os.fstat(fileno).st_size
|
||||
|
||||
# Having used fstat to determine the file length, we need to
|
||||
# confirm that this file was opened up in binary mode.
|
||||
if 'b' not in o.mode:
|
||||
warnings.warn((
|
||||
"Requests has determined the content-length for this "
|
||||
"request using the binary size of the file: however, the "
|
||||
"file has been opened in text mode (i.e. without the 'b' "
|
||||
"flag in the mode). This may lead to an incorrect "
|
||||
"content-length. In Requests 3.0, support will be removed "
|
||||
"for files in text mode."),
|
||||
FileModeWarning
|
||||
warnings.warn(
|
||||
(
|
||||
"Requests has determined the content-length for this "
|
||||
"request using the binary size of the file: however, the "
|
||||
"file has been opened in typing.Text mode (i.e. without the 'b' "
|
||||
"flag in the mode). This may lead to an incorrect "
|
||||
"content-length. In Requests 3.0, support will be removed "
|
||||
"for files in typing.Text mode."
|
||||
),
|
||||
FileModeWarning,
|
||||
)
|
||||
|
||||
if hasattr(o, 'tell'):
|
||||
try:
|
||||
current_position = o.tell()
|
||||
@@ -144,35 +160,32 @@ def super_len(o):
|
||||
# seek to end of file
|
||||
o.seek(0, 2)
|
||||
total_length = o.tell()
|
||||
|
||||
# seek back to current position to support
|
||||
# partially read file-like objects
|
||||
o.seek(current_position or 0)
|
||||
except (OSError, IOError):
|
||||
total_length = 0
|
||||
|
||||
if total_length is None:
|
||||
total_length = 0
|
||||
|
||||
return max(0, total_length - current_position)
|
||||
|
||||
|
||||
def get_netrc_auth(url, raise_errors=False):
|
||||
def get_netrc_auth(
|
||||
url: str, raise_errors: bool = False
|
||||
) -> typing.Optional[typing.Tuple[typing.Text, typing.Text]]:
|
||||
"""Returns the Requests tuple auth for a given url from netrc."""
|
||||
|
||||
try:
|
||||
from netrc import netrc, NetrcParseError
|
||||
|
||||
netrc_path = None
|
||||
|
||||
for f in NETRC_FILES:
|
||||
try:
|
||||
loc = os.path.expanduser('~/{0}'.format(f))
|
||||
loc = os.path.expanduser(f'~/{f}')
|
||||
except KeyError:
|
||||
# os.path.expanduser can fail when $HOME is undefined and
|
||||
# getpwuid fails. See http://bugs.python.org/issue20164 &
|
||||
# https://github.com/requests/requests/issues/1846
|
||||
return
|
||||
return None
|
||||
|
||||
if os.path.exists(loc):
|
||||
netrc_path = loc
|
||||
@@ -180,23 +193,17 @@ def get_netrc_auth(url, raise_errors=False):
|
||||
|
||||
# Abort early if there isn't one.
|
||||
if netrc_path is None:
|
||||
return
|
||||
return None
|
||||
|
||||
ri = urlparse(url)
|
||||
|
||||
# Strip port numbers from netloc. This weird `if...encode`` dance is
|
||||
# used for Python 3.2, which doesn't support unicode literals.
|
||||
splitstr = b':'
|
||||
if isinstance(url, str):
|
||||
splitstr = splitstr.decode('ascii')
|
||||
host = ri.netloc.split(splitstr)[0]
|
||||
|
||||
host = ri.netloc.split(':')[0]
|
||||
try:
|
||||
_netrc = netrc(netrc_path).authenticators(host)
|
||||
if _netrc:
|
||||
# Return with login / password
|
||||
login_i = (0 if _netrc[0] else 1)
|
||||
return (_netrc[login_i], _netrc[2])
|
||||
|
||||
except (NetrcParseError, IOError):
|
||||
# If there was a parsing error or a permissions issue reading the file,
|
||||
# we'll just skip netrc auth unless explicitly asked to raise errors.
|
||||
@@ -206,13 +213,18 @@ def get_netrc_auth(url, raise_errors=False):
|
||||
# AppEngine hackiness.
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def guess_filename(obj):
|
||||
def guess_filename(obj) -> str:
|
||||
"""Tries to guess the filename of the given object."""
|
||||
name = getattr(obj, 'name', None)
|
||||
if (name and isinstance(name, basestring) and name[0] != '<' and
|
||||
name[-1] != '>'):
|
||||
if (
|
||||
name and
|
||||
isinstance(name, basestring) and
|
||||
name[0] != '<' and
|
||||
name[-1] != '>'
|
||||
):
|
||||
return os.path.basename(name)
|
||||
|
||||
|
||||
@@ -238,10 +250,12 @@ def from_key_val_list(value):
|
||||
if isinstance(value, (str, bytes, bool, int)):
|
||||
raise ValueError('cannot encode objects that are not 2-tuples')
|
||||
|
||||
return OrderedDict(value)
|
||||
return collections.OrderedDict(value)
|
||||
|
||||
|
||||
def to_key_val_list(value):
|
||||
def to_key_val_list(
|
||||
value
|
||||
) -> typing.List[typing.Tuple[typing.Text, typing.Text]]:
|
||||
"""Take an object and test to see if it can be represented as a
|
||||
dictionary. If it can be, return a list of tuples, e.g.,
|
||||
|
||||
@@ -264,12 +278,13 @@ def to_key_val_list(value):
|
||||
|
||||
if isinstance(value, collections.Mapping):
|
||||
value = value.items()
|
||||
|
||||
return list(value)
|
||||
|
||||
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def parse_list_header(value):
|
||||
def parse_list_header(value: str) -> typing.List[typing.Text]:
|
||||
"""Parse lists as described by RFC 2068 Section 2.
|
||||
|
||||
In particular, parse comma-separated lists where the elements of
|
||||
@@ -300,8 +315,10 @@ def parse_list_header(value):
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def parse_dict_header(value):
|
||||
def parse_dict_header(value) -> dict:
|
||||
"""Parse lists of key, value pairs as described by RFC 2068 Section 2 and
|
||||
convert them into a python dict:
|
||||
|
||||
@@ -323,11 +340,12 @@ def parse_dict_header(value):
|
||||
:return: :class:`dict`
|
||||
:rtype: dict
|
||||
"""
|
||||
result = {}
|
||||
result = {} # type: dict
|
||||
for item in _parse_list_header(value):
|
||||
if '=' not in item:
|
||||
result[item] = None
|
||||
continue
|
||||
|
||||
name, value = item.split('=', 1)
|
||||
if value[:1] == value[-1:] == '"':
|
||||
value = unquote_header_value(value[1:-1])
|
||||
@@ -335,8 +353,10 @@ def parse_dict_header(value):
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def unquote_header_value(value, is_filename=False):
|
||||
def unquote_header_value(value: str, is_filename: bool = False):
|
||||
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
|
||||
This does not use the real unquoting but what browsers are actually
|
||||
using for quoting.
|
||||
@@ -350,7 +370,6 @@ def unquote_header_value(value, is_filename=False):
|
||||
# probably some other browsers as well. IE for example is
|
||||
# uploading files with "C:\foo\bar.txt" as filename
|
||||
value = value[1:-1]
|
||||
|
||||
# if this is a filename and the starting characters look like
|
||||
# a UNC path, then just return the value without quotes. Using the
|
||||
# replace sequence below on a UNC path has the effect of turning
|
||||
@@ -358,69 +377,70 @@ def unquote_header_value(value, is_filename=False):
|
||||
# _fix_ie_filename() doesn't work correctly. See #458.
|
||||
if not is_filename or value[:2] != '\\\\':
|
||||
return value.replace('\\\\', '\\').replace('\\"', '"')
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
def dict_from_cookiejar(cj: RequestsCookieJar) -> dict:
|
||||
"""Returns a key/value dictionary from a CookieJar.
|
||||
|
||||
:param cj: CookieJar object to extract cookies from.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
cookie_dict = {}
|
||||
|
||||
for cookie in cj:
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
|
||||
return cookie_dict
|
||||
|
||||
|
||||
def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
def add_dict_to_cookiejar(
|
||||
cj: RequestsCookieJar, cookie_dict: dict
|
||||
) -> RequestsCookieJar:
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cj: CookieJar to insert cookies into.
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
:rtype: CookieJar
|
||||
"""
|
||||
|
||||
return cookiejar_from_dict(cookie_dict, cj)
|
||||
|
||||
|
||||
def get_encodings_from_content(content):
|
||||
def get_encodings_from_content(content: str) -> typing.List[str]:
|
||||
"""Returns encodings from given content string.
|
||||
|
||||
:param content: bytestring to extract encodings from.
|
||||
"""
|
||||
warnings.warn((
|
||||
'In requests 3.0, get_encodings_from_content will be removed. For '
|
||||
'more information, please see the discussion on issue #2266. (This'
|
||||
' warning should only appear once.)'),
|
||||
DeprecationWarning)
|
||||
|
||||
warnings.warn(
|
||||
(
|
||||
'In requests 3.0, get_encodings_from_content will be removed. For '
|
||||
'more information, please see the discussion on issue #2266. (This'
|
||||
' warning should only appear once.)'
|
||||
),
|
||||
DeprecationWarning,
|
||||
)
|
||||
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
|
||||
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
|
||||
pragma_re = re.compile(
|
||||
r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I
|
||||
)
|
||||
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
|
||||
|
||||
return (charset_re.findall(content) +
|
||||
pragma_re.findall(content) +
|
||||
xml_re.findall(content))
|
||||
return (
|
||||
charset_re.findall(content) +
|
||||
pragma_re.findall(content) +
|
||||
xml_re.findall(content)
|
||||
)
|
||||
|
||||
|
||||
def get_encoding_from_headers(headers):
|
||||
def get_encoding_from_headers(headers: typing.MutableMapping) -> str:
|
||||
"""Returns encodings from given HTTP Header Dict.
|
||||
|
||||
:param headers: dictionary to extract encoding from.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
content_type = headers.get('content-type')
|
||||
|
||||
content_type = headers.get('Content-Type')
|
||||
if not content_type:
|
||||
return None
|
||||
|
||||
content_type, params = cgi.parse_header(content_type)
|
||||
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
|
||||
@@ -430,17 +450,12 @@ 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)
|
||||
if rv:
|
||||
yield rv
|
||||
|
||||
rv = decoder.decode(b'', final=True)
|
||||
if rv:
|
||||
yield rv
|
||||
@@ -452,58 +467,38 @@ def iter_slices(string, slice_length):
|
||||
if slice_length is None or slice_length <= 0:
|
||||
slice_length = len(string)
|
||||
while pos < len(string):
|
||||
yield string[pos:pos + slice_length]
|
||||
yield string[pos: pos + slice_length]
|
||||
|
||||
pos += slice_length
|
||||
|
||||
|
||||
def get_unicode_from_response(r):
|
||||
"""Returns the requested content back in unicode.
|
||||
|
||||
:param r: Response object to get unicode content from.
|
||||
|
||||
Tried:
|
||||
|
||||
1. charset from content-type
|
||||
2. fall back and replace all unicode characters
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
warnings.warn((
|
||||
'In requests 3.0, get_unicode_from_response will be removed. For '
|
||||
'more information, please see the discussion on issue #2266. (This'
|
||||
' warning should only appear once.)'),
|
||||
DeprecationWarning)
|
||||
|
||||
tried_encodings = []
|
||||
|
||||
# Try charset from content-type
|
||||
encoding = get_encoding_from_headers(r.headers)
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
return str(r.content, encoding)
|
||||
except UnicodeError:
|
||||
tried_encodings.append(encoding)
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
return str(r.content, encoding, errors='replace')
|
||||
except TypeError:
|
||||
return r.content
|
||||
|
||||
|
||||
# The unreserved URI characters (RFC 3986)
|
||||
UNRESERVED_SET = frozenset(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~")
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
|
||||
)
|
||||
|
||||
|
||||
def unquote_unreserved(uri):
|
||||
def unquote_unreserved(uri: str) -> str:
|
||||
"""Un-escape any percent-escape sequences in a URI that are unreserved
|
||||
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
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_bytes:
|
||||
return c.encode('ascii')
|
||||
|
||||
else:
|
||||
return c
|
||||
|
||||
# Handle both bytestrings and unicode strings.
|
||||
splitchar = '%'
|
||||
base = ''
|
||||
parts = uri.split(splitchar)
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2 and h.isalnum():
|
||||
@@ -513,15 +508,15 @@ 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=False, c=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):
|
||||
def requote_uri(uri: str) -> str:
|
||||
"""Re-quote the given URI.
|
||||
|
||||
This function passes the given URI through an unquote/quote cycle to
|
||||
@@ -536,6 +531,7 @@ def requote_uri(uri):
|
||||
# Then quote only illegal characters (do not quote reserved,
|
||||
# unreserved, or '%')
|
||||
return quote(unquote_unreserved(uri), safe=safe_with_percent)
|
||||
|
||||
except InvalidURL:
|
||||
# We couldn't unquote the given URI, so let's try quoting it, but
|
||||
# there may be unquoted '%'s in the URI. We need to make sure they're
|
||||
@@ -543,7 +539,7 @@ def requote_uri(uri):
|
||||
return quote(uri, safe=safe_without_percent)
|
||||
|
||||
|
||||
def address_in_network(ip, net):
|
||||
def address_in_network(ip: str, net: str) -> bool:
|
||||
"""This function allows you to check if an IP belongs to a network subnet
|
||||
|
||||
Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24
|
||||
@@ -553,12 +549,14 @@ def address_in_network(ip, net):
|
||||
"""
|
||||
ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0]
|
||||
netaddr, bits = net.split('/')
|
||||
netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0]
|
||||
netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[
|
||||
0
|
||||
]
|
||||
network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask
|
||||
return (ipaddr & netmask) == (network & netmask)
|
||||
return ( ipaddr & netmask) == ( network & netmask)
|
||||
|
||||
|
||||
def dotted_netmask(mask):
|
||||
def dotted_netmask(mask: str) -> str:
|
||||
"""Converts mask from /xx format to xxx.xxx.xxx.xxx
|
||||
|
||||
Example: if mask is 24 function returns 255.255.255.0
|
||||
@@ -569,7 +567,7 @@ def dotted_netmask(mask):
|
||||
return socket.inet_ntoa(struct.pack('>I', bits))
|
||||
|
||||
|
||||
def is_ipv4_address(string_ip):
|
||||
def is_ipv4_address(string_ip: str) -> bool:
|
||||
"""
|
||||
:rtype: bool
|
||||
"""
|
||||
@@ -577,10 +575,11 @@ def is_ipv4_address(string_ip):
|
||||
socket.inet_aton(string_ip)
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_valid_cidr(string_network):
|
||||
def is_valid_cidr(string_network: str) -> bool:
|
||||
"""
|
||||
Very simple check of the cidr format in no_proxy variable.
|
||||
|
||||
@@ -599,13 +598,17 @@ def is_valid_cidr(string_network):
|
||||
socket.inet_aton(string_network.split('/')[0])
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def set_environ(env_name, value):
|
||||
def set_environ(
|
||||
env_name: str, value: typing.Optional[str]
|
||||
) -> typing.Generator:
|
||||
"""Set the environment variable 'env_name' to 'value'
|
||||
|
||||
Save previous value, yield, and then restore the previous value stored in
|
||||
@@ -618,6 +621,7 @@ def set_environ(env_name, value):
|
||||
os.environ[env_name] = value
|
||||
try:
|
||||
yield
|
||||
|
||||
finally:
|
||||
if value_changed:
|
||||
if old_value is None:
|
||||
@@ -626,64 +630,53 @@ def set_environ(env_name, value):
|
||||
os.environ[env_name] = old_value
|
||||
|
||||
|
||||
def should_bypass_proxies(url, no_proxy):
|
||||
def should_bypass_proxies(url: str, no_proxy: typing.Optional[str]) -> bool:
|
||||
"""
|
||||
Returns whether we should bypass proxies or not.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
|
||||
|
||||
# First check whether no_proxy is defined. If it is, check that the URL
|
||||
# we're getting isn't in the no_proxy list.
|
||||
no_proxy_arg = no_proxy
|
||||
if no_proxy is None:
|
||||
no_proxy = get_proxy('no_proxy')
|
||||
netloc = urlparse(url).netloc
|
||||
|
||||
if no_proxy:
|
||||
# We need to check whether we match here. We need to see if we match
|
||||
# the end of the netloc, both with and without the port.
|
||||
no_proxy = (
|
||||
host for host in no_proxy.replace(' ', '').split(',') if host
|
||||
)
|
||||
|
||||
ip = netloc.split(':')[0]
|
||||
if is_ipv4_address(ip):
|
||||
for proxy_ip in no_proxy:
|
||||
if is_valid_cidr(proxy_ip):
|
||||
if address_in_network(ip, proxy_ip):
|
||||
return True
|
||||
|
||||
elif ip == proxy_ip:
|
||||
# If no_proxy ip was defined in plain IP notation instead of cidr notation &
|
||||
# matches the IP of the index
|
||||
return True
|
||||
|
||||
else:
|
||||
for host in no_proxy:
|
||||
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
|
||||
if netloc.endswith(host) or netloc.split(':')[0].endswith(
|
||||
host
|
||||
):
|
||||
# The URL does match something in no_proxy, so we don't want
|
||||
# to apply the proxies on this URL.
|
||||
return True
|
||||
|
||||
# If the system proxy settings indicate that this URL should be bypassed,
|
||||
# don't proxy.
|
||||
# The proxy_bypass function is incredibly buggy on OS X in early versions
|
||||
# of Python 2.6, so allow this call to fail. Only catch the specific
|
||||
# exceptions we've seen, though: this call failing in other ways can reveal
|
||||
# legitimate problems.
|
||||
with set_environ('no_proxy', no_proxy_arg):
|
||||
try:
|
||||
bypass = proxy_bypass(netloc)
|
||||
except (TypeError, socket.gaierror):
|
||||
bypass = False
|
||||
|
||||
if bypass:
|
||||
return True
|
||||
|
||||
return False
|
||||
return bool(proxy_bypass(netloc))
|
||||
|
||||
|
||||
def get_environ_proxies(url, no_proxy=None):
|
||||
def get_environ_proxies(
|
||||
url: str, no_proxy: typing.Optional[bool] = None
|
||||
) -> dict:
|
||||
"""
|
||||
Return a dict of environment proxies.
|
||||
|
||||
@@ -691,11 +684,15 @@ def get_environ_proxies(url, no_proxy=None):
|
||||
"""
|
||||
if should_bypass_proxies(url, no_proxy=no_proxy):
|
||||
return {}
|
||||
|
||||
else:
|
||||
return getproxies()
|
||||
|
||||
|
||||
def select_proxy(url, proxies):
|
||||
def select_proxy(
|
||||
url: str,
|
||||
proxies: typing.Optional[typing.MutableMapping[typing.Text, typing.Text]],
|
||||
):
|
||||
"""Select a proxy for the url, if applicable.
|
||||
|
||||
:param url: The url being for the request
|
||||
@@ -721,7 +718,7 @@ def select_proxy(url, proxies):
|
||||
return proxy
|
||||
|
||||
|
||||
def default_user_agent(name="python-requests"):
|
||||
def default_user_agent(name: str = "python-requests") -> str:
|
||||
"""
|
||||
Return a string representing the default user agent.
|
||||
|
||||
@@ -730,38 +727,39 @@ def default_user_agent(name="python-requests"):
|
||||
return '%s/%s' % (name, __version__)
|
||||
|
||||
|
||||
def default_headers():
|
||||
def default_headers() -> HTTPHeaderDict:
|
||||
"""
|
||||
:rtype: requests.structures.CaseInsensitiveDict
|
||||
:rtype: requests.structures.HTTPHeaderDict
|
||||
"""
|
||||
return CaseInsensitiveDict({
|
||||
'User-Agent': default_user_agent(),
|
||||
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
|
||||
'Accept': '*/*',
|
||||
'Connection': 'keep-alive',
|
||||
})
|
||||
return HTTPHeaderDict(
|
||||
{
|
||||
'User-Agent': default_user_agent(),
|
||||
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
|
||||
'Accept': '*/*',
|
||||
'Connection': 'keep-alive',
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def parse_header_links(value):
|
||||
"""Return a dict of parsed link headers proxies.
|
||||
def parse_header_links(value: str) -> typing.List[typing.MutableMapping]:
|
||||
"""Return a list of parsed link headers proxies.
|
||||
|
||||
i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"
|
||||
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
links = []
|
||||
|
||||
links = [] # type: typing.List
|
||||
replace_chars = ' \'"'
|
||||
value = value.strip(replace_chars)
|
||||
if not value:
|
||||
return links
|
||||
|
||||
for val in re.split(', *<', value):
|
||||
try:
|
||||
url, params = val.split(';', 1)
|
||||
except ValueError:
|
||||
url, params = val, ''
|
||||
|
||||
link = {'url': url.strip('<> \'"')}
|
||||
|
||||
for param in params.split(';'):
|
||||
try:
|
||||
key, value = param.split('=')
|
||||
@@ -769,19 +767,31 @@ def parse_header_links(value):
|
||||
break
|
||||
|
||||
link[key.strip(replace_chars)] = value.strip(replace_chars)
|
||||
|
||||
links.append(link)
|
||||
|
||||
return links
|
||||
|
||||
|
||||
def is_valid_location(response) -> bool:
|
||||
"""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
|
||||
_null3 = _null * 3
|
||||
|
||||
|
||||
def guess_json_utf(data):
|
||||
def guess_json_utf(data: bytes) -> typing.Optional[str]:
|
||||
"""
|
||||
:rtype: str
|
||||
"""
|
||||
@@ -790,59 +800,63 @@ def guess_json_utf(data):
|
||||
# determine the encoding. Also detect a BOM, if present.
|
||||
sample = data[:4]
|
||||
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
|
||||
return 'utf-32' # BOM included
|
||||
return 'utf-32' # BOM included
|
||||
|
||||
if sample[:3] == codecs.BOM_UTF8:
|
||||
return 'utf-8-sig' # BOM included, MS style (discouraged)
|
||||
|
||||
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
|
||||
return 'utf-16' # BOM included
|
||||
return 'utf-16' # BOM included
|
||||
|
||||
nullcount = sample.count(_null)
|
||||
if nullcount == 0:
|
||||
return 'utf-8'
|
||||
|
||||
if nullcount == 2:
|
||||
if sample[::2] == _null2: # 1st and 3rd are null
|
||||
if sample[::2] == _null2: # 1st and 3rd are null
|
||||
return 'utf-16-be'
|
||||
|
||||
if sample[1::2] == _null2: # 2nd and 4th are null
|
||||
return 'utf-16-le'
|
||||
# Did not detect 2 valid UTF-16 ascii-range characters
|
||||
|
||||
# Did not detect 2 valid UTF-16 ascii-range characters
|
||||
if nullcount == 3:
|
||||
if sample[:3] == _null3:
|
||||
return 'utf-32-be'
|
||||
|
||||
if sample[1:] == _null3:
|
||||
return 'utf-32-le'
|
||||
# Did not detect a valid UTF-32 ascii-range character
|
||||
|
||||
# Did not detect a valid UTF-32 ascii-range character
|
||||
return None
|
||||
|
||||
|
||||
def prepend_scheme_if_needed(url, new_scheme):
|
||||
def prepend_scheme_if_needed(url: str, new_scheme: str) -> str:
|
||||
"""Given a URL that may or may not have a scheme, prepend the given scheme.
|
||||
Does not replace a present scheme with the one provided as an argument.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
|
||||
|
||||
# urlparse is a finicky beast, and sometimes decides that there isn't a
|
||||
# netloc present. Assume that it's being over-cautious, and switch netloc
|
||||
# and path if urlparse decided there was no netloc.
|
||||
if not netloc:
|
||||
netloc, path = path, netloc
|
||||
|
||||
return urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
|
||||
def get_auth_from_url(url):
|
||||
def get_auth_from_url(url: str) -> typing.Tuple[typing.Text, typing.Text]:
|
||||
"""Given a url with authentication components, extract them into a tuple of
|
||||
username,password.
|
||||
|
||||
:rtype: (str,str)
|
||||
"""
|
||||
parsed = urlparse(url)
|
||||
|
||||
try:
|
||||
auth = (unquote(parsed.username), unquote(parsed.password))
|
||||
except (AttributeError, TypeError):
|
||||
auth = ('', '')
|
||||
|
||||
return auth
|
||||
|
||||
|
||||
@@ -851,7 +865,9 @@ _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):
|
||||
def check_header_validity(
|
||||
header: typing.Tuple[typing.Text, typing.Text]
|
||||
) -> None:
|
||||
"""Verifies that header value is a string which doesn't contain
|
||||
leading whitespace or return characters. This prevents unintended
|
||||
header injection.
|
||||
@@ -859,46 +875,61 @@ def check_header_validity(header):
|
||||
:param header: tuple, in the format (name, value).
|
||||
"""
|
||||
name, value = header
|
||||
|
||||
if isinstance(value, bytes):
|
||||
pat = _CLEAN_HEADER_REGEX_BYTE
|
||||
else:
|
||||
pat = _CLEAN_HEADER_REGEX_STR
|
||||
pat = _CLEAN_HEADER_REGEX_STR
|
||||
try:
|
||||
if not pat.match(value):
|
||||
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
|
||||
raise InvalidHeader(
|
||||
"Invalid return character or leading space in header: %s" %
|
||||
name
|
||||
)
|
||||
|
||||
except TypeError:
|
||||
raise InvalidHeader("Header value %s must be of type str or bytes, "
|
||||
"not %s" % (value, type(value)))
|
||||
raise InvalidHeader(
|
||||
"Value for header {%s: %s} must be of type str or "
|
||||
"bytes, not %s" % (name, value, type(value))
|
||||
)
|
||||
|
||||
|
||||
def urldefragauth(url):
|
||||
def urldefragauth(url: str) -> str:
|
||||
"""
|
||||
Given a url remove the fragment and the authentication part.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
||||
|
||||
# see func:`prepend_scheme_if_needed`
|
||||
if not netloc:
|
||||
netloc, path = path, netloc
|
||||
|
||||
netloc = netloc.rsplit('@', 1)[-1]
|
||||
|
||||
return urlunparse((scheme, netloc, path, params, query, ''))
|
||||
|
||||
|
||||
def rewind_body(prepared_request):
|
||||
def rewind_body(prepared_request) -> None:
|
||||
"""Move file pointer back to its recorded starting position
|
||||
so it can be read again on redirect.
|
||||
"""
|
||||
body_seek = getattr(prepared_request.body, 'seek', None)
|
||||
if body_seek is not None and isinstance(prepared_request._body_position, integer_types):
|
||||
if body_seek is not None and isinstance(
|
||||
prepared_request._body_position, integer_types
|
||||
):
|
||||
try:
|
||||
body_seek(prepared_request._body_position)
|
||||
except (IOError, OSError):
|
||||
raise UnrewindableBodyError("An error occurred when rewinding request "
|
||||
"body for redirect.")
|
||||
raise UnrewindableBodyError(
|
||||
"An error occurred when rewinding request "
|
||||
"body for redirect."
|
||||
)
|
||||
|
||||
else:
|
||||
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
|
||||
raise UnrewindableBodyError(
|
||||
"Unable to rewind request body for redirect."
|
||||
)
|
||||
|
||||
|
||||
def is_stream(data: bytes) -> bool:
|
||||
"""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,16 +0,0 @@
|
||||
-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
|
||||
81
setup.py
81
setup.py
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Learn more: https://github.com/kennethreitz/setup.py
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from codecs import open
|
||||
@@ -11,16 +10,27 @@ from setuptools.command.test import test as TestCommand
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
class Format(TestCommand):
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
TestCommand.initialize_options(self)
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run_tests(self):
|
||||
os.system('white requests')
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
|
||||
|
||||
def initialize_options(self):
|
||||
TestCommand.initialize_options(self)
|
||||
try:
|
||||
from multiprocessing import cpu_count
|
||||
self.pytest_args = ['-n', str(cpu_count()), '--boxed']
|
||||
except (ImportError, NotImplementedError):
|
||||
self.pytest_args = ['-n', '1', '--boxed']
|
||||
self.pytest_args = ['-n', 'auto']
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
@@ -33,32 +43,57 @@ class PyTest(TestCommand):
|
||||
errno = pytest.main(self.pytest_args)
|
||||
sys.exit(errno)
|
||||
|
||||
|
||||
class MyPyTest(TestCommand):
|
||||
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
|
||||
|
||||
def initialize_options(self):
|
||||
TestCommand.initialize_options(self)
|
||||
self.pytest_args = ['-n', 'auto', '--mypy', 'tests']
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.test_args = []
|
||||
self.test_suite = True
|
||||
|
||||
def run_tests(self):
|
||||
import pytest
|
||||
|
||||
errno = pytest.main(self.pytest_args)
|
||||
sys.exit(errno)
|
||||
|
||||
|
||||
# 'setup.py publish' shortcut.
|
||||
if sys.argv[-1] == 'publish':
|
||||
os.system('python setup.py sdist bdist_wheel')
|
||||
os.system('twine upload dist/*')
|
||||
sys.exit()
|
||||
|
||||
packages = ['requests']
|
||||
|
||||
requires = [
|
||||
'chardet>=3.0.2,<3.1.0',
|
||||
'idna>=2.5,<2.6',
|
||||
'idna>=2.5,<2.7',
|
||||
'urllib3>=1.21.1,<1.23',
|
||||
'certifi>=2017.4.17'
|
||||
|
||||
'certifi>=2017.4.17',
|
||||
'rfc3986>=1.1.0<2',
|
||||
]
|
||||
test_requirements = [
|
||||
'pytest-httpbin==0.0.7',
|
||||
'pytest-cov',
|
||||
'pytest-mock',
|
||||
'pytest-xdist',
|
||||
'PySocks>=1.5.6, !=1.5.7',
|
||||
'pytest>=2.8.0',
|
||||
'pytest-mypy',
|
||||
'mypy',
|
||||
'white',
|
||||
]
|
||||
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'), 'r', 'utf-8') as f:
|
||||
exec(f.read(), about)
|
||||
|
||||
exec (f.read(), about)
|
||||
with open('README.rst', 'r', 'utf-8') as f:
|
||||
readme = f.read()
|
||||
with open('HISTORY.rst', 'r', 'utf-8') as f:
|
||||
history = f.read()
|
||||
|
||||
setup(
|
||||
name=about['__title__'],
|
||||
version=about['__version__'],
|
||||
@@ -80,22 +115,20 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy'
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
),
|
||||
cmdclass={'test': PyTest},
|
||||
cmdclass={'test': PyTest, 'mypy': MyPyTest, 'format': Format},
|
||||
tests_require=test_requirements,
|
||||
extras_require={
|
||||
'security': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'],
|
||||
'socks': ['PySocks>=1.5.6, !=1.5.7'],
|
||||
'socks:sys_platform == "win32" and (python_version == "2.7" or python_version == "2.6")': ['win_inet_pton'],
|
||||
'socks:sys_platform == "win32" and python_version == "2.7"': [
|
||||
'win_inet_pton'
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Requests test package initialisation."""
|
||||
|
||||
import warnings
|
||||
|
||||
@@ -1,21 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from requests.compat import is_py3
|
||||
import io as StringIO
|
||||
|
||||
|
||||
try:
|
||||
import StringIO
|
||||
except ImportError:
|
||||
import io as StringIO
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO as cStringIO
|
||||
except ImportError:
|
||||
cStringIO = None
|
||||
|
||||
if is_py3:
|
||||
def u(s):
|
||||
return s
|
||||
else:
|
||||
def u(s):
|
||||
return s.decode('unicode-escape')
|
||||
def u(s):
|
||||
return s
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from requests.compat import urljoin
|
||||
from urllib.parse import urljoin
|
||||
|
||||
|
||||
def prepare_url(value):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- encoding: utf-8
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
@@ -7,7 +6,9 @@ import pytest
|
||||
from requests.help import info
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[:2] != (2,6), reason="Only run on Python 2.6")
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info[:2] != (2, 6), reason="Only run on Python 2.6"
|
||||
)
|
||||
def test_system_ssl_py26():
|
||||
"""OPENSSL_VERSION_NUMBER isn't provided in Python 2.6, verify we don't
|
||||
blow up in this case.
|
||||
@@ -15,13 +16,16 @@ def test_system_ssl_py26():
|
||||
assert info()['system_ssl'] == {'version': ''}
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (2,7), reason="Only run on Python 2.7+")
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (2, 7), reason="Only run on Python 2.7+"
|
||||
)
|
||||
def test_system_ssl():
|
||||
"""Verify we're actually setting system_ssl when it should be available."""
|
||||
assert info()['system_ssl']['version'] != ''
|
||||
|
||||
|
||||
class VersionedPackage(object):
|
||||
|
||||
def __init__(self, version):
|
||||
self.__version__ = version
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
from requests import hooks
|
||||
@@ -10,13 +9,12 @@ def hook(value):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'hooks_list, result', (
|
||||
(hook, 'ata'),
|
||||
([hook, lambda x: None, hook], 'ta'),
|
||||
)
|
||||
'hooks_list, result', ((hook, 'ata'), ([hook, lambda x: None, hook], 'ta'))
|
||||
)
|
||||
def test_hooks(hooks_list, result):
|
||||
assert hooks.dispatch_hook('response', {'response': hooks_list}, 'Data') == result
|
||||
assert hooks.dispatch_hook(
|
||||
'response', {'response': hooks_list}, 'Data'
|
||||
) == result
|
||||
|
||||
|
||||
def test_default_hooks():
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
import threading
|
||||
import requests
|
||||
@@ -14,39 +13,54 @@ def test_chunked_upload():
|
||||
close_server = threading.Event()
|
||||
server = Server.basic_response_server(wait_to_close_event=close_server)
|
||||
data = iter([b'a', b'b', b'c'])
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.post(url, data=data, stream=True)
|
||||
close_server.set() # release server block
|
||||
|
||||
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/requests/requests/issues/1979.
|
||||
"""
|
||||
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
|
||||
|
||||
text_302 = (b'HTTP/1.1 302 FOUND\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'Location: /\r\n\r\n')
|
||||
|
||||
text_200 = (b'HTTP/1.1 200 OK\r\n'
|
||||
b'Content-Length: 0\r\n\r\n')
|
||||
|
||||
expected_digest = (b'Authorization: Digest username="user", '
|
||||
b'realm="me@kennethreitz.com", '
|
||||
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
|
||||
|
||||
text_401 = (
|
||||
b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n'
|
||||
)
|
||||
text_302 = (
|
||||
b'HTTP/1.1 302 FOUND\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'Location: /\r\n\r\n'
|
||||
)
|
||||
text_200 = (b'HTTP/1.1 200 OK\r\n' b'Content-Length: 0\r\n\r\n')
|
||||
expected_digest = (
|
||||
b'Authorization: Digest username="user", '
|
||||
b'realm="me@kennethreitz.com", '
|
||||
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"'
|
||||
)
|
||||
auth = requests.auth.HTTPDigestAuth('user', 'pass')
|
||||
|
||||
def digest_response_handler(sock):
|
||||
@@ -54,28 +68,23 @@ def test_digestauth_401_count_reset_on_redirect():
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content.startswith(b"GET / HTTP/1.1")
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify we receive an Authorization header in response, then redirect.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert expected_digest in request_content
|
||||
sock.send(text_302)
|
||||
|
||||
# Verify Authorization isn't sent to the redirected host,
|
||||
# then send another challenge.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert b'Authorization:' not in request_content
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify Authorization is sent correctly again, and return 200 OK.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert expected_digest in request_content
|
||||
sock.send(text_200)
|
||||
|
||||
return request_content
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(digest_response_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.get(url, auth=auth)
|
||||
@@ -93,16 +102,18 @@ def test_digestauth_401_only_sent_once():
|
||||
"""Ensure we correctly respond to a 401 challenge once, and then
|
||||
stop responding if challenged again.
|
||||
"""
|
||||
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
|
||||
|
||||
expected_digest = (b'Authorization: Digest username="user", '
|
||||
b'realm="me@kennethreitz.com", '
|
||||
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
|
||||
|
||||
text_401 = (
|
||||
b'HTTP/1.1 401 UNAUTHORIZED\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n'
|
||||
)
|
||||
expected_digest = (
|
||||
b'Authorization: Digest username="user", '
|
||||
b'realm="me@kennethreitz.com", '
|
||||
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"'
|
||||
)
|
||||
auth = requests.auth.HTTPDigestAuth('user', 'pass')
|
||||
|
||||
def digest_failed_response_handler(sock):
|
||||
@@ -110,22 +121,20 @@ def test_digestauth_401_only_sent_once():
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content.startswith(b"GET / HTTP/1.1")
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify we receive an Authorization header in response, then
|
||||
# challenge again.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert expected_digest in request_content
|
||||
sock.send(text_401)
|
||||
|
||||
# Verify the client didn't respond to second challenge.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content == b''
|
||||
|
||||
return request_content
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(digest_failed_response_handler, wait_to_close_event=close_server)
|
||||
|
||||
server = Server(
|
||||
digest_failed_response_handler, wait_to_close_event=close_server
|
||||
)
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.get(url, auth=auth)
|
||||
@@ -140,12 +149,13 @@ def test_digestauth_only_on_4xx():
|
||||
|
||||
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'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
|
||||
|
||||
text_200_chal = (
|
||||
b'HTTP/1.1 200 OK\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
|
||||
b', opaque="372825293d1c26955496c80ed6426e9e", '
|
||||
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n'
|
||||
)
|
||||
auth = requests.auth.HTTPDigestAuth('user', 'pass')
|
||||
|
||||
def digest_response_handler(sock):
|
||||
@@ -153,16 +163,13 @@ def test_digestauth_only_on_4xx():
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content.startswith(b"GET / HTTP/1.1")
|
||||
sock.send(text_200_chal)
|
||||
|
||||
# Verify the client didn't respond with auth.
|
||||
request_content = consume_socket_content(sock, timeout=0.5)
|
||||
assert request_content == b''
|
||||
|
||||
return request_content
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(digest_response_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.get(url, auth=auth)
|
||||
@@ -173,23 +180,20 @@ def test_digestauth_only_on_4xx():
|
||||
|
||||
|
||||
_schemes_by_var_prefix = [
|
||||
('http', ['http']),
|
||||
('https', ['https']),
|
||||
('all', ['http', 'https']),
|
||||
('http', ['http']), ('https', ['https']), ('all', ['http', 'https'])
|
||||
]
|
||||
|
||||
_proxy_combos = []
|
||||
for prefix, schemes in _schemes_by_var_prefix:
|
||||
for scheme in schemes:
|
||||
_proxy_combos.append(("{0}_proxy".format(prefix), scheme))
|
||||
|
||||
_proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var,scheme", _proxy_combos)
|
||||
def test_use_proxy_from_environment(httpbin, var, scheme):
|
||||
url = "{0}://httpbin.org".format(scheme)
|
||||
fake_proxy = Server() # do nothing with the requests; just close the socket
|
||||
fake_proxy = Server(
|
||||
) # do nothing with the requests; just close the socket
|
||||
with fake_proxy as (host, port):
|
||||
proxy_url = "socks5://{0}:{1}".format(host, port)
|
||||
kwargs = {var: proxy_url}
|
||||
@@ -197,10 +201,8 @@ def test_use_proxy_from_environment(httpbin, var, scheme):
|
||||
# fake proxy's lack of response will cause a ConnectionError
|
||||
with pytest.raises(requests.exceptions.ConnectionError):
|
||||
requests.get(url)
|
||||
|
||||
# the fake proxy received a request
|
||||
assert len(fake_proxy.handler_results) == 1
|
||||
|
||||
# it had actual content (not checking for SOCKS protocol for now)
|
||||
assert len(fake_proxy.handler_results[0]) > 0
|
||||
|
||||
@@ -224,14 +226,14 @@ def test_redirect_rfc1808_to_non_ascii_location():
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(redirect_resp_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = u'http://{0}:{1}'.format(host, port)
|
||||
r = requests.get(url=url, allow_redirects=True)
|
||||
assert r.status_code == 200
|
||||
assert len(r.history) == 1
|
||||
assert r.history[0].status_code == 301
|
||||
assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1')
|
||||
assert redirect_request[0].startswith(
|
||||
b'GET /' + expected_path + b' HTTP/1.1'
|
||||
)
|
||||
assert r.url == u'{0}/{1}'.format(url, expected_path.decode('ascii'))
|
||||
|
||||
close_server.set()
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
from requests.structures import CaseInsensitiveDict, LookupDict
|
||||
from requests.structures import CaseInsensitiveDict, LookupDict, HTTPHeaderDict
|
||||
from urllib3._collections import HTTPHeaderDict as U3HeaderDict
|
||||
|
||||
|
||||
class TestCaseInsensitiveDict:
|
||||
@@ -16,7 +16,9 @@ class TestCaseInsensitiveDict:
|
||||
def test_list(self):
|
||||
assert list(self.case_insensitive_dict) == ['Accept']
|
||||
|
||||
possible_keys = pytest.mark.parametrize('key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept'))
|
||||
possible_keys = pytest.mark.parametrize(
|
||||
'key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept')
|
||||
)
|
||||
|
||||
@possible_keys
|
||||
def test_getitem(self, key):
|
||||
@@ -28,10 +30,14 @@ class TestCaseInsensitiveDict:
|
||||
assert key not in self.case_insensitive_dict
|
||||
|
||||
def test_lower_items(self):
|
||||
assert list(self.case_insensitive_dict.lower_items()) == [('accept', 'application/json')]
|
||||
assert list(self.case_insensitive_dict.lower_items()) == [
|
||||
('accept', 'application/json')
|
||||
]
|
||||
|
||||
def test_repr(self):
|
||||
assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}"
|
||||
assert repr(
|
||||
self.case_insensitive_dict
|
||||
) == "{'Accept': 'application/json'}"
|
||||
|
||||
def test_copy(self):
|
||||
copy = self.case_insensitive_dict.copy()
|
||||
@@ -39,16 +45,186 @@ class TestCaseInsensitiveDict:
|
||||
assert copy == self.case_insensitive_dict
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'other, result', (
|
||||
({'AccePT': 'application/json'}, True),
|
||||
({}, False),
|
||||
(None, False)
|
||||
)
|
||||
'other, result',
|
||||
(({'AccePT': 'application/json'}, True), ({}, False), (None, False)),
|
||||
)
|
||||
def test_instance_equality(self, other, result):
|
||||
assert (self.case_insensitive_dict == other) is result
|
||||
|
||||
|
||||
class TestHTTPHeaderDictCompatibility(TestCaseInsensitiveDict):
|
||||
"""HTTPHeaderDict should be completely compatible with CaseInsensitiveDict
|
||||
when used for headers, so ensure that all the tests for the base class
|
||||
also pass for this one."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.case_insensitive_dict = HTTPHeaderDict()
|
||||
self.case_insensitive_dict['Accept'] = 'application/json'
|
||||
|
||||
|
||||
class TestHTTPHeaderDict:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.kvs = [
|
||||
('animal', 'chicken'),
|
||||
('AnimaL', 'Cow'),
|
||||
('CAKE', 'Cheese!'),
|
||||
('Sauce', 'Bread'),
|
||||
('Sauce', 'Cherry, or Plum Tomato'),
|
||||
]
|
||||
# HTTPHeaderDict from urllib3.
|
||||
self.u3dict = ud = U3HeaderDict()
|
||||
[ud.add(*tpl) for tpl in self.kvs]
|
||||
# Regular dictionary.
|
||||
self.ddict = dict(self.kvs)
|
||||
self.ddict['Sauce'] = ['Bread!', 'Cherry, or Plum Tomato']
|
||||
# Used by test_extend. All of these "extra" values are mostly
|
||||
# equivalent to each other.
|
||||
self.extra_hd = hd2 = HTTPHeaderDict(ANIMAL=['Dog', 'elephant'])
|
||||
hd2['cake'] = 'Babka'
|
||||
hd2.setlist('sound', ['quiet', 'LOUD'])
|
||||
hd2['CUTLERY'] = 'fork'
|
||||
self.extra_tuple_pairs = tuple_pairs = [
|
||||
('ANIMAL', 'Dog'),
|
||||
('Animal', 'elephant'),
|
||||
('cake', ['Babka']),
|
||||
('sound', 'quiet'),
|
||||
('sound', 'LOUD'),
|
||||
('CUTLERY', 'fork'),
|
||||
]
|
||||
self.extra_simple_dict = dict(tuple_pairs)
|
||||
self.extra_simple_dict['sound'] = ('quiet', 'LOUD')
|
||||
self.extra_u3 = U3HeaderDict()
|
||||
for k, v in tuple_pairs:
|
||||
if isinstance(v, (tuple, list)):
|
||||
for vi in v:
|
||||
self.extra_u3.add(k, vi)
|
||||
else:
|
||||
self.extra_u3.add(k, v)
|
||||
|
||||
def test_item_access(self):
|
||||
hd = HTTPHeaderDict(self.kvs)
|
||||
# Test that values are combined.
|
||||
assert hd['Sauce'] == 'Bread, Cherry, or Plum Tomato'
|
||||
assert hd['ANIMAL'] == 'chicken, Cow'
|
||||
# Test we can overwrite values.
|
||||
hd['animal'] = 'Goat!'
|
||||
assert hd['anIMal'] == 'Goat!'
|
||||
# Test deletion works.
|
||||
del hd['sauce']
|
||||
pytest.raises(KeyError, hd.__getitem__, 'sauce')
|
||||
# Only string types allowed.
|
||||
pytest.raises(ValueError, hd.__setitem__, 'cake', ['Cheese', 'sponge'])
|
||||
|
||||
def test_equality(self):
|
||||
hd = HTTPHeaderDict(self.u3dict)
|
||||
assert hd == self.u3dict
|
||||
assert hd == HTTPHeaderDict(hd)
|
||||
# Test that we still work even if we are comparing to a
|
||||
# CaseInsensitiveDict instance.
|
||||
cid = CaseInsensitiveDict(hd)
|
||||
assert hd == cid
|
||||
assert cid == hd
|
||||
|
||||
def test_lower_items(self):
|
||||
hd = HTTPHeaderDict(self.kvs, cutlery='fork')
|
||||
assert list(hd.lower_items()) == [
|
||||
('animal', 'chicken, Cow'),
|
||||
('cake', 'Cheese!'),
|
||||
('sauce', 'Bread, Cherry, or Plum Tomato'),
|
||||
('cutlery', 'fork'),
|
||||
]
|
||||
|
||||
def test_copy(self):
|
||||
hd = HTTPHeaderDict(self.u3dict)
|
||||
hd2 = hd.copy()
|
||||
assert hd is not hd2
|
||||
assert hd == hd2
|
||||
|
||||
def test_get_and_set_list(self):
|
||||
hd = HTTPHeaderDict(self.kvs)
|
||||
assert hd.getlist('SAUCE') == ['Bread', 'Cherry, or Plum Tomato']
|
||||
assert hd.getlist('CAKE') == ['Cheese!']
|
||||
assert hd.getlist('DRINK') == []
|
||||
# Needs to be a regular sequence type containing just strings.
|
||||
pytest.raises(ValueError, hd.setlist, 'Drink', 'Water')
|
||||
pytest.raises(ValueError, hd.setlist, 'Drink', ['H', 2, 'O'])
|
||||
# Test multi-setting.
|
||||
hd.setlist('Drink', ['Water', 'Juice'])
|
||||
assert hd.getlist('DRINK') == ['Water', 'Juice']
|
||||
# Setting to an empty sequence should remove the entry.
|
||||
hd.setlist('DRInk', [])
|
||||
pytest.raises(KeyError, hd.__getitem__, 'DrinK')
|
||||
assert hd.getlist('DRiNK') == []
|
||||
|
||||
def test_add(self):
|
||||
hd = HTTPHeaderDict()
|
||||
hd.add('sound', 'quiet')
|
||||
hd.add('SOUND', 'LOUD')
|
||||
assert hd.getlist('Sound') == ['quiet', 'LOUD']
|
||||
# Enforce type-checking in the add method.
|
||||
pytest.raises(ValueError, hd.add, 'Sound', 5)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'attr,as_arg,animal_arg_is_ordered',
|
||||
[('extra_hd', True, True), ('extra_tuple_pairs', True, True), ('extra_simple_dict', True, False), ('extra_u3', True, False), ('extra_simple_dict', False, False)],
|
||||
# These types will have the "animal" arguments in our preferred order.
|
||||
# And these types will lose the ordering, so we can't make assertions
|
||||
# about the final order of those values.
|
||||
)
|
||||
def test_extend(self, attr, as_arg, animal_arg_is_ordered):
|
||||
item = getattr(self, attr)
|
||||
# Call extend with the associated values - we should see all of the
|
||||
# merged data in the HTTPHeaderDict instance.
|
||||
extras = {'cutlery': 'knife'}
|
||||
hd = HTTPHeaderDict(self.kvs)
|
||||
if as_arg:
|
||||
hd.extend(item, **extras)
|
||||
else:
|
||||
hd.extend(extras, **item)
|
||||
# Test all the stored values are what we expect.
|
||||
mget = hd.getlist
|
||||
# Depending on the item we merged in, we might be able to make
|
||||
# assumptions what the overall order of the structure is.
|
||||
animal_seq = mget('animal')
|
||||
if animal_arg_is_ordered:
|
||||
assert animal_seq == ['chicken', 'Cow', 'Dog', 'elephant']
|
||||
else:
|
||||
# The existing order in HTTPHeadersDict of the first two values
|
||||
# should be preserved - no guarantees in which order the other
|
||||
# two values are added.
|
||||
assert animal_seq in [
|
||||
['chicken', 'Cow', 'Dog', 'elephant'],
|
||||
['chicken', 'Cow', 'elephant', 'Dog'],
|
||||
]
|
||||
assert mget('cake') == ['Cheese!', 'Babka']
|
||||
assert mget('sound') == ['quiet', 'LOUD']
|
||||
# We don't mandate the order in which these dictionaries are
|
||||
# processed, so it's fine whichever order it is.
|
||||
assert mget('cutlery') in [['fork', 'knife'], ['knife', 'fork']]
|
||||
|
||||
def test_extend_type_checking(self):
|
||||
hd = HTTPHeaderDict()
|
||||
pytest.raises(ValueError, hd.extend, dict(type=['xml', None, 'html']))
|
||||
|
||||
def test_repr(self):
|
||||
hd = HTTPHeaderDict()
|
||||
assert repr(hd) == '{}'
|
||||
hd.add('type', 'xml')
|
||||
assert repr(hd) == "{'type': 'xml'}"
|
||||
hd.add('type', 'html')
|
||||
assert repr(hd) == "{'type': ('xml', 'html')}"
|
||||
# We can't guarantee order once we have more than one key.
|
||||
hd.add('Accept', 'text/html')
|
||||
assert repr(hd) in [
|
||||
"{'type': ('xml', 'html'), 'Accept': 'text/html'}",
|
||||
"{'Accept': 'text/html', 'type': ('xml', 'html')}",
|
||||
]
|
||||
assert str(hd) == repr(hd)
|
||||
|
||||
|
||||
class TestLookupDict:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -61,10 +237,7 @@ class TestLookupDict:
|
||||
assert repr(self.lookup_dict) == "<lookup 'test'>"
|
||||
|
||||
get_item_parameters = pytest.mark.parametrize(
|
||||
'key, value', (
|
||||
('bad_gateway', 502),
|
||||
('not_a_key', None)
|
||||
)
|
||||
'key, value', (('bad_gateway', 502), ('not_a_key', None))
|
||||
)
|
||||
|
||||
@get_item_parameters
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import threading
|
||||
import socket
|
||||
import time
|
||||
@@ -34,9 +33,7 @@ class TestTestServer:
|
||||
with Server.basic_response_server() as (host, port):
|
||||
sock = socket.socket()
|
||||
sock.connect((host, port))
|
||||
|
||||
sock.close()
|
||||
|
||||
with pytest.raises(socket.error):
|
||||
new_sock = socket.socket()
|
||||
new_sock.connect((host, port))
|
||||
@@ -44,14 +41,10 @@ class TestTestServer:
|
||||
def test_text_response(self):
|
||||
"""the text_response_server sends the given text"""
|
||||
server = Server.text_response_server(
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 6\r\n" +
|
||||
"\r\nroflol"
|
||||
"HTTP/1.1 200 OK\r\n" + "Content-Length: 6\r\n" + "\r\nroflol"
|
||||
)
|
||||
|
||||
with server as (host, port):
|
||||
r = requests.get('http://{0}:{1}'.format(host, port))
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.text == u'roflol'
|
||||
assert r.headers['Content-Length'] == '6'
|
||||
@@ -67,8 +60,11 @@ class TestTestServer:
|
||||
def test_basic_waiting_server(self):
|
||||
"""the server waits for the block_server event to be set before closing"""
|
||||
block_server = threading.Event()
|
||||
|
||||
with Server.basic_response_server(wait_to_close_event=block_server) as (host, port):
|
||||
with Server.basic_response_server(
|
||||
wait_to_close_event=block_server
|
||||
) as (
|
||||
host, port
|
||||
):
|
||||
sock = socket.socket()
|
||||
sock.connect((host, port))
|
||||
sock.sendall(b'send something')
|
||||
@@ -79,67 +75,60 @@ class TestTestServer:
|
||||
def test_multiple_requests(self):
|
||||
"""multiple requests can be served"""
|
||||
requests_to_handle = 5
|
||||
|
||||
server = Server.basic_response_server(requests_to_handle=requests_to_handle)
|
||||
|
||||
server = Server.basic_response_server(
|
||||
requests_to_handle=requests_to_handle
|
||||
)
|
||||
with server as (host, port):
|
||||
server_url = 'http://{0}:{1}'.format(host, port)
|
||||
for _ in range(requests_to_handle):
|
||||
r = requests.get(server_url)
|
||||
assert r.status_code == 200
|
||||
|
||||
# the (n+1)th request fails
|
||||
with pytest.raises(requests.exceptions.ConnectionError):
|
||||
r = requests.get(server_url)
|
||||
|
||||
@pytest.mark.skip(reason="this fails non-deterministically under pytest-xdist")
|
||||
@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'
|
||||
|
||||
with server as address:
|
||||
sock1 = socket.socket()
|
||||
sock2 = socket.socket()
|
||||
|
||||
sock1.connect(address)
|
||||
sock1.sendall(first_request)
|
||||
sock1.close()
|
||||
|
||||
sock2.connect(address)
|
||||
sock2.sendall(second_request)
|
||||
sock2.close()
|
||||
|
||||
assert server.handler_results[0] == first_request
|
||||
assert server.handler_results[1] == second_request
|
||||
|
||||
def test_requests_after_timeout_are_not_received(self):
|
||||
"""the basic response handler times out when receiving requests"""
|
||||
server = Server.basic_response_server(request_timeout=1)
|
||||
|
||||
with server as address:
|
||||
sock = socket.socket()
|
||||
sock.connect(address)
|
||||
time.sleep(1.5)
|
||||
sock.sendall(b'hehehe, not received')
|
||||
sock.close()
|
||||
|
||||
assert server.handler_results[0] == b''
|
||||
|
||||
def test_request_recovery_with_bigger_timeout(self):
|
||||
"""a biggest timeout can be specified"""
|
||||
server = Server.basic_response_server(request_timeout=3)
|
||||
data = b'bananadine'
|
||||
|
||||
with server as address:
|
||||
sock = socket.socket()
|
||||
sock.connect(address)
|
||||
time.sleep(1.5)
|
||||
sock.sendall(data)
|
||||
sock.close()
|
||||
|
||||
assert server.handler_results[0] == data
|
||||
|
||||
def test_server_finishes_on_error(self):
|
||||
@@ -151,16 +140,16 @@ class TestTestServer:
|
||||
|
||||
assert len(server.handler_results) == 0
|
||||
|
||||
# if the server thread fails to finish, the test suite will hang
|
||||
# and get killed by the jenkins timeout.
|
||||
|
||||
# if the server thread fails to finish, the test suite will hang
|
||||
# and get killed by the jenkins timeout.
|
||||
def test_server_finishes_when_no_connections(self):
|
||||
"""the server thread exits even if there are no connections"""
|
||||
server = Server.basic_response_server()
|
||||
with server:
|
||||
pass
|
||||
|
||||
assert len(server.handler_results) == 0
|
||||
|
||||
# if the server thread fails to finish, the test suite will hang
|
||||
# and get killed by the jenkins timeout.
|
||||
|
||||
# if the server thread fails to finish, the test suite will hang
|
||||
# and get killed by the jenkins timeout.
|
||||
|
||||
@@ -1,37 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import copy
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
from requests import compat
|
||||
from requests import basics
|
||||
from requests.cookies import RequestsCookieJar
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
from requests.utils import (
|
||||
address_in_network, dotted_netmask,
|
||||
get_auth_from_url, get_encoding_from_headers,
|
||||
get_encodings_from_content, get_environ_proxies,
|
||||
guess_filename, guess_json_utf, is_ipv4_address,
|
||||
is_valid_cidr, iter_slices, parse_dict_header,
|
||||
parse_header_links, prepend_scheme_if_needed,
|
||||
requote_uri, select_proxy, should_bypass_proxies, super_len,
|
||||
to_key_val_list, to_native_string,
|
||||
unquote_header_value, unquote_unreserved,
|
||||
urldefragauth, add_dict_to_cookiejar, set_environ)
|
||||
address_in_network,
|
||||
dotted_netmask,
|
||||
get_auth_from_url,
|
||||
get_encoding_from_headers,
|
||||
get_encodings_from_content,
|
||||
get_environ_proxies,
|
||||
guess_filename,
|
||||
guess_json_utf,
|
||||
is_ipv4_address,
|
||||
is_valid_cidr,
|
||||
iter_slices,
|
||||
parse_dict_header,
|
||||
parse_header_links,
|
||||
prepend_scheme_if_needed,
|
||||
requote_uri,
|
||||
select_proxy,
|
||||
should_bypass_proxies,
|
||||
super_len,
|
||||
to_key_val_list,
|
||||
unquote_header_value,
|
||||
unquote_unreserved,
|
||||
urldefragauth,
|
||||
add_dict_to_cookiejar,
|
||||
set_environ,
|
||||
)
|
||||
from requests._internal_utils import unicode_is_ascii
|
||||
|
||||
from .compat import StringIO, cStringIO
|
||||
from .compat import StringIO
|
||||
|
||||
|
||||
class TestSuperLen:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'stream, value', (
|
||||
(StringIO.StringIO, 'Test'),
|
||||
(BytesIO, b'Test'),
|
||||
pytest.mark.skipif('cStringIO is None')((cStringIO, 'Test')),
|
||||
))
|
||||
'stream, value', ((StringIO.StringIO, 'Test'), (BytesIO, b'Test'))
|
||||
)
|
||||
def test_io_streams(self, stream, value):
|
||||
"""Ensures that we properly deal with different kinds of IO streams."""
|
||||
assert super_len(stream()) == 0
|
||||
@@ -46,7 +57,9 @@ class TestSuperLen:
|
||||
@pytest.mark.parametrize('error', [IOError, OSError])
|
||||
def test_super_len_handles_files_raising_weird_errors_in_tell(self, error):
|
||||
"""If tell() raises errors, assume the cursor is at position zero."""
|
||||
|
||||
class BoomFile(object):
|
||||
|
||||
def __len__(self):
|
||||
return 5
|
||||
|
||||
@@ -58,7 +71,9 @@ class TestSuperLen:
|
||||
@pytest.mark.parametrize('error', [IOError, OSError])
|
||||
def test_super_len_tell_ioerror(self, error):
|
||||
"""Ensure that if tell gives an IOError super_len doesn't fail"""
|
||||
|
||||
class NoLenBoomFile(object):
|
||||
|
||||
def tell(self):
|
||||
raise error()
|
||||
|
||||
@@ -70,11 +85,7 @@ class TestSuperLen:
|
||||
def test_string(self):
|
||||
assert super_len('Test') == 4
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'mode, warnings_num', (
|
||||
('r', 1),
|
||||
('rb', 0),
|
||||
))
|
||||
@pytest.mark.parametrize('mode, warnings_num', (('r', 1), ('rb', 0)))
|
||||
def test_file(self, tmpdir, mode, warnings_num, recwarn):
|
||||
file_obj = tmpdir.join('test.txt')
|
||||
file_obj.write('Test')
|
||||
@@ -83,12 +94,14 @@ class TestSuperLen:
|
||||
assert len(recwarn) == warnings_num
|
||||
|
||||
def test_super_len_with__len__(self):
|
||||
foo = [1,2,3,4]
|
||||
foo = [1, 2, 3, 4]
|
||||
len_foo = super_len(foo)
|
||||
assert len_foo == 4
|
||||
|
||||
def test_super_len_with_no__len__(self):
|
||||
|
||||
class LenFile(object):
|
||||
|
||||
def __init__(self):
|
||||
self.len = 5
|
||||
|
||||
@@ -114,12 +127,14 @@ class TestSuperLen:
|
||||
class TestToKeyValList:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
'value, expected',
|
||||
(
|
||||
([('key', 'val')], [('key', 'val')]),
|
||||
((('key', 'val'), ), [('key', 'val')]),
|
||||
((('key', 'val'),), [('key', 'val')]),
|
||||
({'key': 'val'}, [('key', 'val')]),
|
||||
(None, None)
|
||||
))
|
||||
(None, None),
|
||||
),
|
||||
)
|
||||
def test_valid(self, value, expected):
|
||||
assert to_key_val_list(value) == expected
|
||||
|
||||
@@ -131,13 +146,15 @@ class TestToKeyValList:
|
||||
class TestUnquoteHeaderValue:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
'value, expected',
|
||||
(
|
||||
(None, None),
|
||||
('Test', 'Test'),
|
||||
('"Test"', 'Test'),
|
||||
('"Test\\\\"', 'Test\\'),
|
||||
('"\\\\Comp\\Res"', '\\Comp\\Res'),
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_valid(self, value, expected):
|
||||
assert unquote_header_value(value) == expected
|
||||
|
||||
@@ -152,46 +169,57 @@ class TestGetEnvironProxies:
|
||||
|
||||
@pytest.fixture(autouse=True, params=['no_proxy', 'NO_PROXY'])
|
||||
def no_proxy(self, request, monkeypatch):
|
||||
monkeypatch.setenv(request.param, '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
|
||||
monkeypatch.setenv(
|
||||
request.param,
|
||||
'192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1',
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url', (
|
||||
'url',
|
||||
(
|
||||
'http://192.168.0.1:5000/',
|
||||
'http://192.168.0.1/',
|
||||
'http://172.16.1.1/',
|
||||
'http://172.16.1.1:5000/',
|
||||
'http://localhost.localdomain:5000/v1.0/',
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_bypass(self, url):
|
||||
assert get_environ_proxies(url, no_proxy=None) == {}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url', (
|
||||
'url',
|
||||
(
|
||||
'http://192.168.1.1:5000/',
|
||||
'http://192.168.1.1/',
|
||||
'http://www.requests.com/',
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_not_bypass(self, url):
|
||||
assert get_environ_proxies(url, no_proxy=None) != {}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url', (
|
||||
'url',
|
||||
(
|
||||
'http://192.168.1.1:5000/',
|
||||
'http://192.168.1.1/',
|
||||
'http://www.requests.com/',
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_bypass_no_proxy_keyword(self, url):
|
||||
no_proxy = '192.168.1.1,requests.com'
|
||||
assert get_environ_proxies(url, no_proxy=no_proxy) == {}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url', (
|
||||
'url',
|
||||
(
|
||||
'http://192.168.0.1:5000/',
|
||||
'http://192.168.0.1/',
|
||||
'http://172.16.1.1/',
|
||||
'http://172.16.1.1:5000/',
|
||||
'http://localhost.localdomain:5000/v1.0/',
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_not_bypass_no_proxy_keyword(self, url, monkeypatch):
|
||||
# This is testing that the 'no_proxy' argument overrides the
|
||||
# environment variable 'no_proxy'
|
||||
@@ -216,13 +244,15 @@ class TestIsValidCIDR:
|
||||
assert is_valid_cidr('192.168.1.0/24')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value', (
|
||||
'value',
|
||||
(
|
||||
'8.8.8.8',
|
||||
'192.168.1.0/a',
|
||||
'192.168.1.0/128',
|
||||
'192.168.1.0/-1',
|
||||
'192.168.1.999/24',
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_invalid(self, value):
|
||||
assert not is_valid_cidr(value)
|
||||
|
||||
@@ -239,16 +269,15 @@ class TestAddressInNetwork:
|
||||
class TestGuessFilename:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value', (1, type('Fake', (object,), {'name': 1})()),
|
||||
'value', (1, type('Fake', (object,), {'name': 1})())
|
||||
)
|
||||
def test_guess_filename_invalid(self, value):
|
||||
assert guess_filename(value) is None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected_type', (
|
||||
(b'value', compat.bytes),
|
||||
(b'value'.decode('utf-8'), compat.str)
|
||||
))
|
||||
'value, expected_type',
|
||||
((b'value', basics.bytes), (b'value'.decode('utf-8'), basics.str)),
|
||||
)
|
||||
def test_guess_filename_valid(self, value, expected_type):
|
||||
obj = type('Fake', (object,), {'name': value})()
|
||||
result = guess_filename(obj)
|
||||
@@ -263,16 +292,13 @@ class TestContentEncodingDetection:
|
||||
assert not len(encodings)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'content', (
|
||||
# HTML5 meta charset attribute
|
||||
'<meta charset="UTF-8">',
|
||||
# HTML4 pragma directive
|
||||
'<meta http-equiv="Content-type" content="text/html;charset=UTF-8">',
|
||||
# XHTML 1.x served with text/html MIME type
|
||||
'<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />',
|
||||
# XHTML 1.x served as XML
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
))
|
||||
'content',
|
||||
('<meta charset="UTF-8">', '<meta http-equiv="Content-type" content="text/html;charset=UTF-8">', '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />', '<?xml version="1.0" encoding="UTF-8"?>'),
|
||||
# HTML5 meta charset attribute
|
||||
# HTML4 pragma directive
|
||||
# XHTML 1.x served with text/html MIME type
|
||||
# XHTML 1.x served as XML
|
||||
)
|
||||
def test_pragmas(self, content):
|
||||
encodings = get_encodings_from_content(content)
|
||||
assert len(encodings) == 1
|
||||
@@ -283,17 +309,26 @@ class TestContentEncodingDetection:
|
||||
<?xml version="1.0" encoding="XML"?>
|
||||
<meta charset="HTML5">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=HTML4" />
|
||||
'''.strip()
|
||||
'''.strip(
|
||||
)
|
||||
assert get_encodings_from_content(content) == ['HTML5', 'HTML4', 'XML']
|
||||
|
||||
|
||||
class TestGuessJSONUTF:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'encoding', (
|
||||
'utf-32', 'utf-8-sig', 'utf-16', 'utf-8', 'utf-16-be', 'utf-16-le',
|
||||
'utf-32-be', 'utf-32-le'
|
||||
))
|
||||
'encoding',
|
||||
(
|
||||
'utf-32',
|
||||
'utf-8-sig',
|
||||
'utf-16',
|
||||
'utf-8',
|
||||
'utf-16-be',
|
||||
'utf-16-le',
|
||||
'utf-32-be',
|
||||
'utf-32-le',
|
||||
),
|
||||
)
|
||||
def test_encoded(self, encoding):
|
||||
data = '{}'.encode(encoding)
|
||||
assert guess_json_utf(data) == encoding
|
||||
@@ -302,173 +337,154 @@ class TestGuessJSONUTF:
|
||||
assert guess_json_utf(b'\x00\x00\x00\x00') is None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('encoding', 'expected'), (
|
||||
('encoding', 'expected'),
|
||||
(
|
||||
('utf-16-be', 'utf-16'),
|
||||
('utf-16-le', 'utf-16'),
|
||||
('utf-32-be', 'utf-32'),
|
||||
('utf-32-le', 'utf-32')
|
||||
))
|
||||
('utf-32-le', 'utf-32'),
|
||||
),
|
||||
)
|
||||
def test_guess_by_bom(self, encoding, expected):
|
||||
data = u'\ufeff{}'.encode(encoding)
|
||||
assert guess_json_utf(data) == expected
|
||||
|
||||
|
||||
USER = PASSWORD = "%!*'();:@&=+$,/?#[] "
|
||||
ENCODED_USER = compat.quote(USER, '')
|
||||
ENCODED_PASSWORD = compat.quote(PASSWORD, '')
|
||||
ENCODED_USER = basics.quote(USER, '')
|
||||
ENCODED_PASSWORD = basics.quote(PASSWORD, '')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, auth', (
|
||||
'url, auth',
|
||||
(
|
||||
(
|
||||
'http://' + ENCODED_USER + ':' + ENCODED_PASSWORD + '@' +
|
||||
'http://' +
|
||||
ENCODED_USER +
|
||||
':' +
|
||||
ENCODED_PASSWORD +
|
||||
'@' +
|
||||
'request.com/url.html#test',
|
||||
(USER, PASSWORD)
|
||||
),
|
||||
(
|
||||
'http://user:pass@complex.url.com/path?query=yes',
|
||||
('user', 'pass')
|
||||
(USER, PASSWORD),
|
||||
),
|
||||
('http://user:pass@complex.url.com/path?query=yes', ('user', 'pass')),
|
||||
(
|
||||
'http://user:pass%20pass@complex.url.com/path?query=yes',
|
||||
('user', 'pass pass')
|
||||
('user', 'pass pass'),
|
||||
),
|
||||
(
|
||||
'http://user:pass pass@complex.url.com/path?query=yes',
|
||||
('user', 'pass pass')
|
||||
('user', 'pass pass'),
|
||||
),
|
||||
(
|
||||
'http://user%25user:pass@complex.url.com/path?query=yes',
|
||||
('user%user', 'pass')
|
||||
('user%user', 'pass'),
|
||||
),
|
||||
(
|
||||
'http://user:pass%23pass@complex.url.com/path?query=yes',
|
||||
('user', 'pass#pass')
|
||||
('user', 'pass#pass'),
|
||||
),
|
||||
(
|
||||
'http://complex.url.com/path?query=yes',
|
||||
('', '')
|
||||
),
|
||||
))
|
||||
('http://complex.url.com/path?query=yes', ('', '')),
|
||||
),
|
||||
)
|
||||
def test_get_auth_from_url(url, auth):
|
||||
assert get_auth_from_url(url) == auth
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'uri, expected', (
|
||||
(
|
||||
# Ensure requoting doesn't break expectations
|
||||
'http://example.com/fiz?buz=%25ppicture',
|
||||
'http://example.com/fiz?buz=%25ppicture',
|
||||
),
|
||||
(
|
||||
# Ensure we handle unquoted percent signs in redirects
|
||||
'http://example.com/fiz?buz=%ppicture',
|
||||
'http://example.com/fiz?buz=%25ppicture',
|
||||
),
|
||||
))
|
||||
'uri, expected',
|
||||
(('http://example.com/fiz?buz=%25ppicture', 'http://example.com/fiz?buz=%25ppicture'), ('http://example.com/fiz?buz=%ppicture', 'http://example.com/fiz?buz=%25ppicture')),
|
||||
# Ensure requoting doesn't break expectations
|
||||
# Ensure we handle unquoted percent signs in redirects
|
||||
)
|
||||
def test_requote_uri_with_unquoted_percents(uri, expected):
|
||||
"""See: https://github.com/requests/requests/issues/2356"""
|
||||
assert requote_uri(uri) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'uri, expected', (
|
||||
(
|
||||
# Illegal bytes
|
||||
'http://example.com/?a=%--',
|
||||
'http://example.com/?a=%--',
|
||||
),
|
||||
(
|
||||
# Reserved characters
|
||||
'http://example.com/?a=%300',
|
||||
'http://example.com/?a=00',
|
||||
)
|
||||
))
|
||||
'uri, expected',
|
||||
(('http://example.com/?a=%--', 'http://example.com/?a=%--'), ('http://example.com/?a=%300', 'http://example.com/?a=00')),
|
||||
# Illegal bytes
|
||||
# Reserved characters
|
||||
)
|
||||
def test_unquote_unreserved(uri, expected):
|
||||
assert unquote_unreserved(uri) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'mask, expected', (
|
||||
(8, '255.0.0.0'),
|
||||
(24, '255.255.255.0'),
|
||||
(25, '255.255.255.128'),
|
||||
))
|
||||
'mask, expected',
|
||||
((8, '255.0.0.0'), (24, '255.255.255.0'), (25, '255.255.255.128')),
|
||||
)
|
||||
def test_dotted_netmask(mask, expected):
|
||||
assert dotted_netmask(mask) == expected
|
||||
|
||||
|
||||
http_proxies = {'http': 'http://http.proxy',
|
||||
'http://some.host': 'http://some.host.proxy'}
|
||||
all_proxies = {'all': 'socks5://http.proxy',
|
||||
'all://some.host': 'socks5://some.host.proxy'}
|
||||
mixed_proxies = {'http': 'http://http.proxy',
|
||||
'http://some.host': 'http://some.host.proxy',
|
||||
'all': 'socks5://http.proxy'}
|
||||
http_proxies = {
|
||||
'http': 'http://http.proxy', 'http://some.host': 'http://some.host.proxy'
|
||||
}
|
||||
all_proxies = {
|
||||
'all': 'socks5://http.proxy', 'all://some.host': 'socks5://some.host.proxy'
|
||||
}
|
||||
mixed_proxies = {
|
||||
'http': 'http://http.proxy',
|
||||
'http://some.host': 'http://some.host.proxy',
|
||||
'all': 'socks5://http.proxy',
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected, proxies', (
|
||||
('hTTp://u:p@Some.Host/path', 'http://some.host.proxy', http_proxies),
|
||||
('hTTp://u:p@Other.Host/path', 'http://http.proxy', http_proxies),
|
||||
('hTTp:///path', 'http://http.proxy', http_proxies),
|
||||
('hTTps://Other.Host', None, http_proxies),
|
||||
('file:///etc/motd', None, http_proxies),
|
||||
|
||||
('hTTp://u:p@Some.Host/path', 'socks5://some.host.proxy', all_proxies),
|
||||
('hTTp://u:p@Other.Host/path', 'socks5://http.proxy', all_proxies),
|
||||
('hTTp:///path', 'socks5://http.proxy', all_proxies),
|
||||
('hTTps://Other.Host', 'socks5://http.proxy', all_proxies),
|
||||
|
||||
('http://u:p@other.host/path', 'http://http.proxy', mixed_proxies),
|
||||
('http://u:p@some.host/path', 'http://some.host.proxy', mixed_proxies),
|
||||
('https://u:p@other.host/path', 'socks5://http.proxy', mixed_proxies),
|
||||
('https://u:p@some.host/path', 'socks5://http.proxy', mixed_proxies),
|
||||
('https://', 'socks5://http.proxy', mixed_proxies),
|
||||
# XXX: unsure whether this is reasonable behavior
|
||||
('file:///etc/motd', 'socks5://http.proxy', all_proxies),
|
||||
))
|
||||
'url, expected, proxies',
|
||||
(('hTTp://u:p@Some.Host/path', 'http://some.host.proxy', http_proxies), ('hTTp://u:p@Other.Host/path', 'http://http.proxy', http_proxies), ('hTTp:///path', 'http://http.proxy', http_proxies), ('hTTps://Other.Host', None, http_proxies), ('file:///etc/motd', None, http_proxies), ('hTTp://u:p@Some.Host/path', 'socks5://some.host.proxy', all_proxies), ('hTTp://u:p@Other.Host/path', 'socks5://http.proxy', all_proxies), ('hTTp:///path', 'socks5://http.proxy', all_proxies), ('hTTps://Other.Host', 'socks5://http.proxy', all_proxies), ('http://u:p@other.host/path', 'http://http.proxy', mixed_proxies), ('http://u:p@some.host/path', 'http://some.host.proxy', mixed_proxies), ('https://u:p@other.host/path', 'socks5://http.proxy', mixed_proxies), ('https://u:p@some.host/path', 'socks5://http.proxy', mixed_proxies), ('https://', 'socks5://http.proxy', mixed_proxies), ('file:///etc/motd', 'socks5://http.proxy', all_proxies)),
|
||||
# XXX: unsure whether this is reasonable behavior
|
||||
)
|
||||
def test_select_proxies(url, expected, proxies):
|
||||
"""Make sure we can select per-host proxies correctly."""
|
||||
assert select_proxy(url, proxies) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
('foo="is a fish", bar="as well"', {'foo': 'is a fish', 'bar': 'as well'}),
|
||||
('key_without_value', {'key_without_value': None})
|
||||
))
|
||||
'value, expected',
|
||||
(
|
||||
(
|
||||
'foo="is a fish", bar="as well"',
|
||||
{'foo': 'is a fish', 'bar': 'as well'},
|
||||
),
|
||||
('key_without_value', {'key_without_value': None}),
|
||||
),
|
||||
)
|
||||
def test_parse_dict_header(value, expected):
|
||||
assert parse_dict_header(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
'value, expected',
|
||||
(
|
||||
(CaseInsensitiveDict(), None),
|
||||
(
|
||||
CaseInsensitiveDict(),
|
||||
None
|
||||
CaseInsensitiveDict(
|
||||
{'content-type': 'application/json; charset=utf-8'}
|
||||
),
|
||||
'utf-8',
|
||||
),
|
||||
(
|
||||
CaseInsensitiveDict({'content-type': 'application/json; charset=utf-8'}),
|
||||
'utf-8'
|
||||
),
|
||||
(
|
||||
CaseInsensitiveDict({'content-type': 'text/plain'}),
|
||||
'ISO-8859-1'
|
||||
),
|
||||
))
|
||||
(CaseInsensitiveDict({'content-type': 'text/plain'}), 'ISO-8859-1'),
|
||||
),
|
||||
)
|
||||
def test_get_encoding_from_headers(value, expected):
|
||||
assert get_encoding_from_headers(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, length', (
|
||||
'value, length',
|
||||
(
|
||||
('', 0),
|
||||
('T', 1),
|
||||
('Test', 4),
|
||||
('Cont', 0),
|
||||
('Other', -5),
|
||||
('Content', None),
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_iter_slices(value, length):
|
||||
if length is None or (length <= 0 and len(value) > 0):
|
||||
# Reads all content at once
|
||||
@@ -478,123 +494,124 @@ def test_iter_slices(value, length):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
'value, expected',
|
||||
(
|
||||
(
|
||||
'<http:/.../front.jpeg>; rel=front; type="image/jpeg"',
|
||||
[{'url': 'http:/.../front.jpeg', 'rel': 'front', 'type': 'image/jpeg'}]
|
||||
),
|
||||
(
|
||||
'<http:/.../front.jpeg>',
|
||||
[{'url': 'http:/.../front.jpeg'}]
|
||||
),
|
||||
(
|
||||
'<http:/.../front.jpeg>;',
|
||||
[{'url': 'http:/.../front.jpeg'}]
|
||||
[
|
||||
{
|
||||
'url': 'http:/.../front.jpeg',
|
||||
'rel': 'front',
|
||||
'type': 'image/jpeg',
|
||||
}
|
||||
],
|
||||
),
|
||||
('<http:/.../front.jpeg>', [{'url': 'http:/.../front.jpeg'}]),
|
||||
('<http:/.../front.jpeg>;', [{'url': 'http:/.../front.jpeg'}]),
|
||||
(
|
||||
'<http:/.../front.jpeg>; type="image/jpeg",<http://.../back.jpeg>;',
|
||||
[
|
||||
{'url': 'http:/.../front.jpeg', 'type': 'image/jpeg'},
|
||||
{'url': 'http://.../back.jpeg'}
|
||||
]
|
||||
{'url': 'http://.../back.jpeg'},
|
||||
],
|
||||
),
|
||||
))
|
||||
('', []),
|
||||
),
|
||||
)
|
||||
def test_parse_header_links(value, expected):
|
||||
assert parse_header_links(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
'value, expected',
|
||||
(
|
||||
('example.com/path', 'http://example.com/path'),
|
||||
('//example.com/path', 'http://example.com/path'),
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_prepend_scheme_if_needed(value, expected):
|
||||
assert prepend_scheme_if_needed(value, 'http') == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
('T', 'T'),
|
||||
(b'T', 'T'),
|
||||
(u'T', 'T'),
|
||||
))
|
||||
def test_to_native_string(value, expected):
|
||||
assert to_native_string(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected', (
|
||||
('http://u:p@example.com/path?a=1#test', 'http://example.com/path?a=1'),
|
||||
'url, expected',
|
||||
(
|
||||
(
|
||||
'http://u:p@example.com/path?a=1#test',
|
||||
'http://example.com/path?a=1',
|
||||
),
|
||||
('http://example.com/path', 'http://example.com/path'),
|
||||
('//u:p@example.com/path', '//example.com/path'),
|
||||
('//example.com/path', '//example.com/path'),
|
||||
('example.com/path', '//example.com/path'),
|
||||
('scheme:u:p@example.com/path', 'scheme://example.com/path'),
|
||||
))
|
||||
),
|
||||
)
|
||||
def test_urldefragauth(url, expected):
|
||||
assert urldefragauth(url) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected', (
|
||||
('http://192.168.0.1:5000/', True),
|
||||
('http://192.168.0.1/', True),
|
||||
('http://172.16.1.1/', True),
|
||||
('http://172.16.1.1:5000/', True),
|
||||
('http://localhost.localdomain:5000/v1.0/', True),
|
||||
('http://172.16.1.12/', False),
|
||||
('http://172.16.1.12:5000/', False),
|
||||
('http://google.com:5000/v1.0/', False),
|
||||
))
|
||||
'url, expected',
|
||||
(
|
||||
('http://192.168.0.1:5000/', True),
|
||||
('http://192.168.0.1/', True),
|
||||
('http://172.16.1.1/', True),
|
||||
('http://172.16.1.1:5000/', True),
|
||||
('http://localhost.localdomain:5000/v1.0/', True),
|
||||
('http://172.16.1.12/', False),
|
||||
('http://172.16.1.12:5000/', False),
|
||||
('http://google.com:5000/v1.0/', False),
|
||||
),
|
||||
)
|
||||
def test_should_bypass_proxies(url, expected, monkeypatch):
|
||||
"""Tests for function should_bypass_proxies to check if proxy
|
||||
can be bypassed or not
|
||||
"""
|
||||
monkeypatch.setenv('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
|
||||
monkeypatch.setenv('NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
|
||||
monkeypatch.setenv(
|
||||
'no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1'
|
||||
)
|
||||
monkeypatch.setenv(
|
||||
'NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1'
|
||||
)
|
||||
assert should_bypass_proxies(url, no_proxy=None) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'cookiejar', (
|
||||
compat.cookielib.CookieJar(),
|
||||
RequestsCookieJar()
|
||||
))
|
||||
'cookiejar', (basics.cookielib.CookieJar(), RequestsCookieJar())
|
||||
)
|
||||
def test_add_dict_to_cookiejar(cookiejar):
|
||||
"""Ensure add_dict_to_cookiejar works for
|
||||
non-RequestsCookieJar CookieJars
|
||||
"""
|
||||
cookiedict = {'test': 'cookies',
|
||||
'good': 'cookies'}
|
||||
cookiedict = {'test': 'cookies', 'good': 'cookies'}
|
||||
cj = add_dict_to_cookiejar(cookiejar, cookiedict)
|
||||
cookies = dict((cookie.name, cookie.value) for cookie in cj)
|
||||
cookies = {cookie.name: cookie.value for cookie in cj}
|
||||
assert cookiedict == cookies
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
(u'test', True),
|
||||
(u'æíöû', False),
|
||||
(u'ジェーピーニック', False),
|
||||
)
|
||||
'value, expected',
|
||||
((u'test', True), (u'æíöû', False), (u'ジェーピーニック', False)),
|
||||
)
|
||||
def test_unicode_is_ascii(value, expected):
|
||||
assert unicode_is_ascii(value) is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected', (
|
||||
('http://192.168.0.1:5000/', True),
|
||||
('http://192.168.0.1/', True),
|
||||
('http://172.16.1.1/', True),
|
||||
('http://172.16.1.1:5000/', True),
|
||||
('http://localhost.localdomain:5000/v1.0/', True),
|
||||
('http://172.16.1.12/', False),
|
||||
('http://172.16.1.12:5000/', False),
|
||||
('http://google.com:5000/v1.0/', False),
|
||||
))
|
||||
def test_should_bypass_proxies_no_proxy(
|
||||
url, expected, monkeypatch):
|
||||
'url, expected',
|
||||
(
|
||||
('http://192.168.0.1:5000/', True),
|
||||
('http://192.168.0.1/', True),
|
||||
('http://172.16.1.1/', True),
|
||||
('http://172.16.1.1:5000/', True),
|
||||
('http://localhost.localdomain:5000/v1.0/', True),
|
||||
('http://172.16.1.12/', False),
|
||||
('http://172.16.1.12:5000/', False),
|
||||
('http://google.com:5000/v1.0/', False),
|
||||
),
|
||||
)
|
||||
def test_should_bypass_proxies_no_proxy(url, expected, monkeypatch):
|
||||
"""Tests for function should_bypass_proxies to check if proxy
|
||||
can be bypassed or not using the 'no_proxy' argument
|
||||
"""
|
||||
@@ -605,31 +622,33 @@ def test_should_bypass_proxies_no_proxy(
|
||||
|
||||
@pytest.mark.skipif(os.name != 'nt', reason='Test only on Windows')
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected, override', (
|
||||
('http://192.168.0.1:5000/', True, None),
|
||||
('http://192.168.0.1/', True, None),
|
||||
('http://172.16.1.1/', True, None),
|
||||
('http://172.16.1.1:5000/', True, None),
|
||||
('http://localhost.localdomain:5000/v1.0/', True, None),
|
||||
('http://172.16.1.22/', False, None),
|
||||
('http://172.16.1.22:5000/', False, None),
|
||||
('http://google.com:5000/v1.0/', False, None),
|
||||
('http://mylocalhostname:5000/v1.0/', True, '<local>'),
|
||||
('http://192.168.0.1/', False, ''),
|
||||
))
|
||||
def test_should_bypass_proxies_win_registry(url, expected, override,
|
||||
monkeypatch):
|
||||
'url, expected, override',
|
||||
(
|
||||
('http://192.168.0.1:5000/', True, None),
|
||||
('http://192.168.0.1/', True, None),
|
||||
('http://172.16.1.1/', True, None),
|
||||
('http://172.16.1.1:5000/', True, None),
|
||||
('http://localhost.localdomain:5000/v1.0/', True, None),
|
||||
('http://172.16.1.22/', False, None),
|
||||
('http://172.16.1.22:5000/', False, None),
|
||||
('http://google.com:5000/v1.0/', False, None),
|
||||
('http://mylocalhostname:5000/v1.0/', True, '<local>'),
|
||||
('http://192.168.0.1/', False, ''),
|
||||
),
|
||||
)
|
||||
def test_should_bypass_proxies_win_registry(
|
||||
url, expected, override, monkeypatch
|
||||
):
|
||||
"""Tests for function should_bypass_proxies to check if proxy
|
||||
can be bypassed or not with Windows registry settings
|
||||
"""
|
||||
if override is None:
|
||||
override = '192.168.*;127.0.0.1;localhost.localdomain;172.16.1.1'
|
||||
if compat.is_py3:
|
||||
import winreg
|
||||
else:
|
||||
import _winreg as winreg
|
||||
|
||||
import winreg
|
||||
|
||||
class RegHandle:
|
||||
|
||||
def Close(self):
|
||||
pass
|
||||
|
||||
@@ -642,6 +661,7 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
|
||||
if key is ie_settings:
|
||||
if value_name == 'ProxyEnable':
|
||||
return [1]
|
||||
|
||||
elif value_name == 'ProxyOverride':
|
||||
return [override]
|
||||
|
||||
@@ -655,18 +675,19 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'env_name, value', (
|
||||
('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain'),
|
||||
('no_proxy', None),
|
||||
('a_new_key', '192.168.0.0/24,127.0.0.1,localhost.localdomain'),
|
||||
('a_new_key', None),
|
||||
))
|
||||
'env_name, value',
|
||||
(
|
||||
('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain'),
|
||||
('no_proxy', None),
|
||||
('a_new_key', '192.168.0.0/24,127.0.0.1,localhost.localdomain'),
|
||||
('a_new_key', None),
|
||||
),
|
||||
)
|
||||
def test_set_environ(env_name, value):
|
||||
"""Tests set_environ will set environ values and will restore the environ."""
|
||||
environ_copy = copy.deepcopy(os.environ)
|
||||
with set_environ(env_name, value):
|
||||
assert os.environ.get(env_name) == value
|
||||
|
||||
assert os.environ == environ_copy
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import threading
|
||||
import socket
|
||||
import select
|
||||
@@ -8,7 +7,6 @@ import select
|
||||
def consume_socket_content(sock, timeout=0.5):
|
||||
chunks = 65536
|
||||
content = b''
|
||||
|
||||
while True:
|
||||
more_to_read = select.select([sock], [], [], timeout)[0]
|
||||
if not more_to_read:
|
||||
@@ -19,7 +17,6 @@ def consume_socket_content(sock, timeout=0.5):
|
||||
break
|
||||
|
||||
content += new_content
|
||||
|
||||
return content
|
||||
|
||||
|
||||
@@ -27,37 +24,40 @@ class Server(threading.Thread):
|
||||
"""Dummy server using for unit testing"""
|
||||
WAIT_EVENT_TIMEOUT = 5
|
||||
|
||||
def __init__(self, handler=None, host='localhost', port=0, requests_to_handle=1, wait_to_close_event=None):
|
||||
def __init__(
|
||||
self,
|
||||
handler=None,
|
||||
host='localhost',
|
||||
port=0,
|
||||
requests_to_handle=1,
|
||||
wait_to_close_event=None,
|
||||
):
|
||||
super(Server, self).__init__()
|
||||
|
||||
self.handler = handler or consume_socket_content
|
||||
self.handler_results = []
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.requests_to_handle = requests_to_handle
|
||||
|
||||
self.wait_to_close_event = wait_to_close_event
|
||||
self.ready_event = threading.Event()
|
||||
self.stop_event = threading.Event()
|
||||
|
||||
@classmethod
|
||||
def text_response_server(cls, text, request_timeout=0.5, **kwargs):
|
||||
|
||||
def text_response_handler(sock):
|
||||
request_content = consume_socket_content(sock, timeout=request_timeout)
|
||||
request_content = consume_socket_content(
|
||||
sock, timeout=request_timeout
|
||||
)
|
||||
sock.send(text.encode('utf-8'))
|
||||
|
||||
return request_content
|
||||
|
||||
|
||||
return Server(text_response_handler, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def basic_response_server(cls, **kwargs):
|
||||
return cls.text_response_server(
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 0\r\n\r\n",
|
||||
**kwargs
|
||||
"HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n\r\n", **kwargs
|
||||
)
|
||||
|
||||
def run(self):
|
||||
@@ -67,11 +67,10 @@ class Server(threading.Thread):
|
||||
self.port = self.server_sock.getsockname()[1]
|
||||
self.ready_event.set()
|
||||
self._handle_requests()
|
||||
|
||||
if self.wait_to_close_event:
|
||||
self.wait_to_close_event.wait(self.WAIT_EVENT_TIMEOUT)
|
||||
finally:
|
||||
self.ready_event.set() # just in case of exception
|
||||
self.ready_event.set() # just in case of exception
|
||||
self._close_server_sock_ignore_errors()
|
||||
self.stop_event.set()
|
||||
|
||||
@@ -94,16 +93,18 @@ class Server(threading.Thread):
|
||||
break
|
||||
|
||||
handler_result = self.handler(sock)
|
||||
|
||||
self.handler_results.append(handler_result)
|
||||
|
||||
def _accept_connection(self):
|
||||
try:
|
||||
ready, _, _ = select.select([self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT)
|
||||
ready, _, _ = select.select(
|
||||
[self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT
|
||||
)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
return self.server_sock.accept()[0]
|
||||
|
||||
except (select.error, socket.error):
|
||||
return None
|
||||
|
||||
@@ -120,8 +121,7 @@ class Server(threading.Thread):
|
||||
# avoid server from waiting for event timeouts
|
||||
# if an exception is found in the main thread
|
||||
self.wait_to_close_event.set()
|
||||
|
||||
# ensure server thread doesn't get stuck waiting for connections
|
||||
self._close_server_sock_ignore_errors()
|
||||
self.join()
|
||||
return False # allow exceptions to propagate
|
||||
return False # allow exceptions to propagate
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
|
||||
@@ -14,6 +13,7 @@ def override_environ(**kwargs):
|
||||
os.environ[key] = value
|
||||
try:
|
||||
yield
|
||||
|
||||
finally:
|
||||
os.environ.clear()
|
||||
os.environ.update(save_env)
|
||||
|
||||
Reference in New Issue
Block a user