Compare commits
351 Commits
update-3.0
...
v2.19.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
883caaf145 | ||
|
|
2530323faf | ||
|
|
8031ec76b8 | ||
|
|
991e8b76b7 | ||
|
|
7e297ed95b | ||
|
|
10915e3b0d | ||
|
|
ace0c200f4 | ||
|
|
3ef49272f2 | ||
|
|
d1aeb7ea8c | ||
|
|
de64f0f23f | ||
|
|
bc46de58e6 | ||
|
|
16fd7e03a9 | ||
|
|
35a1874912 | ||
|
|
d98f881118 | ||
|
|
ac944b7439 | ||
|
|
00fd4c8eb4 | ||
|
|
c1b828bc95 | ||
|
|
fe8290b3d0 | ||
|
|
8546a15587 | ||
|
|
5f09f0ca7e | ||
|
|
8bffd3da5f | ||
|
|
9b5071cdb6 | ||
|
|
c6db87978f | ||
|
|
f3fc892b90 | ||
|
|
161acb6a50 | ||
|
|
371ee7cbec | ||
|
|
80e2637a83 | ||
|
|
9055b33f6f | ||
|
|
9616f82905 | ||
|
|
919c168cdb | ||
|
|
adceafd79d | ||
|
|
e02effb774 | ||
|
|
60a5a393d8 | ||
|
|
9e1d8edcb7 | ||
|
|
1e8a3142d2 | ||
|
|
71e402fe40 | ||
|
|
048c926b46 | ||
|
|
41c455182b | ||
|
|
5f306dbe99 | ||
|
|
239ee19431 | ||
|
|
75c57985b0 | ||
|
|
11b82ca87c | ||
|
|
cd1278ef99 | ||
|
|
2de907ad77 | ||
|
|
165804b25e | ||
|
|
b66908e7b6 | ||
|
|
35af0b077f | ||
|
|
8246b82a16 | ||
|
|
cd6eb30b20 | ||
|
|
e203d50e10 | ||
|
|
b920c6e3f8 | ||
|
|
6576370b7b | ||
|
|
85e536f7eb | ||
|
|
130773a2d3 | ||
|
|
87872b9587 | ||
|
|
3de173e1c9 | ||
|
|
b5eacad638 | ||
|
|
b1ba9173d0 | ||
|
|
ee58368c9f | ||
|
|
cf0b8ed291 | ||
|
|
280c8b2a72 | ||
|
|
16b56d822e | ||
|
|
742610c6b9 | ||
|
|
eb692cb1e1 | ||
|
|
f9a9233058 | ||
|
|
9de992e22c | ||
|
|
c063938abb | ||
|
|
667367d6b9 | ||
|
|
8ce2fd2557 | ||
|
|
d8cc045a2e | ||
|
|
dc10a5dadd | ||
|
|
bf32936955 | ||
|
|
05b1401795 | ||
|
|
873e8914c1 | ||
|
|
c55258792b | ||
|
|
afad1aab14 | ||
|
|
d05840d34d | ||
|
|
3323e65e96 | ||
|
|
74cf6fc38e | ||
|
|
d91b70bd29 | ||
|
|
5daa6cc63c | ||
|
|
0730b231ec | ||
|
|
77aa42943e | ||
|
|
b98027af9e | ||
|
|
8ce2c1a182 | ||
|
|
327a54d29d | ||
|
|
38a1947957 | ||
|
|
f8ade96762 | ||
|
|
665bb2a687 | ||
|
|
f21fd19ac5 | ||
|
|
f51c489d38 | ||
|
|
773eeff1a2 | ||
|
|
ac0a7ed152 | ||
|
|
a4f313a0e5 | ||
|
|
8ca453a8d7 | ||
|
|
c436165a2d | ||
|
|
97b28fdf7f | ||
|
|
40f4b984ef | ||
|
|
529c423fb5 | ||
|
|
e292f302b7 | ||
|
|
c95b63736d | ||
|
|
2d357f9c3b | ||
|
|
7fb1d70b86 | ||
|
|
c78c5002b5 | ||
|
|
d123e9fe78 | ||
|
|
8ad24ad430 | ||
|
|
85ec7d759e | ||
|
|
4ea09e49f7 | ||
|
|
06e788e253 | ||
|
|
27aad3666c | ||
|
|
44dbf8cf40 | ||
|
|
6e76aaea45 | ||
|
|
265ef609d5 | ||
|
|
cafa3c16c2 | ||
|
|
9c0db92249 | ||
|
|
3c1d36b827 | ||
|
|
3cd87fac5d | ||
|
|
22120d423e | ||
|
|
654da610e1 | ||
|
|
e8205c0131 | ||
|
|
8da118dfeb | ||
|
|
d69c068ac2 | ||
|
|
3ad14bb54e | ||
|
|
8e040523a4 | ||
|
|
99533b0f0f | ||
|
|
512066488e | ||
|
|
d2e1130603 | ||
|
|
f60324a3de | ||
|
|
f89c325fbe | ||
|
|
d55b81b964 | ||
|
|
6a74c70a3a | ||
|
|
599a8f11f8 | ||
|
|
0813c865ee | ||
|
|
07372dc058 | ||
|
|
2255c34a65 | ||
|
|
020f96577f | ||
|
|
7cefa939f5 | ||
|
|
b9d8e00cbc | ||
|
|
620a5391c3 | ||
|
|
c98e30b5cb | ||
|
|
030dcce20c | ||
|
|
3dc84cde5b | ||
|
|
e0ab287317 | ||
|
|
7deee699ad | ||
|
|
cb0914407b | ||
|
|
80a790443e | ||
|
|
071796d83f | ||
|
|
1988d9cf72 | ||
|
|
2dc51c887f | ||
|
|
19cfec28a8 | ||
|
|
cef0830419 | ||
|
|
d8666e1906 | ||
|
|
efacb9e312 | ||
|
|
1e46cbb4f0 | ||
|
|
24092b11d7 | ||
|
|
351ec982bb | ||
|
|
714c9dc967 | ||
|
|
d1fb1a29ab | ||
|
|
c2507013ab | ||
|
|
e68ef0ecad | ||
|
|
19919b44c4 | ||
|
|
39446def39 | ||
|
|
acd2645444 | ||
|
|
e4fc3539b4 | ||
|
|
775cde0914 | ||
|
|
a31531fb9c | ||
|
|
c3f2553114 | ||
|
|
7cc3d8dc6a | ||
|
|
6d69f944a4 | ||
|
|
7f08ad3b6c | ||
|
|
d55de2d391 | ||
|
|
9a8a826f22 | ||
|
|
1dbaddd75e | ||
|
|
234f80af88 | ||
|
|
c86b09b3c6 | ||
|
|
81b63419cb | ||
|
|
c5ed41e00a | ||
|
|
40c5a8b0c2 | ||
|
|
87f3b0a559 | ||
|
|
2c4849defe | ||
|
|
c4c031fc5c | ||
|
|
1595e43812 | ||
|
|
8982efa9e4 | ||
|
|
2afc1cb37b | ||
|
|
db9da3e0d2 | ||
|
|
a05aac7007 | ||
|
|
d165b18b6e | ||
|
|
e11989e8ec | ||
|
|
af88af64e6 | ||
|
|
d3f14af44d | ||
|
|
54ad3d6dcc | ||
|
|
c14bd018ad | ||
|
|
e86df6e0eb | ||
|
|
ec5804c706 | ||
|
|
4316af7ae8 | ||
|
|
8c4f78448a | ||
|
|
15054aa390 | ||
|
|
9713289e74 | ||
|
|
e6afc37be5 | ||
|
|
5eb0811ef8 | ||
|
|
40c8bcf70c | ||
|
|
0eb9a4c161 | ||
|
|
cda429aa5d | ||
|
|
28ae74dbad | ||
|
|
a9d05f06cd | ||
|
|
d552132464 | ||
|
|
c3ecb825c4 | ||
|
|
70a4ae744c | ||
|
|
4c300c2067 | ||
|
|
fdb9b753b4 | ||
|
|
4bda7b66e7 | ||
|
|
068d8863d1 | ||
|
|
c49f3643ae | ||
|
|
328cff252d | ||
|
|
c57bd07a34 | ||
|
|
0d82afa0c3 | ||
|
|
18e37c9368 | ||
|
|
389da34fc9 | ||
|
|
8ef20d5c36 | ||
|
|
e63d481a7a | ||
|
|
f3eeb38542 | ||
|
|
afaaae185c | ||
|
|
8af65a1eca | ||
|
|
c04b044b30 | ||
|
|
530198e0c0 | ||
|
|
8c57515696 | ||
|
|
175452a6b6 | ||
|
|
e35c829b59 | ||
|
|
1ad51b0a41 | ||
|
|
8be8105c42 | ||
|
|
49ec082a77 | ||
|
|
0488c640e2 | ||
|
|
8371b56d0f | ||
|
|
830c18e4f9 | ||
|
|
2c438cacd1 | ||
|
|
9c8ae76928 | ||
|
|
d5a05469f1 | ||
|
|
e22e3a5059 | ||
|
|
054ad4aa67 | ||
|
|
b9638ec7be | ||
|
|
940c1246e0 | ||
|
|
e0e5b2e3aa | ||
|
|
96b9a3f0fc | ||
|
|
657e44ad9a | ||
|
|
ac9a3e5320 | ||
|
|
6b21682712 | ||
|
|
785c733415 | ||
|
|
369be8afd0 | ||
|
|
572d2f4534 | ||
|
|
7e20b9193a | ||
|
|
ad1aff52a0 | ||
|
|
3a6dec9827 | ||
|
|
3cceee3459 | ||
|
|
dd57439fa5 | ||
|
|
f09e462165 | ||
|
|
3a2ea9deba | ||
|
|
5dcbdb50fc | ||
|
|
8ad09b68ea | ||
|
|
88dc33522d | ||
|
|
e5666b93cf | ||
|
|
554a210f88 | ||
|
|
8e411d0f99 | ||
|
|
7d190ef010 | ||
|
|
965ada6f66 | ||
|
|
af9dd61fcb | ||
|
|
a3d7cf3f27 | ||
|
|
3c5ecfc9f5 | ||
|
|
d528106876 | ||
|
|
cbc7c2d1c9 | ||
|
|
e36083b89c | ||
|
|
308343508b | ||
|
|
ba21d14976 | ||
|
|
cee8fae65e | ||
|
|
1c2022cf86 | ||
|
|
8480cf937a | ||
|
|
f6952a9b8d | ||
|
|
d758f0cf97 | ||
|
|
c31a450c55 | ||
|
|
0a2dc74ecb | ||
|
|
f78a57936b | ||
|
|
218978601f | ||
|
|
cb9620780f | ||
|
|
963c78853f | ||
|
|
b670b61d54 | ||
|
|
9ec2005d9f | ||
|
|
f4ddf00c36 | ||
|
|
248f122970 | ||
|
|
be8ce365ab | ||
|
|
3393dc8221 | ||
|
|
7c67c4adf9 | ||
|
|
2a4dd64fb4 | ||
|
|
9e0fb37fff | ||
|
|
4f49f6b3ed | ||
|
|
8b3f20ca91 | ||
|
|
fdf426125b | ||
|
|
d6b57c6fb2 | ||
|
|
39b121d791 | ||
|
|
12c7456789 | ||
|
|
2629c0906e | ||
|
|
205755834d | ||
|
|
e2f5a135c3 | ||
|
|
8de858f0de | ||
|
|
667896c557 | ||
|
|
2b6f1e6236 | ||
|
|
b77c1a3deb | ||
|
|
9e8d403a21 | ||
|
|
6cc891fd32 | ||
|
|
49c44a0c08 | ||
|
|
c6e18b04e3 | ||
|
|
c10b8384e4 | ||
|
|
8d1228cd19 | ||
|
|
8e227aeb89 | ||
|
|
d8c2fdf92f | ||
|
|
cada19b300 | ||
|
|
c6a80640d1 | ||
|
|
649efc8c9b | ||
|
|
14176d1d63 | ||
|
|
1ea4e21958 | ||
|
|
51feabbc27 | ||
|
|
f6e07bb27f | ||
|
|
c054a722bb | ||
|
|
bbeb1c32d2 | ||
|
|
1263b8660d | ||
|
|
2fde9ee407 | ||
|
|
51de61f914 | ||
|
|
dd13b13479 | ||
|
|
d93b37cee7 | ||
|
|
a5b8f8d40a | ||
|
|
a8972a5bd2 | ||
|
|
bb4326c676 | ||
|
|
a0f0258eee | ||
|
|
c5901ac893 | ||
|
|
c6a6347156 | ||
|
|
132040cc03 | ||
|
|
613f61d77c | ||
|
|
edcc894a2e | ||
|
|
761e39b443 | ||
|
|
1d7fd6c8b3 | ||
|
|
b3d138e0fd | ||
|
|
8a8b71f786 | ||
|
|
2872f17ab8 | ||
|
|
22d12b0501 | ||
|
|
13eb907c48 | ||
|
|
d8e2367809 | ||
|
|
f8ccc60498 | ||
|
|
4f1b17c9c1 | ||
|
|
4847f5b8cd | ||
|
|
40ce8144d1 | ||
|
|
5a616ee980 | ||
|
|
b6ee4b2156 | ||
|
|
0abb69b1cb |
34
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
Summary.
|
||||
|
||||
## Expected Result
|
||||
|
||||
What you expected.
|
||||
|
||||
## Actual Result
|
||||
|
||||
What happened instead.
|
||||
|
||||
## Reproduction Steps
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
```
|
||||
|
||||
## System Information
|
||||
|
||||
$ python -m requests.help
|
||||
|
||||
```
|
||||
<paste here>
|
||||
```
|
||||
|
||||
This command is only available on Requests v2.16.4 and greater. Otherwise,
|
||||
please provide some basic information about your system (Python version,
|
||||
operating system, &c).
|
||||
7
.github/ISSUE_TEMPLATE/Custom.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE/Custom.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: Request for Help
|
||||
about: Guidance on using Requests.
|
||||
|
||||
---
|
||||
|
||||
Please refer to our [StackOverflow tag](https://stackoverflow.com/questions/tagged/python-requests) for guidance.
|
||||
7
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
Requests is not accepting feature requests at this time.
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,6 +5,7 @@ nosetests.xml
|
||||
junit-report.xml
|
||||
pylint.txt
|
||||
toy.py
|
||||
.cache/
|
||||
cover/
|
||||
build/
|
||||
docs/_build
|
||||
@@ -20,3 +21,5 @@ t.py
|
||||
|
||||
t2.py
|
||||
dist
|
||||
|
||||
/.mypy_cache/
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
# - "2.6"
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
# - "3.7-dev"
|
||||
- "3.7-dev"
|
||||
# - "pypy" -- appears to hang
|
||||
# - "pypy3"
|
||||
# command to install dependencies
|
||||
@@ -28,5 +27,3 @@ jobs:
|
||||
- stage: coverage
|
||||
python: 3.6
|
||||
script: codecov
|
||||
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
3.0.0 (2017-xx-xx)
|
||||
++++++++++++++++++
|
||||
|
||||
- Simplified logic for determining Content-Length and Transfer-Encoding.
|
||||
Requests will now avoid setting both headers on the same request, and
|
||||
raise an exception if this is done manually by a user.
|
||||
|
||||
- Remove the HTTPProxyAuth class in favor of supporting proxy auth via
|
||||
the proxies parameter.
|
||||
|
||||
- Relax how Requests strips bodies from redirects. 3.0.0 only supports body
|
||||
removal on 301/302 POST redirects and all 303 redirects.
|
||||
|
||||
- Remove support for non-string/bytes parameters in ``_basic_auth_str``.
|
||||
|
||||
- Prevent ``Session.merge_environment`` from erroneously setting the
|
||||
``verify`` parameter to ``None`` instead of ``True``.
|
||||
|
||||
- Streaming responses with ``Response.iter_lines`` or ``Response.iter_content``
|
||||
now requires an encoding to be set if one isn't provided by the server.
|
||||
|
||||
- Raise exception if multiple locations are returned during a redirect.
|
||||
|
||||
- Update ConnectionPool connections when TLS/SSL settings change.
|
||||
|
||||
- Remove simplejson import and only use standard json module.
|
||||
|
||||
- Strip surrounding whitespace from urls.
|
||||
|
||||
- MissingSchema and InvalidSchema renamed to MissingScheme and InvalidScheme
|
||||
respectively.
|
||||
|
||||
- Change merge order for environment settings to avoid excluding Session-level
|
||||
settings.
|
||||
|
||||
- Encode redirect URIs as latin-1 before performing redirects in Python 3 to
|
||||
avoid mangling during the requoting process.
|
||||
|
||||
- Remove the ``__bool__`` and ``__nonzero__`` methods from a ``Response``
|
||||
object.
|
||||
|
||||
This has been a planned feature for over a year. The behaviour is surprising
|
||||
to most people and breaks most of the assumptions that people have about
|
||||
Response objects. This resolves issue `#2002`_
|
||||
|
||||
- Skip over empty chunks in iterators. Empty chunks could prematurely signal
|
||||
the end of a request body's transmission, skipping them allows all of the
|
||||
data through. See `#2631`_ for more details.
|
||||
|
||||
- Rename the ``req`` argument from ``Session.resolve_redirects`` method
|
||||
to ``request``.
|
||||
|
||||
- Rename the ``resp`` argument from ``Session.resolve_redirects`` to
|
||||
``response``.
|
||||
|
||||
- New ``PreparedRequest.send`` method. Now, you can
|
||||
``Request().prepare().send()``.
|
||||
|
||||
- All porcelain API functions (e.g. ``requests.get``, etc) now accept an
|
||||
optional ``session`` parameter. If provided, the session given will be used
|
||||
for the request, in place of one being created for you.
|
||||
|
||||
- URLs are now automatically stripped of leading/trailing whitespace.
|
||||
|
||||
- ``Response.raise_for_status()`` now returns the response object for good responses
|
||||
|
||||
.. _#2002: https://github.com/kennethreitz/requests/issues/2002
|
||||
.. _#2631: https://github.com/kennethreitz/requests/issues/2631
|
||||
30
AUTHORS.rst
Executable file → Normal file
30
AUTHORS.rst
Executable file → Normal file
@@ -1,20 +1,17 @@
|
||||
Requests is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
|
||||
Keepers of the Four Crystals
|
||||
````````````````````````````
|
||||
Keepers of the Crystals
|
||||
```````````````````````
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_, Keeper of the Master Crystal.
|
||||
- Ian Cordasco <graffatcolmingov@gmail.com> `@sigmavirus24 <https://github.com/sigmavirus24>`_.
|
||||
- Nate Prewitt <nate.prewitt@gmail.com> `@nateprewitt <https://github.com/nateprewitt>`_.
|
||||
|
||||
Previous Keepers of Crystals
|
||||
````````````````````````````
|
||||
|
||||
- Cory Benfield <cory@lukasa.co.uk> `@lukasa <https://github.com/lukasa>`_
|
||||
- Ian Cordasco <graffatcolmingov@gmail.com> `@sigmavirus24 <https://github.com/sigmavirus24>`_
|
||||
- Nate Prewitt <nate.prewitt@gmail.com> `@nateprewitt <https://github.com/nateprewitt>`_
|
||||
|
||||
|
||||
|
||||
Urllib3
|
||||
```````
|
||||
|
||||
- Andrey Petrov <andrey.petrov@shazow.net>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
@@ -160,19 +157,16 @@ 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>`_)
|
||||
@@ -186,3 +180,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>`_)
|
||||
- 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>`_)
|
||||
- Arthur Vigil (`@ahvigil <https://github.com/ahvigil>`_)
|
||||
- Nehal J Wani (`@nehaljwani <https://github.com/nehaljwani>`_)
|
||||
- Demetrios Bairaktaris (`@DemetriosBairaktaris <https://github.com/demetriosbairaktaris>`_)
|
||||
- Darren Dormer (`@ddormer <https://github.com/ddormer>`_)
|
||||
- Rajiv Mayani (`@mayani <https://github.com/mayani>`_)
|
||||
|
||||
100
HISTORY.rst
100
HISTORY.rst
@@ -6,10 +6,102 @@ Release History
|
||||
dev
|
||||
+++
|
||||
|
||||
- [Short description of non-trivial change.]
|
||||
|
||||
2.19.1 (2018-06-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed issue where status_codes.py's ``init`` function failed trying to append to
|
||||
a ``__doc__`` value of ``None``.
|
||||
|
||||
2.19.0 (2018-06-12)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Warn user about possible slowdown when using cryptography version < 1.3.4
|
||||
- Check for invalid host in proxy URL, before forwarding request to adapter.
|
||||
- Fragments are now properly maintained across redirects. (RFC7231 7.1.2)
|
||||
- Removed use of cgi module to expedite library load time.
|
||||
- Added support for SHA-256 and SHA-512 digest auth algorithms.
|
||||
- Minor performance improvement to ``Request.content``.
|
||||
- Migrate to using collections.abc for 3.7 compatibility.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Parsing empty ``Link`` headers with ``parse_header_links()`` no longer return one bogus entry.
|
||||
- Fixed issue where loading the default certificate bundle from a zip archive
|
||||
would raise an ``IOError``.
|
||||
- Fixed issue with unexpected ``ImportError`` on windows system which do not support ``winreg`` module.
|
||||
- DNS resolution in proxy bypass no longer includes the username and password in
|
||||
the request. This also fixes the issue of DNS queries failing on macOS.
|
||||
- Properly normalize adapter prefixes for url comparison.
|
||||
- Passing ``None`` as a file pointer to the ``files`` param no longer raises an exception.
|
||||
- Calling ``copy`` on a ``RequestsCookieJar`` will now preserve the cookie policy correctly.
|
||||
|
||||
**Dependencies**
|
||||
|
||||
- We now support idna v2.7.
|
||||
- We now support urllib3 v1.23.
|
||||
|
||||
2.18.4 (2017-08-15)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Error messages for invalid headers now include the header name for easier debugging
|
||||
|
||||
**Dependencies**
|
||||
|
||||
- We now support idna v2.6.
|
||||
|
||||
2.18.3 (2017-08-02)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Running ``$ python -m requests.help`` now includes the installed version of idna.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fixed issue where Requests would raise ``ConnectionError`` instead of
|
||||
``SSLError`` when encountering SSL problems when using urllib3 v1.22.
|
||||
|
||||
2.18.2 (2017-07-25)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- ``requests.help`` no longer fails on Python 2.6 due to the absence of
|
||||
``ssl.OPENSSL_VERSION_NUMBER``.
|
||||
|
||||
**Dependencies**
|
||||
|
||||
- We now support urllib3 v1.22.
|
||||
|
||||
2.18.1 (2017-06-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fix an error in the packaging whereby the ``*.whl`` contained incorrect data
|
||||
that regressed the fix in v2.17.3.
|
||||
|
||||
2.18.0 (2017-06-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- ``Response`` is now a context manager, so can be used directly in a ``with`` statement
|
||||
without first having to be wrapped by ``contextlib.closing()``.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Resolve installation failure if multiprocessing is not available
|
||||
- Resolve tests crash if multiprocessing is not able to determine the number of CPU cores
|
||||
- Resolve error swallowing in utils set_environ generator
|
||||
|
||||
|
||||
2.17.3 (2017-05-29)
|
||||
@@ -64,7 +156,7 @@ dev
|
||||
|
||||
- Further restored the ``requests.packages`` namespace for compatibility reasons.
|
||||
|
||||
No code modification (noted below) should be neccessary any longer.
|
||||
No code modification (noted below) should be necessary any longer.
|
||||
|
||||
2.16.1 (2017-05-27)
|
||||
+++++++++++++++++++
|
||||
@@ -174,11 +266,6 @@ 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)
|
||||
+++++++++++++++++++
|
||||
|
||||
@@ -1481,4 +1568,3 @@ This is not a backwards compatible change.
|
||||
|
||||
* Frustration
|
||||
* Conception
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2017 Kenneth Reitz
|
||||
Copyright 2018 Kenneth Reitz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini
|
||||
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini requirements.txt
|
||||
recursive-include tests *.py
|
||||
|
||||
11
Makefile
11
Makefile
@@ -1,20 +1,21 @@
|
||||
.PHONY: docs
|
||||
init:
|
||||
pip install -r requirements.txt
|
||||
pip install pipenv --upgrade
|
||||
pipenv install --dev --skip-lock
|
||||
test:
|
||||
# This runs all of the tests, on both Python 2 and Python 3.
|
||||
detox
|
||||
ci:
|
||||
py.test -n 8 --boxed --junitxml=report.xml
|
||||
pipenv run py.test -n 8 --boxed --junitxml=report.xml
|
||||
|
||||
test-readme:
|
||||
python setup.py check -r -s
|
||||
@pipenv run python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
|
||||
|
||||
flake8:
|
||||
flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
|
||||
pipenv run flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
|
||||
|
||||
coverage:
|
||||
py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
|
||||
pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
|
||||
|
||||
publish:
|
||||
pip install 'twine>=1.5.0'
|
||||
|
||||
24
Pipfile
Normal file
24
Pipfile
Normal file
@@ -0,0 +1,24 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple/"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[dev-packages]
|
||||
pytest = ">=2.8.0"
|
||||
codecov = "*"
|
||||
pytest-httpbin = ">=0.0.7"
|
||||
pytest-mock = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-xdist = "*"
|
||||
alabaster = "*"
|
||||
readme-renderer = "*"
|
||||
sphinx = "<=1.5.5"
|
||||
pysocks = "*"
|
||||
docutils = "*"
|
||||
"flake8" = "*"
|
||||
tox = "*"
|
||||
detox = "*"
|
||||
httpbin = ">=0.7.0"
|
||||
|
||||
[packages]
|
||||
"e1839a8" = {path = ".", editable = true, extras = ["socks"]}
|
||||
641
Pipfile.lock
generated
Normal file
641
Pipfile.lock
generated
Normal file
@@ -0,0 +1,641 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "0735b243455d8e924fbd05188ed435bfd3917f4acdade9b9e8f14741f3fc47e9"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple/",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"e1839a8": {
|
||||
"editable": true,
|
||||
"extras": [
|
||||
"socks"
|
||||
],
|
||||
"path": "."
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
||||
],
|
||||
"version": "==2.6"
|
||||
},
|
||||
"pysocks": {
|
||||
"hashes": [
|
||||
"sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672"
|
||||
],
|
||||
"version": "==1.6.8"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
],
|
||||
"version": "==1.22"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
"sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732",
|
||||
"sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.10"
|
||||
},
|
||||
"apipkg": {
|
||||
"hashes": [
|
||||
"sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6",
|
||||
"sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9"
|
||||
],
|
||||
"version": "==1.4"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
|
||||
"sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
|
||||
],
|
||||
"version": "==18.1.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14",
|
||||
"sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80"
|
||||
],
|
||||
"version": "==2.5.3"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:b8fa79e91f96c2c2cd9fd1f9eda906efb1b88b483048978ba62fef680e962b34",
|
||||
"sha256:eb7386f632349d10d9ce9d4a838b134d4731571851149f9cc2c05a9a837a9a44"
|
||||
],
|
||||
"version": "==2.1.3"
|
||||
},
|
||||
"blinker": {
|
||||
"hashes": [
|
||||
"sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"
|
||||
],
|
||||
"version": "==1.4"
|
||||
},
|
||||
"brotlipy": {
|
||||
"hashes": [
|
||||
"sha256:07194f4768eb62a4f4ea76b6d0df6ade185e24ebd85877c351daa0a069f1111a",
|
||||
"sha256:091b299bf36dd6ef7a06570dbc98c0f80a504a56c5b797f31934d2ad01ae7d17",
|
||||
"sha256:09ec3e125d16749b31c74f021aba809541b3564e5359f8c265cbae442810b41a",
|
||||
"sha256:0be698678a114addcf87a4b9496c552c68a2c99bf93cf8e08f5738b392e82057",
|
||||
"sha256:0fa6088a9a87645d43d7e21e32b4a6bf8f7c3939015a50158c10972aa7f425b7",
|
||||
"sha256:1ea4e578241504b58f2456a6c69952c88866c794648bdc74baee74839da61d44",
|
||||
"sha256:2699945a0a992c04fc7dc7fa2f1d0575a2c8b4b769f2874a08e8eae46bef36ae",
|
||||
"sha256:2a80319ae13ea8dd60ecdc4f5ccf6da3ae64787765923256b62c598c5bba4121",
|
||||
"sha256:2e5c64522364a9ebcdf47c5744a5ddeb3f934742d31e61ebfbbc095460b47162",
|
||||
"sha256:36def0b859beaf21910157b4c33eb3b06d8ce459c942102f16988cca6ea164df",
|
||||
"sha256:3a3e56ced8b15fbbd363380344f70f3b438e0fd1fcf27b7526b6172ea950e867",
|
||||
"sha256:3c1d5e2cf945a46975bdb11a19257fa057b67591eb232f393d260e7246d9e571",
|
||||
"sha256:50ca336374131cfad20612f26cc43c637ac0bfd2be3361495e99270883b52962",
|
||||
"sha256:5de6f7d010b7558f72f4b061a07395c5c3fd57f0285c5af7f126a677b976a868",
|
||||
"sha256:637847560d671657f993313ecc6c6c6666a936b7a925779fd044065c7bc035b9",
|
||||
"sha256:653faef61241bf8bf99d73ca7ec4baa63401ba7b2a2aa88958394869379d67c7",
|
||||
"sha256:786afc8c9bd67de8d31f46e408a3386331e126829114e4db034f91eacb05396d",
|
||||
"sha256:79aaf217072840f3e9a3b641cccc51f7fc23037496bd71e26211856b93f4b4cb",
|
||||
"sha256:7e31f7adcc5851ca06134705fcf3478210da45d35ad75ec181e1ce9ce345bb38",
|
||||
"sha256:8b39abc3256c978f575df5cd7893153277216474f303e26f0e43ba3d3969ef96",
|
||||
"sha256:9448227b0df082e574c45c983fa5cd4bda7bfb11ea6b59def0940c1647be0c3c",
|
||||
"sha256:96bc59ff9b5b5552843dc67999486a220e07a0522dddd3935da05dc194fa485c",
|
||||
"sha256:a07647886e24e2fb2d68ca8bf3ada398eb56fd8eac46c733d4d95c64d17f743b",
|
||||
"sha256:af65d2699cb9f13b26ec3ba09e75e80d31ff422c03675fcb36ee4dabe588fdc2",
|
||||
"sha256:b4c98b0d2c9c7020a524ca5bbff42027db1004c6571f8bc7b747f2b843128e7a",
|
||||
"sha256:c6cc0036b1304dd0073eec416cb2f6b9e37ac8296afd9e481cac3b1f07f9db25",
|
||||
"sha256:d2c1c724c4ac375feb2110f1af98ecdc0e5a8ea79d068efb5891f621a5b235cb",
|
||||
"sha256:dc6c5ee0df9732a44d08edab32f8a616b769cc5a4155a12d2d010d248eb3fb07",
|
||||
"sha256:fd1d1c64214af5d90014d82cee5d8141b13d44c92ada7a0c0ec0679c6f15a471"
|
||||
],
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
|
||||
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
|
||||
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
|
||||
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
|
||||
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
|
||||
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
|
||||
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
|
||||
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
|
||||
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
|
||||
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
|
||||
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
|
||||
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
|
||||
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
|
||||
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
|
||||
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
|
||||
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
|
||||
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
|
||||
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
|
||||
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
|
||||
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
|
||||
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
|
||||
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
|
||||
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
|
||||
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
|
||||
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
|
||||
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
|
||||
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
|
||||
],
|
||||
"version": "==1.11.5"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
|
||||
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
|
||||
],
|
||||
"version": "==6.7"
|
||||
},
|
||||
"cmarkgfm": {
|
||||
"hashes": [
|
||||
"sha256:0186dccca79483e3405217993b83b914ba4559fe9a8396efc4eea56561b74061",
|
||||
"sha256:1a625afc6f62da428df96ec325dc30866cc5781520cbd904ff4ec44cf018171c",
|
||||
"sha256:275905bb371a99285c74931700db3f0c078e7603bed383e8cf1a09f3ee05a3de",
|
||||
"sha256:50098f1c4950722521f0671e54139e0edc1837d63c990cf0f3d2c49607bb51a2",
|
||||
"sha256:50ed116d0b60a07df0dc7b180c28569064b9d37d1578d4c9021cff04d725cb63",
|
||||
"sha256:61a72def110eed903cd1848245897bcb80d295cd9d13944d4f9f30cba5b76655",
|
||||
"sha256:64186fb75d973a06df0e6ea12879533b71f6e7ba1ab01ffee7fc3e7534758889",
|
||||
"sha256:665303d34d7f14f10d7b0651082f25ebf7107f29ef3d699490cac16cdc0fc8ce",
|
||||
"sha256:70b18f843aec58e4e64aadce48a897fe7c50426718b7753aaee399e72df64190",
|
||||
"sha256:761ee7b04d1caee2931344ac6bfebf37102ffb203b136b676b0a71a3f0ea3c87",
|
||||
"sha256:811527e9b7280b136734ed6cb6845e5fbccaeaa132ddf45f0246cbe544016957",
|
||||
"sha256:987b0e157f70c72a84f3c2f9ef2d7ab0f26c08f2bf326c12c087ff9eebcb3ff5",
|
||||
"sha256:9fc6a2183d0a9b0974ec7cdcdad42bd78a3be674cc3e65f87dd694419b3b0ab7",
|
||||
"sha256:c573ea89dd95d41b6d8cf36799c34b6d5b1eac4aed0212dee0f0a11fb7b01e8f",
|
||||
"sha256:c5f1b9e8592d2c448c44e6bc0d91224b16ea5f8293908b1561de1f6d2d0658b1",
|
||||
"sha256:cbe581456357d8f0674d6a590b1aaf46c11d01dd0a23af147a51a798c3818034",
|
||||
"sha256:cf219bec69e601fe27e3974b7307d2f06082ab385d42752738ad2eb630a47d65",
|
||||
"sha256:d08bad67fa18f7e8ff738c090628ee0cbf0505d74a991c848d6d04abfe67b697",
|
||||
"sha256:d6f716d7b1182bf35862b5065112f933f43dd1aa4f8097c9bcfb246f71528a34",
|
||||
"sha256:e08e479102627641c7cb4ece421c6ed4124820b1758765db32201136762282d9",
|
||||
"sha256:e20ac21418af0298437d29599f7851915497ce9f2866bc8e86b084d8911ee061",
|
||||
"sha256:e25f53c37e319241b9a412382140dffac98ca756ba8f360ac7ab5e30cad9670a",
|
||||
"sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c"
|
||||
],
|
||||
"version": "==0.4.2"
|
||||
},
|
||||
"codecov": {
|
||||
"hashes": [
|
||||
"sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788",
|
||||
"sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.15"
|
||||
},
|
||||
"configparser": {
|
||||
"hashes": [
|
||||
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
|
||||
],
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"contextlib2": {
|
||||
"hashes": [
|
||||
"sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48",
|
||||
"sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"
|
||||
],
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==0.5.5"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
|
||||
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
|
||||
"sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a",
|
||||
"sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd",
|
||||
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
|
||||
"sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2",
|
||||
"sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
|
||||
"sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
|
||||
"sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
|
||||
"sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
|
||||
"sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
|
||||
"sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
|
||||
"sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
|
||||
"sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
|
||||
"sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
|
||||
"sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
|
||||
"sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
|
||||
"sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
|
||||
"sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
|
||||
"sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
|
||||
"sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
|
||||
"sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
|
||||
"sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
|
||||
"sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
|
||||
"sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
|
||||
"sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
|
||||
"sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4",
|
||||
"sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91",
|
||||
"sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d",
|
||||
"sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
|
||||
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
|
||||
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
|
||||
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
|
||||
"sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77",
|
||||
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80",
|
||||
"sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"
|
||||
],
|
||||
"version": "==4.5.1"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
|
||||
"sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"
|
||||
],
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"detox": {
|
||||
"hashes": [
|
||||
"sha256:cb24895a0e4f95c0bcb1087a201c453600e075568af00848e91518fb2b984568",
|
||||
"sha256:f3119bca4444f1e8a1d7189b064c52cfdd9a89ad3a1c921d78b49bf7f5dc5b1b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.12"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
|
||||
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.14"
|
||||
},
|
||||
"enum34": {
|
||||
"hashes": [
|
||||
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
|
||||
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
|
||||
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
|
||||
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
|
||||
],
|
||||
"markers": "python_version < '3.4'",
|
||||
"version": "==1.1.6"
|
||||
},
|
||||
"eventlet": {
|
||||
"hashes": [
|
||||
"sha256:06cffa55b335cc4fc32d0079242a81e8a9cddf2581d64d5f0543e2d412b26ca8",
|
||||
"sha256:554a50dad7abee0a9775b0780ce9d9c0bd9123dda4743c46d4314170267c6c47"
|
||||
],
|
||||
"version": "==0.23.0"
|
||||
},
|
||||
"execnet": {
|
||||
"hashes": [
|
||||
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
|
||||
"sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
|
||||
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
|
||||
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"funcsigs": {
|
||||
"hashes": [
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"markers": "python_version < '3.3'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"future": {
|
||||
"hashes": [
|
||||
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
|
||||
],
|
||||
"version": "==0.16.0"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
"sha256:09ef2636ea35782364c830f07127d6c7a70542b178268714a9a9ba16318e7e8b",
|
||||
"sha256:0fef83d43bf87a5196c91e73cb9772f945a4caaff91242766c5916d1dd1381e4",
|
||||
"sha256:1b7df09c6598f5cfb40f843ade14ed1eb40596e75cd79b6fa2efc750ba01bb01",
|
||||
"sha256:1fff21a2da5f9e03ddc5bd99131a6b8edf3d7f9d6bc29ba21784323d17806ed7",
|
||||
"sha256:42118bf608e0288e35304b449a2d87e2ba77d1e373e8aa221ccdea073de026fa",
|
||||
"sha256:50643fd6d54fd919f9a0a577c5f7b71f5d21f0959ab48767bd4bb73ae0839500",
|
||||
"sha256:58798b5d30054bb4f6cf0f712f08e6092df23a718b69000786634a265e8911a9",
|
||||
"sha256:5b49b3049697aeae17ef7bf21267e69972d9e04917658b4e788986ea5cc518e8",
|
||||
"sha256:75c413551a436b462d5929255b6dc9c0c3c2b25cbeaee5271a56c7fda8ca49c0",
|
||||
"sha256:769b740aeebd584cd59232be84fdcaf6270b8adc356596cdea5b2152c82caaac",
|
||||
"sha256:ad2383d39f13534f3ca5c48fe1fc0975676846dc39c2cece78c0f1f9891418e0",
|
||||
"sha256:b417bb7ff680d43e7bd7a13e2e08956fa6acb11fd432f74c97b7664f8bdb6ec1",
|
||||
"sha256:b6ef0cabaf5a6ecb5ac122e689d25ba12433a90c7b067b12e5f28bdb7fb78254",
|
||||
"sha256:c2de19c88bdb0366c976cc125dca1002ec1b346989d59524178adfd395e62421",
|
||||
"sha256:c7b04a6dc74087b1598de8d713198de4718fa30ec6cbb84959b26426c198e041",
|
||||
"sha256:f8f2a0ae8de0b49c7b5b2daca4f150fdd9c1173e854df2cce3b04123244f9f45",
|
||||
"sha256:fcfadaf4bf68a27e5dc2f42cbb2f4b4ceea9f05d1d0b8f7787e640bed2801634"
|
||||
],
|
||||
"version": "==0.4.13"
|
||||
},
|
||||
"html5lib": {
|
||||
"hashes": [
|
||||
"sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3",
|
||||
"sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"httpbin": {
|
||||
"hashes": [
|
||||
"sha256:7a04b5904c80b7aa04dd0a6af6520d68ce17a5db175e66a64b971f8e93d73a26",
|
||||
"sha256:cbb37790c91575f4f15757f42ad41d9f729eb227d5edbe89e4ec175486db8dfa"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
|
||||
],
|
||||
"version": "==2.6"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18",
|
||||
"sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
|
||||
],
|
||||
"version": "==0.24"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||
],
|
||||
"version": "==2.10"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
|
||||
],
|
||||
"version": "==1.0"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"mock": {
|
||||
"hashes": [
|
||||
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
||||
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
|
||||
"sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
|
||||
"sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
|
||||
],
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:680bf5ba9b28dd56e08eb7c267991a37c7a5f90a92c2e07108829931a50ff80a",
|
||||
"sha256:6874feb22334a1e9a515193cba797664e940b763440c88115009ec323a7f2df5"
|
||||
],
|
||||
"version": "==4.0.3"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
|
||||
"sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
|
||||
"sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881",
|
||||
"sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a"
|
||||
],
|
||||
"version": "==1.5.3"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
|
||||
],
|
||||
"version": "==2.18"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
|
||||
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
|
||||
],
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
|
||||
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pysocks": {
|
||||
"hashes": [
|
||||
"sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672"
|
||||
],
|
||||
"version": "==1.6.8"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:54713b26c97538db6ff0703a12b19aeaeb60b5e599de542e7fca0ec83b9038e8",
|
||||
"sha256:829230122facf05a5f81a6d4dfe6454a04978ea3746853b2b84567ecf8e5c526"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.1"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
"sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d",
|
||||
"sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.5.1"
|
||||
},
|
||||
"pytest-forked": {
|
||||
"hashes": [
|
||||
"sha256:e4500cd0509ec4a26535f7d4112a8cc0f17d3a41c29ffd4eab479d2a55b30805",
|
||||
"sha256:f275cb48a73fc61a6710726348e1da6d68a978f0ec0c54ece5a5fae5977e5a08"
|
||||
],
|
||||
"version": "==0.2"
|
||||
},
|
||||
"pytest-httpbin": {
|
||||
"hashes": [
|
||||
"sha256:8cd57e27418a7d7d205fcc9802eea246ed06170e3065abfa76c6d9b40553592c",
|
||||
"sha256:d3919c5df0b644454129c0066a8ae62db40ac54bacb4cfd89d8cfa58615a4b42"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"pytest-mock": {
|
||||
"hashes": [
|
||||
"sha256:53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928",
|
||||
"sha256:d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:be2662264b035920ba740ed6efb1c816a83c8a22253df7766d129f6a7bfdbd35",
|
||||
"sha256:e8f5744acc270b3e7d915bdb4d5f471670f049b6fbd163d4cbd52203b075d30f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.22.2"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
|
||||
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
|
||||
],
|
||||
"version": "==2018.4"
|
||||
},
|
||||
"raven": {
|
||||
"hashes": [
|
||||
"sha256:1c641e5ebc2d4185560608e253970ca0d4b98475f4edf67735015a415f9e1d48",
|
||||
"sha256:95aecf76c414facaddbb056f3e98c7936318123e467728f2e50b3a66b65a6ef7"
|
||||
],
|
||||
"version": "==6.8.0"
|
||||
},
|
||||
"readme-renderer": {
|
||||
"hashes": [
|
||||
"sha256:bde909eaa84d65b7942f7e6998c8b427b90b568b2630ff0306f4ca75f6d2a909",
|
||||
"sha256:e7e43a7ba49f08c3cb660d0f2e25b561f6b10b36c63f029060230aab2dc2875e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==20.0"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||
],
|
||||
"version": "==2.18.4"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
|
||||
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
|
||||
],
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:11f271e7a9398385ed730e90f0bb41dc3815294bdcd395b46ed2d033bc2e7d87",
|
||||
"sha256:4064ea6c56feeb268838cb8fbbee507d0c3d5d92fa63a7df935a916b52c9e2f5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.5.5"
|
||||
},
|
||||
"tox": {
|
||||
"hashes": [
|
||||
"sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f",
|
||||
"sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
],
|
||||
"version": "==1.22"
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:1d7e241b431e7afce47e77f8843a276f652699d1fa4f93b9d8ce0076fd7b0b54",
|
||||
"sha256:e8e05d4714a1c51a2f5921e62f547fcb0f713ebbe959e0a7f585cc8bef71d11f"
|
||||
],
|
||||
"version": "==15.2.0"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
22
README.rst
22
README.rst
@@ -2,13 +2,13 @@ Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
:target: https://pypi.org/project/requests/
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
:target: https://pypi.org/project/requests/
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
:target: https://pypi.org/project/requests/
|
||||
|
||||
.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/requests/requests
|
||||
@@ -20,14 +20,10 @@ Requests: HTTP for Humans
|
||||
.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
|
||||
|
||||
Requests is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||
**Warning:** Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
|
||||
including: security vulnerabilities, verbose code, reinventing the wheel,
|
||||
constantly reading documentation, depression, headaches, or even death.
|
||||
.. image:: https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg
|
||||
|
||||
Behold, the power of Requests:
|
||||
|
||||
@@ -81,19 +77,19 @@ Requests is ready for today's web.
|
||||
- ``.netrc`` Support
|
||||
- Chunked Requests
|
||||
|
||||
Requests officially supports Python 2.6–2.7 & 3.3–3.7, and runs great on PyPy.
|
||||
Requests officially supports Python 2.7 & 3.4–3.6, and runs great on PyPy.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To install Requests, simply:
|
||||
To install Requests, simply use `pipenv <http://pipenv.org/>`_ (or pip, of course):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pip install requests
|
||||
$ pipenv install requests
|
||||
✨🍰✨
|
||||
|
||||
Satisfaction, guaranteed.
|
||||
Satisfaction guaranteed.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -109,6 +105,6 @@ How to Contribute
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/requests/requests
|
||||
.. _`the repository`: https://github.com/requests/requests
|
||||
.. _AUTHORS: https://github.com/requests/requests/blob/master/AUTHORS.rst
|
||||
.. _Contributor Friendly: https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open
|
||||
|
||||
10
appveyor.yml
10
appveyor.yml
@@ -10,11 +10,6 @@ environment:
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py27"
|
||||
|
||||
- PYTHON: "C:\\Python33-x64"
|
||||
PYTHON_VERSION: "3.3.x"
|
||||
PYTHON_ARCH: "64"
|
||||
TOXENV: "py33"
|
||||
|
||||
- PYTHON: "C:\\Python34-x64"
|
||||
PYTHON_VERSION: "3.4.x"
|
||||
PYTHON_ARCH: "64"
|
||||
@@ -46,11 +41,12 @@ install:
|
||||
|
||||
# Upgrade to the latest version of pip to avoid it displaying warnings
|
||||
# about it being out of date.
|
||||
- "pip install --disable-pip-version-check --user --upgrade pip"
|
||||
- "python -m pip install --upgrade pip wheel"
|
||||
- "C:\\MinGW\\bin\\mingw32-make"
|
||||
- "pipenv install -e .[socks] --skip-lock"
|
||||
|
||||
test_script:
|
||||
- "C:\\MinGW\\bin\\mingw32-make coverage"
|
||||
|
||||
on_success:
|
||||
- "codecov -f coverage.xml"
|
||||
- "pipenv run codecov -f coverage.xml"
|
||||
|
||||
47
docs/_static/custom.css
vendored
Normal file
47
docs/_static/custom.css
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-child(2) > td > select {
|
||||
width: 100%!important;
|
||||
}
|
||||
|
||||
#python27 > a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#carbonads {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding: 1em;
|
||||
background-color: #eeeeee;
|
||||
text-align: center;
|
||||
border: solid 1px #cccccc;
|
||||
margin: 1.5em 0 2em;
|
||||
border-radius: 2px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#carbonads a {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
#carbonads span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carbon-img {
|
||||
display: block;
|
||||
margin: 0 auto 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.carbon-text {
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.carbon-poweredby {
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1;
|
||||
font-size: 10px;
|
||||
}
|
||||
31
docs/_templates/hacks.html
vendored
31
docs/_templates/hacks.html
vendored
@@ -1,4 +1,33 @@
|
||||
<!-- Alabaster (krTheme++) Hacks -->
|
||||
<aside id="python27">
|
||||
<a href="https://www.kennethreitz.org/requests3"><strong>Requests 3.0</strong> development is underway, and your financial help is appreciated!</a>
|
||||
</aside>
|
||||
|
||||
<!-- Python 2 Death Clock™ -->
|
||||
<style type="text/css">
|
||||
body { margin-top: 4em; }
|
||||
#python27 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0; right: 0;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background-color: black;
|
||||
font-size: larger;
|
||||
line-height: 3;
|
||||
}
|
||||
|
||||
</style>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
|
||||
<script>
|
||||
var death = new Date('2020-04-12');
|
||||
|
||||
var diff = moment.duration(death - moment());
|
||||
|
||||
document.querySelector('#python27 time').innerText = (diff.years() * 12) + diff.months();
|
||||
</script> -->
|
||||
|
||||
|
||||
<!-- CSS Adjustments (I'm very picky.) -->
|
||||
<style type="text/css">
|
||||
@@ -26,6 +55,7 @@
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<!-- Analytics tracking for Kenneth. -->
|
||||
<script type="text/javascript">
|
||||
var _gauges = _gauges || [];
|
||||
@@ -42,6 +72,7 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<!-- There are no more hacks. -->
|
||||
<!-- இڿڰۣ-ڰۣ— -->
|
||||
<!-- Love, Kenneth Reitz -->
|
||||
|
||||
29
docs/_templates/sidebarintro.html
vendored
29
docs/_templates/sidebarintro.html
vendored
@@ -5,7 +5,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
|
||||
</p>
|
||||
|
||||
@@ -13,34 +13,26 @@
|
||||
Requests is an elegant and simple HTTP library for Python, built for
|
||||
human beings.
|
||||
</p>
|
||||
<p>Sponsored by <strong><a href="https://linode.com/">Linode</a></strong> and <a href="http://docs.python-requests.org/en/master/community/sponsors/#patron-sponsors">other wonderful organizations</a>.</p>
|
||||
|
||||
|
||||
<h3>The Hitchhiker's Guide to Python</h3>
|
||||
|
||||
<p>This guide is now available in tangible book form!</p>
|
||||
|
||||
<a href="https://www.amazon.com/Hitchhikers-Guide-Python-Practices-Development/dp/1491933178/ref=as_li_ss_il?ie=UTF8&linkCode=li2&tag=bookforkind-20&linkId=804806ebdacaf3b56567347f3afbdbca" target="_blank"><img border="0" src="https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=1491933178&Format=_SL160_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=bookforkind-20" ></a><img src="//ir-na.amazon-adsystem.com/e/ir?t=bookforkind-20&l=li2&o=1&a=1491933178" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
|
||||
|
||||
<p>All proceeds are being directly donated to the <a href="https://djangogirls.org">DjangoGirls</a> organization.</p>
|
||||
|
||||
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg" id="_carbonads_js"></script>
|
||||
|
||||
<p><a href="http://www.unixstickers.com/stickers/coding_stickers/requests-shaped-sticker">Requests Stickers!</a></p>
|
||||
<h3>Stay Informed</h3>
|
||||
<p>Receive updates on new releases and upcoming projects.</p>
|
||||
|
||||
<p><iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=false"
|
||||
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=false"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
|
||||
|
||||
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow @kennethreitz</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
|
||||
<p><a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>
|
||||
<p><a href="http://tinyletter.com/kennethreitz">Join Mailing List</a>.</p>
|
||||
|
||||
|
||||
<h3>Other Projects</h3>
|
||||
|
||||
<p>More <a href="http://kennethreitz.org/">Kenneth Reitz</a> projects:</p>
|
||||
<ul>
|
||||
<li><a href="http://edmsynths.com/">edmsynths.com</a></li>
|
||||
<li><a href="http://html.python-requests.org/">Requests-HTML</a></li>
|
||||
<li><a href="http://howtopython.org/">howtopython.org</a></li>
|
||||
<li><a href="http://pipenv.org/">pipenv</a></li>
|
||||
<li><a href="http://pep8.org/">pep8.org</a></li>
|
||||
<li><a href="http://httpbin.org/">httpbin.org</a></li>
|
||||
@@ -58,9 +50,9 @@
|
||||
|
||||
<p></p>
|
||||
|
||||
<li><a href="http://github.com/requests/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
|
||||
<li><a href="http://github.com/requests/requests/issues">Issue Tracker</a></li>
|
||||
<li><a href="https://github.com/requests/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="https://pypi.org/project/requests/">Requests @ PyPI</a></li>
|
||||
<li><a href="https://github.com/requests/requests/issues">Issue Tracker</a></li>
|
||||
<li><a href="http://docs.python-requests.org/en/latest/community/updates/#software-updates">Release History</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -77,4 +69,3 @@
|
||||
<li><a href="http://it.python-requests.org/">Italian</a></li>
|
||||
<li><a href="http://es.python-requests.org/">Spanish</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
24
docs/_templates/sidebarlogo.html
vendored
24
docs/_templates/sidebarlogo.html
vendored
@@ -4,35 +4,31 @@
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
Requests is an elegant and simple HTTP library for Python, built for
|
||||
human beings. You are currently looking at the documentation of the
|
||||
development release.
|
||||
</p>
|
||||
|
||||
<p>Sponsored by <strong><a href="https://linode.com/">Linode</a></strong> and <a href="http://docs.python-requests.org/en/master/community/sponsors/#patron-sponsors">other wonderful organizations</a>.</p>
|
||||
|
||||
<h3>Stay Informed</h3>
|
||||
<p>Receive updates on new releases and upcoming projects.</p>
|
||||
|
||||
<p><a href="http://www.unixstickers.com/stickers/coding_stickers/requests-shaped-sticker">Requests Stickers!</a></p>
|
||||
|
||||
<p><a href="http://tinyletter.com/kennethreitz">Join Mailing List</a>.</p>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h3>The Hitchhiker's Guide to Python</h3>
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg" id="_carbonads_js"></script>
|
||||
|
||||
<p>This guide is now available in tangible book form!</p>
|
||||
|
||||
<a href="https://www.amazon.com/Hitchhikers-Guide-Python-Practices-Development/dp/1491933178/ref=as_li_ss_il?ie=UTF8&linkCode=li2&tag=bookforkind-20&linkId=804806ebdacaf3b56567347f3afbdbca" target="_blank"><img border="0" src="https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=1491933178&Format=_SL160_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=bookforkind-20" ></a><img src="//ir-na.amazon-adsystem.com/e/ir?t=bookforkind-20&l=li2&o=1&a=1491933178" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
|
||||
|
||||
<p>All proceeds are being directly donated to the <a href="https://djangogirls.org">DjangoGirls</a> organization.</p>
|
||||
|
||||
|
||||
<p>If you enjoy using this project, <a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>
|
||||
|
||||
<p><iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=false"
|
||||
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=false"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
|
||||
|
||||
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow @kennethreitz</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
|
||||
@@ -42,7 +38,8 @@
|
||||
|
||||
<p>More <a href="http://kennethreitz.org/">Kenneth Reitz</a> projects:</p>
|
||||
<ul>
|
||||
<li><a href="http://edmsynths.com/">edmsynths.com</a></li>
|
||||
<li><a href="http://html.python-requests.org/">Requests-HTML</a></li>
|
||||
<li><a href="http://howtopython.org/">howtopython.org</a></li>
|
||||
<li><a href="http://pipenv.org/">pipenv</a></li>
|
||||
<li><a href="http://pep8.org/">pep8.org</a></li>
|
||||
<li><a href="http://httpbin.org/">httpbin.org</a></li>
|
||||
@@ -66,4 +63,3 @@
|
||||
<li><a href="http://it.python-requests.org/">Italian</a></li>
|
||||
<li><a href="http://es.python-requests.org/">Spanish</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
17
docs/api.rst
17
docs/api.rst
@@ -74,6 +74,7 @@ Authentication
|
||||
|
||||
.. autoclass:: requests.auth.AuthBase
|
||||
.. autoclass:: requests.auth.HTTPBasicAuth
|
||||
.. autoclass:: requests.auth.HTTPProxyAuth
|
||||
.. autoclass:: requests.auth.HTTPDigestAuth
|
||||
|
||||
|
||||
@@ -108,17 +109,7 @@ Status Code Lookup
|
||||
|
||||
.. autoclass:: requests.codes
|
||||
|
||||
::
|
||||
|
||||
>>> requests.codes['temporary_redirect']
|
||||
307
|
||||
|
||||
>>> requests.codes.teapot
|
||||
418
|
||||
|
||||
>>> requests.codes['\o/']
|
||||
200
|
||||
|
||||
.. automodule:: requests.status_codes
|
||||
|
||||
|
||||
Migrating to 1.x
|
||||
@@ -190,7 +181,7 @@ API Changes
|
||||
|
||||
logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
requests_log = logging.getLogger("requests.packages.urllib3")
|
||||
requests_log = logging.getLogger("urllib3")
|
||||
requests_log.setLevel(logging.DEBUG)
|
||||
requests_log.propagate = True
|
||||
|
||||
@@ -251,7 +242,7 @@ API Changes
|
||||
}
|
||||
|
||||
# In requests 1.x, this was legal, in requests 2.x,
|
||||
# this raises requests.exceptions.MissingScheme
|
||||
# this raises requests.exceptions.MissingSchema
|
||||
requests.get("http://example.org", proxies=proxies)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4290/35294660055_42c02b2316_k_d.jpg
|
||||
|
||||
This part of the documentation answers common questions about Requests.
|
||||
|
||||
Encoded Data?
|
||||
@@ -54,9 +56,7 @@ Python 3 Support?
|
||||
Yes! Here's a list of Python platforms that are officially
|
||||
supported:
|
||||
|
||||
* Python 2.6
|
||||
* Python 2.7
|
||||
* Python 3.3
|
||||
* Python 3.4
|
||||
* Python 3.5
|
||||
* Python 3.6
|
||||
@@ -69,7 +69,7 @@ These errors occur when :ref:`SSL certificate verification <verification>`
|
||||
fails to match the certificate the server responds with to the hostname
|
||||
Requests thinks it's contacting. If you're certain the server's SSL setup is
|
||||
correct (for example, because you can visit the site with your browser) and
|
||||
you're using Python 2.6 or 2.7, a possible explanation is that you need
|
||||
you're using Python 2.7, a possible explanation is that you need
|
||||
Server-Name-Indication.
|
||||
|
||||
`Server-Name-Indication`_, or SNI, is an official extension to SSL where the
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Integrations
|
||||
============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4239/34450900674_15863ddea0_k_d.jpg
|
||||
|
||||
Python for iOS
|
||||
--------------
|
||||
|
||||
@@ -16,7 +18,7 @@ Articles & Talks
|
||||
- `Python for the Web <http://gun.io/blog/python-for-the-web/>`_ teaches how to use Python to interact with the web, using Requests.
|
||||
- `Daniel Greenfeld's Review of Requests <http://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
|
||||
- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <http://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
|
||||
- `Issac Kelly's 'Consuming Web APIs' talk <http://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
|
||||
- `Issac Kelly's 'Consuming Web APIs' talk <https://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
|
||||
- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
|
||||
- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
|
||||
- `Sending JSON in Requests <http://www.coglib.com/~icordasc/blog/2014/11/sending-json-in-requests.html>`_
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Recommended Packages and Extensions
|
||||
===================================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4218/35224319272_cfc0e621fb_k_d.jpg
|
||||
|
||||
Requests has a great variety of powerful and useful third-party extensions.
|
||||
This page provides an overview of some of the best of them.
|
||||
|
||||
@@ -13,7 +15,7 @@ Certifi CA Bundle
|
||||
validating the trustworthiness of SSL certificates while verifying the
|
||||
identity of TLS hosts. It has been extracted from the Requests project.
|
||||
|
||||
.. _Certifi: http://certifi.io/en/latest/
|
||||
.. _Certifi: https://github.com/certifi/python-certifi
|
||||
|
||||
CacheControl
|
||||
------------
|
||||
@@ -32,7 +34,15 @@ but do not belong in Requests proper. This library is actively maintained
|
||||
by members of the Requests core team, and reflects the functionality most
|
||||
requested by users within the community.
|
||||
|
||||
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/en/latest/index.html
|
||||
.. _Requests-Toolbelt: https://toolbelt.readthedocs.io/en/latest/index.html
|
||||
|
||||
|
||||
Requests-Threads
|
||||
----------------
|
||||
|
||||
`Requests-Threads` is a Requests session that returns the amazing Twisted's awaitable Deferreds instead of Response objects. This allows the use of ``async``/``await`` keyword usage on Python 3, or Twisted's style of programming, if desired.
|
||||
|
||||
.. _Requests-Threads: https://github.com/requests/requests-threads
|
||||
|
||||
Requests-OAuthlib
|
||||
-----------------
|
||||
@@ -52,6 +62,3 @@ Betamax
|
||||
A VCR imitation designed only for Python-Requests.
|
||||
|
||||
.. _betamax: https://github.com/sigmavirus24/betamax
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Release Process and Rules
|
||||
=========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4215/34450901614_b74ae720db_k_d.jpg
|
||||
|
||||
.. versionadded:: v2.6.2
|
||||
|
||||
Starting with the version to be released after ``v2.6.2``, the following rules
|
||||
@@ -17,19 +19,18 @@ Breaking changes are changes that break backwards compatibility with prior
|
||||
versions. If the project were to change the ``text`` attribute on a
|
||||
``Response`` object to a method, that would only happen in a Major release.
|
||||
|
||||
Major releases may also include miscellaneous bug fixes and upgrades to
|
||||
vendored packages. The core developers of Requests are committed to providing
|
||||
a good user experience. This means we're also committed to preserving
|
||||
backwards compatibility as much as possible. Major releases will be infrequent
|
||||
and will need strong justifications before they are considered.
|
||||
Major releases may also include miscellaneous bug fixes. The core developers of
|
||||
Requests are committed to providing a good user experience. This means we're
|
||||
also committed to preserving backwards compatibility as much as possible. Major
|
||||
releases will be infrequent and will need strong justifications before they are
|
||||
considered.
|
||||
|
||||
Minor Releases
|
||||
--------------
|
||||
|
||||
A minor release will not include breaking changes but may include
|
||||
miscellaneous bug fixes and upgrades to vendored packages. If the previous
|
||||
version of Requests released was ``v10.2.7`` a minor release would be
|
||||
versioned as ``v10.3.0``.
|
||||
A minor release will not include breaking changes but may include miscellaneous
|
||||
bug fixes. If the previous version of Requests released was ``v10.2.7`` a minor
|
||||
release would be versioned as ``v10.3.0``.
|
||||
|
||||
Minor releases will be backwards compatible with releases that have the same
|
||||
major version number. In other words, all versions that would start with
|
||||
|
||||
96
docs/community/sponsors.rst
Normal file
96
docs/community/sponsors.rst
Normal file
@@ -0,0 +1,96 @@
|
||||
Community Sponsors
|
||||
==================
|
||||
|
||||
**tl;dr**: Requests development is currently `funded by the Python community <https://www.kennethreitz.org/requests3>`_, and
|
||||
some wonderful organizations that utilize the software in their businesses.
|
||||
|
||||
|
||||
-------------------
|
||||
|
||||
|
||||
Requests is one of the most heavily–utilized Python packages in the world.
|
||||
|
||||
It is used by major corporations worldwide for all tasks, both small and large — from writing one–off scripts to orchestrating millions of dollars of critical infrastructure.
|
||||
|
||||
It's even embedded within pip, that tool that you use to install packages and deploy with every day!
|
||||
|
||||
After losing our primary open source maintainer (who was sponsored by a company to work on Requests, and other projects, full–time), we are seeking community financial contributions towards the development of Requests 3.0.
|
||||
|
||||
Patron Sponsors
|
||||
----------------
|
||||
|
||||
|
||||
`Linode — SSD Cloud Hosting & Linux Servers <https://www.linode.com>`_
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
Whether you’re just getting started or deploying a complex system, launching a Linode cloud server has never been easier. They offer the fastest hardware and network in the industry with scalable environments, and their 24x7 customer support team is always standing by to help with any questions.
|
||||
|
||||
✨🍰✨
|
||||
//////
|
||||
|
||||
----------------------------------
|
||||
|
||||
This slot is reserved for ethical organizations willing to invest $10,000 or more in Requests per year.
|
||||
|
||||
By becoming a patron–level sponsor, your organization will receive the following benefits:
|
||||
|
||||
- Prominent placement on the Requests documentation sidebar (~11,000 uniques / day).
|
||||
- Honorable mention here, with logo.
|
||||
- Peace of mind knowing that the infrastructure you rely on is being actively maintained.
|
||||
|
||||
Organizations that sign up will be listed in order — first come first serve!
|
||||
|
||||
Major Sponsors
|
||||
--------------
|
||||
|
||||
The following organizations have significantly contributed towards Requests' sustainability:
|
||||
|
||||
`Slack — Bring your team together <https://slack.com>`_
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
Slack was extremely kind to be the first organization to generously donate a large sum towards the `2018 Requests 3.0 fundraiser <https://www.kennethreitz.org/requests3>`_, surpassing our entire fundraising goal immediately! They are helping the world become a better place through connectiveness, and reducing the amount of email we all have
|
||||
to deal with on a daily basis.
|
||||
|
||||
P.S. They're `hiring <https://slack.com/careers#openings>`_!
|
||||
|
||||
|
||||
`Twilio — Voice, SMS, and Video for Humans <https://www.twilio.com>`_
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
Twilio was the second organization to generously donate a large sum towards the `2018 Requests 3.0 fundraiser <https://www.kennethreitz.org/requests3>`_, matching the donation of Slack! They are helping the world become a better place through interconnectivity,
|
||||
providing easy–to–use APIs, and empowering developers world-over to help humans communicate in meaningful and effictive ways.
|
||||
|
||||
|
||||
`Azure Cloud Developer Advocates <https://developer.microsoft.com/en-us/advocates/>`_
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Azure was the third organization to generously donate a large sum towards the `2018 Requests 3.0 fundraiser <https://www.kennethreitz.org/requests3>`_, matching the donation of Twilio! Awesome group of generous folks :)
|
||||
|
||||
|
||||
`Niteo — Web Systems Development <https://www.niteoweb.com>`_
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
Niteo was the fourth company to generously donate towards the `2018 Requests 3.0 fundraiser <https://www.kennethreitz.org/requests3>`_. Niteo is a company employing tech enthusiasts from all over the world
|
||||
who love to build great stuff.
|
||||
|
||||
|
||||
`Heroku <https://heroku.com/python>`_
|
||||
/////////////////////////////////////
|
||||
|
||||
Heroku has allowed Kenneth Reitz to work on some open source projects during work hours,
|
||||
including Requests (but mostly Pipenv), from time–to–time, so they are listed
|
||||
here as an honorable mention.
|
||||
|
||||
----------------
|
||||
|
||||
If your organization is interested in becoming either a sponsor or a patron, please `send us an email <mailto:me@kennethreitz.org>`_.
|
||||
|
||||
|
||||
Individual Sponsors
|
||||
-------------------
|
||||
|
||||
Countless individuals, too many to list here, have individually contributed towards the sustainability of the Requests
|
||||
project over the years. Some, financially, others, with code. Contributions (from humans) of all kinds are greatly
|
||||
appreciated.
|
||||
|
||||
✨🍰✨
|
||||
@@ -3,6 +3,8 @@
|
||||
Support
|
||||
=======
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4198/34080352913_5c13ffb336_k_d.jpg
|
||||
|
||||
If you have questions or issues about Requests, there are several options:
|
||||
|
||||
StackOverflow
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
Community Updates
|
||||
=================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4244/34080354873_516c283ad0_k_d.jpg
|
||||
|
||||
If you'd like to stay up to date on the community and development of Requests,
|
||||
there are several options:
|
||||
|
||||
@@ -27,4 +29,3 @@ Release and Version History
|
||||
===========================
|
||||
|
||||
.. include:: ../../HISTORY.rst
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Vulnerability Disclosure
|
||||
========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4211/34709353644_b041e9e1c2_k_d.jpg
|
||||
|
||||
If you think you have found a potential security vulnerability in requests,
|
||||
please email `sigmavirus24 <mailto:graffatcolmingov@gmail.com>`_ and
|
||||
`Lukasa <mailto:cory@lukasa.co.uk>`_ directly. **Do not file a public issue.**
|
||||
|
||||
@@ -58,7 +58,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'MMXVII. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
copyright = u'MMXVIII. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
author = u'Kenneth Reitz'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@@ -129,7 +129,8 @@ html_theme_options = {
|
||||
'github_user': 'requests',
|
||||
'github_repo': 'requests',
|
||||
'github_banner': True,
|
||||
'show_related': False
|
||||
'show_related': False,
|
||||
'note_bg': '#FFF59C'
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
@@ -375,4 +376,4 @@ epub_exclude_files = ['search.html']
|
||||
# If false, no index is generated.
|
||||
#epub_use_index = True
|
||||
|
||||
intersphinx_mapping = {'urllib3': ('http://urllib3.readthedocs.io/en/latest', None)}
|
||||
intersphinx_mapping = {'urllib3': ('https://urllib3.readthedocs.io/en/latest', None)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Authors
|
||||
=======
|
||||
|
||||
.. image:: https://static1.squarespace.com/static/533ad9bde4b098d084a846b1/t/534f6e1ce4b09b70f38ee6c1/1432265542589/DSCF3147.jpg?format=2500w
|
||||
|
||||
.. include:: ../../AUTHORS.rst
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Contributor's Guide
|
||||
===================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4237/35550408335_7671fde302_k_d.jpg
|
||||
|
||||
If you're reading this, you're probably interested in contributing to Requests.
|
||||
Thank you very much! Open source projects live-and-die based on the support
|
||||
they receive from others, and the fact that you're even considering
|
||||
@@ -203,4 +205,4 @@ while keeping an open ear and mind.
|
||||
|
||||
If you believe there is a feature missing, feel free to raise a feature
|
||||
request, but please do be aware that the overwhelming likelihood is that your
|
||||
feature request will not be accepted.
|
||||
feature request will not be accepted.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Development Philosophy
|
||||
======================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4231/34484831073_636008a23d_k_d.jpg
|
||||
|
||||
Requests is an open but opinionated library, created by an open but opinionated developer.
|
||||
|
||||
|
||||
@@ -31,6 +33,10 @@ Standard Library?
|
||||
|
||||
Requests has no *active* plans to be included in the standard library. This decision has been discussed at length with Guido as well as numerous core developers.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script async class="speakerdeck-embed" data-id="68f22f0841734d848315c618111b13ea" data-ratio="1.33333333333333" src="//speakerdeck.com/assets/embed.js"></script>
|
||||
|
||||
Essentially, the standard library is where a library goes to die. It is appropriate for a module to be included when active development is no longer necessary.
|
||||
|
||||
Linux Distro Packages
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
How to Help
|
||||
===========
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4290/34450900104_bc1d424213_k_d.jpg
|
||||
|
||||
Requests is under active development, and contributions are more than welcome!
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a bug.
|
||||
@@ -49,9 +51,7 @@ Runtime Environments
|
||||
|
||||
Requests currently supports the following versions of Python:
|
||||
|
||||
- Python 2.6
|
||||
- Python 2.7
|
||||
- Python 3.3
|
||||
- Python 3.4
|
||||
- Python 3.5
|
||||
- Python 3.6
|
||||
@@ -60,5 +60,4 @@ Requests currently supports the following versions of Python:
|
||||
Google AppEngine is not officially supported although support is available
|
||||
with the `Requests-Toolbelt`_.
|
||||
|
||||
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/
|
||||
|
||||
.. _Requests-Toolbelt: https://toolbelt.readthedocs.io/
|
||||
|
||||
@@ -9,13 +9,13 @@ Requests: HTTP for Humans
|
||||
Release v\ |version|. (:ref:`Installation <install>`)
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
:target: https://pypi.org/project/requests/
|
||||
|
||||
.. image:: https://img.shields.io/pypi/wheel/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
:target: https://pypi.org/project/requests/
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
:target: https://pypi.org/project/requests/
|
||||
|
||||
.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master
|
||||
:target: https://codecov.io/github/requests/requests
|
||||
@@ -28,9 +28,9 @@ Release v\ |version|. (:ref:`Installation <install>`)
|
||||
**Requests** is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||
*Warning: Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
|
||||
including: security vulnerabilities, verbose code, reinventing the wheel,
|
||||
constantly reading documentation, depression, headaches, or even death.*
|
||||
.. note:: The use of **Python 3** is *highly* preferred over Python 2. Consider upgrading your applications and infrastructure if you find yourself *still* using Python 2 in production today. If you are using Python 3, congratulations — you are indeed a person of excellent taste.
|
||||
—*Kenneth Reitz*
|
||||
|
||||
|
||||
-------------------
|
||||
|
||||
@@ -59,12 +59,12 @@ are 100% automatic, thanks to `urllib3 <https://github.com/shazow/urllib3>`_.
|
||||
User Testimonials
|
||||
-----------------
|
||||
|
||||
Twitter, Spotify, Microsoft, Amazon, Lyft, BuzzFeed, Reddit, The NSA, Her Majesty's Government, Google, Twilio, Runscope, Mozilla, Heroku,
|
||||
Nike, Twitter, Spotify, Microsoft, Amazon, Lyft, BuzzFeed, Reddit, The NSA, Her Majesty's Government, Google, Twilio, Runscope, Mozilla, Heroku,
|
||||
PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington
|
||||
Post, SoundCloud, Kippt, Sony, and Federal U.S.
|
||||
Institutions that prefer to be unnamed claim to use Requests internally.
|
||||
|
||||
**Armin Ronacher**—
|
||||
**Armin Ronacher**, creator of Flask—
|
||||
*Requests is the perfect example how beautiful an API can be with the
|
||||
right level of abstraction.*
|
||||
|
||||
@@ -74,14 +74,18 @@ Institutions that prefer to be unnamed claim to use Requests internally.
|
||||
|
||||
**Daniel Greenfeld**—
|
||||
*Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to
|
||||
Kenneth Reitz's request library. Today has been AWESOME.*
|
||||
Kenneth Reitz's Requests library. Today has been AWESOME.*
|
||||
|
||||
**Kenny Meyers**—
|
||||
*Python HTTP: When in doubt, or when not in doubt, use Requests. Beautiful,
|
||||
simple, Pythonic.*
|
||||
|
||||
Requests is one of the most downloaded Python packages of all time, pulling in
|
||||
over 11,000,000 downloads every month. All the cool kids are doing it!
|
||||
over 400,000 downloads **each day**. Join the party!
|
||||
|
||||
If your organization uses Requests internally, consider `supporting the development of 3.0 <https://www.kennethreitz.org/requests3>`_. Your
|
||||
generosity will be greatly appreciated, and help drive the project forward
|
||||
into the future.
|
||||
|
||||
Beloved Features
|
||||
----------------
|
||||
@@ -104,7 +108,7 @@ Requests is ready for today's web.
|
||||
- Chunked Requests
|
||||
- ``.netrc`` Support
|
||||
|
||||
Requests officially supports Python 2.6–2.7 & 3.3–3.7, and runs great on PyPy.
|
||||
Requests officially supports Python 2.7 & 3.4–3.7, and runs great on PyPy.
|
||||
|
||||
|
||||
The User Guide
|
||||
@@ -131,10 +135,11 @@ This part of the documentation, which is mostly prose, details the
|
||||
Requests ecosystem and community.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:maxdepth: 2
|
||||
|
||||
community/faq
|
||||
community/sponsors
|
||||
community/recommended
|
||||
community/faq
|
||||
community/out-there
|
||||
community/support
|
||||
community/vulnerabilities
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Advanced Usage
|
||||
==============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4263/35163665790_d182d84f5e_k_d.jpg
|
||||
|
||||
This document covers some of Requests more advanced features.
|
||||
|
||||
.. _session-objects:
|
||||
@@ -187,6 +189,25 @@ applied, replace the call to :meth:`Request.prepare()
|
||||
|
||||
print(resp.status_code)
|
||||
|
||||
When you are using the prepared request flow, keep in mind that it does not take into account the environment.
|
||||
This can cause problems if you are using environment variables to change the behaviour of requests.
|
||||
For example: Self-signed SSL certificates specified in ``REQUESTS_CA_BUNDLE`` will not be taken into account.
|
||||
As a result an ``SSL: CERTIFICATE_VERIFY_FAILED`` is thrown.
|
||||
You can get around this behaviour by explicity merging the environment settings into your session::
|
||||
|
||||
from requests import Request, Session
|
||||
|
||||
s = Session()
|
||||
req = Request('GET', url)
|
||||
|
||||
prepped = s.prepare_request(req)
|
||||
|
||||
# Merge environment settings into session
|
||||
settings = s.merge_environment_settings(prepped.url, None, None, None, None)
|
||||
resp = s.send(prepped, **settings)
|
||||
|
||||
print(resp.status_code)
|
||||
|
||||
.. _verification:
|
||||
|
||||
SSL Cert Verification
|
||||
@@ -253,21 +274,20 @@ If you specify a wrong path or an invalid cert, you'll get a SSLError::
|
||||
CA Certificates
|
||||
---------------
|
||||
|
||||
By default, Requests bundles a set of root CAs that it trusts, sourced from the
|
||||
`Mozilla trust store`_. However, these are only updated once for each Requests
|
||||
version. This means that if you pin a Requests version your certificates can
|
||||
become extremely out of date.
|
||||
Requests uses certificates from the package `certifi`_. This allows for users
|
||||
to update their trusted certificates without changing the version of Requests.
|
||||
|
||||
From Requests version 2.4.0 onwards, Requests will attempt to use certificates
|
||||
from `certifi`_ if it is present on the system. This allows for users to update
|
||||
their trusted certificates without having to change the code that runs on their
|
||||
system.
|
||||
Before version 2.16, Requests bundled a set of root CAs that it trusted,
|
||||
sourced from the `Mozilla trust store`_. The certificates were only updated
|
||||
once for each Requests version. When ``certifi`` was not installed, this led to
|
||||
extremely out-of-date certificate bundles when using significantly older
|
||||
versions of Requests.
|
||||
|
||||
For the sake of security we recommend upgrading certifi frequently!
|
||||
|
||||
.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection
|
||||
.. _connection pooling: http://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.connectionpool
|
||||
.. _certifi: http://certifi.io/
|
||||
.. _connection pooling: https://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.connectionpool
|
||||
.. _certifi: https://certifiio.readthedocs.io/
|
||||
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
|
||||
.. _body-content-workflow:
|
||||
@@ -301,15 +321,11 @@ release the connection back to the pool unless you consume all the data or call
|
||||
:meth:`Response.close <requests.Response.close>`. This can lead to
|
||||
inefficiency with connections. If you find yourself partially reading request
|
||||
bodies (or not reading them at all) while using ``stream=True``, you should
|
||||
consider using ``contextlib.closing`` (`documented here`_), like this::
|
||||
make the request within a ``with`` statement to ensure it's always closed::
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
with closing(requests.get('http://httpbin.org/get', stream=True)) as r:
|
||||
with requests.get('http://httpbin.org/get', stream=True) as r:
|
||||
# Do things with the response here.
|
||||
|
||||
.. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing
|
||||
|
||||
.. _keep-alive:
|
||||
|
||||
Keep-Alive
|
||||
@@ -341,7 +357,7 @@ file-like object for your body::
|
||||
be set to the number of *bytes* in the file. Errors may occur if
|
||||
you open the file in *text mode*.
|
||||
|
||||
.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
|
||||
.. _binary mode: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
|
||||
|
||||
|
||||
.. _chunk-encoding:
|
||||
@@ -398,7 +414,7 @@ To do that, just set files to a list of tuples of ``(form_field_name, file_info)
|
||||
be set to the number of *bytes* in the file. Errors may occur if
|
||||
you open the file in *text mode*.
|
||||
|
||||
.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
|
||||
.. _binary mode: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
|
||||
|
||||
|
||||
.. _event-hooks:
|
||||
@@ -419,7 +435,7 @@ You can assign a hook function on a per-request basis by passing a
|
||||
``{hook_name: callback_function}`` dictionary to the ``hooks`` request
|
||||
parameter::
|
||||
|
||||
hooks=dict(response=print_url)
|
||||
hooks={'response': print_url}
|
||||
|
||||
That ``callback_function`` will receive a chunk of data as its first
|
||||
argument.
|
||||
@@ -433,14 +449,38 @@ If an error occurs while executing your callback, a warning is given.
|
||||
|
||||
If the callback function returns a value, it is assumed that it is to
|
||||
replace the data that was passed in. If the function doesn't return
|
||||
anything, nothing else is effected.
|
||||
anything, nothing else is affected.
|
||||
|
||||
::
|
||||
|
||||
def record_hook(r, *args, **kwargs):
|
||||
r.hook_called = True
|
||||
return r
|
||||
|
||||
Let's print some request method arguments at runtime::
|
||||
|
||||
>>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
|
||||
>>> requests.get('http://httpbin.org', hooks={'response': print_url})
|
||||
http://httpbin.org
|
||||
<Response [200]>
|
||||
|
||||
You can add multiple hooks to a single request. Let's call two hooks at once::
|
||||
|
||||
>>> r = requests.get('http://httpbin.org', hooks={'response': [print_url, record_hook]})
|
||||
>>> r.hook_called
|
||||
True
|
||||
|
||||
You can also add hooks to a ``Session`` instance. Any hooks you add will then
|
||||
be called on every request made to the session. For example::
|
||||
|
||||
>>> s = requests.Session()
|
||||
>>> s.hooks['response'].append(print_url)
|
||||
>>> s.get('http://httpbin.org')
|
||||
http://httpbin.org
|
||||
<Response [200]>
|
||||
|
||||
A ``Session`` can have multiple hooks, which will be called in the order
|
||||
they are added.
|
||||
|
||||
.. _custom-auth:
|
||||
|
||||
Custom Authentication
|
||||
@@ -597,6 +637,8 @@ as using a HTTP one::
|
||||
'https': 'socks5://user:pass@host:port'
|
||||
}
|
||||
|
||||
Using the scheme ``socks5`` causes the DNS resolution to happen on the client, rather than on the proxy server. This is in line with curl, which uses the scheme to decide whether to do the DNS resolution on the client or proxy. If you want to resolve the domains on the proxy server, use ``socks5h`` as the scheme.
|
||||
|
||||
.. _compliance:
|
||||
|
||||
Compliance
|
||||
@@ -614,7 +656,7 @@ When you receive a response, Requests makes a guess at the encoding to
|
||||
use for decoding the response when you access the :attr:`Response.text
|
||||
<requests.Response.text>` attribute. Requests will first check for an
|
||||
encoding in the HTTP header, and if none is present, will use `chardet
|
||||
<http://pypi.python.org/pypi/chardet>`_ to attempt to guess the encoding.
|
||||
<https://pypi.org/project/chardet/>`_ to attempt to guess the encoding.
|
||||
|
||||
The only time Requests will not do this is if no explicit charset
|
||||
is present in the HTTP headers **and** the ``Content-Type``
|
||||
@@ -841,7 +883,7 @@ Link Headers
|
||||
Many HTTP APIs feature Link headers. They make APIs more self describing and
|
||||
discoverable.
|
||||
|
||||
GitHub uses these for `pagination <http://developer.github.com/v3/#pagination>`_
|
||||
GitHub uses these for `pagination <https://developer.github.com/v3/#pagination>`_
|
||||
in their API, for example::
|
||||
|
||||
>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10'
|
||||
@@ -908,9 +950,9 @@ passed-through to `urllib3`. We'll make a Transport Adapter that instructs the
|
||||
library to use SSLv3::
|
||||
|
||||
import ssl
|
||||
from urllib3.poolmanager import PoolManager
|
||||
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.poolmanager import PoolManager
|
||||
|
||||
|
||||
class Ssl3HttpAdapter(HTTPAdapter):
|
||||
@@ -938,8 +980,9 @@ response at a time. However, these calls will still block.
|
||||
|
||||
If you are concerned about the use of blocking IO, there are lots of projects
|
||||
out there that combine Requests with one of Python's asynchronicity frameworks.
|
||||
Two excellent examples are `grequests`_ and `requests-futures`_.
|
||||
Some excellent examples are `requests-threads`_, `grequests`_, and `requests-futures`_.
|
||||
|
||||
.. _`requests-threads`: https://github.com/requests/requests-threads
|
||||
.. _`grequests`: https://github.com/kennethreitz/grequests
|
||||
.. _`requests-futures`: https://github.com/ross/requests-futures
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Authentication
|
||||
==============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4258/35550409215_3b08d49d22_k_d.jpg
|
||||
|
||||
This document discusses using various kinds of authentication with Requests.
|
||||
|
||||
Many web services require authentication, and there are many different types.
|
||||
@@ -134,11 +136,11 @@ Further examples can be found under the `Requests organization`_ and in the
|
||||
|
||||
.. _OAuth: http://oauth.net/
|
||||
.. _requests_oauthlib: https://github.com/requests/requests-oauthlib
|
||||
.. _requests-oauthlib OAuth2 documentation: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html
|
||||
.. _Web Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#web-application-flow
|
||||
.. _Mobile Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#mobile-application-flow
|
||||
.. _Legacy Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#legacy-application-flow
|
||||
.. _Backend Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#backend-application-flow
|
||||
.. _requests-oauthlib OAuth2 documentation: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html
|
||||
.. _Web Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#web-application-flow
|
||||
.. _Mobile Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#mobile-application-flow
|
||||
.. _Legacy Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#legacy-application-flow
|
||||
.. _Backend Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#backend-application-flow
|
||||
.. _Kerberos: https://github.com/requests/requests-kerberos
|
||||
.. _NTLM: https://github.com/requests/requests-ntlm
|
||||
.. _Requests organization: https://github.com/requests
|
||||
|
||||
@@ -3,18 +3,21 @@
|
||||
Installation of Requests
|
||||
========================
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4230/35550376215_da1bf77a8c_k_d.jpg
|
||||
|
||||
|
||||
This part of the documentation covers the installation of Requests.
|
||||
The first step to using any software package is getting it properly installed.
|
||||
|
||||
|
||||
$ pip install requests
|
||||
----------------------
|
||||
$ pipenv install requests
|
||||
-------------------------
|
||||
|
||||
To install Requests, simply run this simple command in your terminal of choice::
|
||||
|
||||
$ pip install requests
|
||||
$ pipenv install requests
|
||||
|
||||
If you don't have `pip <https://pip.pypa.io>`_ installed (tisk tisk!),
|
||||
If you don't have `pipenv <http://pipenv.org/>`_ installed (tisk tisk!), head over to the Pipenv website for installation instructions. Or, if you prefer to just use pip and don't have it installed,
|
||||
`this Python installation guide <http://docs.python-guide.org/en/latest/starting/installation/>`_
|
||||
can guide you through the process.
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg
|
||||
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. image:: https://farm5.staticflickr.com/4259/35163667010_8bfcaef274_k_d.jpg
|
||||
|
||||
.. module:: requests.models
|
||||
|
||||
Eager to get started? This page gives a good introduction in how to get started
|
||||
@@ -108,7 +110,7 @@ using, and change it, using the ``r.encoding`` property::
|
||||
If you change the encoding, Requests will use the new value of ``r.encoding``
|
||||
whenever you call ``r.text``. You might want to do this in any situation where
|
||||
you can apply special logic to work out what the encoding of the content will
|
||||
be. For example, HTTP and XML have the ability to specify their encoding in
|
||||
be. For example, HTML and XML have the ability to specify their encoding in
|
||||
their body. In situations like this, you should use ``r.content`` to find the
|
||||
encoding, and then set ``r.encoding``. This will let you use ``r.text`` with
|
||||
the correct encoding.
|
||||
@@ -169,7 +171,7 @@ server, you can access ``r.raw``. If you want to do this, make sure you set
|
||||
>>> r = requests.get('https://api.github.com/events', stream=True)
|
||||
|
||||
>>> r.raw
|
||||
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
|
||||
<urllib3.response.HTTPResponse object at 0x101194810>
|
||||
|
||||
>>> r.raw.read(10)
|
||||
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
|
||||
@@ -187,6 +189,14 @@ download, the above is the preferred and recommended way to retrieve the
|
||||
content. Note that ``chunk_size`` can be freely adjusted to a number that
|
||||
may better fit your use cases.
|
||||
|
||||
.. note::
|
||||
|
||||
An important note about using ``Response.iter_content`` versus ``Response.raw``.
|
||||
``Response.iter_content`` will automatically decode the ``gzip`` and ``deflate``
|
||||
transfer-encodings. ``Response.raw`` is a raw stream of bytes -- it does not
|
||||
transform the response content. If you really need access to the bytes as they
|
||||
were returned, use ``Response.raw``.
|
||||
|
||||
|
||||
Custom Headers
|
||||
--------------
|
||||
@@ -271,6 +281,9 @@ the ``json`` parameter (added in version 2.4.2) and it will be encoded automatic
|
||||
|
||||
>>> r = requests.post(url, json=payload)
|
||||
|
||||
Note, the ``json`` parameter is ignored if either ``data`` or ``files`` is passed.
|
||||
|
||||
Using the ``json`` parameter in the request will change the ``Content-Type`` in the header to ``application/json``.
|
||||
|
||||
POST a Multipart-Encoded File
|
||||
-----------------------------
|
||||
@@ -335,7 +348,7 @@ section.
|
||||
be set to the number of *bytes* in the file. Errors may occur if
|
||||
you open the file in *text mode*.
|
||||
|
||||
.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
|
||||
.. _binary mode: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
|
||||
|
||||
|
||||
Response Status Codes
|
||||
@@ -371,14 +384,10 @@ But, since our ``status_code`` for ``r`` was ``200``, when we call
|
||||
``raise_for_status()`` we get::
|
||||
|
||||
>>> r.raise_for_status()
|
||||
<Response [200]>
|
||||
None
|
||||
|
||||
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
|
||||
----------------
|
||||
|
||||
@@ -40,44 +40,67 @@ is at <http://python-requests.org>.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
# Check urllib3 for compatibility.
|
||||
import urllib3
|
||||
urllib3_version = urllib3.__version__.split('.')
|
||||
# Sometimes, urllib3 only reports its version as 16.1.
|
||||
if len(urllib3_version) == 2:
|
||||
urllib3_version.append('0')
|
||||
major, minor, patch = urllib3_version
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# urllib3 >= 1.21.1, < 1.22
|
||||
try:
|
||||
import chardet
|
||||
import warnings
|
||||
from .exceptions import RequestsDependencyWarning
|
||||
|
||||
|
||||
def check_compatibility(urllib3_version, chardet_version):
|
||||
urllib3_version = urllib3_version.split('.')
|
||||
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')
|
||||
|
||||
# Check urllib3 for compatibility.
|
||||
major, minor, patch = urllib3_version # noqa: F811
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# urllib3 >= 1.21.1, <= 1.23
|
||||
assert major == 1
|
||||
assert minor >= 21
|
||||
assert minor <= 22
|
||||
except AssertionError:
|
||||
raise RuntimeError('Requests dependency \'urllib3\' must be version >= 1.21.1, < 1.22!')
|
||||
assert minor <= 23
|
||||
|
||||
|
||||
# Check chardet for compatibility.
|
||||
import chardet
|
||||
major, minor, patch = chardet.__version__.split('.')[:3]
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# chardet >= 3.0.2, < 3.1.0
|
||||
try:
|
||||
# Check chardet for compatibility.
|
||||
major, minor, patch = chardet_version.split('.')[:3]
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# chardet >= 3.0.2, < 3.1.0
|
||||
assert major == 3
|
||||
assert minor < 1
|
||||
assert patch >= 2
|
||||
except AssertionError:
|
||||
raise RuntimeError('Requests dependency \'chardet\' must be version >= 3.0.2, < 3.1.0!')
|
||||
|
||||
|
||||
def _check_cryptography(cryptography_version):
|
||||
# cryptography < 1.3.4
|
||||
try:
|
||||
cryptography_version = list(map(int, cryptography_version.split('.')))
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
if cryptography_version < [1, 3, 4]:
|
||||
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)
|
||||
|
||||
# Attempt to enable urllib3's SNI support, if possible
|
||||
try:
|
||||
from urllib3.contrib import pyopenssl
|
||||
pyopenssl.inject_into_urllib3()
|
||||
|
||||
# Check cryptography version
|
||||
from cryptography import __version__ as cryptography_version
|
||||
_check_cryptography(cryptography_version)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import warnings
|
||||
|
||||
# urllib3's DependencyWarnings should be silenced.
|
||||
from urllib3.exceptions import DependencyWarning
|
||||
warnings.simplefilter('ignore', DependencyWarning)
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
__title__ = 'requests'
|
||||
__description__ = 'Python HTTP for Humans.'
|
||||
__url__ = 'http://python-requests.org'
|
||||
__version__ = '3.0.0'
|
||||
__build__ = 0x030000
|
||||
__version__ = '2.19.1'
|
||||
__build__ = 0x021901
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__author_email__ = 'me@kennethreitz.org'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2017 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2018 Kenneth Reitz'
|
||||
__cake__ = u'\u2728 \U0001f370 \u2728'
|
||||
|
||||
@@ -10,13 +10,10 @@ and maintain connections.
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
try:
|
||||
from threading import RLock
|
||||
except ImportError: # threading is an optional module and may not be present.
|
||||
from dummy_threading import RLock
|
||||
|
||||
from urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from urllib3.response import HTTPResponse
|
||||
from urllib3.util import parse_url
|
||||
from urllib3.util import Timeout as TimeoutSauce
|
||||
from urllib3.util.retry import Retry
|
||||
from urllib3.exceptions import ClosedPoolError
|
||||
@@ -32,20 +29,20 @@ 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 .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths,
|
||||
get_encoding_from_headers, prepend_scheme_if_needed,
|
||||
get_auth_from_url, urldefragauth, select_proxy)
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
||||
ProxyError, RetryError, InvalidScheme)
|
||||
ProxyError, RetryError, InvalidSchema, InvalidProxyURL)
|
||||
from .auth import _basic_auth_str
|
||||
|
||||
try:
|
||||
from urllib3.contrib.socks import SOCKSProxyManager
|
||||
except ImportError:
|
||||
def SOCKSProxyManager(*args, **kwargs):
|
||||
raise InvalidScheme("Missing dependencies for SOCKS support.")
|
||||
raise InvalidSchema("Missing dependencies for SOCKS support.")
|
||||
|
||||
DEFAULT_POOLBLOCK = False
|
||||
DEFAULT_POOLSIZE = 10
|
||||
@@ -126,7 +123,6 @@ class HTTPAdapter(BaseAdapter):
|
||||
self._pool_connections = pool_connections
|
||||
self._pool_maxsize = pool_maxsize
|
||||
self._pool_block = pool_block
|
||||
self._pool_kw_lock = RLock()
|
||||
|
||||
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
|
||||
|
||||
@@ -139,7 +135,6 @@ class HTTPAdapter(BaseAdapter):
|
||||
# self.poolmanager uses a lambda function, which isn't pickleable.
|
||||
self.proxy_manager = {}
|
||||
self.config = {}
|
||||
self._pool_kw_lock = RLock()
|
||||
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
@@ -204,21 +199,19 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
return manager
|
||||
|
||||
def _update_poolmanager_ssl_kw(self, verify, cert):
|
||||
"""Update the :class:`PoolManager <urllib3.poolmanager.PoolManager>`
|
||||
connection_pool_kw with the necessary SSL configuration. This method
|
||||
should not be called from user code, and is only exposed for use when
|
||||
subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
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 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).
|
||||
: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 verify:
|
||||
if url.lower().startswith('https') and verify:
|
||||
|
||||
cert_loc = None
|
||||
|
||||
@@ -227,41 +220,36 @@ class HTTPAdapter(BaseAdapter):
|
||||
cert_loc = verify
|
||||
|
||||
if not cert_loc:
|
||||
cert_loc = DEFAULT_CA_BUNDLE_PATH
|
||||
cert_loc = extract_zipped_paths(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))
|
||||
|
||||
self.poolmanager.connection_pool_kw['cert_reqs'] = 'CERT_REQUIRED'
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
|
||||
if not os.path.isdir(cert_loc):
|
||||
self.poolmanager.connection_pool_kw['ca_certs'] = cert_loc
|
||||
self.poolmanager.connection_pool_kw['ca_cert_dir'] = None
|
||||
conn.ca_certs = cert_loc
|
||||
else:
|
||||
self.poolmanager.connection_pool_kw['ca_cert_dir'] = cert_loc
|
||||
self.poolmanager.connection_pool_kw['ca_certs'] = None
|
||||
conn.ca_cert_dir = cert_loc
|
||||
else:
|
||||
self.poolmanager.connection_pool_kw['cert_reqs'] = 'CERT_NONE'
|
||||
self.poolmanager.connection_pool_kw['ca_certs'] = None
|
||||
self.poolmanager.connection_pool_kw['ca_cert_dir'] = None
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
conn.ca_cert_dir = None
|
||||
|
||||
if cert:
|
||||
if not isinstance(cert, basestring):
|
||||
self.poolmanager.connection_pool_kw['cert_file'] = cert[0]
|
||||
self.poolmanager.connection_pool_kw['key_file'] = cert[1]
|
||||
conn.cert_file = cert[0]
|
||||
conn.key_file = cert[1]
|
||||
else:
|
||||
self.poolmanager.connection_pool_kw['cert_file'] = cert
|
||||
self.poolmanager.connection_pool_kw['key_file'] = None
|
||||
|
||||
cert_file = self.poolmanager.connection_pool_kw['cert_file']
|
||||
key_file = self.poolmanager.connection_pool_kw['key_file']
|
||||
if cert_file and not os.path.exists(cert_file):
|
||||
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(cert_file))
|
||||
if key_file and not os.path.exists(key_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(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
|
||||
@@ -300,7 +288,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
return response
|
||||
|
||||
def get_connection(self, url, proxies=None, verify=None, cert=None):
|
||||
def get_connection(self, url, proxies=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>`.
|
||||
@@ -309,21 +297,21 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
:rtype: urllib3.ConnectionPool
|
||||
"""
|
||||
with self._pool_kw_lock:
|
||||
if url.lower().startswith('https'):
|
||||
self._update_poolmanager_ssl_kw(verify, cert)
|
||||
proxy = select_proxy(url, proxies)
|
||||
|
||||
proxy = select_proxy(url, proxies)
|
||||
|
||||
if proxy:
|
||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||
proxy_manager = self.proxy_manager_for(proxy)
|
||||
conn = proxy_manager.connection_from_url(url)
|
||||
else:
|
||||
# Only scheme should be lower case
|
||||
parsed = urlparse(url)
|
||||
url = parsed.geturl()
|
||||
conn = self.poolmanager.connection_from_url(url)
|
||||
if proxy:
|
||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||
proxy_url = parse_url(proxy)
|
||||
if not proxy_url.host:
|
||||
raise InvalidProxyURL("Please check proxy URL. It is malformed"
|
||||
" and could be missing the host.")
|
||||
proxy_manager = self.proxy_manager_for(proxy)
|
||||
conn = proxy_manager.connection_from_url(url)
|
||||
else:
|
||||
# Only scheme should be lower case
|
||||
parsed = urlparse(url)
|
||||
url = parsed.geturl()
|
||||
conn = self.poolmanager.connection_from_url(url)
|
||||
|
||||
return conn
|
||||
|
||||
@@ -418,10 +406,12 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) The proxies dictionary to apply to the request.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
conn = self.get_connection(request.url, proxies, verify, cert)
|
||||
|
||||
conn = self.get_connection(request.url, proxies)
|
||||
|
||||
self.cert_verify(conn, request.url, verify, cert)
|
||||
url = self.request_url(request, proxies)
|
||||
self.add_headers(request)
|
||||
self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
|
||||
|
||||
chunked = not (request.body is None or 'Content-Length' in request.headers)
|
||||
|
||||
@@ -452,8 +442,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=self.max_retries,
|
||||
timeout=timeout,
|
||||
enforce_content_length=True
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Send the request.
|
||||
@@ -474,10 +463,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
low_conn.endheaders()
|
||||
|
||||
for i in request.body:
|
||||
chunk_size = len(i)
|
||||
if chunk_size == 0:
|
||||
continue
|
||||
low_conn.send(hex(chunk_size)[2:].encode('utf-8'))
|
||||
low_conn.send(hex(len(i))[2:].encode('utf-8'))
|
||||
low_conn.send(b'\r\n')
|
||||
low_conn.send(i)
|
||||
low_conn.send(b'\r\n')
|
||||
@@ -497,9 +483,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
pool=conn,
|
||||
connection=low_conn,
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
enforce_content_length=True,
|
||||
request_method=request.method
|
||||
decode_content=False
|
||||
)
|
||||
except:
|
||||
# If we hit any problems here, clean up the connection.
|
||||
@@ -522,6 +506,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
if isinstance(e.reason, _ProxyError):
|
||||
raise ProxyError(e, request=request)
|
||||
|
||||
if isinstance(e.reason, _SSLError):
|
||||
# This branch is for urllib3 v1.22 and later.
|
||||
raise SSLError(e, request=request)
|
||||
|
||||
raise ConnectionError(e, request=request)
|
||||
|
||||
except ClosedPoolError as e:
|
||||
@@ -532,6 +520,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
except (_SSLError, _HTTPError) as e:
|
||||
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)
|
||||
|
||||
@@ -13,15 +13,14 @@ This module implements the Requests API.
|
||||
from . import sessions
|
||||
|
||||
|
||||
def request(method, url, session=None, **kwargs):
|
||||
def request(method, url, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param session: :class:`Session` object to use for this request. If none is given, one will be provided.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
|
||||
@@ -55,10 +54,7 @@ def request(method, url, session=None, **kwargs):
|
||||
# By using the 'with' statement we are sure the session is closed, thus we
|
||||
# avoid leaving sockets open which can trigger a ResourceWarning in some
|
||||
# cases, and look like a memory leak in others.
|
||||
|
||||
session = sessions.Session() if session is None else session
|
||||
|
||||
with session:
|
||||
with sessions.Session() as session:
|
||||
return session.request(method=method, url=url, **kwargs)
|
||||
|
||||
|
||||
@@ -77,7 +73,7 @@ def get(url, params=None, **kwargs):
|
||||
|
||||
|
||||
def options(url, **kwargs):
|
||||
r"""Sends a OPTIONS request.
|
||||
r"""Sends an OPTIONS request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
|
||||
@@ -12,6 +12,7 @@ import re
|
||||
import time
|
||||
import hashlib
|
||||
import threading
|
||||
import warnings
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
@@ -27,13 +28,33 @@ 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):
|
||||
raise TypeError('username must be of type str or bytes, '
|
||||
'instead it was %s' % type(username))
|
||||
warnings.warn(
|
||||
"Non-string usernames will no longer be supported in Requests "
|
||||
"3.0.0. Please convert the object you've passed in ({0!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(username),
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
username = str(username)
|
||||
|
||||
if not isinstance(password, basestring):
|
||||
raise TypeError('password must be of type str or bytes, '
|
||||
'instead it was %s' % type(password))
|
||||
warnings.warn(
|
||||
"Non-string passwords will no longer be supported in Requests "
|
||||
"3.0.0. Please convert the object you've passed in ({0!r}) to "
|
||||
"a string or bytes object in the near future to avoid "
|
||||
"problems.".format(password),
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
password = str(password)
|
||||
# -- End Removal --
|
||||
|
||||
if isinstance(username, str):
|
||||
username = username.encode('latin1')
|
||||
@@ -76,6 +97,14 @@ class HTTPBasicAuth(AuthBase):
|
||||
return r
|
||||
|
||||
|
||||
class HTTPProxyAuth(HTTPBasicAuth):
|
||||
"""Attaches HTTP Proxy Authentication to a given Request object."""
|
||||
|
||||
def __call__(self, r):
|
||||
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
|
||||
|
||||
class HTTPDigestAuth(AuthBase):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object."""
|
||||
|
||||
@@ -124,6 +153,18 @@ class HTTPDigestAuth(AuthBase):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
hash_utf8 = sha_utf8
|
||||
elif _algorithm == 'SHA-256':
|
||||
def sha256_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha256(x).hexdigest()
|
||||
hash_utf8 = sha256_utf8
|
||||
elif _algorithm == 'SHA-512':
|
||||
def sha512_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha512(x).hexdigest()
|
||||
hash_utf8 = sha512_utf8
|
||||
|
||||
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ is_py2 = (_ver[0] == 2)
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
@@ -39,6 +43,7 @@ if is_py2:
|
||||
import cookielib
|
||||
from Cookie import Morsel
|
||||
from StringIO import StringIO
|
||||
from collections import Callable, Mapping, MutableMapping
|
||||
|
||||
from urllib3.packages.ordered_dict import OrderedDict
|
||||
|
||||
@@ -56,6 +61,7 @@ elif is_py3:
|
||||
from http.cookies import Morsel
|
||||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Callable, Mapping, MutableMapping
|
||||
|
||||
builtin_str = str
|
||||
str = str
|
||||
|
||||
@@ -12,10 +12,9 @@ requests.utils imports from here, so be careful with imports.
|
||||
import copy
|
||||
import time
|
||||
import calendar
|
||||
import collections
|
||||
|
||||
from ._internal_utils import to_native_string
|
||||
from .compat import cookielib, urlparse, urlunparse, Morsel
|
||||
from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping
|
||||
|
||||
try:
|
||||
import threading
|
||||
@@ -169,7 +168,7 @@ class CookieConflictError(RuntimeError):
|
||||
"""
|
||||
|
||||
|
||||
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
|
||||
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
|
||||
interface.
|
||||
|
||||
@@ -414,10 +413,15 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of this RequestsCookieJar."""
|
||||
new_cj = RequestsCookieJar(self._policy)
|
||||
new_cj = RequestsCookieJar()
|
||||
new_cj.set_policy(self.get_policy())
|
||||
new_cj.update(self)
|
||||
return new_cj
|
||||
|
||||
def get_policy(self):
|
||||
"""Return the CookiePolicy instance used."""
|
||||
return self._policy
|
||||
|
||||
|
||||
def _copy_cookie_jar(jar):
|
||||
if jar is None:
|
||||
|
||||
@@ -69,12 +69,12 @@ class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
|
||||
|
||||
class MissingScheme(RequestException, ValueError):
|
||||
"""The URL scheme (e.g. http or https) is missing."""
|
||||
class MissingSchema(RequestException, ValueError):
|
||||
"""The URL schema (e.g. http or https) is missing."""
|
||||
|
||||
|
||||
class InvalidScheme(RequestException, ValueError):
|
||||
"""See defaults.py for valid schemes."""
|
||||
class InvalidSchema(RequestException, ValueError):
|
||||
"""See defaults.py for valid schemas."""
|
||||
|
||||
|
||||
class InvalidURL(RequestException, ValueError):
|
||||
@@ -85,6 +85,10 @@ class InvalidHeader(RequestException, ValueError):
|
||||
"""The header value provided was somehow invalid."""
|
||||
|
||||
|
||||
class InvalidProxyURL(InvalidURL):
|
||||
"""The proxy URL provided is invalid."""
|
||||
|
||||
|
||||
class ChunkedEncodingError(RequestException):
|
||||
"""The server declared chunked encoding but sent an invalid chunk."""
|
||||
|
||||
@@ -104,10 +108,6 @@ 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
|
||||
|
||||
|
||||
@@ -119,3 +119,8 @@ class RequestsWarning(Warning):
|
||||
class FileModeWarning(RequestsWarning, DeprecationWarning):
|
||||
"""A file was opened in text mode, but Requests determined its binary length."""
|
||||
pass
|
||||
|
||||
|
||||
class RequestsDependencyWarning(RequestsWarning):
|
||||
"""An imported dependency doesn't match the expected version range."""
|
||||
pass
|
||||
|
||||
@@ -6,13 +6,14 @@ import platform
|
||||
import sys
|
||||
import ssl
|
||||
|
||||
import idna
|
||||
import urllib3
|
||||
import chardet
|
||||
|
||||
from . import __version__ as requests_version
|
||||
|
||||
try:
|
||||
from .packages.urllib3.contrib import pyopenssl
|
||||
from urllib3.contrib import pyopenssl
|
||||
except ImportError:
|
||||
pyopenssl = None
|
||||
OpenSSL = None
|
||||
@@ -23,7 +24,7 @@ else:
|
||||
|
||||
|
||||
def _implementation():
|
||||
"""Return a dict with the Python implementation and verison.
|
||||
"""Return a dict with the Python implementation and version.
|
||||
|
||||
Provide both the name and the version of the Python implementation
|
||||
currently running. For example, on CPython 2.7.5 it will return
|
||||
@@ -84,18 +85,26 @@ def info():
|
||||
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,
|
||||
'system_ssl': {
|
||||
'version': '%x' % ssl.OPENSSL_VERSION_NUMBER,
|
||||
},
|
||||
'system_ssl': system_ssl_info,
|
||||
'using_pyopenssl': pyopenssl is not None,
|
||||
'pyOpenSSL': pyopenssl_info,
|
||||
'urllib3': urllib3_info,
|
||||
'chardet': chardet_info,
|
||||
'cryptography': cryptography_info,
|
||||
'idna': idna_info,
|
||||
'requests': {
|
||||
'version': requests_version,
|
||||
},
|
||||
|
||||
@@ -7,9 +7,7 @@ requests.models
|
||||
This module contains the primary objects that power Requests.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import codecs
|
||||
import sys
|
||||
|
||||
# Import encoding now, to avoid implicit import later.
|
||||
@@ -27,23 +25,21 @@ from io import UnsupportedOperation
|
||||
from .hooks import default_hooks
|
||||
from .structures import CaseInsensitiveDict
|
||||
|
||||
import requests
|
||||
from .auth import HTTPBasicAuth
|
||||
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
|
||||
from .exceptions import (
|
||||
HTTPError, MissingScheme, InvalidURL, ChunkedEncodingError,
|
||||
ContentDecodingError, ConnectionError, StreamConsumedError,
|
||||
InvalidHeader, InvalidBodyError)
|
||||
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
|
||||
ContentDecodingError, ConnectionError, StreamConsumedError)
|
||||
from ._internal_utils import to_native_string, unicode_is_ascii
|
||||
from .utils import (
|
||||
guess_filename, get_auth_from_url, requote_uri,
|
||||
stream_decode_response_unicode, to_key_val_list, parse_header_links,
|
||||
iter_slices, guess_json_utf, super_len, check_header_validity,
|
||||
is_stream)
|
||||
iter_slices, guess_json_utf, super_len, check_header_validity)
|
||||
from .compat import (
|
||||
Callable, Mapping,
|
||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
|
||||
is_py2, chardet, builtin_str, basestring)
|
||||
import json as complexjson
|
||||
from .compat import json as complexjson
|
||||
from .status_codes import codes
|
||||
|
||||
#: The set of HTTP status codes that indicate an automatically
|
||||
@@ -159,8 +155,12 @@ class RequestEncodingMixin(object):
|
||||
|
||||
if isinstance(fp, (str, bytes, bytearray)):
|
||||
fdata = fp
|
||||
else:
|
||||
elif hasattr(fp, 'read'):
|
||||
fdata = fp.read()
|
||||
elif fp is None:
|
||||
continue
|
||||
else:
|
||||
fdata = fp
|
||||
|
||||
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
|
||||
rf.make_multipart(content_type=ft)
|
||||
@@ -178,10 +178,10 @@ class RequestHooksMixin(object):
|
||||
if event not in self.hooks:
|
||||
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
|
||||
|
||||
if isinstance(hook, collections.Callable):
|
||||
if isinstance(hook, Callable):
|
||||
self.hooks[event].append(hook)
|
||||
elif hasattr(hook, '__iter__'):
|
||||
self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable))
|
||||
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
|
||||
|
||||
def deregister_hook(self, event, hook):
|
||||
"""Deregister a previously registered hook.
|
||||
@@ -335,9 +335,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
def prepare_method(self, method):
|
||||
"""Prepares the given HTTP method."""
|
||||
self.method = method
|
||||
if self.method is None:
|
||||
raise ValueError('Request method cannot be "None"')
|
||||
self.method = to_native_string(self.method.upper())
|
||||
if self.method is not None:
|
||||
self.method = to_native_string(self.method.upper())
|
||||
|
||||
@staticmethod
|
||||
def _get_idna_encoded_host(host):
|
||||
@@ -361,8 +360,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
url = unicode(url) if is_py2 else str(url)
|
||||
|
||||
# Ignore any leading and trailing whitespace characters.
|
||||
url = url.strip()
|
||||
# Remove leading whitespaces from url
|
||||
url = url.lstrip()
|
||||
|
||||
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
|
||||
# `data` etc to work around exceptions from `url_parse`, which
|
||||
@@ -378,10 +377,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
raise InvalidURL(*e.args)
|
||||
|
||||
if not scheme:
|
||||
error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?")
|
||||
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
|
||||
error = error.format(to_native_string(url, 'utf8'))
|
||||
|
||||
raise MissingScheme(error)
|
||||
raise MissingSchema(error)
|
||||
|
||||
if not host:
|
||||
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
||||
@@ -464,7 +463,17 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if not isinstance(body, bytes):
|
||||
body = body.encode('utf-8')
|
||||
|
||||
if is_stream(data):
|
||||
is_stream = all([
|
||||
hasattr(data, '__iter__'),
|
||||
not isinstance(data, (basestring, list, tuple, Mapping))
|
||||
])
|
||||
|
||||
try:
|
||||
length = super_len(data)
|
||||
except (TypeError, AttributeError, UnsupportedOperation):
|
||||
length = None
|
||||
|
||||
if is_stream:
|
||||
body = data
|
||||
|
||||
if getattr(body, 'tell', None) is not None:
|
||||
@@ -481,6 +490,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if files:
|
||||
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
|
||||
|
||||
if length:
|
||||
self.headers['Content-Length'] = builtin_str(length)
|
||||
else:
|
||||
self.headers['Transfer-Encoding'] = 'chunked'
|
||||
else:
|
||||
# Multi-part file uploads.
|
||||
if files:
|
||||
@@ -493,40 +506,27 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
|
||||
self.prepare_content_length(body)
|
||||
|
||||
# Add content-type if it wasn't explicitly provided.
|
||||
if content_type and ('content-type' not in self.headers):
|
||||
self.headers['Content-Type'] = content_type
|
||||
|
||||
self.prepare_content_length(body)
|
||||
self.body = body
|
||||
|
||||
def prepare_content_length(self, body):
|
||||
"""Prepares Content-Length header.
|
||||
|
||||
If the length of the body of the request can be computed, Content-Length
|
||||
is set using ``super_len``. If user has manually set either a
|
||||
Transfer-Encoding or Content-Length header when it should not be set
|
||||
(they should be mutually exclusive) an InvalidHeader
|
||||
error will be raised.
|
||||
"""
|
||||
"""Prepare Content-Length header based on request method and body"""
|
||||
if body is not None:
|
||||
length = super_len(body)
|
||||
|
||||
if length:
|
||||
# If length exists, set it. Otherwise, we fallback
|
||||
# to Transfer-Encoding: chunked.
|
||||
self.headers['Content-Length'] = builtin_str(length)
|
||||
elif is_stream(body):
|
||||
self.headers['Transfer-Encoding'] = 'chunked'
|
||||
else:
|
||||
raise InvalidBodyError('Non-null body must have length or be streamable.')
|
||||
elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None:
|
||||
# Set Content-Length to 0 for methods that can have a body
|
||||
# but don't provide one. (i.e. not GET or HEAD)
|
||||
self.headers['Content-Length'] = '0'
|
||||
|
||||
if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers:
|
||||
raise InvalidHeader('Conflicting Headers: Both Transfer-Encoding and '
|
||||
'Content-Length are set.')
|
||||
|
||||
def prepare_auth(self, auth, url=''):
|
||||
"""Prepares the given HTTP auth data."""
|
||||
|
||||
@@ -578,14 +578,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
for event in hooks:
|
||||
self.register_hook(event, hooks[event])
|
||||
|
||||
def send(self, session=None, **send_kwargs):
|
||||
"""Sends the PreparedRequest to the given Session.
|
||||
If none is provided, one is created for you."""
|
||||
session = requests.Session() if session is None else session
|
||||
|
||||
with session:
|
||||
return session.send(self, **send_kwargs)
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""The :class:`Response <Response>` object, which contains a
|
||||
@@ -598,8 +590,6 @@ class Response(object):
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super(Response, self).__init__()
|
||||
|
||||
self._content = False
|
||||
self._content_consumed = False
|
||||
self._next = None
|
||||
@@ -620,8 +610,7 @@ class Response(object):
|
||||
#: Final URL location of Response.
|
||||
self.url = None
|
||||
|
||||
#: Encoding to decode with when accessing r.text or
|
||||
#: r.iter_content(decode_unicode=True)
|
||||
#: Encoding to decode with when accessing r.text.
|
||||
self.encoding = None
|
||||
|
||||
#: A list of :class:`Response <Response>` objects from
|
||||
@@ -647,6 +636,12 @@ class Response(object):
|
||||
#: is a response.
|
||||
self.request = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
def __getstate__(self):
|
||||
# Consume everything; accessing the content attribute makes
|
||||
# sure the content has been fully read.
|
||||
@@ -669,17 +664,37 @@ class Response(object):
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
return self.ok
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
return self.ok
|
||||
|
||||
def __iter__(self):
|
||||
"""Allows you to use a response as an iterator."""
|
||||
return self.iter_content(128)
|
||||
|
||||
@property
|
||||
def ok(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
"""Returns True if :attr:`status_code` is less than 400, False if not.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
the status code is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
try:
|
||||
@@ -723,8 +738,8 @@ class Response(object):
|
||||
chunks are received. If stream=False, data is returned as
|
||||
a single chunk.
|
||||
|
||||
If using decode_unicode, the encoding must be set to a valid encoding
|
||||
enumeration before invoking iter_content.
|
||||
If decode_unicode is True, content will be decoded using the best
|
||||
available encoding based on the response.
|
||||
"""
|
||||
|
||||
def generate():
|
||||
@@ -734,10 +749,7 @@ class Response(object):
|
||||
for chunk in self.raw.stream(chunk_size, decode_content=True):
|
||||
yield chunk
|
||||
except ProtocolError as e:
|
||||
if self.headers.get('Transfer-Encoding') == 'chunked':
|
||||
raise ChunkedEncodingError(e)
|
||||
else:
|
||||
raise ConnectionError(e)
|
||||
raise ChunkedEncodingError(e)
|
||||
except DecodeError as e:
|
||||
raise ContentDecodingError(e)
|
||||
except ReadTimeoutError as e:
|
||||
@@ -764,16 +776,6 @@ class Response(object):
|
||||
chunks = reused_chunks if self._content_consumed else stream_chunks
|
||||
|
||||
if decode_unicode:
|
||||
if self.encoding is None:
|
||||
raise TypeError(
|
||||
'encoding must be set before consuming streaming '
|
||||
'responses'
|
||||
)
|
||||
|
||||
# check encoding value here, don't wait for the generator to be
|
||||
# consumed before raising an exception
|
||||
codecs.lookup(self.encoding)
|
||||
|
||||
chunks = stream_decode_response_unicode(chunks, self)
|
||||
|
||||
return chunks
|
||||
@@ -785,67 +787,23 @@ class Response(object):
|
||||
|
||||
.. note:: This method is not reentrant safe.
|
||||
"""
|
||||
carriage_return = u'\r' if decode_unicode else b'\r'
|
||||
line_feed = u'\n' if decode_unicode else b'\n'
|
||||
|
||||
pending = None
|
||||
last_chunk_ends_with_cr = False
|
||||
|
||||
for chunk in self.iter_content(chunk_size=chunk_size,
|
||||
decode_unicode=decode_unicode):
|
||||
# Skip any null responses: if there is pending data it is necessarily an
|
||||
# incomplete chunk, so if we don't have more data we don't want to bother
|
||||
# trying to get it. Unconsumed pending data will be yielded anyway in the
|
||||
# end of the loop if the stream ends.
|
||||
if not chunk:
|
||||
continue
|
||||
for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
|
||||
|
||||
# Consume any pending data
|
||||
if pending is not None:
|
||||
chunk = pending + chunk
|
||||
pending = None
|
||||
|
||||
# Either split on a line, or split on a specified delimiter
|
||||
if delimiter:
|
||||
lines = chunk.split(delimiter)
|
||||
else:
|
||||
# Python splitlines() supports the universal newline (PEP 278).
|
||||
# That means, '\r', '\n', and '\r\n' are all treated as end of
|
||||
# line. If the last chunk ends with '\r', and the current chunk
|
||||
# starts with '\n', they should be merged and treated as only
|
||||
# *one* new line separator '\r\n' by splitlines().
|
||||
# This rule only applies when splitlines() is used.
|
||||
|
||||
# The last chunk ends with '\r', so the '\n' at chunk[0]
|
||||
# is just the second half of a '\r\n' pair rather than a
|
||||
# new line break. Just skip it.
|
||||
skip_first_char = last_chunk_ends_with_cr and chunk.startswith(line_feed)
|
||||
last_chunk_ends_with_cr = chunk.endswith(carriage_return)
|
||||
if skip_first_char:
|
||||
chunk = chunk[1:]
|
||||
# it's possible that after stripping the '\n' then chunk becomes empty
|
||||
if not chunk:
|
||||
continue
|
||||
lines = chunk.splitlines()
|
||||
|
||||
# Calling `.split(delimiter)` will always end with whatever text
|
||||
# remains beyond the delimiter, or '' if the delimiter is the end
|
||||
# of the text. On the other hand, `.splitlines()` doesn't include
|
||||
# a '' if the text ends in a line delimiter.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# 'abc\ndef\n'.split('\n') ~> ['abc', 'def', '']
|
||||
# 'abc\ndef\n'.splitlines() ~> ['abc', 'def']
|
||||
#
|
||||
# So if we have a specified delimiter, we always pop the final
|
||||
# item and prepend it to the next chunk.
|
||||
#
|
||||
# If we're using `splitlines()`, we only do this if the chunk
|
||||
# ended midway through a line.
|
||||
incomplete_line = lines[-1] and lines[-1][-1] == chunk[-1]
|
||||
if delimiter or incomplete_line:
|
||||
if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
|
||||
pending = lines.pop()
|
||||
else:
|
||||
pending = None
|
||||
|
||||
for line in lines:
|
||||
yield line
|
||||
@@ -866,7 +824,7 @@ class Response(object):
|
||||
if self.status_code == 0 or self.raw is None:
|
||||
self._content = None
|
||||
else:
|
||||
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
|
||||
self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
|
||||
|
||||
self._content_consumed = True
|
||||
# don't need to release the connection; that's been handled by urllib3
|
||||
@@ -956,8 +914,7 @@ class Response(object):
|
||||
return l
|
||||
|
||||
def raise_for_status(self):
|
||||
"""Raises stored :class:`HTTPError`, if one occurred.
|
||||
Otherwise, returns the response object (self)."""
|
||||
"""Raises stored :class:`HTTPError`, if one occurred."""
|
||||
|
||||
http_error_msg = ''
|
||||
if isinstance(self.reason, bytes):
|
||||
@@ -981,8 +938,6 @@ class Response(object):
|
||||
if http_error_msg:
|
||||
raise HTTPError(http_error_msg, response=self)
|
||||
|
||||
return self
|
||||
|
||||
def close(self):
|
||||
"""Releases the connection back to the pool. Once this method has been
|
||||
called the underlying ``raw`` object must not be accessed again.
|
||||
|
||||
219
requests/sessions.py
Executable file → Normal file
219
requests/sessions.py
Executable file → Normal file
@@ -8,30 +8,27 @@ This module provides a Session object to manage and persist settings across
|
||||
requests (cookies, auth, proxies).
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
from collections import Mapping
|
||||
from datetime import timedelta
|
||||
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import cookielib, OrderedDict, urljoin, urlparse, is_py3, str
|
||||
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping
|
||||
from .cookies import (
|
||||
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar,
|
||||
merge_cookies, _copy_cookie_jar)
|
||||
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
|
||||
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, InvalidScheme, ChunkedEncodingError,
|
||||
ConnectionError, ContentDecodingError, InvalidHeader)
|
||||
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
|
||||
|
||||
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, is_valid_location, rewind_body
|
||||
get_auth_from_url, rewind_body
|
||||
)
|
||||
|
||||
from .status_codes import codes
|
||||
@@ -40,8 +37,8 @@ from .status_codes import codes
|
||||
from .models import REDIRECT_STATI
|
||||
|
||||
# Preferred clock, based on which one is more accurate on a given system.
|
||||
if platform.system() == 'Windows':
|
||||
try: # Python 3.3+
|
||||
if sys.platform == 'win32':
|
||||
try: # Python 3.4+
|
||||
preferred_clock = time.perf_counter
|
||||
except AttributeError: # Earlier than Python 3.
|
||||
preferred_clock = time.clock
|
||||
@@ -52,7 +49,7 @@ else:
|
||||
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||
"""Determines appropriate setting for a given request, taking into account
|
||||
the explicit setting on that request, and the setting in the session. If a
|
||||
setting is a dictionary, they will be merged together using `dict_class`.
|
||||
setting is a dictionary, they will be merged together using `dict_class`
|
||||
"""
|
||||
|
||||
if session_setting is None:
|
||||
@@ -97,14 +94,16 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
|
||||
|
||||
class SessionRedirectMixin(object):
|
||||
|
||||
def get_redirect_target(self, response):
|
||||
def get_redirect_target(self, resp):
|
||||
"""Receives a Response. Returns a redirect URI or ``None``"""
|
||||
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']
|
||||
# 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
|
||||
# on each subsequent redirect response (if any).
|
||||
# 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']
|
||||
# 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.
|
||||
@@ -116,56 +115,60 @@ class SessionRedirectMixin(object):
|
||||
return to_native_string(location, 'utf8')
|
||||
return None
|
||||
|
||||
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.
|
||||
"""
|
||||
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."""
|
||||
|
||||
history = [response] # keep track of history; seed it with the original response
|
||||
hist = [] # keep track of history
|
||||
|
||||
location_url = self.get_redirect_target(response)
|
||||
url = self.get_redirect_target(resp)
|
||||
previous_fragment = urlparse(req.url).fragment
|
||||
while url:
|
||||
prepared_request = req.copy()
|
||||
|
||||
while location_url:
|
||||
prepared_request = request.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:]
|
||||
|
||||
try:
|
||||
response.content # Consume socket so it can be released
|
||||
except (ChunkedEncodingError, ConnectionError, ContentDecodingError, RuntimeError):
|
||||
response.raw.read(decode_content=False)
|
||||
resp.content # Consume socket so it can be released
|
||||
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
|
||||
resp.raw.read(decode_content=False)
|
||||
|
||||
if len(response.history) >= self.max_redirects:
|
||||
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=response)
|
||||
if len(resp.history) >= self.max_redirects:
|
||||
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
|
||||
|
||||
# Release the connection back into the pool.
|
||||
response.close()
|
||||
resp.close()
|
||||
|
||||
# Handle redirection without scheme (see: RFC 1808 Section 4)
|
||||
if location_url.startswith('//'):
|
||||
parsed_rurl = urlparse(response.url)
|
||||
location_url = '%s:%s' % (to_native_string(parsed_rurl.scheme), location_url)
|
||||
if url.startswith('//'):
|
||||
parsed_rurl = urlparse(resp.url)
|
||||
url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
|
||||
|
||||
# The scheme should be lower case...
|
||||
parsed = urlparse(location_url)
|
||||
location_url = parsed.geturl()
|
||||
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
|
||||
parsed = urlparse(url)
|
||||
if parsed.fragment == '' and previous_fragment:
|
||||
parsed = parsed._replace(fragment=previous_fragment)
|
||||
elif parsed.fragment:
|
||||
previous_fragment = parsed.fragment
|
||||
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:
|
||||
location_url = urljoin(response.url, requote_uri(location_url))
|
||||
url = urljoin(resp.url, requote_uri(url))
|
||||
else:
|
||||
location_url = requote_uri(location_url)
|
||||
url = requote_uri(url)
|
||||
|
||||
prepared_request.url = to_native_string(location_url)
|
||||
prepared_request.url = to_native_string(url)
|
||||
|
||||
method_changed = self.rebuild_method(prepared_request, response)
|
||||
self.rebuild_method(prepared_request, resp)
|
||||
|
||||
# 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/1084
|
||||
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
|
||||
# https://github.com/requests/requests/issues/3490
|
||||
purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')
|
||||
for header in purged_headers:
|
||||
@@ -181,13 +184,13 @@ class SessionRedirectMixin(object):
|
||||
# Extract any cookies sent on the response to the cookiejar
|
||||
# in the new request. Because we've mutated our copied prepared
|
||||
# request, use the old one that we haven't yet touched.
|
||||
extract_cookies_to_jar(prepared_request._cookies, request, response.raw)
|
||||
extract_cookies_to_jar(prepared_request._cookies, req, resp.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, response)
|
||||
self.rebuild_auth(prepared_request, resp)
|
||||
|
||||
# A failed tell() sets `_body_position` to `object()`. This non-None
|
||||
# value ensures `rewindable` will be True, allowing us to raise an
|
||||
@@ -202,14 +205,14 @@ class SessionRedirectMixin(object):
|
||||
rewind_body(prepared_request)
|
||||
|
||||
# Override the original request.
|
||||
request = prepared_request
|
||||
req = prepared_request
|
||||
|
||||
if yield_requests:
|
||||
yield request
|
||||
yield req
|
||||
else:
|
||||
|
||||
response = self.send(
|
||||
request,
|
||||
resp = self.send(
|
||||
req,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
verify=verify,
|
||||
@@ -218,21 +221,16 @@ class SessionRedirectMixin(object):
|
||||
allow_redirects=False,
|
||||
**adapter_kwargs
|
||||
)
|
||||
# copy our history tracker into the response
|
||||
response.history = history[:]
|
||||
# append the new response to the history tracker for the next iteration
|
||||
history.append(response)
|
||||
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, response.raw)
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
|
||||
|
||||
# extract redirect url, if any, for the next loop
|
||||
location_url = self.get_redirect_target(response)
|
||||
yield response
|
||||
url = self.get_redirect_target(resp)
|
||||
yield resp
|
||||
|
||||
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
|
||||
@@ -255,11 +253,11 @@ class SessionRedirectMixin(object):
|
||||
return
|
||||
|
||||
def rebuild_proxies(self, prepared_request, proxies):
|
||||
"""This method re-evaluates the proxy configuration by
|
||||
considering the environment variables. If we are redirected to a
|
||||
URL covered by NO_PROXY, we strip the proxy configuration.
|
||||
Otherwise, we set missing proxy keys for this URL (in case they
|
||||
were stripped by a previous redirect).
|
||||
"""This method re-evaluates the proxy configuration by considering the
|
||||
environment variables. If we are redirected to a URL covered by
|
||||
NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
|
||||
proxy keys for this URL (in case they were stripped by a previous
|
||||
redirect).
|
||||
|
||||
This method also replaces the Proxy-Authorization header where
|
||||
necessary.
|
||||
@@ -298,26 +296,24 @@ class SessionRedirectMixin(object):
|
||||
def rebuild_method(self, prepared_request, response):
|
||||
"""When being redirected we may want to change the method of the request
|
||||
based on certain specs or browser behavior.
|
||||
|
||||
:rtype bool:
|
||||
:return: boolean expressing if the method changed during rebuild.
|
||||
"""
|
||||
method = original_method = prepared_request.method
|
||||
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'
|
||||
|
||||
# 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':
|
||||
# Do what the browsers do, despite standards...
|
||||
# First, turn 302s into GETs.
|
||||
if response.status_code == codes.found and method != 'HEAD':
|
||||
method = 'GET'
|
||||
|
||||
# Second, if a POST is responded to with a 301, turn it into a GET.
|
||||
# This bizarre behaviour is explained in Issue 1704.
|
||||
if response.status_code == codes.moved and method == 'POST':
|
||||
method = 'GET'
|
||||
|
||||
prepared_request.method = method
|
||||
return method != original_method
|
||||
|
||||
|
||||
class Session(SessionRedirectMixin):
|
||||
@@ -413,7 +409,7 @@ class Session(SessionRedirectMixin):
|
||||
:class:`Session`.
|
||||
|
||||
:param request: :class:`Request` instance to prepare with this
|
||||
Session's settings.
|
||||
session's settings.
|
||||
:rtype: requests.PreparedRequest
|
||||
"""
|
||||
cookies = request.cookies or {}
|
||||
@@ -423,8 +419,8 @@ class Session(SessionRedirectMixin):
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Merge with session cookies
|
||||
session_cookies = _copy_cookie_jar(self.cookies)
|
||||
merged_cookies = merge_cookies(session_cookies, cookies)
|
||||
merged_cookies = merge_cookies(
|
||||
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
|
||||
|
||||
# Set environment's basic authentication if not explicitly set.
|
||||
auth = request.auth
|
||||
@@ -450,7 +446,7 @@ class Session(SessionRedirectMixin):
|
||||
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.
|
||||
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
@@ -611,8 +607,7 @@ class Session(SessionRedirectMixin):
|
||||
if isinstance(request, Request):
|
||||
raise ValueError('You can only send PreparedRequests.')
|
||||
|
||||
# Set up variables needed for resolve_redirects and dispatching of
|
||||
# hooks
|
||||
# Set up variables needed for resolve_redirects and dispatching of hooks
|
||||
allow_redirects = kwargs.pop('allow_redirects', True)
|
||||
stream = kwargs.get('stream')
|
||||
hooks = request.hooks
|
||||
@@ -630,7 +625,7 @@ class Session(SessionRedirectMixin):
|
||||
elapsed = preferred_clock() - start
|
||||
r.elapsed = timedelta(seconds=elapsed)
|
||||
|
||||
# Response manipulation hooks.
|
||||
# Response manipulation hooks
|
||||
r = dispatch_hook('response', hooks, r, **kwargs)
|
||||
|
||||
# Persist cookies
|
||||
@@ -645,12 +640,16 @@ class Session(SessionRedirectMixin):
|
||||
# Redirect resolving generator.
|
||||
gen = self.resolve_redirects(r, request, **kwargs)
|
||||
|
||||
# Resolve redirects, if allowed.
|
||||
# Resolve redirects if allowed.
|
||||
history = [resp for resp in gen] if allow_redirects else []
|
||||
|
||||
# If there is a history, replace ``r`` with the last response
|
||||
# Shuffle things around if there's history.
|
||||
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:
|
||||
@@ -670,37 +669,25 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
# Merge all the kwargs except for proxies.
|
||||
stream = merge_setting(stream, self.stream)
|
||||
verify = merge_setting(verify, self.verify)
|
||||
cert = merge_setting(cert, self.cert)
|
||||
# Gather clues from the surrounding environment.
|
||||
# We do this after merging the Session values to make sure we don't
|
||||
# accidentally exclude them.
|
||||
if self.trust_env:
|
||||
# Set environment's proxies.
|
||||
no_proxy = proxies.get('no_proxy') if proxies is not None else None
|
||||
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
|
||||
for (k, v) in env_proxies.items():
|
||||
proxies.setdefault(k, v)
|
||||
|
||||
# Look for requests environment configuration and be compatible
|
||||
# with cURL.
|
||||
if verify is True or verify is None:
|
||||
verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
|
||||
os.environ.get('CURL_CA_BUNDLE') or
|
||||
verify)
|
||||
os.environ.get('CURL_CA_BUNDLE'))
|
||||
|
||||
# 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)
|
||||
# Merge all the kwargs.
|
||||
proxies = merge_setting(proxies, self.proxies)
|
||||
stream = merge_setting(stream, self.stream)
|
||||
verify = merge_setting(verify, self.verify)
|
||||
cert = merge_setting(cert, self.cert)
|
||||
|
||||
return {'verify': verify, 'proxies': proxies, 'stream': stream,
|
||||
'cert': cert}
|
||||
@@ -713,21 +700,21 @@ class Session(SessionRedirectMixin):
|
||||
"""
|
||||
for (prefix, adapter) in self.adapters.items():
|
||||
|
||||
if url.lower().startswith(prefix):
|
||||
if url.lower().startswith(prefix.lower()):
|
||||
return adapter
|
||||
|
||||
# Nothing matches :-/
|
||||
raise InvalidScheme("No connection adapters were found for '%s'" % url)
|
||||
raise InvalidSchema("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()
|
||||
|
||||
def mount(self, prefix, adapter):
|
||||
"""Registers a connection adapter to a prefix.
|
||||
|
||||
Adapters are sorted in descending order by key length.
|
||||
Adapters are sorted in descending order by prefix length.
|
||||
"""
|
||||
self.adapters[prefix] = adapter
|
||||
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The ``codes`` object defines a mapping from common names for HTTP statuses
|
||||
to their numerical codes, accessible either as attributes or as dictionary
|
||||
items.
|
||||
|
||||
>>> requests.codes['temporary_redirect']
|
||||
307
|
||||
>>> requests.codes.teapot
|
||||
418
|
||||
>>> requests.codes['\o/']
|
||||
200
|
||||
|
||||
Some codes have multiple names, and both upper- and lower-case versions of
|
||||
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
|
||||
``codes.okay`` all correspond to the HTTP status code 200.
|
||||
"""
|
||||
|
||||
from .structures import LookupDict
|
||||
|
||||
_codes = {
|
||||
@@ -84,8 +101,20 @@ _codes = {
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for code, titles in _codes.items():
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith(('\\', '/')):
|
||||
setattr(codes, title.upper(), code)
|
||||
def _init():
|
||||
for code, titles in _codes.items():
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith(('\\', '/')):
|
||||
setattr(codes, title.upper(), code)
|
||||
|
||||
def doc(code):
|
||||
names = ', '.join('``%s``' % n for n in _codes[code])
|
||||
return '* %d: %s' % (code, names)
|
||||
|
||||
global __doc__
|
||||
__doc__ = (__doc__ + '\n' +
|
||||
'\n'.join(doc(code) for code in sorted(_codes))
|
||||
if __doc__ is not None else None)
|
||||
|
||||
_init()
|
||||
|
||||
@@ -7,16 +7,14 @@ requests.structures
|
||||
Data structures that power Requests.
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from .compat import OrderedDict
|
||||
from .compat import OrderedDict, Mapping, MutableMapping
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
class CaseInsensitiveDict(MutableMapping):
|
||||
"""A case-insensitive ``dict``-like object.
|
||||
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
``MutableMapping`` as well as dict's ``copy``. Also
|
||||
provides ``lower_items``.
|
||||
|
||||
All keys are expected to be strings. The structure remembers the
|
||||
@@ -71,7 +69,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, collections.Mapping):
|
||||
if isinstance(other, Mapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
@@ -8,17 +8,17 @@ This module provides utility functions that are used within Requests
|
||||
that are also useful for external consumption.
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import collections
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
from .__version__ import __version__
|
||||
from . import certs
|
||||
@@ -27,8 +27,8 @@ 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_py2, is_py3,
|
||||
proxy_bypass_environment, getproxies_environment)
|
||||
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
|
||||
proxy_bypass_environment, getproxies_environment, Mapping)
|
||||
from .cookies import cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .exceptions import (
|
||||
@@ -39,19 +39,25 @@ NETRC_FILES = ('.netrc', '_netrc')
|
||||
DEFAULT_CA_BUNDLE_PATH = certs.where()
|
||||
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
if sys.platform == 'win32':
|
||||
# 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
|
||||
try:
|
||||
if is_py3:
|
||||
import winreg
|
||||
else:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
try:
|
||||
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
|
||||
proxyEnable = winreg.QueryValueEx(internetSettings,
|
||||
'ProxyEnable')[0]
|
||||
# ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
|
||||
proxyEnable = int(winreg.QueryValueEx(internetSettings,
|
||||
'ProxyEnable')[0])
|
||||
# ProxyOverride is almost always a string
|
||||
proxyOverride = winreg.QueryValueEx(internetSettings,
|
||||
'ProxyOverride')[0]
|
||||
except OSError:
|
||||
@@ -216,6 +222,38 @@ def guess_filename(obj):
|
||||
return os.path.basename(name)
|
||||
|
||||
|
||||
def extract_zipped_paths(path):
|
||||
"""Replace nonexistent paths that look like they refer to a member of a zip
|
||||
archive with the location of an extracted copy of the target, or else
|
||||
just return the provided path unchanged.
|
||||
"""
|
||||
if os.path.exists(path):
|
||||
# this is already a valid path, no need to do anything further
|
||||
return path
|
||||
|
||||
# find the first valid part of the provided path and treat that as a zip archive
|
||||
# assume the rest of the path is the name of a member in the archive
|
||||
archive, member = os.path.split(path)
|
||||
while archive and not os.path.exists(archive):
|
||||
archive, prefix = os.path.split(archive)
|
||||
member = '/'.join([prefix, member])
|
||||
|
||||
if not zipfile.is_zipfile(archive):
|
||||
return path
|
||||
|
||||
zip_file = zipfile.ZipFile(archive)
|
||||
if member not in zip_file.namelist():
|
||||
return path
|
||||
|
||||
# we have a valid zip archive and a valid member of that archive
|
||||
tmp = tempfile.gettempdir()
|
||||
extracted_path = os.path.join(tmp, *member.split('/'))
|
||||
if not os.path.exists(extracted_path):
|
||||
extracted_path = zip_file.extract(member, path=tmp)
|
||||
|
||||
return extracted_path
|
||||
|
||||
|
||||
def from_key_val_list(value):
|
||||
"""Take an object and test to see if it can be represented as a
|
||||
dictionary. Unless it can not be represented as such, return an
|
||||
@@ -262,7 +300,7 @@ def to_key_val_list(value):
|
||||
if isinstance(value, (str, bytes, bool, int)):
|
||||
raise ValueError('cannot encode objects that are not 2-tuples')
|
||||
|
||||
if isinstance(value, collections.Mapping):
|
||||
if isinstance(value, Mapping):
|
||||
value = value.items()
|
||||
|
||||
return list(value)
|
||||
@@ -407,6 +445,31 @@ def get_encodings_from_content(content):
|
||||
xml_re.findall(content))
|
||||
|
||||
|
||||
def _parse_content_type_header(header):
|
||||
"""Returns content type and parameters from given header
|
||||
|
||||
:param header: string
|
||||
:return: tuple containing content type and dictionary of
|
||||
parameters
|
||||
"""
|
||||
|
||||
tokens = header.split(';')
|
||||
content_type, params = tokens[0].strip(), tokens[1:]
|
||||
params_dict = {}
|
||||
items_to_strip = "\"' "
|
||||
|
||||
for param in params:
|
||||
param = param.strip()
|
||||
if param:
|
||||
key, value = param, True
|
||||
index_of_equals = param.find("=")
|
||||
if index_of_equals != -1:
|
||||
key = param[:index_of_equals].strip(items_to_strip)
|
||||
value = param[index_of_equals + 1:].strip(items_to_strip)
|
||||
params_dict[key] = value
|
||||
return content_type, params_dict
|
||||
|
||||
|
||||
def get_encoding_from_headers(headers):
|
||||
"""Returns encodings from given HTTP Header Dict.
|
||||
|
||||
@@ -419,7 +482,7 @@ def get_encoding_from_headers(headers):
|
||||
if not content_type:
|
||||
return None
|
||||
|
||||
content_type, params = cgi.parse_header(content_type)
|
||||
content_type, params = _parse_content_type_header(content_type)
|
||||
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
@@ -431,6 +494,11 @@ 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)
|
||||
@@ -498,26 +566,7 @@ def unquote_unreserved(uri):
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
# This convert function is used to optionally convert the output of `chr`.
|
||||
# In Python 3, `chr` returns a unicode string, while in Python 2 it returns
|
||||
# a bytestring. Here we deal with that by optionally converting.
|
||||
def convert(is_bytes, c):
|
||||
if is_py2 and not is_bytes:
|
||||
return c.decode('ascii')
|
||||
elif is_py3 and is_bytes:
|
||||
return c.encode('ascii')
|
||||
else:
|
||||
return c
|
||||
|
||||
# Handle both bytestrings and unicode strings.
|
||||
is_bytes = isinstance(uri, bytes)
|
||||
splitchar = u'%'
|
||||
base = u''
|
||||
if is_bytes:
|
||||
splitchar = splitchar.encode('ascii')
|
||||
base = base.encode('ascii')
|
||||
|
||||
parts = uri.split(splitchar)
|
||||
parts = uri.split('%')
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2 and h.isalnum():
|
||||
@@ -527,12 +576,12 @@ def unquote_unreserved(uri):
|
||||
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
|
||||
|
||||
if c in UNRESERVED_SET:
|
||||
parts[i] = convert(is_bytes, c) + parts[i][2:]
|
||||
parts[i] = c + parts[i][2:]
|
||||
else:
|
||||
parts[i] = splitchar + parts[i]
|
||||
parts[i] = '%' + parts[i]
|
||||
else:
|
||||
parts[i] = splitchar + parts[i]
|
||||
return base.join(parts)
|
||||
parts[i] = '%' + parts[i]
|
||||
return ''.join(parts)
|
||||
|
||||
|
||||
def requote_uri(uri):
|
||||
@@ -626,18 +675,18 @@ def set_environ(env_name, value):
|
||||
the environment variable 'env_name'.
|
||||
|
||||
If 'value' is None, do nothing"""
|
||||
if value is not None:
|
||||
value_changed = value is not None
|
||||
if value_changed:
|
||||
old_value = os.environ.get(env_name)
|
||||
os.environ[env_name] = value
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if value is None:
|
||||
return
|
||||
if old_value is None:
|
||||
del os.environ[env_name]
|
||||
else:
|
||||
os.environ[env_name] = old_value
|
||||
if value_changed:
|
||||
if old_value is None:
|
||||
del os.environ[env_name]
|
||||
else:
|
||||
os.environ[env_name] = old_value
|
||||
|
||||
|
||||
def should_bypass_proxies(url, no_proxy):
|
||||
@@ -646,6 +695,8 @@ def should_bypass_proxies(url, no_proxy):
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
# Prioritize lowercase environment variables over uppercase
|
||||
# to keep a consistent behaviour with other http projects (curl, wget).
|
||||
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
|
||||
@@ -653,28 +704,31 @@ def should_bypass_proxies(url, no_proxy):
|
||||
no_proxy_arg = no_proxy
|
||||
if no_proxy is None:
|
||||
no_proxy = get_proxy('no_proxy')
|
||||
netloc = urlparse(url).netloc
|
||||
parsed = urlparse(url)
|
||||
|
||||
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.
|
||||
# the end of the hostname, 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):
|
||||
if is_ipv4_address(parsed.hostname):
|
||||
for proxy_ip in no_proxy:
|
||||
if is_valid_cidr(proxy_ip):
|
||||
if address_in_network(ip, proxy_ip):
|
||||
if address_in_network(parsed.hostname, proxy_ip):
|
||||
return True
|
||||
elif ip == proxy_ip:
|
||||
elif parsed.hostname == 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:
|
||||
host_with_port = parsed.hostname
|
||||
if parsed.port:
|
||||
host_with_port += ':{0}'.format(parsed.port)
|
||||
|
||||
for host in no_proxy:
|
||||
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
|
||||
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
|
||||
# The URL does match something in no_proxy, so we don't want
|
||||
# to apply the proxies on this URL.
|
||||
return True
|
||||
@@ -687,7 +741,7 @@ def should_bypass_proxies(url, no_proxy):
|
||||
# legitimate problems.
|
||||
with set_environ('no_proxy', no_proxy_arg):
|
||||
try:
|
||||
bypass = proxy_bypass(netloc)
|
||||
bypass = proxy_bypass(parsed.hostname)
|
||||
except (TypeError, socket.gaierror):
|
||||
bypass = False
|
||||
|
||||
@@ -757,7 +811,7 @@ def default_headers():
|
||||
|
||||
|
||||
def parse_header_links(value):
|
||||
"""Return a dict of parsed link headers proxies.
|
||||
"""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"
|
||||
|
||||
@@ -768,6 +822,10 @@ def parse_header_links(value):
|
||||
|
||||
replace_chars = ' \'"'
|
||||
|
||||
value = value.strip(replace_chars)
|
||||
if not value:
|
||||
return links
|
||||
|
||||
for val in re.split(', *<', value):
|
||||
try:
|
||||
url, params = val.split(';', 1)
|
||||
@@ -789,19 +847,6 @@ def parse_header_links(value):
|
||||
return links
|
||||
|
||||
|
||||
def is_valid_location(response):
|
||||
"""Verify that multiple Location headers weren't
|
||||
returned from the last response.
|
||||
"""
|
||||
headers = getattr(response.raw, 'headers', None)
|
||||
if headers is not None:
|
||||
getlist = getattr(headers, 'getlist', None)
|
||||
if getlist is not None:
|
||||
return len(getlist('location')) <= 1
|
||||
# If response.raw isn't urllib3-like we can't reliably check this
|
||||
return True
|
||||
|
||||
|
||||
# Null bytes; no need to recreate these on each call to guess_json_utf
|
||||
_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3
|
||||
_null2 = _null * 2
|
||||
@@ -895,8 +940,8 @@ def check_header_validity(header):
|
||||
if not pat.match(value):
|
||||
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
|
||||
except TypeError:
|
||||
raise InvalidHeader("Header value %s must be of type str or bytes, "
|
||||
"not %s" % (value, type(value)))
|
||||
raise InvalidHeader("Value for header {%s: %s} must be of type str or "
|
||||
"bytes, not %s" % (name, value, type(value)))
|
||||
|
||||
|
||||
def urldefragauth(url):
|
||||
@@ -929,10 +974,3 @@ def rewind_body(prepared_request):
|
||||
"body for redirect.")
|
||||
else:
|
||||
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
|
||||
|
||||
|
||||
def is_stream(data):
|
||||
"""Given data, determines if it should be sent as a stream."""
|
||||
is_iterable = getattr(data, '__iter__', False)
|
||||
is_io_type = not isinstance(data, (basestring, list, tuple, collections.Mapping))
|
||||
return is_iterable and is_io_type
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
-e .[socks]
|
||||
pytest>=2.8.0
|
||||
codecov
|
||||
pytest-httpbin==0.0.7
|
||||
pytest-mock
|
||||
pytest-cov
|
||||
pytest-xdist
|
||||
alabaster
|
||||
readme_renderer
|
||||
Sphinx<=1.5.5
|
||||
PySocks
|
||||
setuptools>=18.5
|
||||
docutils
|
||||
flake8
|
||||
tox
|
||||
detox
|
||||
19
setup.py
19
setup.py
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# Learn more: https://github.com/kennethreitz/setup.py
|
||||
|
||||
import os
|
||||
import re
|
||||
@@ -39,12 +40,18 @@ if sys.argv[-1] == 'publish':
|
||||
os.system('twine upload dist/*')
|
||||
sys.exit()
|
||||
|
||||
# pyOpenSSL version 18.0.0 dropped support for Python 2.6
|
||||
if sys.version_info < (2, 7):
|
||||
PYOPENSSL_VERSION = 'pyOpenSSL >= 0.14, < 18.0.0'
|
||||
else:
|
||||
PYOPENSSL_VERSION = 'pyOpenSSL >= 0.14'
|
||||
|
||||
packages = ['requests']
|
||||
|
||||
requires = [
|
||||
'chardet>=3.0.2,<3.1.0',
|
||||
'idna>=2.5,<2.6',
|
||||
'urllib3>=1.21.1,<1.22',
|
||||
'idna>=2.5,<2.8',
|
||||
'urllib3>=1.21.1,<1.24',
|
||||
'certifi>=2017.4.17'
|
||||
|
||||
]
|
||||
@@ -64,6 +71,7 @@ setup(
|
||||
version=about['__version__'],
|
||||
description=about['__description__'],
|
||||
long_description=readme + '\n\n' + history,
|
||||
long_description_content_type='text/x-rst',
|
||||
author=about['__author__'],
|
||||
author_email=about['__author_email__'],
|
||||
url=about['__url__'],
|
||||
@@ -71,6 +79,7 @@ setup(
|
||||
package_data={'': ['LICENSE', 'NOTICE'], 'requests': ['*.pem']},
|
||||
package_dir={'requests': 'requests'},
|
||||
include_package_data=True,
|
||||
python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
install_requires=requires,
|
||||
license=about['__license__'],
|
||||
zip_safe=False,
|
||||
@@ -80,10 +89,9 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
@@ -93,9 +101,8 @@ setup(
|
||||
cmdclass={'test': PyTest},
|
||||
tests_require=test_requirements,
|
||||
extras_require={
|
||||
'security': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'],
|
||||
'security': [PYOPENSSL_VERSION, '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'],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
40
tests/test_help.py
Normal file
40
tests/test_help.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- encoding: utf-8
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from requests.help import info
|
||||
|
||||
|
||||
@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.
|
||||
"""
|
||||
assert info()['system_ssl'] == {'version': ''}
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
def test_idna_without_version_attribute(mocker):
|
||||
"""Older versions of IDNA don't provide a __version__ attribute, verify
|
||||
that if we have such a package, we don't blow up.
|
||||
"""
|
||||
mocker.patch('requests.help.idna', new=None)
|
||||
assert info()['idna'] == {'version': ''}
|
||||
|
||||
|
||||
def test_idna_with_version_attribute(mocker):
|
||||
"""Verify we're actually setting idna version when it should be available."""
|
||||
mocker.patch('requests.help.idna', new=VersionedPackage('2.6'))
|
||||
assert info()['idna'] == {'version': '2.6'}
|
||||
89
tests/test_lowlevel.py
Executable file → Normal file
89
tests/test_lowlevel.py
Executable file → Normal file
@@ -23,23 +23,6 @@ def test_chunked_upload():
|
||||
assert r.status_code == 200
|
||||
assert r.request.headers['Transfer-Encoding'] == 'chunked'
|
||||
|
||||
def test_incorrect_content_length():
|
||||
"""Test ConnectionError raised for incomplete responses"""
|
||||
close_server = threading.Event()
|
||||
server = Server.text_response_server(
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 50\r\n\r\n" +
|
||||
"Hello World."
|
||||
)
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/'.format(host, port)
|
||||
r = requests.Request('GET', url).prepare()
|
||||
s = requests.Session()
|
||||
with pytest.raises(requests.exceptions.ConnectionError) as e:
|
||||
resp = s.send(r)
|
||||
assert "12 bytes read, 38 more expected" in str(e)
|
||||
close_server.set() # release server block
|
||||
|
||||
|
||||
def test_digestauth_401_count_reset_on_redirect():
|
||||
"""Ensure we correctly reset num_401_calls after a successful digest auth,
|
||||
@@ -252,3 +235,75 @@ def test_redirect_rfc1808_to_non_ascii_location():
|
||||
assert r.url == u'{0}/{1}'.format(url, expected_path.decode('ascii'))
|
||||
|
||||
close_server.set()
|
||||
|
||||
def test_fragment_not_sent_with_request():
|
||||
"""Verify that the fragment portion of a URI isn't sent to the server."""
|
||||
def response_handler(sock):
|
||||
req = consume_socket_content(sock, timeout=0.5)
|
||||
sock.send(
|
||||
b'HTTP/1.1 200 OK\r\n'
|
||||
b'Content-Length: '+bytes(len(req))+b'\r\n'
|
||||
b'\r\n'+req
|
||||
)
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(response_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
|
||||
r = requests.get(url)
|
||||
raw_request = r.content
|
||||
|
||||
assert r.status_code == 200
|
||||
headers, body = raw_request.split(b'\r\n\r\n', 1)
|
||||
status_line, headers = headers.split(b'\r\n', 1)
|
||||
|
||||
assert status_line == b'GET /path/to/thing/ HTTP/1.1'
|
||||
for frag in (b'view', b'edit', b'token', b'hunter2'):
|
||||
assert frag not in headers
|
||||
assert frag not in body
|
||||
|
||||
close_server.set()
|
||||
|
||||
def test_fragment_update_on_redirect():
|
||||
"""Verify we only append previous fragment if one doesn't exist on new
|
||||
location. If a new fragment is encounterd in a Location header, it should
|
||||
be added to all subsequent requests.
|
||||
"""
|
||||
|
||||
def response_handler(sock):
|
||||
consume_socket_content(sock, timeout=0.5)
|
||||
sock.send(
|
||||
b'HTTP/1.1 302 FOUND\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'Location: /get#relevant-section\r\n\r\n'
|
||||
)
|
||||
consume_socket_content(sock, timeout=0.5)
|
||||
sock.send(
|
||||
b'HTTP/1.1 302 FOUND\r\n'
|
||||
b'Content-Length: 0\r\n'
|
||||
b'Location: /final-url/\r\n\r\n'
|
||||
)
|
||||
consume_socket_content(sock, timeout=0.5)
|
||||
sock.send(
|
||||
b'HTTP/1.1 200 OK\r\n\r\n'
|
||||
)
|
||||
|
||||
close_server = threading.Event()
|
||||
server = Server(response_handler, wait_to_close_event=close_server)
|
||||
|
||||
with server as (host, port):
|
||||
url = 'http://{0}:{1}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
|
||||
r = requests.get(url)
|
||||
raw_request = r.content
|
||||
|
||||
assert r.status_code == 200
|
||||
assert len(r.history) == 2
|
||||
assert r.history[0].request.url == url
|
||||
|
||||
# Verify we haven't overwritten the location with our previous fragment.
|
||||
assert r.history[1].request.url == 'http://{0}:{1}/get#relevant-section'.format(host, port)
|
||||
# Verify previous fragment is used and not the original.
|
||||
assert r.url == 'http://{0}:{1}/final-url/#relevant-section'.format(host, port)
|
||||
|
||||
close_server.set()
|
||||
|
||||
697
tests/test_requests.py
Executable file → Normal file
697
tests/test_requests.py
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Tests for Requests."""
|
||||
@@ -14,7 +13,6 @@ import warnings
|
||||
import io
|
||||
import requests
|
||||
import pytest
|
||||
import pytest_httpbin
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import HTTPDigestAuth, _basic_auth_str
|
||||
from requests.compat import (
|
||||
@@ -23,34 +21,20 @@ from requests.compat import (
|
||||
from requests.cookies import (
|
||||
cookiejar_from_dict, morsel_to_cookie)
|
||||
from requests.exceptions import (
|
||||
ConnectionError, ConnectTimeout, InvalidScheme, InvalidURL,
|
||||
MissingScheme, ReadTimeout, Timeout, RetryError, TooManyRedirects,
|
||||
ProxyError, InvalidHeader, UnrewindableBodyError, InvalidBodyError)
|
||||
ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL,
|
||||
MissingSchema, ReadTimeout, Timeout, RetryError, TooManyRedirects,
|
||||
ProxyError, InvalidHeader, UnrewindableBodyError, SSLError, InvalidProxyURL)
|
||||
from requests.models import PreparedRequest
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
from requests.sessions import SessionRedirectMixin
|
||||
from requests.models import urlencode
|
||||
from requests.hooks import default_hooks
|
||||
from requests.compat import MutableMapping
|
||||
|
||||
from .compat import StringIO, u
|
||||
from .utils import override_environ
|
||||
from urllib3.util import Timeout as Urllib3Timeout
|
||||
|
||||
class SendRecordingAdapter(HTTPAdapter):
|
||||
"""
|
||||
A basic subclass of the HTTPAdapter that records the arguments used to
|
||||
``send``.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SendRecordingAdapter, self).__init__(*args, **kwargs)
|
||||
|
||||
self.send_calls = []
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
self.send_calls.append((args, kwargs))
|
||||
return super(SendRecordingAdapter, self).send(*args, **kwargs)
|
||||
|
||||
|
||||
# Requests to this URL should always fail with a connection timeout (nothing
|
||||
# listening on that port)
|
||||
TARPIT = 'http://10.255.255.1'
|
||||
@@ -71,6 +55,8 @@ except AttributeError:
|
||||
|
||||
class TestRequests:
|
||||
|
||||
digest_auth_algo = ('MD5', 'SHA-256', 'SHA-512')
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
@@ -86,10 +72,10 @@ class TestRequests:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'exception, url', (
|
||||
(MissingScheme, 'hiwpefhipowhefopw'),
|
||||
(InvalidScheme, 'localhost:3128'),
|
||||
(InvalidScheme, 'localhost.localdomain:3128/'),
|
||||
(InvalidScheme, '10.122.1.1:3128/'),
|
||||
(MissingSchema, 'hiwpefhipowhefopw'),
|
||||
(InvalidSchema, 'localhost:3128'),
|
||||
(InvalidSchema, 'localhost.localdomain:3128/'),
|
||||
(InvalidSchema, '10.122.1.1:3128/'),
|
||||
(InvalidURL, 'http://'),
|
||||
))
|
||||
def test_invalid_url(self, exception, url):
|
||||
@@ -97,7 +83,7 @@ class TestRequests:
|
||||
requests.get(url)
|
||||
|
||||
def test_basic_building(self):
|
||||
req = requests.Request(method='GET')
|
||||
req = requests.Request()
|
||||
req.url = 'http://kennethreitz.org/'
|
||||
req.data = {'life': '42'}
|
||||
|
||||
@@ -228,106 +214,48 @@ class TestRequests:
|
||||
else:
|
||||
pytest.fail('Expected custom max number of redirects to be respected but was not')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'method, body, expected', (
|
||||
('GET', None, 'GET'),
|
||||
('HEAD', None, 'HEAD'),
|
||||
('POST', 'test', 'GET'),
|
||||
('PUT', 'put test', 'PUT'),
|
||||
('PATCH', 'patch test', 'PATCH'),
|
||||
('DELETE', '', 'DELETE')
|
||||
)
|
||||
)
|
||||
def test_http_301_for_redirectable_methods(self, httpbin, method, body, expected):
|
||||
"""Tests all methods except OPTIONS for expected redirect behaviour.
|
||||
|
||||
OPTIONS responses can behave differently depending on the server, so
|
||||
we don't have anything uniform to test except how httpbin responds
|
||||
to them. For that reason they aren't included here.
|
||||
"""
|
||||
params = {'url': '/%s' % expected.lower(), 'status_code': '301'}
|
||||
r = requests.request(method, httpbin('redirect-to'), data=body, params=params)
|
||||
|
||||
assert r.request.url == httpbin(expected.lower())
|
||||
assert r.request.method == expected
|
||||
def test_http_301_changes_post_to_get(self, httpbin):
|
||||
r = requests.post(httpbin('status', '301'))
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'GET'
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
if expected in ('GET', 'HEAD'):
|
||||
assert r.request.body is None
|
||||
else:
|
||||
assert r.json()['data'] == body
|
||||
def test_http_301_doesnt_change_head_to_get(self, httpbin):
|
||||
r = requests.head(httpbin('status', '301'), allow_redirects=True)
|
||||
print(r.content)
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'HEAD'
|
||||
assert r.history[0].status_code == 301
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'method, body, expected', (
|
||||
('GET', None, 'GET'),
|
||||
('HEAD', None, 'HEAD'),
|
||||
('POST', 'test', 'GET'),
|
||||
('PUT', 'put test', 'PUT'),
|
||||
('PATCH', 'patch test', 'PATCH'),
|
||||
('DELETE', '', 'DELETE')
|
||||
)
|
||||
)
|
||||
def test_http_302_for_redirectable_methods(self, httpbin, method, body, expected):
|
||||
"""Tests all methods except OPTIONS for expected redirect behaviour.
|
||||
|
||||
OPTIONS responses can behave differently depending on the server, so
|
||||
we don't have anything uniform to test except how httpbin responds
|
||||
to them. For that reason they aren't included here.
|
||||
"""
|
||||
params = {'url': '/%s' % expected.lower()}
|
||||
r = requests.request(method, httpbin('redirect-to'), data=body, params=params)
|
||||
|
||||
assert r.request.url == httpbin(expected.lower())
|
||||
assert r.request.method == expected
|
||||
def test_http_302_changes_post_to_get(self, httpbin):
|
||||
r = requests.post(httpbin('status', '302'))
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'GET'
|
||||
assert r.history[0].status_code == 302
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
if expected in ('GET', 'HEAD'):
|
||||
assert r.request.body is None
|
||||
else:
|
||||
assert r.json()['data'] == body
|
||||
def test_http_302_doesnt_change_head_to_get(self, httpbin):
|
||||
r = requests.head(httpbin('status', '302'), allow_redirects=True)
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'HEAD'
|
||||
assert r.history[0].status_code == 302
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'method, body, expected', (
|
||||
('GET', None, 'GET'),
|
||||
('HEAD', None, 'HEAD'),
|
||||
('POST', 'test', 'GET'),
|
||||
('PUT', 'put test', 'GET'),
|
||||
('PATCH', 'patch test', 'GET'),
|
||||
('DELETE', '', 'GET')
|
||||
)
|
||||
)
|
||||
def test_http_303_for_redirectable_methods(self, httpbin, method, body, expected):
|
||||
"""Tests all methods except OPTIONS for expected redirect behaviour.
|
||||
|
||||
OPTIONS responses can behave differently depending on the server, so
|
||||
we don't have anything uniform to test except how httpbin responds
|
||||
to them. For that reason they aren't included here.
|
||||
"""
|
||||
params = {'url': '/%s' % expected.lower(), 'status_code': '303'}
|
||||
r = requests.request(method, httpbin('redirect-to'), data=body, params=params)
|
||||
|
||||
assert r.request.url == httpbin(expected.lower())
|
||||
assert r.request.method == expected
|
||||
def test_http_303_changes_post_to_get(self, httpbin):
|
||||
r = requests.post(httpbin('status', '303'))
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'GET'
|
||||
assert r.history[0].status_code == 303
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
assert r.request.body is None
|
||||
|
||||
def test_multiple_location_headers(self, httpbin):
|
||||
headers = [('Location', 'http://example.com'),
|
||||
('Location', 'https://example.com/1')]
|
||||
params = '&'.join(['%s=%s' % (k, v) for k, v in headers])
|
||||
ses = requests.Session()
|
||||
req = requests.Request('GET', httpbin('response-headers?%s' % params))
|
||||
prep = ses.prepare_request(req)
|
||||
resp = ses.send(prep)
|
||||
# change response to redirect
|
||||
resp.status_code = 302
|
||||
with pytest.raises(InvalidHeader):
|
||||
# next triggers yield on generator
|
||||
next(ses.resolve_redirects(resp, prep))
|
||||
def test_http_303_doesnt_change_head_to_get(self, httpbin):
|
||||
r = requests.head(httpbin('status', '303'), allow_redirects=True)
|
||||
assert r.status_code == 200
|
||||
assert r.request.method == 'HEAD'
|
||||
assert r.history[0].status_code == 303
|
||||
assert r.history[0].is_redirect
|
||||
|
||||
def test_header_and_body_removal_on_redirect(self, httpbin):
|
||||
purged_headers = ('Content-Length', 'Content-Type')
|
||||
@@ -369,6 +297,14 @@ class TestRequests:
|
||||
for header in purged_headers:
|
||||
assert header not in next_resp.request.headers
|
||||
|
||||
def test_fragment_maintained_on_redirect(self, httpbin):
|
||||
fragment = "#view=edit&token=hunter2"
|
||||
r = requests.get(httpbin('redirect-to?url=get')+fragment)
|
||||
|
||||
assert len(r.history) > 0
|
||||
assert r.history[0].request.url == httpbin('redirect-to?url=get')+fragment
|
||||
assert r.url == httpbin('get')+fragment
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self, httpbin):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
@@ -484,35 +420,6 @@ class TestRequests:
|
||||
assert cookies['foo'] == 'bar'
|
||||
assert cookies['cookie'] == 'tasty'
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'jar', (
|
||||
requests.cookies.RequestsCookieJar(),
|
||||
cookielib.CookieJar()
|
||||
))
|
||||
def test_custom_cookie_policy_persistence(self, httpbin, jar):
|
||||
"""Verify a custom CookiePolicy is propagated on each session request."""
|
||||
|
||||
class TestCookiePolicy(cookielib.DefaultCookiePolicy):
|
||||
"""Policy to restrict all cookies from localhost (127.0.0.1)."""
|
||||
def __init__(self):
|
||||
cookielib.DefaultCookiePolicy.__init__(self, blocked_domains=['127.0.0.1'])
|
||||
|
||||
# Establish session with jar and set some cookies.
|
||||
s = requests.Session()
|
||||
s.cookies = jar
|
||||
s.get(httpbin('cookies/set?k1=v1&k2=v2'))
|
||||
assert len(s.cookies) == 2
|
||||
|
||||
# Set different policy.
|
||||
s.cookies.set_policy(TestCookiePolicy())
|
||||
assert isinstance(s.cookies._policy, TestCookiePolicy)
|
||||
|
||||
# No cookies were sent to our blocked domain and none were set.
|
||||
resp = s.get(httpbin('cookies/set?k3=v3'))
|
||||
assert 'Cookie' not in resp.request.headers
|
||||
assert len(s.cookies) == 2
|
||||
assert 'k3' not in s.cookies
|
||||
|
||||
def test_requests_in_history_are_not_overridden(self, httpbin):
|
||||
resp = requests.get(httpbin('redirect/3'))
|
||||
urls = [r.url for r in resp.history]
|
||||
@@ -590,6 +497,8 @@ class TestRequests:
|
||||
'username, password', (
|
||||
('user', 'pass'),
|
||||
(u'имя'.encode('utf-8'), u'пароль'.encode('utf-8')),
|
||||
(42, 42),
|
||||
(None, None),
|
||||
))
|
||||
def test_set_basicauth(self, httpbin, username, password):
|
||||
auth = (username, password)
|
||||
@@ -600,18 +509,6 @@ class TestRequests:
|
||||
|
||||
assert p.headers['Authorization'] == _basic_auth_str(username, password)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'username, password', (
|
||||
('user', 1234),
|
||||
(None, 'test'),
|
||||
))
|
||||
def test_non_str_basicauth(self, username, password):
|
||||
"""Ensure we only allow string or bytes values for basicauth"""
|
||||
with pytest.raises(TypeError) as e:
|
||||
requests.auth._basic_auth_str(username, password)
|
||||
|
||||
assert 'must be of type str or bytes' in str(e)
|
||||
|
||||
def test_basicauth_encodes_byte_strings(self):
|
||||
"""Ensure b'test' formats as the byte string "test" rather
|
||||
than the unicode string "b'test'" in Python 3.
|
||||
@@ -640,6 +537,19 @@ class TestRequests:
|
||||
with pytest.raises(ProxyError):
|
||||
requests.get('http://localhost:1', proxies={'http': 'non-resolvable-address'})
|
||||
|
||||
def test_proxy_error_on_bad_url(self, httpbin, httpbin_secure):
|
||||
with pytest.raises(InvalidProxyURL):
|
||||
requests.get(httpbin_secure(), proxies={'https': 'http:/badproxyurl:3128'})
|
||||
|
||||
with pytest.raises(InvalidProxyURL):
|
||||
requests.get(httpbin(), proxies={'http': 'http://:8080'})
|
||||
|
||||
with pytest.raises(InvalidProxyURL):
|
||||
requests.get(httpbin_secure(), proxies={'https': 'https://'})
|
||||
|
||||
with pytest.raises(InvalidProxyURL):
|
||||
requests.get(httpbin(), proxies={'http': 'http:///example.com:8080'})
|
||||
|
||||
def test_basicauth_with_netrc(self, httpbin):
|
||||
auth = ('user', 'pass')
|
||||
wrong_auth = ('wronguser', 'wrongpass')
|
||||
@@ -675,70 +585,79 @@ class TestRequests:
|
||||
|
||||
def test_DIGEST_HTTP_200_OK_GET(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
for authtype in self.digest_auth_algo:
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype, 'never')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 200
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 200
|
||||
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 401
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 401
|
||||
print(r.headers['WWW-Authenticate'])
|
||||
|
||||
s = requests.session()
|
||||
s.auth = HTTPDigestAuth('user', 'pass')
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
s = requests.session()
|
||||
s.auth = HTTPDigestAuth('user', 'pass')
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_DIGEST_AUTH_RETURNS_COOKIE(self, httpbin):
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
r = requests.get(url)
|
||||
assert r.cookies['fake'] == 'fake_value'
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 200
|
||||
for authtype in self.digest_auth_algo:
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype)
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
r = requests.get(url)
|
||||
assert r.cookies['fake'] == 'fake_value'
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self, httpbin):
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
s = requests.Session()
|
||||
s.get(url, auth=auth)
|
||||
assert s.cookies['fake'] == 'fake_value'
|
||||
|
||||
for authtype in self.digest_auth_algo:
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype)
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
s = requests.Session()
|
||||
s.get(url, auth=auth)
|
||||
assert s.cookies['fake'] == 'fake_value'
|
||||
|
||||
def test_DIGEST_STREAM(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
for authtype in self.digest_auth_algo:
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype)
|
||||
|
||||
r = requests.get(url, auth=auth, stream=True)
|
||||
assert r.raw.read() != b''
|
||||
r = requests.get(url, auth=auth, stream=True)
|
||||
assert r.raw.read() != b''
|
||||
|
||||
r = requests.get(url, auth=auth, stream=False)
|
||||
assert r.raw.read() == b''
|
||||
r = requests.get(url, auth=auth, stream=False)
|
||||
assert r.raw.read() == b''
|
||||
|
||||
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'wrongpass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
for authtype in self.digest_auth_algo:
|
||||
auth = HTTPDigestAuth('user', 'wrongpass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype)
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 401
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 401
|
||||
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 401
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 401
|
||||
|
||||
s = requests.session()
|
||||
s.auth = auth
|
||||
r = s.get(url)
|
||||
assert r.status_code == 401
|
||||
s = requests.session()
|
||||
s.auth = auth
|
||||
r = s.get(url)
|
||||
assert r.status_code == 401
|
||||
|
||||
def test_DIGESTAUTH_QUOTES_QOP_VALUE(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
for authtype in self.digest_auth_algo:
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype)
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
assert '"auth"' in r.request.headers['Authorization']
|
||||
r = requests.get(url, auth=auth)
|
||||
assert '"auth"' in r.request.headers['Authorization']
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self, httpbin):
|
||||
|
||||
@@ -748,7 +667,7 @@ class TestRequests:
|
||||
post1 = requests.post(url, data={'some': 'data'})
|
||||
assert post1.status_code == 200
|
||||
|
||||
with open('requirements.txt') as f:
|
||||
with open('Pipfile') as f:
|
||||
post2 = requests.post(url, files={'some': f})
|
||||
assert post2.status_code == 200
|
||||
|
||||
@@ -758,6 +677,14 @@ class TestRequests:
|
||||
with pytest.raises(ValueError):
|
||||
requests.post(url, files=['bad file data'])
|
||||
|
||||
def test_invalid_files_input(self, httpbin):
|
||||
|
||||
url = httpbin('post')
|
||||
post = requests.post(url,
|
||||
files={"random-file-1": None, "random-file-2": 1})
|
||||
assert b'name="random-file-1"' not in post.request.body
|
||||
assert b'name="random-file-2"' in post.request.body
|
||||
|
||||
def test_POSTBIN_SEEKED_OBJECT_WITH_NO_ITER(self, httpbin):
|
||||
|
||||
class TestStream(object):
|
||||
@@ -808,7 +735,7 @@ class TestRequests:
|
||||
post1 = requests.post(url, data={'some': 'data'})
|
||||
assert post1.status_code == 200
|
||||
|
||||
with open('requirements.txt') as f:
|
||||
with open('Pipfile') as f:
|
||||
post2 = requests.post(url, data={'some': 'data'}, files={'some': f})
|
||||
assert post2.status_code == 200
|
||||
|
||||
@@ -819,7 +746,7 @@ class TestRequests:
|
||||
requests.post(url, files=['bad file data'])
|
||||
|
||||
def test_post_with_custom_mapping(self, httpbin):
|
||||
class CustomMapping(collections.MutableMapping):
|
||||
class CustomMapping(MutableMapping):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.data = dict(*args, **kwargs)
|
||||
|
||||
@@ -845,7 +772,7 @@ class TestRequests:
|
||||
|
||||
def test_conflicting_post_params(self, httpbin):
|
||||
url = httpbin('post')
|
||||
with open('requirements.txt') as f:
|
||||
with open('Pipfile') as f:
|
||||
pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})")
|
||||
pytest.raises(ValueError, "requests.post(url, data=u('[{\"some\": \"data\"}]'), files={'some': f})")
|
||||
|
||||
@@ -861,10 +788,6 @@ class TestRequests:
|
||||
r = requests.get(httpbin('status', '500'))
|
||||
assert not r.ok
|
||||
|
||||
def test_raise_for_status_returns_self(self, httpbin):
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
assert r.raise_for_status() is r
|
||||
|
||||
def test_decompress_gzip(self, httpbin):
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
@@ -930,6 +853,15 @@ class TestRequests:
|
||||
item.category.__name__ for item in warning_records)
|
||||
assert warnings_category == warnings_expected
|
||||
|
||||
def test_certificate_failure(self, httpbin_secure):
|
||||
"""
|
||||
When underlying SSL problems occur, an SSLError is raised.
|
||||
"""
|
||||
with pytest.raises(SSLError):
|
||||
# Our local httpbin does not have a trusted CA, so this call will
|
||||
# fail if we use our default trust bundle.
|
||||
requests.get(httpbin_secure('status', '200'))
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self, httpbin):
|
||||
|
||||
r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz']))
|
||||
@@ -943,8 +875,8 @@ class TestRequests:
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
assert r.status_code == 200
|
||||
|
||||
@pytest.mark.parametrize('data',
|
||||
(
|
||||
@pytest.mark.parametrize(
|
||||
'data', (
|
||||
{'stuff': u('ëlïxr')},
|
||||
{'stuff': u('ëlïxr').encode('utf-8')},
|
||||
{'stuff': 'elixr'},
|
||||
@@ -1240,6 +1172,14 @@ class TestRequests:
|
||||
with pytest.raises(requests.cookies.CookieConflictError):
|
||||
jar.get(key)
|
||||
|
||||
def test_cookie_policy_copy(self):
|
||||
class MyCookiePolicy(cookielib.DefaultCookiePolicy):
|
||||
pass
|
||||
|
||||
jar = requests.cookies.RequestsCookieJar()
|
||||
jar.set_policy(MyCookiePolicy())
|
||||
assert isinstance(jar.copy().get_policy(), MyCookiePolicy)
|
||||
|
||||
def test_time_elapsed_blank(self, httpbin):
|
||||
r = requests.get(httpbin('get'))
|
||||
td = r.elapsed
|
||||
@@ -1278,24 +1218,9 @@ class TestRequests:
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
r.encoding = 'ascii'
|
||||
|
||||
chunks = r.iter_content(decode_unicode=True)
|
||||
assert all(isinstance(chunk, str) for chunk in chunks)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'encoding, exception', (
|
||||
(None, TypeError),
|
||||
('invalid encoding', LookupError),
|
||||
))
|
||||
def test_decode_unicode_encoding(self, encoding, exception):
|
||||
# raise an exception if encoding isn't set
|
||||
r = requests.Response()
|
||||
r.raw = io.BytesIO(b'the content')
|
||||
r.encoding = encoding
|
||||
|
||||
with pytest.raises(exception):
|
||||
chunks = r.iter_content(decode_unicode=True)
|
||||
|
||||
def test_response_reason_unicode(self):
|
||||
# check for unicode HTTP status
|
||||
r = requests.Response()
|
||||
@@ -1348,109 +1273,6 @@ class TestRequests:
|
||||
assert r.request.url == pr.request.url
|
||||
assert r.request.headers == pr.request.headers
|
||||
|
||||
|
||||
def test_response_lines(self):
|
||||
"""
|
||||
iter_lines should be able to handle data dribbling in which delimiters
|
||||
might not be lined up ideally.
|
||||
"""
|
||||
mock_chunks = [
|
||||
b'This \r\n',
|
||||
b'',
|
||||
b'is\r',
|
||||
b'\n',
|
||||
b'a',
|
||||
b' ',
|
||||
b'',
|
||||
b'',
|
||||
b'test.',
|
||||
b'\r',
|
||||
b'\n',
|
||||
b'end.',
|
||||
]
|
||||
mock_data = b''.join(mock_chunks)
|
||||
unicode_mock_data = mock_data.decode('utf-8')
|
||||
|
||||
def mock_iter_content(*args, **kwargs):
|
||||
if kwargs.get("decode_unicode"):
|
||||
return (e.decode('utf-8') for e in mock_chunks)
|
||||
return (e for e in mock_chunks)
|
||||
|
||||
r = requests.Response()
|
||||
r._content_consumed = True
|
||||
r.iter_content = mock_iter_content
|
||||
|
||||
# decode_unicode=None, output raw bytes
|
||||
assert list(r.iter_lines(delimiter=b'\r\n')) == mock_data.split(b'\r\n')
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
assert list(r.iter_lines(decode_unicode=True, delimiter=u'\r\n')) == unicode_mock_data.split(u'\r\n')
|
||||
|
||||
# When delimiter is None, we should yield the same result as splitlines()
|
||||
# which supports the universal newline.
|
||||
# '\r', '\n', and '\r\n' are all treated as one line break.
|
||||
|
||||
# decode_unicode=None, output raw bytes
|
||||
result = list(r.iter_lines())
|
||||
assert result == mock_data.splitlines()
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
result = list(r.iter_lines(decode_unicode=True))
|
||||
assert result == unicode_mock_data.splitlines()
|
||||
|
||||
# If we change all the line breaks to `\r`, we should be okay.
|
||||
# decode_unicode=None, output raw bytes
|
||||
mock_chunks = [chunk.replace(b'\n', b'\r') for chunk in mock_chunks]
|
||||
mock_data = b''.join(mock_chunks)
|
||||
assert list(r.iter_lines()) == mock_data.splitlines()
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
unicode_mock_data = mock_data.decode('utf-8')
|
||||
assert list(r.iter_lines(decode_unicode=True)) == unicode_mock_data.splitlines()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'content, expected_no_delimiter, expected_delimiter', (
|
||||
([b''], [], []),
|
||||
([b'line\n'], [u'line'], [u'line\n']),
|
||||
([b'line', b'\n'], [u'line'], [u'line\n']),
|
||||
([b'line\r\n'], [u'line'], [u'line', u'']),
|
||||
# Empty chunk in the end of stream, same behavior as the previous
|
||||
([b'line\r\n', b''], [u'line'], [u'line', u'']),
|
||||
([b'line', b'\r\n'], [u'line'], [u'line', u'']),
|
||||
([b'a\r', b'\nb\r'], [u'a', u'b'], [u'a', u'b\r']),
|
||||
([b'a\r', b'\n', b'\nb'], [u'a', u'', u'b'], [u'a', u'\nb']),
|
||||
([b'a\n', b'\nb'], [u'a', u'', u'b'], [u'a\n\nb']),
|
||||
([b'a\r\n', b'\rb\n'], [u'a', u'', u'b'], [u'a', u'\rb\n']),
|
||||
([b'a\nb', b'c'], [u'a', u'bc'], [u'a\nbc']),
|
||||
([b'a\n', b'\rb', b'\r\nc'], [u'a', u'', u'b', u'c'], [u'a\n\rb', u'c']),
|
||||
([b'a\r\nb', b'', b'c'], [u'a', u'bc'], [u'a', u'bc']) # Empty chunk with pending data
|
||||
))
|
||||
def test_response_lines_parametrized(self, content, expected_no_delimiter, expected_delimiter):
|
||||
"""
|
||||
Test a lot of potential chunk splits to ensure consistency of
|
||||
iter_lines(delimiter=x), as well as the legacy behavior of
|
||||
iter_lines() without delimiter
|
||||
https://github.com/kennethreitz/requests/pull/2431#issuecomment-72333964
|
||||
"""
|
||||
mock_chunks = content
|
||||
def mock_iter_content(*args, **kwargs):
|
||||
if kwargs.get("decode_unicode"):
|
||||
return (e.decode('utf-8') for e in mock_chunks)
|
||||
return (e for e in mock_chunks)
|
||||
|
||||
r = requests.Response()
|
||||
r._content_consumed = True
|
||||
r.iter_content = mock_iter_content
|
||||
|
||||
# decode_unicode=True, output unicode strings
|
||||
assert list(r.iter_lines(decode_unicode=True)) == expected_no_delimiter
|
||||
assert list(r.iter_lines(decode_unicode=True, delimiter='\r\n')) == expected_delimiter
|
||||
|
||||
# decode_unicode=None, output raw bytes
|
||||
assert list(r.iter_lines()) == [line.encode('utf-8') for line in expected_no_delimiter]
|
||||
assert list(r.iter_lines(delimiter=b'\r\n')) == [line.encode('utf-8') for line in expected_delimiter]
|
||||
|
||||
def test_prepared_request_is_pickleable(self, httpbin):
|
||||
p = requests.Request('GET', httpbin('get')).prepare()
|
||||
|
||||
@@ -1578,6 +1400,44 @@ class TestRequests:
|
||||
assert 'http://' in s2.adapters
|
||||
assert 'https://' in s2.adapters
|
||||
|
||||
def test_session_get_adapter_prefix_matching(self, httpbin):
|
||||
prefix = 'https://example.com'
|
||||
more_specific_prefix = prefix + '/some/path'
|
||||
|
||||
url_matching_only_prefix = prefix + '/another/path'
|
||||
url_matching_more_specific_prefix = more_specific_prefix + '/longer/path'
|
||||
url_not_matching_prefix = 'https://another.example.com/'
|
||||
|
||||
s = requests.Session()
|
||||
prefix_adapter = HTTPAdapter()
|
||||
more_specific_prefix_adapter = HTTPAdapter()
|
||||
s.mount(prefix, prefix_adapter)
|
||||
s.mount(more_specific_prefix, more_specific_prefix_adapter)
|
||||
|
||||
assert s.get_adapter(url_matching_only_prefix) is prefix_adapter
|
||||
assert s.get_adapter(url_matching_more_specific_prefix) is more_specific_prefix_adapter
|
||||
assert s.get_adapter(url_not_matching_prefix) not in (prefix_adapter, more_specific_prefix_adapter)
|
||||
|
||||
def test_session_get_adapter_prefix_matching_mixed_case(self, httpbin):
|
||||
mixed_case_prefix = 'hTtPs://eXamPle.CoM/MixEd_CAse_PREfix'
|
||||
url_matching_prefix = mixed_case_prefix + '/full_url'
|
||||
|
||||
s = requests.Session()
|
||||
my_adapter = HTTPAdapter()
|
||||
s.mount(mixed_case_prefix, my_adapter)
|
||||
|
||||
assert s.get_adapter(url_matching_prefix) is my_adapter
|
||||
|
||||
def test_session_get_adapter_prefix_matching_is_case_insensitive(self, httpbin):
|
||||
mixed_case_prefix = 'hTtPs://eXamPle.CoM/MixEd_CAse_PREfix'
|
||||
url_matching_prefix_with_different_case = 'HtTpS://exaMPLe.cOm/MiXeD_caSE_preFIX/another_url'
|
||||
|
||||
s = requests.Session()
|
||||
my_adapter = HTTPAdapter()
|
||||
s.mount(mixed_case_prefix, my_adapter)
|
||||
|
||||
assert s.get_adapter(url_matching_prefix_with_different_case) is my_adapter
|
||||
|
||||
def test_header_remove_is_case_insensitive(self, httpbin):
|
||||
# From issue #1321
|
||||
s = requests.Session()
|
||||
@@ -1628,14 +1488,17 @@ class TestRequests:
|
||||
headers_list = {'baz': ['foo', 'bar']}
|
||||
|
||||
# Test for int
|
||||
with pytest.raises(InvalidHeader):
|
||||
with pytest.raises(InvalidHeader) as excinfo:
|
||||
r = requests.get(httpbin('get'), headers=headers_int)
|
||||
assert 'foo' in str(excinfo.value)
|
||||
# Test for dict
|
||||
with pytest.raises(InvalidHeader):
|
||||
with pytest.raises(InvalidHeader) as excinfo:
|
||||
r = requests.get(httpbin('get'), headers=headers_dict)
|
||||
assert 'bar' in str(excinfo.value)
|
||||
# Test for list
|
||||
with pytest.raises(InvalidHeader):
|
||||
with pytest.raises(InvalidHeader) as excinfo:
|
||||
r = requests.get(httpbin('get'), headers=headers_list)
|
||||
assert 'baz' in str(excinfo.value)
|
||||
|
||||
def test_header_no_return_chars(self, httpbin):
|
||||
"""Ensure that a header containing return character sequences raise an
|
||||
@@ -1723,10 +1586,9 @@ class TestRequests:
|
||||
|
||||
def test_manual_redirect_with_partial_body_read(self, httpbin):
|
||||
s = requests.Session()
|
||||
req = requests.Request('GET', httpbin('redirect/2')).prepare()
|
||||
r1 = s.send(req, allow_redirects=False, stream=True)
|
||||
r1 = s.get(httpbin('redirect/2'), allow_redirects=False, stream=True)
|
||||
assert r1.is_redirect
|
||||
rg = s.resolve_redirects(r1, req, stream=True)
|
||||
rg = s.resolve_redirects(r1, r1.request, stream=True)
|
||||
|
||||
# read only the first eight bytes of the response body,
|
||||
# then follow the redirect
|
||||
@@ -1896,15 +1758,20 @@ class TestRequests:
|
||||
prep = r.prepare()
|
||||
assert 'stuff=elixr' == prep.body
|
||||
|
||||
@pytest.mark.parametrize('decode_unicode', (True, False))
|
||||
def test_response_iter_lines(self, httpbin, decode_unicode):
|
||||
def test_response_iter_lines(self, httpbin):
|
||||
r = requests.get(httpbin('stream/4'), stream=True)
|
||||
assert r.status_code == 200
|
||||
r.encoding = 'utf-8'
|
||||
it = r.iter_lines(decode_unicode=decode_unicode)
|
||||
|
||||
it = r.iter_lines()
|
||||
next(it)
|
||||
assert len(list(it)) == 3
|
||||
|
||||
def test_response_context_manager(self, httpbin):
|
||||
with requests.get(httpbin('stream/4'), stream=True) as response:
|
||||
assert isinstance(response, requests.Response)
|
||||
|
||||
assert response.raw.closed
|
||||
|
||||
def test_unconsumed_session_response_closes_connection(self, httpbin):
|
||||
s = requests.session()
|
||||
|
||||
@@ -1923,59 +1790,6 @@ class TestRequests:
|
||||
next(r.iter_lines())
|
||||
assert len(list(r.iter_lines())) == 3
|
||||
|
||||
def test_environment_comes_after_session(self, httpbin):
|
||||
"""The Session arguments should come before environment arguments."""
|
||||
# We get proxies from the environment and verify from the argument.
|
||||
s = requests.Session()
|
||||
a = SendRecordingAdapter()
|
||||
s.mount('http://', a)
|
||||
|
||||
# Both of these arguments are safe fallbacks that we can easily
|
||||
# detect, but which will allow the request to succeed.
|
||||
s.verify = False
|
||||
s.proxies = {'http': None}
|
||||
|
||||
old_proxy = os.environ.get('HTTP_PROXY')
|
||||
old_bundle = os.environ.get('REQUESTS_CA_BUNDLE')
|
||||
|
||||
try:
|
||||
os.environ['HTTP_PROXY'] = '10.10.10.10:3128'
|
||||
os.environ['REQUESTS_CA_BUNDLE'] = '/path/to/nowhere'
|
||||
|
||||
s.get(httpbin('get'), timeout=5)
|
||||
finally:
|
||||
if old_proxy is not None:
|
||||
os.environ['HTTP_PROXY'] = old_proxy
|
||||
else:
|
||||
del os.environ['HTTP_PROXY']
|
||||
|
||||
if old_bundle is not None:
|
||||
os.environ['REQUESTS_CA_BUNDLE'] = old_bundle
|
||||
else:
|
||||
del os.environ['REQUESTS_CA_BUNDLE']
|
||||
|
||||
call = a.send_calls[0]
|
||||
assert call[1]['verify'] == False
|
||||
|
||||
proxies = call[1]['proxies']
|
||||
with pytest.raises(KeyError):
|
||||
proxies['http']
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def test_merge_environment_settings_verify(self, monkeypatch):
|
||||
"""Assert CA environment settings are merged as expected when missing"""
|
||||
session = requests.Session()
|
||||
monkeypatch.delenv('CURL_CA_BUNDLE', raising=False)
|
||||
monkeypatch.delenv('REQUESTS_CA_BUNDLE', raising=False)
|
||||
|
||||
assert session.trust_env is True
|
||||
assert session.verify is True
|
||||
assert 'REQUESTS_CA_BUNDLE' not in os.environ
|
||||
assert 'CURL_CA_BUNDLE' not in os.environ
|
||||
merged_settings = session.merge_environment_settings(
|
||||
'http://example.com', {}, False, True, None)
|
||||
assert merged_settings['verify'] is True
|
||||
|
||||
def test_session_close_proxy_clear(self, mocker):
|
||||
proxies = {
|
||||
'one': mocker.Mock(),
|
||||
@@ -2018,33 +1832,6 @@ class TestRequests:
|
||||
resp.close()
|
||||
assert resp.raw.closed
|
||||
|
||||
def test_updating_ca_cert(self, httpbin_secure):
|
||||
"""Assert that requests use the latest configured CA certificates."""
|
||||
session = requests.session()
|
||||
session.verify = pytest_httpbin.certs.where()
|
||||
session.get(httpbin_secure('/'))
|
||||
session.verify = True
|
||||
with pytest.raises(requests.exceptions.SSLError) as e:
|
||||
session.get(httpbin_secure('/'))
|
||||
assert 'certificate verify failed' in str(e)
|
||||
|
||||
def test_updating_client_cert(self, httpbin_secure):
|
||||
"""Assert that requests use the latest configured client certificates."""
|
||||
ca_file = pytest_httpbin.certs.where()
|
||||
cert_dir = os.path.dirname(ca_file)
|
||||
# All we need is a valid certificate and key to make a request. httpbin_secure
|
||||
# won't check the signature or subject name, so it's okay that these happen to
|
||||
# be the server's certificate and key.
|
||||
cert = os.path.join(cert_dir, 'cert.pem')
|
||||
key = os.path.join(cert_dir, 'key.pem')
|
||||
session = requests.session()
|
||||
session.verify = ca_file
|
||||
resp = session.get(httpbin_secure('/'))
|
||||
resp_with_cert = session.get(httpbin_secure('/'), cert=(cert, key))
|
||||
assert resp_with_cert.raw._pool.cert_file == cert
|
||||
assert resp_with_cert.raw._pool.key_file == key
|
||||
assert resp.raw._pool is not resp_with_cert.raw._pool
|
||||
|
||||
def test_empty_stream_with_auth_does_not_set_content_length_header(self, httpbin):
|
||||
"""Ensure that a byte stream with size 0 will not set both a Content-Length
|
||||
and Transfer-Encoding header.
|
||||
@@ -2080,61 +1867,6 @@ class TestRequests:
|
||||
assert 'Transfer-Encoding' in prepared_request.headers
|
||||
assert 'Content-Length' not in prepared_request.headers
|
||||
|
||||
def test_chunked_upload_with_manually_set_content_length_header_raises_error(self, httpbin):
|
||||
"""Ensure that if a user manually sets a content length header, when
|
||||
the data is chunked, that an InvalidHeader error is raised.
|
||||
"""
|
||||
data = (i for i in [b'a', b'b', b'c'])
|
||||
url = httpbin('post')
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.post(url, data=data, headers={'Content-Length': 'foo'})
|
||||
|
||||
def test_content_length_with_manually_set_transfer_encoding_raises_error(self, httpbin):
|
||||
"""Ensure that if a user manually sets a Transfer-Encoding header when
|
||||
data is not chunked that an InvalidHeader error is raised.
|
||||
"""
|
||||
data = 'test data'
|
||||
url = httpbin('post')
|
||||
with pytest.raises(InvalidHeader):
|
||||
r = requests.post(url, data=data, headers={'Transfer-Encoding': 'chunked'})
|
||||
|
||||
def test_null_body_does_not_raise_error(self, httpbin):
|
||||
url = httpbin('post')
|
||||
try:
|
||||
requests.post(url, data=None)
|
||||
except InvalidHeader:
|
||||
pytest.fail('InvalidHeader error raised unexpectedly.')
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'body, expected', (
|
||||
(None, ('Content-Length', '0')),
|
||||
('test_data', ('Content-Length', '9')),
|
||||
(io.BytesIO(b'test_data'), ('Content-Length', '9')),
|
||||
(StringIO.StringIO(''), ('Transfer-Encoding', 'chunked'))
|
||||
))
|
||||
def test_prepare_content_length(self, httpbin, body, expected):
|
||||
"""Test prepare_content_length creates expected header."""
|
||||
prep = requests.PreparedRequest()
|
||||
prep.headers = {}
|
||||
prep.method = 'POST'
|
||||
|
||||
# Ensure Content-Length is set appropriately.
|
||||
key, value = expected
|
||||
prep.prepare_content_length(body)
|
||||
assert prep.headers[key] == value
|
||||
|
||||
def test_prepare_content_length_with_bad_body(self, httpbin):
|
||||
"""Test prepare_content_length raises exception with unsendable body."""
|
||||
# Initialize minimum required PreparedRequest.
|
||||
prep = requests.PreparedRequest()
|
||||
prep.headers = {}
|
||||
prep.method = 'POST'
|
||||
|
||||
with pytest.raises(InvalidBodyError) as e:
|
||||
# Send object that isn't iterable and has no accessible content.
|
||||
prep.prepare_content_length(object())
|
||||
assert "Non-null body must have length or be streamable." in str(e)
|
||||
|
||||
def test_custom_redirect_mixin(self, httpbin):
|
||||
"""Tests a custom mixin to overwrite ``get_redirect_target``.
|
||||
|
||||
@@ -2320,18 +2052,6 @@ class TestCaseInsensitiveDict:
|
||||
cid['changed'] = True
|
||||
assert cid != cid_copy
|
||||
|
||||
def test_url_surrounding_whitespace(self, httpbin):
|
||||
"""Test case with URLs surrounded by whitespace characters."""
|
||||
get_url = httpbin('get')
|
||||
# All surrounding whitespaces are supposed to be ignored:
|
||||
assert requests.get(get_url + ' ').status_code == 200
|
||||
assert requests.get(' ' + get_url).status_code == 200
|
||||
assert requests.get(get_url + ' \t ').status_code == 200
|
||||
assert requests.get(' \t' + get_url).status_code == 200
|
||||
assert requests.get(get_url + '\n').status_code == 200
|
||||
# The whitespaces can't be in the middle of the URL though:
|
||||
assert requests.get(get_url + ' abc').status_code == 404
|
||||
|
||||
|
||||
class TestMorselToCookieExpires:
|
||||
"""Tests for morsel_to_cookie when morsel contains expires."""
|
||||
@@ -2474,7 +2194,6 @@ class RedirectSession(SessionRedirectMixin):
|
||||
self.max_redirects = 30
|
||||
self.cookies = {}
|
||||
self.trust_env = False
|
||||
self.location = '/'
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
self.calls.append(SendCall(args, kwargs))
|
||||
@@ -2489,7 +2208,7 @@ class RedirectSession(SessionRedirectMixin):
|
||||
except IndexError:
|
||||
r.status_code = 200
|
||||
|
||||
r.headers = CaseInsensitiveDict({'Location': self.location})
|
||||
r.headers = CaseInsensitiveDict({'Location': '/'})
|
||||
r.raw = self._build_raw()
|
||||
r.request = request
|
||||
return r
|
||||
@@ -2604,16 +2323,6 @@ def test_prepared_copy(kwargs):
|
||||
assert getattr(p, attr) == getattr(copy, attr)
|
||||
|
||||
|
||||
def test_prepare_requires_a_request_method():
|
||||
req = requests.Request()
|
||||
with pytest.raises(ValueError):
|
||||
req.prepare()
|
||||
|
||||
prepped = PreparedRequest()
|
||||
with pytest.raises(ValueError):
|
||||
prepped.prepare()
|
||||
|
||||
|
||||
def test_urllib3_retries(httpbin):
|
||||
from urllib3.util import Retry
|
||||
s = requests.Session()
|
||||
@@ -2759,7 +2468,7 @@ class TestPreparingURLs(object):
|
||||
)
|
||||
def test_parameters_for_nonstandard_schemes(self, input, params, expected):
|
||||
"""
|
||||
Setting paramters for nonstandard schemes is allowed if those schemes
|
||||
Setting parameters for nonstandard schemes is allowed if those schemes
|
||||
begin with "http", and is forbidden otherwise.
|
||||
"""
|
||||
r = requests.Request('GET', url=input, params=params)
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import copy
|
||||
import filecmp
|
||||
from io import BytesIO
|
||||
import zipfile
|
||||
from collections import deque
|
||||
|
||||
import pytest
|
||||
from requests import compat
|
||||
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,
|
||||
address_in_network, dotted_netmask, extract_zipped_paths,
|
||||
get_auth_from_url, _parse_content_type_header, 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,
|
||||
@@ -17,7 +21,7 @@ from requests.utils import (
|
||||
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)
|
||||
urldefragauth, add_dict_to_cookiejar, set_environ)
|
||||
from requests._internal_utils import unicode_is_ascii
|
||||
|
||||
from .compat import StringIO, cStringIO
|
||||
@@ -255,6 +259,32 @@ class TestGuessFilename:
|
||||
assert isinstance(result, expected_type)
|
||||
|
||||
|
||||
class TestExtractZippedPaths:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'path', (
|
||||
'/',
|
||||
__file__,
|
||||
pytest.__file__,
|
||||
'/etc/invalid/location',
|
||||
))
|
||||
def test_unzipped_paths_unchanged(self, path):
|
||||
assert path == extract_zipped_paths(path)
|
||||
|
||||
def test_zipped_paths_extracted(self, tmpdir):
|
||||
zipped_py = tmpdir.join('test.zip')
|
||||
with zipfile.ZipFile(zipped_py.strpath, 'w') as f:
|
||||
f.write(__file__)
|
||||
|
||||
_, name = os.path.splitdrive(__file__)
|
||||
zipped_path = os.path.join(zipped_py.strpath, name.lstrip(r'\/'))
|
||||
extracted_path = extract_zipped_paths(zipped_path)
|
||||
|
||||
assert extracted_path != zipped_path
|
||||
assert os.path.exists(extracted_path)
|
||||
assert filecmp.cmp(extracted_path, __file__)
|
||||
|
||||
|
||||
class TestContentEncodingDetection:
|
||||
|
||||
def test_none(self):
|
||||
@@ -440,6 +470,45 @@ def test_parse_dict_header(value, expected):
|
||||
assert parse_dict_header(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
(
|
||||
'application/xml',
|
||||
('application/xml', {})
|
||||
),
|
||||
(
|
||||
'application/json ; charset=utf-8',
|
||||
('application/json', {'charset': 'utf-8'})
|
||||
),
|
||||
(
|
||||
'text/plain',
|
||||
('text/plain', {})
|
||||
),
|
||||
(
|
||||
'multipart/form-data; boundary = something ; boundary2=\'something_else\' ; no_equals ',
|
||||
('multipart/form-data', {'boundary': 'something', 'boundary2': 'something_else', 'no_equals': True})
|
||||
),
|
||||
(
|
||||
'multipart/form-data; boundary = something ; boundary2="something_else" ; no_equals ',
|
||||
('multipart/form-data', {'boundary': 'something', 'boundary2': 'something_else', 'no_equals': True})
|
||||
),
|
||||
(
|
||||
'multipart/form-data; boundary = something ; \'boundary2=something_else\' ; no_equals ',
|
||||
('multipart/form-data', {'boundary': 'something', 'boundary2': 'something_else', 'no_equals': True})
|
||||
),
|
||||
(
|
||||
'multipart/form-data; boundary = something ; "boundary2=something_else" ; no_equals ',
|
||||
('multipart/form-data', {'boundary': 'something', 'boundary2': 'something_else', 'no_equals': True})
|
||||
),
|
||||
(
|
||||
'application/json ; ; ',
|
||||
('application/json', {})
|
||||
)
|
||||
))
|
||||
def test__parse_content_type_header(value, expected):
|
||||
assert _parse_content_type_header(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected', (
|
||||
(
|
||||
@@ -497,6 +566,10 @@ def test_iter_slices(value, length):
|
||||
{'url': 'http://.../back.jpeg'}
|
||||
]
|
||||
),
|
||||
(
|
||||
'',
|
||||
[]
|
||||
),
|
||||
))
|
||||
def test_parse_header_links(value, expected):
|
||||
assert parse_header_links(value) == expected
|
||||
@@ -541,6 +614,7 @@ def test_urldefragauth(url, expected):
|
||||
('http://172.16.1.1/', True),
|
||||
('http://172.16.1.1:5000/', True),
|
||||
('http://localhost.localdomain:5000/v1.0/', True),
|
||||
('http://google.com:6000/', True),
|
||||
('http://172.16.1.12/', False),
|
||||
('http://172.16.1.12:5000/', False),
|
||||
('http://google.com:5000/v1.0/', False),
|
||||
@@ -549,11 +623,31 @@ 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, google.com:6000')
|
||||
monkeypatch.setenv('NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1, google.com:6000')
|
||||
assert should_bypass_proxies(url, no_proxy=None) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'url, expected', (
|
||||
('http://172.16.1.1/', '172.16.1.1'),
|
||||
('http://172.16.1.1:5000/', '172.16.1.1'),
|
||||
('http://user:pass@172.16.1.1', '172.16.1.1'),
|
||||
('http://user:pass@172.16.1.1:5000', '172.16.1.1'),
|
||||
('http://hostname/', 'hostname'),
|
||||
('http://hostname:5000/', 'hostname'),
|
||||
('http://user:pass@hostname', 'hostname'),
|
||||
('http://user:pass@hostname:5000', 'hostname'),
|
||||
))
|
||||
def test_should_bypass_proxies_pass_only_hostname(url, expected, mocker):
|
||||
"""The proxy_bypass function should be called with a hostname or IP without
|
||||
a port number or auth credentials.
|
||||
"""
|
||||
proxy_bypass = mocker.patch('requests.utils.proxy_bypass')
|
||||
should_bypass_proxies(url, no_proxy=None)
|
||||
proxy_bypass.assert_called_once_with(expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'cookiejar', (
|
||||
compat.cookielib.CookieJar(),
|
||||
@@ -633,6 +727,7 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
|
||||
pass
|
||||
|
||||
ie_settings = RegHandle()
|
||||
proxyEnableValues = deque([1, "1"])
|
||||
|
||||
def OpenKey(key, subkey):
|
||||
return ie_settings
|
||||
@@ -640,7 +735,9 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
|
||||
def QueryValueEx(key, value_name):
|
||||
if key is ie_settings:
|
||||
if value_name == 'ProxyEnable':
|
||||
return [1]
|
||||
# this could be a string (REG_SZ) or a 32-bit number (REG_DWORD)
|
||||
proxyEnableValues.rotate()
|
||||
return [proxyEnableValues[0]]
|
||||
elif value_name == 'ProxyOverride':
|
||||
return [override]
|
||||
|
||||
@@ -651,4 +748,30 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
|
||||
monkeypatch.setenv('NO_PROXY', '')
|
||||
monkeypatch.setattr(winreg, 'OpenKey', OpenKey)
|
||||
monkeypatch.setattr(winreg, 'QueryValueEx', QueryValueEx)
|
||||
assert should_bypass_proxies(url, no_proxy=None) == expected
|
||||
assert should_bypass_proxies(url, None) == expected
|
||||
|
||||
|
||||
@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),
|
||||
))
|
||||
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
|
||||
|
||||
|
||||
def test_set_environ_raises_exception():
|
||||
"""Tests set_environ will raise exceptions in context when the
|
||||
value parameter is None."""
|
||||
with pytest.raises(Exception) as exception:
|
||||
with set_environ('test1', None):
|
||||
raise Exception('Expected exception')
|
||||
|
||||
assert 'Expected exception' in str(exception.value)
|
||||
|
||||
Reference in New Issue
Block a user