Compare commits
234 Commits
init_level
...
3.0
| 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 | ||
|
|
4f0b496d2b | ||
|
|
a36247e148 | ||
|
|
6dc3003f16 | ||
|
|
5aef6e7583 | ||
|
|
95b127714b | ||
|
|
5980d5df3e | ||
|
|
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/
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,28 +1,27 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
# - "2.6"
|
||||
- "2.7"
|
||||
- "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
|
||||
10
AUTHORS.rst
10
AUTHORS.rst
@@ -121,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>`_)
|
||||
@@ -153,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>`_)
|
||||
@@ -176,6 +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
|
||||
|
||||
@@ -9,6 +9,8 @@ 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**
|
||||
|
||||
@@ -234,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)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
4
Makefile
4
Makefile
@@ -4,7 +4,9 @@ init:
|
||||
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:
|
||||
pipenv run py.test -n 8 --boxed --junitxml=report.xml
|
||||
|
||||
|
||||
20
Pipfile
20
Pipfile
@@ -1,17 +1,19 @@
|
||||
[[source]]
|
||||
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
|
||||
[dev-packages]
|
||||
|
||||
pytest = ">=2.8.0"
|
||||
codecov = "*"
|
||||
"pytest-httpbin" = "==0.0.7"
|
||||
"pytest-mock" = "*"
|
||||
"pytest-cov" = "*"
|
||||
"pytest-xdist" = "*"
|
||||
pytest-httpbin = "*"
|
||||
pytest-mock = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-xdist = "*"
|
||||
alabaster = "*"
|
||||
"readme-renderer" = "*"
|
||||
readme-renderer = "*"
|
||||
sphinx = "<=1.5.5"
|
||||
pysocks = "*"
|
||||
docutils = "*"
|
||||
@@ -19,6 +21,12 @@ docutils = "*"
|
||||
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]
|
||||
"e1839a8" = {path = ".", editable = true, extras=["socks"]}
|
||||
|
||||
|
||||
517
Pipfile.lock
generated
517
Pipfile.lock
generated
@@ -1,517 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "72b5a08e9c266b930d308024036e928e6b99ed4b7a50f22af377a463b7867a14"
|
||||
},
|
||||
"host-environment-markers": {
|
||||
"implementation_name": "cpython",
|
||||
"implementation_version": "3.6.2",
|
||||
"os_name": "posix",
|
||||
"platform_machine": "x86_64",
|
||||
"platform_python_implementation": "CPython",
|
||||
"platform_release": "16.7.0",
|
||||
"platform_system": "Darwin",
|
||||
"platform_version": "Darwin Kernel Version 16.7.0: Thu Jun 15 17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64",
|
||||
"python_full_version": "3.6.2",
|
||||
"python_version": "3.6",
|
||||
"sys_platform": "darwin"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
|
||||
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
|
||||
],
|
||||
"version": "==2017.7.27.1"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"e1839a8": {
|
||||
"editable": true,
|
||||
"extras": [
|
||||
"socks"
|
||||
],
|
||||
"path": "."
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
|
||||
],
|
||||
"version": "==2.6"
|
||||
},
|
||||
"pysocks": {
|
||||
"hashes": [
|
||||
"sha256:18842328a4e6061f084cfba70f6950d9140ecf7418b3df7cef558ebb217bac8d",
|
||||
"sha256:d00329f27efa157db7efe3ca26fcd69033cd61f83822461ee3f8a353b48e33cf"
|
||||
],
|
||||
"version": "==1.6.7"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
],
|
||||
"version": "==1.22"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
|
||||
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
|
||||
],
|
||||
"version": "==0.7.10"
|
||||
},
|
||||
"apipkg": {
|
||||
"hashes": [
|
||||
"sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9",
|
||||
"sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6"
|
||||
],
|
||||
"version": "==1.4"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:f20b2acd44f587988ff185d8949c3e208b4b3d5d20fcab7d91fe481ffa435528",
|
||||
"sha256:6007daf714d0cd5524bbe436e2d42b3c20e68da66289559341e48d2cd6d25811"
|
||||
],
|
||||
"version": "==2.5.1"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:a6d9d5f5b7368c1689ad7f128af8e792beea23393688872b576c0271e6564a16",
|
||||
"sha256:b9522130003e4caedf4f00a39c120a906dcd4242329c1c8f621f5370203cbc30"
|
||||
],
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
|
||||
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
|
||||
],
|
||||
"version": "==2017.7.27.1"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
|
||||
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
|
||||
],
|
||||
"version": "==6.7"
|
||||
},
|
||||
"codecov": {
|
||||
"hashes": [
|
||||
"sha256:ad82f054837b02081f86ed1eb6c04cddc029fbc734eaf92ff73da1db3a79188b",
|
||||
"sha256:db1c182ca896244d8644d8410a33f6f6dd1cc24d80209907a65077445923f00c"
|
||||
],
|
||||
"version": "==2.0.9"
|
||||
},
|
||||
"configparser": {
|
||||
"hashes": [
|
||||
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
|
||||
],
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:c1456f66c536010cf9e4633a8853a9153e8fd588393695295afd4d0fc16c1d74",
|
||||
"sha256:97a7ec51cdde3a386e390b159b20f247ccb478084d925c75f1faa3d26c01335e",
|
||||
"sha256:83e955b975666b5a07d217135e7797857ce844eb340a99e46cc25525120417c4",
|
||||
"sha256:483ed14080c5301048128bb027b77978c632dd9e92e3ecb09b7e28f5b92abfcf",
|
||||
"sha256:ef574ab9640bcfa2f3c671831faf03f65788945fdf8efa4d4a1fffc034838e2a",
|
||||
"sha256:c5a205b4da3c624f5119dc4d84240789b5906bb8468902ec22dcc4aad8aa4638",
|
||||
"sha256:5dea90ed140e7fa9bc00463313f9bc4a6e6aff297b4969615e7a688615c4c4d2",
|
||||
"sha256:f9e83b39d29c2815a38e4118d776b482d4082b5bf9c9147fbc99a3f83abe480a",
|
||||
"sha256:700040c354f0230287906b1276635552a3def4b646e0145555bc9e2e5da9e365",
|
||||
"sha256:7f1eacae700c66c3d7362a433b228599c9d94a5a3a52613dddd9474e04deb6bc",
|
||||
"sha256:13ef9f799c8fb45c446a239df68034de3a6f3de274881b088bebd7f5661f79f8",
|
||||
"sha256:dfb011587e2b7299112f08a2a60d2601706aac9abde37aa1177ea825adaed923",
|
||||
"sha256:381be5d31d3f0d912334cf2c159bc7bea6bfe6b0e3df6061a3bf2bf88359b1f6",
|
||||
"sha256:83a477ac4f55a6ef59552683a0544d47b68a85ce6a80fd0ca6b3dc767f6495fb",
|
||||
"sha256:dfd35f1979da31bcabbe27bcf78d4284d69870731874af629082590023a77336",
|
||||
"sha256:9681efc2d310cfc53863cc6f63e88ebe7a48124550fa822147996cb09390b6ab",
|
||||
"sha256:53770b20ac5b4a12e99229d4bae57af0945be87cc257fce6c6c7571a39f0c5d4",
|
||||
"sha256:8801880d32f11b6df11c32a961e186774b4634ae39d7c43235f5a24368a85f07",
|
||||
"sha256:16db2c69a1acbcb3c13211e9f954e22b22a729909d81f983b6b9badacc466eda",
|
||||
"sha256:ef43a06a960b46c73c018704051e023ee6082030f145841ffafc8728039d5a88",
|
||||
"sha256:c3e2736664a6074fc9bd54fb643f5af0fc60bfedb2963b3d3f98c7450335e34c",
|
||||
"sha256:17709e22e4c9f5412ba90f446fb13b245cc20bf4a60377021bbff6c0f1f63e7c",
|
||||
"sha256:a2f7106d1167825c4115794c2ba57cc3b15feb6183db5328fa66f94c12902d8b",
|
||||
"sha256:2a08e978f402696c6956eee9d1b7e95d3ad042959b71bafe1f3e4557cbd6e0ac",
|
||||
"sha256:57f510bb16efaec0b6f371b64a8000c62e7e3b3e48e8b0a5745ade078d849814",
|
||||
"sha256:0f1883eab9c19aa243f51308751b8a2a547b9b817b721cc0ecf3efb99fafbea7",
|
||||
"sha256:e00fe141e22ce6e9395aa24d862039eb180c6b7e89df0bbaf9765e9aebe560a9",
|
||||
"sha256:ec596e4401553caa6dd2e3349ce47f9ef82c1f1bcba5d8ac3342724f0df8d6ff",
|
||||
"sha256:c820a533a943ebc860acc0ce6a00dd36e0fdf2c6f619ff8225755169428c5fa2",
|
||||
"sha256:b7f7283eb7badd2b8a9c6a9d6eeca200a0a24db6be79baee2c11398f978edcaa",
|
||||
"sha256:a5ed27ad3e8420b2d6b625dcbd3e59488c14ccc06030167bcf14ffb0f4189b77",
|
||||
"sha256:d7b70b7b4eb14d0753d33253fe4f121ca99102612e2719f0993607deb30c6f33",
|
||||
"sha256:4047dc83773869701bde934fb3c4792648eda7c0e008a77a0aec64157d246801",
|
||||
"sha256:7a9c44400ee0f3b4546066e0710e1250fd75831adc02ab99dda176ad8726f424",
|
||||
"sha256:0f649e68db74b1b5b8ca4161d08eb2b8fa8ae11af1ebfb80e80e112eb0ef5300",
|
||||
"sha256:52964fae0fafef8bd283ad8e9a9665205a9fdf912535434defc0ec3def1da26b",
|
||||
"sha256:36aa6c8db83bc27346ddcd8c2a60846a7178ecd702672689d3ea1828eb1a4d11",
|
||||
"sha256:9824e15b387d331c0fc0fef905a539ab69784368a1d6ac3db864b4182e520948",
|
||||
"sha256:4a678e1b9619a29c51301af61ab84122e2f8cc7a0a6b40854b808ac6be604300",
|
||||
"sha256:8bb7c8dca54109b61013bc4114d96effbf10dea136722c586bce3a5d9fc4e730",
|
||||
"sha256:1a41d621aa9b6ab6457b557a754d50aaff0813fad3453434de075496fca8a183",
|
||||
"sha256:0fa423599fc3d9e18177f913552cdb34a8d9ad33efcf52a98c9d4b644edb42c5",
|
||||
"sha256:e61a4ba0b2686040cb4828297c7e37bcaf3a1a1c0bc0dbe46cc789dde51a80fa",
|
||||
"sha256:ce9ef0fc99d11d418662e36fd8de6d71b19ec87c2eab961a117cc9d087576e72"
|
||||
],
|
||||
"version": "==4.4.1"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:95a26b17806e284452bfd97fa20aa1e8cb4ee23542bda4dbac5e4562aa1642cd",
|
||||
"sha256:7cb64d38cb8002971710c8899fbdfb859a23a364b7c99dab19d1f719c2ba16b5"
|
||||
],
|
||||
"version": "==4.1.2"
|
||||
},
|
||||
"detox": {
|
||||
"hashes": [
|
||||
"sha256:af0097ea01263f68f546826df69b9301458d6cec0ed278c53c01f9529fbd349e",
|
||||
"sha256:4719ca48c4ea5ffd908b1bc3d5d1b593b41e71dee17180d58d8a3e7e8f588d45"
|
||||
],
|
||||
"version": "==0.11"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6",
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274"
|
||||
],
|
||||
"version": "==0.14"
|
||||
},
|
||||
"enum-compat": {
|
||||
"hashes": [
|
||||
"sha256:939ceff18186a5762ae4db9fa7bfe017edbd03b66526b798dd8245394c8a4192"
|
||||
],
|
||||
"version": "==0.0.2"
|
||||
},
|
||||
"enum34": {
|
||||
"hashes": [
|
||||
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
|
||||
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
|
||||
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1",
|
||||
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850"
|
||||
],
|
||||
"version": "==1.1.6"
|
||||
},
|
||||
"eventlet": {
|
||||
"hashes": [
|
||||
"sha256:0a7d1e1d2f4dd2e0b2cb627dadf7a0f23de0eca88ba2d6af4229abe32a24dec9",
|
||||
"sha256:08faffab88c1b08bd53ea28bf084a572c89f7e7648bd9d71e6116ac17a51a15d"
|
||||
],
|
||||
"version": "==0.21.0"
|
||||
},
|
||||
"execnet": {
|
||||
"hashes": [
|
||||
"sha256:d2b909c7945832e1c19cfacd96e78da68bdadc656440cfc7dfe59b766744eb8c",
|
||||
"sha256:f66dd4a7519725a1b7e14ad9ae7d3df8e09b2da88062386e08e941cafc0ef3e6"
|
||||
],
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:f1a9d8886a9cbefb52485f4f4c770832c7fb569c084a9a314fb1eaa37c0c2c86",
|
||||
"sha256:c20044779ff848f67f89c56a0e4624c04298cd476e25253ac0c36f910a1a11d8"
|
||||
],
|
||||
"version": "==3.4.1"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856",
|
||||
"sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1"
|
||||
],
|
||||
"version": "==0.12.2"
|
||||
},
|
||||
"funcsigs": {
|
||||
"hashes": [
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
"sha256:96888e47898a471073b394ea641b7d675c1d054c580dd4a04a382bd34e67d89e",
|
||||
"sha256:d2d5103f6cba131e1be660230018e21f276911d2b68b629ead1c5cb5e5472ac7",
|
||||
"sha256:bc339de0e0969de5118d0b62a080a7611e2ba729a90f4a3ad78559c51bc5576d",
|
||||
"sha256:b8ab98f8ae25938326dc4c21e3689a933531500ae4f3bfcefe36e3e25fda4dbf",
|
||||
"sha256:416a3328d7e0a19aa1df3ec09524a109061fd7b80e010ef0dff9f695b4ac5e20",
|
||||
"sha256:21232907c8c26838b16915bd8fbbf82fc70c996073464cc70981dd4a96bc841c",
|
||||
"sha256:6803d8c6b235c861c50afddf00c7467ffbcd5ab960d137ff0f9c36f2cb11ee4b",
|
||||
"sha256:76dab055476dd4dabb00a967b4df1990b25542d17eaa40a18f66971d10193e0b",
|
||||
"sha256:70b9ff28921f5a3c03df4896ec8c55f5f94c593d7a79abd98b4c5c4a692ba873",
|
||||
"sha256:7114b757b4146f4c87a0f00f1e58abd4c4729836679af0fc37266910a4a72eb0",
|
||||
"sha256:0d90c709355ed13f16676f84e5a9cd67826a9f5c5143381c21e8fc3100ade1f1",
|
||||
"sha256:ebae83b6247f83b1e8d887733dfa8046ce6e29d8b3e2a7380256e9de5c6ae55d",
|
||||
"sha256:e841e3ece633acae5e2bf6102140a605ffee7d5d4921dca1625c5fdc0f0b3248",
|
||||
"sha256:3e5e9be157ece49e4f97f3225460caf758ccb00f934fcbc5db34367cc1ff0aee",
|
||||
"sha256:e77b708c37b652c7501b9f8f6056b23633c567aaa0d29edfef1c11673c64b949",
|
||||
"sha256:0da1fc809c3bdb93fbacd0f921f461aacd53e554a7b7d4e9953ba09131c4206e",
|
||||
"sha256:66fa5b101fcf4521138c1a29668074268d938bbb7de739c8faa9f92ea1f05e1f",
|
||||
"sha256:e5451e1ce06b74a4861576c2db74405a4398c4809a105774550a9e52cfc8c4da",
|
||||
"sha256:9c407aa6adfd4eea1232e81aa9f3cb3d9b955a9891c4819bf9b498c77efba14b",
|
||||
"sha256:b56ac981f07b77e72ad5154278b93396d706572ea52c2fce79fee2abfcc8bfa6",
|
||||
"sha256:e4c99c6010a5d153d481fdaf63b8a0782825c0721506d880403a3b9b82ae347e"
|
||||
],
|
||||
"version": "==0.4.12"
|
||||
},
|
||||
"html5lib": {
|
||||
"hashes": [
|
||||
"sha256:b8934484cf22f1db684c0fae27569a0db404d0208d20163fbf51cc537245d008",
|
||||
"sha256:ee747c0ffd3028d2722061936b5c65ee4fe13c8e4613519b4447123fc4546298"
|
||||
],
|
||||
"version": "==0.999999999"
|
||||
},
|
||||
"httpbin": {
|
||||
"hashes": [
|
||||
"sha256:710069973216d4bbf9ab6757f1e9a1f3be05832ce77da023adce0a98dfeecfee",
|
||||
"sha256:79fbc5d27e4194ea908b0fa18e09a59d95d287c91667aa69bcd010342d1589b5"
|
||||
],
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
|
||||
],
|
||||
"version": "==2.6"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:6ebdc9e0ad188f9d1b2cdd9bc59cbe42bf931875e829e7a595e6b3abdc05cdfb",
|
||||
"sha256:0ab2c62b87987e3252f89d30b7cedbec12a01af9274af9ffa48108f2c13c6062"
|
||||
],
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
|
||||
],
|
||||
"version": "==0.24"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054",
|
||||
"sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff"
|
||||
],
|
||||
"version": "==2.9.6"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
||||
],
|
||||
"version": "==1.0"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"mock": {
|
||||
"hashes": [
|
||||
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
||||
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
||||
],
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac",
|
||||
"sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1"
|
||||
],
|
||||
"version": "==3.1.1"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:bd60171dbb250fdebafad46ed16d97065369da40568ae948ef7117eee8536e94"
|
||||
],
|
||||
"version": "==0.5.2"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:2ccb79b01769d99115aa600d7eed99f524bf752bba8f041dc1c184853514655a",
|
||||
"sha256:0f2d585d22050e90c7d293b6451c83db097df77871974d90efd5a30dc12fcde3"
|
||||
],
|
||||
"version": "==1.4.34"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9",
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766"
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:cc5eadfb38041f8366128786b4ca12700ed05bbf1403d808e89d57d67a3875a7",
|
||||
"sha256:aa0d4dff45c0cc2214ba158d29280f8fa1129f3e87858ef825930845146337f4"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
|
||||
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pysocks": {
|
||||
"hashes": [
|
||||
"sha256:18842328a4e6061f084cfba70f6950d9140ecf7418b3df7cef558ebb217bac8d",
|
||||
"sha256:d00329f27efa157db7efe3ca26fcd69033cd61f83822461ee3f8a353b48e33cf"
|
||||
],
|
||||
"version": "==1.6.7"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:b84f554f8ddc23add65c411bf112b2d88e2489fd45f753b1cae5936358bdf314",
|
||||
"sha256:f46e49e0340a532764991c498244a60e3a37d7424a532b3ff1a6a7653f1a403a"
|
||||
],
|
||||
"version": "==3.2.2"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
"sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec",
|
||||
"sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d"
|
||||
],
|
||||
"version": "==2.5.1"
|
||||
},
|
||||
"pytest-forked": {
|
||||
"hashes": [
|
||||
"sha256:f275cb48a73fc61a6710726348e1da6d68a978f0ec0c54ece5a5fae5977e5a08",
|
||||
"sha256:e4500cd0509ec4a26535f7d4112a8cc0f17d3a41c29ffd4eab479d2a55b30805"
|
||||
],
|
||||
"version": "==0.2"
|
||||
},
|
||||
"pytest-httpbin": {
|
||||
"hashes": [
|
||||
"sha256:f430f0b5742a9d325148a3428f890f538f331cb7b244a49873cc322f838c85ea",
|
||||
"sha256:03af8a7055c8bbcb68b14d9a14c103c82c97aeb86a8f1b29cd63d83644c2f021"
|
||||
],
|
||||
"version": "==0.0.7"
|
||||
},
|
||||
"pytest-mock": {
|
||||
"hashes": [
|
||||
"sha256:7ed6e7e8c636fd320927c5d73aedb77ac2eeb37196c3410e6176b7c92fdf2f69",
|
||||
"sha256:920d1167af5c2c2ad3fa0717d0c6c52e97e97810160c15721ac895cac53abb1c"
|
||||
],
|
||||
"version": "==1.6.3"
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:7924d45c2430191fe3679a58116c74ceea13307d7822c169d65fd59a24b3a4fe"
|
||||
],
|
||||
"version": "==1.20.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:c883c2d6670042c7bc1688645cac73dd2b03193d1f7a6847b6154e96890be06d",
|
||||
"sha256:03c9962afe00e503e2d96abab4e8998a0f84d4230fa57afe1e0528473698cdd9",
|
||||
"sha256:487e7d50710661116325747a9cd1744d3323f8e49748e287bc9e659060ec6bf9",
|
||||
"sha256:43f52d4c6a0be301d53ebd867de05e2926c35728b3260157d274635a0a947f1c",
|
||||
"sha256:d1d6729c85acea5423671382868627129432fba9a89ecbb248d8d1c7a9f01c67",
|
||||
"sha256:54a935085f7bf101f86b2aff75bd9672b435f51c3339db2ff616e66845f2b8f9",
|
||||
"sha256:39504670abb5dae77f56f8eb63823937ce727d7cdd0088e6909e6dcac0f89043",
|
||||
"sha256:ddc93b6d41cfb81266a27d23a79e13805d4a5521032b512643af8729041a81b4",
|
||||
"sha256:f5c056e8f62d45ba8215e5cb8f50dfccb198b4b9fbea8500674f3443e4689589"
|
||||
],
|
||||
"version": "==2017.2"
|
||||
},
|
||||
"readme-renderer": {
|
||||
"hashes": [
|
||||
"sha256:c9637bfcf1ff40f7683b3439f4b97eb0f9a1cffc2a1fad5fa01debd667ddb111",
|
||||
"sha256:9deab442963a63a71ab494bf581b1c844473995a2357f4b3228a1df1c8cba8da"
|
||||
],
|
||||
"version": "==17.2"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||
],
|
||||
"version": "==2.18.4"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89",
|
||||
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128"
|
||||
],
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:11f271e7a9398385ed730e90f0bb41dc3815294bdcd395b46ed2d033bc2e7d87",
|
||||
"sha256:4064ea6c56feeb268838cb8fbbee507d0c3d5d92fa63a7df935a916b52c9e2f5"
|
||||
],
|
||||
"version": "==1.5.5"
|
||||
},
|
||||
"tox": {
|
||||
"hashes": [
|
||||
"sha256:49d88f2c217352c499450e9f61ca82fd9c8873d01a45555bb342a32f2b6753a2",
|
||||
"sha256:d9c279e707d2cfef8d77d10f13b38b3e68b7e470018b45747560f6c3c66d6b83"
|
||||
],
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
],
|
||||
"version": "==1.22"
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:39d88b533b422825d644087a21e78c45cf5af0ef7a99a1fc9fbb7b481e5c85b0",
|
||||
"sha256:02f8102c2436bb03b3ee6dede1919d1dac8a427541652e5ec95171ec8adbc93a"
|
||||
],
|
||||
"version": "==15.1.0"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d",
|
||||
"sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26"
|
||||
],
|
||||
"version": "==0.12.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ Requests is ready for today's web.
|
||||
- ``.netrc`` Support
|
||||
- Chunked Requests
|
||||
|
||||
Requests officially supports Python 2.6–2.7 & 3.4–3.6, and runs great on PyPy.
|
||||
Requests officially supports Python 2.7 & 3.4–3.7, and runs great on PyPy.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
15
appveyor.yml
15
appveyor.yml
@@ -5,21 +5,6 @@ build: off
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: "C:\\Python27-x64"
|
||||
PYTHON_VERSION: "2.7.x"
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py27"
|
||||
|
||||
- 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"
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ Python 3 Support?
|
||||
Yes! Here's a list of Python platforms that are officially
|
||||
supported:
|
||||
|
||||
* Python 2.6
|
||||
* Python 2.7
|
||||
* Python 3.4
|
||||
* Python 3.5
|
||||
@@ -70,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
|
||||
|
||||
@@ -51,7 +51,6 @@ Runtime Environments
|
||||
|
||||
Requests currently supports the following versions of Python:
|
||||
|
||||
- Python 2.6
|
||||
- Python 2.7
|
||||
- Python 3.4
|
||||
- Python 3.5
|
||||
|
||||
@@ -104,7 +104,7 @@ Requests is ready for today's web.
|
||||
- Chunked Requests
|
||||
- ``.netrc`` Support
|
||||
|
||||
Requests officially supports Python 2.6–2.7 & 3.4–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
|
||||
|
||||
@@ -373,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
|
||||
----------------
|
||||
@@ -416,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,93 +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):
|
||||
def _check_cryptography(cryptography_version: types.String) -> None:
|
||||
# cryptography < 1.3.4
|
||||
try:
|
||||
cryptography_version = list(map(int, cryptography_version.split('.')))
|
||||
cryptography_version = list(
|
||||
map(int, cryptography_version.split('.'))
|
||||
) # type: ignore
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
if cryptography_version < [1, 3, 4]:
|
||||
warning = 'Old version of cryptography ({0}) may cause slowdown.'.format(cryptography_version)
|
||||
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()
|
||||
|
||||
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.4'
|
||||
__build__ = 0x021804
|
||||
__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
|
||||
@@ -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,30 +727,29 @@ 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):
|
||||
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
|
||||
@@ -763,9 +759,7 @@ def parse_header_links(value):
|
||||
url, params = val.split(';', 1)
|
||||
except ValueError:
|
||||
url, params = val, ''
|
||||
|
||||
link = {'url': url.strip('<> \'"')}
|
||||
|
||||
for param in params.split(';'):
|
||||
try:
|
||||
key, value = param.split('=')
|
||||
@@ -773,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
|
||||
"""
|
||||
@@ -794,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
|
||||
|
||||
|
||||
@@ -855,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.
|
||||
@@ -863,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("Value for header {%s: %s} must be of type str or "
|
||||
"bytes, not %s" % (name, 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
|
||||
|
||||
77
setup.py
77
setup.py
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# Learn more: https://github.com/kennethreitz/setup.py
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from codecs import open
|
||||
@@ -12,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)
|
||||
@@ -34,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.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__'],
|
||||
@@ -81,21 +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.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,127 +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
|
||||
"""
|
||||
@@ -609,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
|
||||
|
||||
@@ -646,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]
|
||||
|
||||
@@ -659,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