Compare commits
473 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f7a3a74aa | ||
|
|
046a0f215d | ||
|
|
424fe263d8 | ||
|
|
2f744b5010 | ||
|
|
3c0f0b9ab5 | ||
|
|
3d33f30436 | ||
|
|
9aca57d4bf | ||
|
|
9a20afd6b5 | ||
|
|
2d91365cba | ||
|
|
3ad66349ea | ||
|
|
e48fea7fb3 | ||
|
|
5c19d3e07b | ||
|
|
40ce36671c | ||
|
|
dfa97addcc | ||
|
|
23d9b077d8 | ||
|
|
6cfe6e775f | ||
|
|
27f5a15364 | ||
|
|
c1f51fd100 | ||
|
|
68db4132e3 | ||
|
|
4c82dbab6f | ||
|
|
6f6b2fd1d5 | ||
|
|
6506dc8f66 | ||
|
|
085a8768a2 | ||
|
|
e5d6630888 | ||
|
|
6c47a0c5e9 | ||
|
|
6597b53834 | ||
|
|
2128321b85 | ||
|
|
de2a5f4332 | ||
|
|
6d0017ee6a | ||
|
|
5397ef71b3 | ||
|
|
0a1aaca024 | ||
|
|
4e89ba7077 | ||
|
|
f835843f87 | ||
|
|
cf9da5b52e | ||
|
|
d849d6e907 | ||
|
|
89178ebcdc | ||
|
|
a25a641fb4 | ||
|
|
35170e180e | ||
|
|
7d5d067e9c | ||
|
|
c1c164ba80 | ||
|
|
02576ae7e7 | ||
|
|
1ced0040bd | ||
|
|
e67cd15bc8 | ||
|
|
f7e0494130 | ||
|
|
43b90d7f72 | ||
|
|
7c91e27d01 | ||
|
|
15cf19431a | ||
|
|
be564e500c | ||
|
|
70d7b134c8 | ||
|
|
a8fdba15b1 | ||
|
|
3bc189b0af | ||
|
|
9b63f9cd37 | ||
|
|
f52287d999 | ||
|
|
a56db9ecc4 | ||
|
|
e5053cd322 | ||
|
|
de53523bef | ||
|
|
0352ec0bd3 | ||
|
|
4dffe5efaa | ||
|
|
8f460faa31 | ||
|
|
7dd94941c8 | ||
|
|
d290a9b4c6 | ||
|
|
f1fd11e54f | ||
|
|
1dff6c9153 | ||
|
|
edc68a0ac8 | ||
|
|
13ea7394cc | ||
|
|
b675377dc2 | ||
|
|
58b20d671c | ||
|
|
8f591682e6 | ||
|
|
6c2d09dc2f | ||
|
|
d8f28f4bca | ||
|
|
7c55446b7e | ||
|
|
18a6b60110 | ||
|
|
14a555ac71 | ||
|
|
2df3c7c75d | ||
|
|
b071c49556 | ||
|
|
978a89d5a3 | ||
|
|
931fd0eece | ||
|
|
3948a9562d | ||
|
|
2da8a604d3 | ||
|
|
f7be4e5f28 | ||
|
|
23051979f4 | ||
|
|
e499b354c4 | ||
|
|
ba7d09354a | ||
|
|
96e9490100 | ||
|
|
397f10bac7 | ||
|
|
8dcd9f12de | ||
|
|
09bd3232dd | ||
|
|
4e44c2ab64 | ||
|
|
d996a95cf7 | ||
|
|
f6afa71281 | ||
|
|
2a635ba87d | ||
|
|
8963e1567b | ||
|
|
f7a43e2664 | ||
|
|
139418475c | ||
|
|
a89d5c003a | ||
|
|
086e19d8e1 | ||
|
|
443d07e0d3 | ||
|
|
3c6f89b8f4 | ||
|
|
63b3b9be55 | ||
|
|
610fbd062f | ||
|
|
bf38d5bf52 | ||
|
|
aead8c347c | ||
|
|
79675a0638 | ||
|
|
21bf8cd9bf | ||
|
|
76a0d3fcb9 | ||
|
|
b8df8911c3 | ||
|
|
46ff1a9a54 | ||
|
|
649aeaa489 | ||
|
|
9d69c7845d | ||
|
|
c5a41269d9 | ||
|
|
7c74050d66 | ||
|
|
2cee3bb57a | ||
|
|
17a9e6da9e | ||
|
|
e9c94e0f06 | ||
|
|
ebc38f7825 | ||
|
|
96e8e8b92f | ||
|
|
1f48a4c9ea | ||
|
|
7a0cd16c1a | ||
|
|
37037607b5 | ||
|
|
56ecdebcc5 | ||
|
|
14ee84b5fd | ||
|
|
f4eaefa63e | ||
|
|
704a922fc2 | ||
|
|
719c76cd9d | ||
|
|
7c958ee28e | ||
|
|
bf9e66364f | ||
|
|
00d8c24fe1 | ||
|
|
4c6540898c | ||
|
|
f3d4480a33 | ||
|
|
fb496e4e86 | ||
|
|
c80121df91 | ||
|
|
500f15da1f | ||
|
|
2c8721540a | ||
|
|
d7a3aa6188 | ||
|
|
bf436ea0a4 | ||
|
|
a2e828d4d7 | ||
|
|
22075f02d0 | ||
|
|
618514231b | ||
|
|
56844d1287 | ||
|
|
e57925223f | ||
|
|
66567e037b | ||
|
|
767cc9d2dc | ||
|
|
8b03cd96cb | ||
|
|
48fe34447d | ||
|
|
cef2589436 | ||
|
|
9d88810d52 | ||
|
|
5d6d1bc284 | ||
|
|
4947cd01ae | ||
|
|
c7c26fb8eb | ||
|
|
7197afa887 | ||
|
|
3cf4ec41b9 | ||
|
|
2411b1f56a | ||
|
|
53ea23128e | ||
|
|
fdc76f12e5 | ||
|
|
f40bd0f707 | ||
|
|
98a59e4894 | ||
|
|
b1de7e7287 | ||
|
|
5a9097dc4b | ||
|
|
b6127ca9ca | ||
|
|
408d75d47a | ||
|
|
1bb91653cc | ||
|
|
313143959e | ||
|
|
8f3ddbeefb | ||
|
|
75c1776bde | ||
|
|
5d7392f140 | ||
|
|
276202f51e | ||
|
|
9b067db19e | ||
|
|
e8a4784c8f | ||
|
|
9648301e1e | ||
|
|
6648defcdd | ||
|
|
873f834fc9 | ||
|
|
31817b66c6 | ||
|
|
b4c1feac43 | ||
|
|
b8a297baab | ||
|
|
d0aecb271f | ||
|
|
d7026bfb2a | ||
|
|
2b5cdc56f3 | ||
|
|
77d7e53c12 | ||
|
|
1e640c8456 | ||
|
|
5ec7576f23 | ||
|
|
0be38a0c37 | ||
|
|
beaa7e4180 | ||
|
|
2b26489f74 | ||
|
|
3b325f1bc5 | ||
|
|
47e71d1774 | ||
|
|
147a305da8 | ||
|
|
2440b6f089 | ||
|
|
e9a1e35f89 | ||
|
|
e4d59bedfd | ||
|
|
884cb7a7fb | ||
|
|
a560e09427 | ||
|
|
f5dacf8446 | ||
|
|
e3bdec5934 | ||
|
|
9bbab338fd | ||
|
|
2e62df456a | ||
|
|
8b5e457b75 | ||
|
|
fd31453aa2 | ||
|
|
a92a2a4263 | ||
|
|
7ba130a0cb | ||
|
|
dfdf371a0f | ||
|
|
f608547bef | ||
|
|
1da62a65d9 | ||
|
|
8d0889b91b | ||
|
|
3c850b3339 | ||
|
|
d249c0a9c6 | ||
|
|
d866d8a44d | ||
|
|
ab1f493c8b | ||
|
|
dd0f164f8e | ||
|
|
059e105f8f | ||
|
|
395a3e8885 | ||
|
|
d3bd3630ee | ||
|
|
639f8347a3 | ||
|
|
553907aa6b | ||
|
|
fb6dade63d | ||
|
|
cda95d3cde | ||
|
|
c3c4c96a95 | ||
|
|
86aff09103 | ||
|
|
4505f71829 | ||
|
|
c6c8d649af | ||
|
|
868625532d | ||
|
|
74609c5c8f | ||
|
|
ab84f9be57 | ||
|
|
3475399a47 | ||
|
|
a6937534ce | ||
|
|
ba7975fd16 | ||
|
|
b7bd297340 | ||
|
|
ee7389da98 | ||
|
|
6acca21bae | ||
|
|
6e9ebc06d1 | ||
|
|
ff71b25efc | ||
|
|
c8004e3dd7 | ||
|
|
e859204554 | ||
|
|
5d722bd0f5 | ||
|
|
1f85ce8670 | ||
|
|
51938de6b7 | ||
|
|
535c51f548 | ||
|
|
09023f5894 | ||
|
|
9dba203952 | ||
|
|
f74fc486be | ||
|
|
773e730d80 | ||
|
|
93216afbaf | ||
|
|
b7fc3a1250 | ||
|
|
ca66267d2c | ||
|
|
73b9b6906d | ||
|
|
3880cf1255 | ||
|
|
ae7c28eb8e | ||
|
|
3e3fc7683c | ||
|
|
3d339ed7e6 | ||
|
|
cfb9e66b04 | ||
|
|
5fcd843eb2 | ||
|
|
1f0e5775f2 | ||
|
|
4cfc5bcd96 | ||
|
|
b906c04a6f | ||
|
|
35045e851a | ||
|
|
16eeee1436 | ||
|
|
576e18a796 | ||
|
|
d0e64d32ba | ||
|
|
a0d9e0bc57 | ||
|
|
5a799dd0f5 | ||
|
|
8cf5e67041 | ||
|
|
a0632d8df0 | ||
|
|
5478dd6271 | ||
|
|
599f834351 | ||
|
|
f02d0f9e79 | ||
|
|
f89b5a5d90 | ||
|
|
0cd9f5855e | ||
|
|
ccc03b93ef | ||
|
|
b0c008c593 | ||
|
|
a57c87a459 | ||
|
|
e3c7b43fc9 | ||
|
|
956ec8321a | ||
|
|
0581f2c7d2 | ||
|
|
1ff9a05181 | ||
|
|
36093e69c7 | ||
|
|
dacf69a16d | ||
|
|
1b5bfe681b | ||
|
|
18e678c1f8 | ||
|
|
1b1866934b | ||
|
|
5a69137ac8 | ||
|
|
36dea43406 | ||
|
|
d294de38c4 | ||
|
|
142e24de7b | ||
|
|
898465756a | ||
|
|
e8d9bc55bc | ||
|
|
e65360dbaf | ||
|
|
845e8f943f | ||
|
|
0ad075c430 | ||
|
|
460caf71a4 | ||
|
|
452ba32edf | ||
|
|
587eb193dd | ||
|
|
1145fe64a5 | ||
|
|
ce234d53a5 | ||
|
|
e95e173b8c | ||
|
|
b2ce5be9d0 | ||
|
|
a0f5776f35 | ||
|
|
1b5317ea08 | ||
|
|
8dda20f8e3 | ||
|
|
54791d1970 | ||
|
|
b7e3370762 | ||
|
|
84cc36017a | ||
|
|
bedd2be0a7 | ||
|
|
b974cd3741 | ||
|
|
86f9b1616a | ||
|
|
dfa0a2925f | ||
|
|
81d2d5535a | ||
|
|
cd3f7eb388 | ||
|
|
3711e04f37 | ||
|
|
8c6d82a244 | ||
|
|
3385c78999 | ||
|
|
6e2f1eb402 | ||
|
|
fa338da717 | ||
|
|
80a3e87bf3 | ||
|
|
bbbd9de6fd | ||
|
|
e776bb2656 | ||
|
|
f7c85685a8 | ||
|
|
8a2fd7ce6d | ||
|
|
76f7ce8a24 | ||
|
|
573d460aa7 | ||
|
|
3bd8afbff2 | ||
|
|
1acc4992d4 | ||
|
|
bc746e158c | ||
|
|
c4bd6ea150 | ||
|
|
9adaae6785 | ||
|
|
a0790f37b7 | ||
|
|
14b653ab3c | ||
|
|
4497a26c37 | ||
|
|
62200cb07e | ||
|
|
c53aa08cce | ||
|
|
bcfcd9f980 | ||
|
|
ec700a9a13 | ||
|
|
e7ac8ea0c1 | ||
|
|
39e49e5fb9 | ||
|
|
61f4faae70 | ||
|
|
1887f32b17 | ||
|
|
326f77d57c | ||
|
|
11b12c3e07 | ||
|
|
2669f0c0b1 | ||
|
|
9d8a57f2a2 | ||
|
|
6ea3f2ada8 | ||
|
|
461b740db6 | ||
|
|
628474d626 | ||
|
|
de674fc82c | ||
|
|
b58318163c | ||
|
|
d8be2473d1 | ||
|
|
58e513579a | ||
|
|
0b6d2e451b | ||
|
|
6f3c91b834 | ||
|
|
ed73d9e45d | ||
|
|
5a22d2060f | ||
|
|
344b51327c | ||
|
|
62df872871 | ||
|
|
a5a314e8ee | ||
|
|
b86330d1f1 | ||
|
|
69d9594338 | ||
|
|
291b6b24b7 | ||
|
|
426493f388 | ||
|
|
80aee7a225 | ||
|
|
ced6550a25 | ||
|
|
c7beb703bf | ||
|
|
53f33eb473 | ||
|
|
69b7c45c2e | ||
|
|
e644857877 | ||
|
|
3a5e2b2f3d | ||
|
|
02f852cc41 | ||
|
|
ff56e431ad | ||
|
|
b137472936 | ||
|
|
383b20b023 | ||
|
|
3314eb9a62 | ||
|
|
cf3a7f4945 | ||
|
|
fb1971778f | ||
|
|
2f7f6a57f3 | ||
|
|
74d85d0a2f | ||
|
|
eec44dbbb1 | ||
|
|
677bbe30d7 | ||
|
|
f37fe551bb | ||
|
|
751b35c346 | ||
|
|
d2d576b6b1 | ||
|
|
a57eacf080 | ||
|
|
b176ccd481 | ||
|
|
176b365287 | ||
|
|
09658b80c0 | ||
|
|
ac34e3b346 | ||
|
|
5549f50258 | ||
|
|
15597c27cd | ||
|
|
673bd6afce | ||
|
|
7612015424 | ||
|
|
892f2560c1 | ||
|
|
2d1ffad80b | ||
|
|
101425ebfc | ||
|
|
f4ec3d2309 | ||
|
|
b83131779c | ||
|
|
ce5b5fe227 | ||
|
|
eccbf4a412 | ||
|
|
86c3ecfd34 | ||
|
|
bd3cf95e34 | ||
|
|
386c589ba0 | ||
|
|
f723ab538f | ||
|
|
925e975295 | ||
|
|
4c61fef13f | ||
|
|
39090cfba6 | ||
|
|
d7c0249aa8 | ||
|
|
01b58ba04e | ||
|
|
a8205bb509 | ||
|
|
da82fb4b1d | ||
|
|
e8d02ea0bb | ||
|
|
d615405519 | ||
|
|
e23bf10cf4 | ||
|
|
508f4b1ca5 | ||
|
|
1d9bf43def | ||
|
|
bf2e73522f | ||
|
|
4e90aa7bd0 | ||
|
|
f172b30356 | ||
|
|
33b25176b8 | ||
|
|
9a0ce93935 | ||
|
|
cf82d4406b | ||
|
|
90857b8b59 | ||
|
|
7aa6c62d6d | ||
|
|
53d02381e2 | ||
|
|
8ae4d02d73 | ||
|
|
1c4a666b16 | ||
|
|
2a5364cf72 | ||
|
|
c8baf88125 | ||
|
|
3246b1fe17 | ||
|
|
8b03f60278 | ||
|
|
087a27aba9 | ||
|
|
adf475ef82 | ||
|
|
2eb7e3c80b | ||
|
|
f54a4e3de1 | ||
|
|
2bc7762cb6 | ||
|
|
9dc660269f | ||
|
|
1601387cbe | ||
|
|
45ef1f8eeb | ||
|
|
e4ddca0f8b | ||
|
|
19b8e3042e | ||
|
|
67c505781c | ||
|
|
ee50afef59 | ||
|
|
34f6088c8f | ||
|
|
431282e778 | ||
|
|
3155bc9936 | ||
|
|
1c52d15d97 | ||
|
|
f28bde7250 | ||
|
|
122c92e590 | ||
|
|
124c0940e9 | ||
|
|
29ef108158 | ||
|
|
a703e24573 | ||
|
|
387c8f852c | ||
|
|
091991be0d | ||
|
|
d505b94fa4 | ||
|
|
f2f3bdea8d | ||
|
|
583859b820 | ||
|
|
8e69c9f0a1 | ||
|
|
26ed7c2e1d | ||
|
|
77687de509 | ||
|
|
be805f942e | ||
|
|
c044a70796 | ||
|
|
cfd6e25749 | ||
|
|
3602690886 | ||
|
|
3d8823cafc | ||
|
|
f5ff05be1e | ||
|
|
6b58a359e8 | ||
|
|
96efb5ec96 | ||
|
|
e1e1855b85 | ||
|
|
3a6348dbb9 | ||
|
|
f7ecad843b | ||
|
|
4d498db3ed | ||
|
|
c41eb27091 | ||
|
|
2ac771b35e | ||
|
|
8339b60fd5 | ||
|
|
589f2f6d6e | ||
|
|
ce1ac88543 | ||
|
|
4594667f07 | ||
|
|
6ff6f756c0 | ||
|
|
c28da22e9c |
23
AUTHORS.rst
23
AUTHORS.rst
@@ -1,14 +1,10 @@
|
||||
Requests is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
|
||||
Development Lead
|
||||
````````````````
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_
|
||||
|
||||
Core Contributors
|
||||
`````````````````
|
||||
Keepers of the Three Crystals
|
||||
`````````````````````````````
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_, Keeper of the Master Crystal.
|
||||
- Cory Benfield <cory@lukasa.co.uk> `@lukasa <https://github.com/lukasa>`_
|
||||
- Ian Cordasco <graffatcolmingov@gmail.com> `@sigmavirus24 <https://github.com/sigmavirus24>`_
|
||||
|
||||
@@ -138,7 +134,7 @@ Patches and Suggestions
|
||||
- Kevin Burke <kev@inburke.com>
|
||||
- Flavio Curella
|
||||
- David Pursehouse <david.pursehouse@gmail.com> (`@dpursehouse <https://github.com/dpursehouse>`_)
|
||||
- Jon Parise
|
||||
- Jon Parise (`@jparise <https://github.com/jparise>`_)
|
||||
- Alexander Karpinsky (`@homm86 <https://twitter.com/homm86>`_)
|
||||
- Marc Schlaich (`@schlamar <https://github.com/schlamar>`_)
|
||||
- Park Ilsu <daftonshady@gmail.com> (`@daftshady <https://github.com/daftshady>`_)
|
||||
@@ -156,3 +152,14 @@ Patches and Suggestions
|
||||
- Jonathan Wong <evolutionace@gmail.com> (`@ContinuousFunction <https://github.com/ContinuousFunction>`_)
|
||||
- Martin Jul (`@mjul <https://github.com/mjul>`_)
|
||||
- Joe Alcorn (`@buttscicles <https://github.com/buttscicles>`_)
|
||||
- Syed Suhail Ahmed <ssuhail.ahmed93@gmail.com> (`@syedsuhail <https://github.com/syedsuhail>`_)
|
||||
- Scott Sadler (`@ssadler <https://github.com/ssadler>`_)
|
||||
- Arthur Darcet (`@arthurdarcet <https://github.com/arthurdarcet>`_)
|
||||
- Ulrich Petri (`@ulope <https://github.com/ulope>`_)
|
||||
- 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>`_)
|
||||
- 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`_)
|
||||
|
||||
57
CONTRIBUTING.md
Normal file
57
CONTRIBUTING.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
Before opening any issues or proposing any pull requests, please do the
|
||||
following:
|
||||
|
||||
1. Read our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/).
|
||||
2. Understand our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/).
|
||||
|
||||
To get the greatest chance of helpful responses, please also observe the
|
||||
following additional notes.
|
||||
|
||||
## Questions
|
||||
|
||||
The GitHub issue tracker is for *bug reports* and *feature requests*. Please do
|
||||
not use it to ask questions about how to use Requests. These questions should
|
||||
instead be directed to [Stack Overflow](https://stackoverflow.com/). Make sure
|
||||
that your question is tagged with the `python-requests` tag when asking it on
|
||||
Stack Overflow, to ensure that it is answered promptly and accurately.
|
||||
|
||||
## Good Bug Reports
|
||||
|
||||
Please be aware of the following things when filing bug reports:
|
||||
|
||||
1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature
|
||||
to check whether your bug report or feature request has been mentioned in
|
||||
the past. Duplicate bug reports and feature requests are a huge maintenance
|
||||
burden on the limited resources of the project. If it is clear from your
|
||||
report that you would have struggled to find the original, that's ok, but
|
||||
if searching for a selection of words in your issue title would have found
|
||||
the duplicate then the issue will likely be closed extremely abruptly.
|
||||
2. When filing bug reports about exceptions or tracebacks, please include the
|
||||
*complete* traceback. Partial tracebacks, or just the exception text, are
|
||||
not helpful. Issues that do not contain complete tracebacks may be closed
|
||||
without warning.
|
||||
3. Make sure you provide a suitable amount of information to work with. This
|
||||
means you should provide:
|
||||
|
||||
- Guidance on **how to reproduce the issue**. Ideally, this should be a
|
||||
*small* code sample that can be run immediately by the maintainers.
|
||||
Failing that, let us know what you're doing, how often it happens, what
|
||||
environment you're using, etc. Be thorough: it prevents us needing to ask
|
||||
further questions.
|
||||
- Tell us **what you expected to happen**. When we run your example code,
|
||||
what are we expecting to happen? What does "success" look like for your
|
||||
code?
|
||||
- Tell us **what actually happens**. It's not helpful for you to say "it
|
||||
doesn't work" or "it fails". Tell us *how* it fails: do you get an
|
||||
exception? A hang? A non-200 status code? How was the actual result
|
||||
different from your expected result?
|
||||
- Tell us **what version of Requests you're using**, and
|
||||
**how you installed it**. Different versions of Requests behave
|
||||
differently and have different bugs, and some distributors of Requests
|
||||
ship patches on top of the code we supply.
|
||||
|
||||
If you do not provide all of these things, it will take us much longer to
|
||||
fix your problem. If we ask you to clarify these and you never respond, we
|
||||
will close your issue without fixing it.
|
||||
275
HISTORY.rst
275
HISTORY.rst
@@ -3,6 +3,263 @@
|
||||
Release History
|
||||
---------------
|
||||
|
||||
2.9.0 (2015-12-15)
|
||||
++++++++++++++++++
|
||||
|
||||
**Minor Improvements** (Backwards compatible)
|
||||
|
||||
- The ``verify`` keyword argument now supports being passed a path to a
|
||||
directory of CA certificates, not just a single-file bundle.
|
||||
- Warnings are now emitted when sending files opened in text mode.
|
||||
- Added the 511 Network Authentication Required status code to the status code
|
||||
registry.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- For file-like objects that are not seeked to the very beginning, we now
|
||||
send the content length for the number of bytes we will actually read, rather
|
||||
than the total size of the file, allowing partial file uploads.
|
||||
- When uploading file-like objects, if they are empty or have no obvious
|
||||
content length we set ``Transfer-Encoding: chunked`` rather than
|
||||
``Content-Length: 0``.
|
||||
- We correctly receive the response in buffered mode when uploading chunked
|
||||
bodies.
|
||||
- We now handle being passed a query string as a bytestring on Python 3, by
|
||||
decoding it as UTF-8.
|
||||
- Sessions are now closed in all cases (exceptional and not) when using the
|
||||
functional API rather than leaking and waiting for the garbage collector to
|
||||
clean them up.
|
||||
- Correctly handle digest auth headers with a malformed ``qop`` directive that
|
||||
contains no token, by treating it the same as if no ``qop`` directive was
|
||||
provided at all.
|
||||
- Minor performance improvements when removing specific cookies by name.
|
||||
|
||||
2.8.1 (2015-10-13)
|
||||
++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Update certificate bundle to match ``certifi`` 2015.9.6.2's weak certificate
|
||||
bundle.
|
||||
- Fix a bug in 2.8.0 where requests would raise ``ConnectTimeout`` instead of
|
||||
``ConnectionError``
|
||||
- When using the PreparedRequest flow, requests will now correctly respect the
|
||||
``json`` parameter. Broken in 2.8.0.
|
||||
- When using the PreparedRequest flow, requests will now correctly handle a
|
||||
Unicode-string method name on Python 2. Broken in 2.8.0.
|
||||
|
||||
2.8.0 (2015-10-05)
|
||||
++++++++++++++++++
|
||||
|
||||
**Minor Improvements** (Backwards Compatible)
|
||||
|
||||
- Requests now supports per-host proxies. This allows the ``proxies``
|
||||
dictionary to have entries of the form
|
||||
``{'<scheme>://<hostname>': '<proxy>'}``. Host-specific proxies will be used
|
||||
in preference to the previously-supported scheme-specific ones, but the
|
||||
previous syntax will continue to work.
|
||||
- ``Response.raise_for_status`` now prints the URL that failed as part of the
|
||||
exception message.
|
||||
- ``requests.utils.get_netrc_auth`` now takes an ``raise_errors`` kwarg,
|
||||
defaulting to ``False``. When ``True``, errors parsing ``.netrc`` files cause
|
||||
exceptions to be thrown.
|
||||
- Change to bundled projects import logic to make it easier to unbundle
|
||||
requests downstream.
|
||||
- Changed the default User-Agent string to avoid leaking data on Linux: now
|
||||
contains only the requests version.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- The ``json`` parameter to ``post()`` and friends will now only be used if
|
||||
neither ``data`` nor ``files`` are present, consistent with the
|
||||
documentation.
|
||||
- We now ignore empty fields in the ``NO_PROXY`` environment variable.
|
||||
- Fixed problem where ``httplib.BadStatusLine`` would get raised if combining
|
||||
``stream=True`` with ``contextlib.closing``.
|
||||
- Prevented bugs where we would attempt to return the same connection back to
|
||||
the connection pool twice when sending a Chunked body.
|
||||
- Miscellaneous minor internal changes.
|
||||
- Digest Auth support is now thread safe.
|
||||
|
||||
**Updates**
|
||||
|
||||
- Updated urllib3 to 1.12.
|
||||
|
||||
2.7.0 (2015-05-03)
|
||||
++++++++++++++++++
|
||||
|
||||
This is the first release that follows our new release process. For more, see
|
||||
`our documentation
|
||||
<http://docs.python-requests.org/en/latest/community/release-process/>`_.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Updated urllib3 to 1.10.4, resolving several bugs involving chunked transfer
|
||||
encoding and response framing.
|
||||
|
||||
2.6.2 (2015-04-23)
|
||||
++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Fix regression where compressed data that was sent as chunked data was not
|
||||
properly decompressed. (#2561)
|
||||
|
||||
2.6.1 (2015-04-22)
|
||||
++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Remove VendorAlias import machinery introduced in v2.5.2.
|
||||
|
||||
- Simplify the PreparedRequest.prepare API: We no longer require the user to
|
||||
pass an empty list to the hooks keyword argument. (c.f. #2552)
|
||||
|
||||
- Resolve redirects now receives and forwards all of the original arguments to
|
||||
the adapter. (#2503)
|
||||
|
||||
- Handle UnicodeDecodeErrors when trying to deal with a unicode URL that
|
||||
cannot be encoded in ASCII. (#2540)
|
||||
|
||||
- Populate the parsed path of the URI field when performing Digest
|
||||
Authentication. (#2426)
|
||||
|
||||
- Copy a PreparedRequest's CookieJar more reliably when it is not an instance
|
||||
of RequestsCookieJar. (#2527)
|
||||
|
||||
2.6.0 (2015-03-14)
|
||||
++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- CVE-2015-2296: Fix handling of cookies on redirect. Previously a cookie
|
||||
without a host value set would use the hostname for the redirected URL
|
||||
exposing requests users to session fixation attacks and potentially cookie
|
||||
stealing. This was disclosed privately by Matthew Daley of
|
||||
`BugFuzz <https://bugfuzz.com>`_. This affects all versions of requests from
|
||||
v2.1.0 to v2.5.3 (inclusive on both ends).
|
||||
|
||||
- Fix error when requests is an ``install_requires`` dependency and ``python
|
||||
setup.py test`` is run. (#2462)
|
||||
|
||||
- Fix error when urllib3 is unbundled and requests continues to use the
|
||||
vendored import location.
|
||||
|
||||
- Include fixes to ``urllib3``'s header handling.
|
||||
|
||||
- Requests' handling of unvendored dependencies is now more restrictive.
|
||||
|
||||
**Features and Improvements**
|
||||
|
||||
- Support bytearrays when passed as parameters in the ``files`` argument.
|
||||
(#2468)
|
||||
|
||||
- Avoid data duplication when creating a request with ``str``, ``bytes``, or
|
||||
``bytearray`` input to the ``files`` argument.
|
||||
|
||||
2.5.3 (2015-02-24)
|
||||
++++++++++++++++++
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Revert changes to our vendored certificate bundle. For more context see
|
||||
(#2455, #2456, and http://bugs.python.org/issue23476)
|
||||
|
||||
2.5.2 (2015-02-23)
|
||||
++++++++++++++++++
|
||||
|
||||
**Features and Improvements**
|
||||
|
||||
- Add sha256 fingerprint support. (`shazow/urllib3#540`_)
|
||||
|
||||
- Improve the performance of headers. (`shazow/urllib3#544`_)
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Copy pip's import machinery. When downstream redistributors remove
|
||||
requests.packages.urllib3 the import machinery will continue to let those
|
||||
same symbols work. Example usage in requests' documentation and 3rd-party
|
||||
libraries relying on the vendored copies of urllib3 will work without having
|
||||
to fallback to the system urllib3.
|
||||
|
||||
- Attempt to quote parts of the URL on redirect if unquoting and then quoting
|
||||
fails. (#2356)
|
||||
|
||||
- Fix filename type check for multipart form-data uploads. (#2411)
|
||||
|
||||
- Properly handle the case where a server issuing digest authentication
|
||||
challenges provides both auth and auth-int qop-values. (#2408)
|
||||
|
||||
- Fix a socket leak. (`shazow/urllib3#549`_)
|
||||
|
||||
- Fix multiple ``Set-Cookie`` headers properly. (`shazow/urllib3#534`_)
|
||||
|
||||
- Disable the built-in hostname verification. (`shazow/urllib3#526`_)
|
||||
|
||||
- Fix the behaviour of decoding an exhausted stream. (`shazow/urllib3#535`_)
|
||||
|
||||
**Security**
|
||||
|
||||
- Pulled in an updated ``cacert.pem``.
|
||||
|
||||
- Drop RC4 from the default cipher list. (`shazow/urllib3#551`_)
|
||||
|
||||
.. _shazow/urllib3#551: https://github.com/shazow/urllib3/pull/551
|
||||
.. _shazow/urllib3#549: https://github.com/shazow/urllib3/pull/549
|
||||
.. _shazow/urllib3#544: https://github.com/shazow/urllib3/pull/544
|
||||
.. _shazow/urllib3#540: https://github.com/shazow/urllib3/pull/540
|
||||
.. _shazow/urllib3#535: https://github.com/shazow/urllib3/pull/535
|
||||
.. _shazow/urllib3#534: https://github.com/shazow/urllib3/pull/534
|
||||
.. _shazow/urllib3#526: https://github.com/shazow/urllib3/pull/526
|
||||
|
||||
2.5.1 (2014-12-23)
|
||||
++++++++++++++++++
|
||||
|
||||
**Behavioural Changes**
|
||||
|
||||
- Only catch HTTPErrors in raise_for_status (#2382)
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Handle LocationParseError from urllib3 (#2344)
|
||||
- Handle file-like object filenames that are not strings (#2379)
|
||||
- Unbreak HTTPDigestAuth handler. Allow new nonces to be negotiated (#2389)
|
||||
|
||||
2.5.0 (2014-12-01)
|
||||
++++++++++++++++++
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Allow usage of urllib3's Retry object with HTTPAdapters (#2216)
|
||||
- The ``iter_lines`` method on a response now accepts a delimiter with which
|
||||
to split the content (#2295)
|
||||
|
||||
**Behavioural Changes**
|
||||
|
||||
- Add deprecation warnings to functions in requests.utils that will be removed
|
||||
in 3.0 (#2309)
|
||||
- Sessions used by the functional API are always closed (#2326)
|
||||
- Restrict requests to HTTP/1.1 and HTTP/1.0 (stop accepting HTTP/0.9) (#2323)
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- Only parse the URL once (#2353)
|
||||
- Allow Content-Length header to always be overridden (#2332)
|
||||
- Properly handle files in HTTPDigestAuth (#2333)
|
||||
- Cap redirect_cache size to prevent memory abuse (#2299)
|
||||
- Fix HTTPDigestAuth handling of redirects after authenticating successfully
|
||||
(#2253)
|
||||
- Fix crash with custom method parameter to Session.request (#2317)
|
||||
- Fix how Link headers are parsed using the regular expression library (#2271)
|
||||
|
||||
**Documentation**
|
||||
|
||||
- Add more references for interlinking (#2348)
|
||||
- Update CSS for theme (#2290)
|
||||
- Update width of buttons and sidebar (#2289)
|
||||
- Replace references of Gittip with Gratipay (#2282)
|
||||
- Add link to changelog in sidebar (#2273)
|
||||
|
||||
2.4.3 (2014-10-06)
|
||||
++++++++++++++++++
|
||||
|
||||
@@ -55,7 +312,7 @@ Release History
|
||||
- Support for connect timeouts! Timeout now accepts a tuple (connect, read) which is used to set individual connect and read timeouts.
|
||||
- Allow copying of PreparedRequests without headers/cookies.
|
||||
- Updated bundled urllib3 version.
|
||||
- Refactored settings loading from environment — new `Session.merge_environment_settings`.
|
||||
- Refactored settings loading from environment -- new `Session.merge_environment_settings`.
|
||||
- Handle socket errors in iter_content.
|
||||
|
||||
|
||||
@@ -299,7 +556,7 @@ This is not a backwards compatible change.
|
||||
- Improved mime-compatible JSON handling
|
||||
- Proxy fixes
|
||||
- Path hack fixes
|
||||
- Case-Insensistive Content-Encoding headers
|
||||
- Case-Insensitive Content-Encoding headers
|
||||
- Support for CJK parameters in form posts
|
||||
|
||||
|
||||
@@ -335,7 +592,7 @@ This is not a backwards compatible change.
|
||||
- Digest Authentication improvements.
|
||||
- Ensure proxy exclusions work properly.
|
||||
- Clearer UnicodeError exceptions.
|
||||
- Automatic casting of URLs to tsrings (fURL and such)
|
||||
- Automatic casting of URLs to strings (fURL and such)
|
||||
- Bugfixes.
|
||||
|
||||
0.13.6 (2012-08-06)
|
||||
@@ -386,8 +643,8 @@ This is not a backwards compatible change.
|
||||
+++++++++++++++++++
|
||||
|
||||
- Removal of Requests.async in favor of `grequests <https://github.com/kennethreitz/grequests>`_
|
||||
- Allow disabling of cookie persistiance.
|
||||
- New implimentation of safe_mode
|
||||
- Allow disabling of cookie persistence.
|
||||
- New implementation of safe_mode
|
||||
- cookies.get now supports default argument
|
||||
- Session cookies not saved when Session.request is called with return_response=False
|
||||
- Env: no_proxy support.
|
||||
@@ -504,7 +761,7 @@ This is not a backwards compatible change.
|
||||
|
||||
* ``Response.content`` is now bytes-only. (*Backwards Incompatible*)
|
||||
* New ``Response.text`` is unicode-only.
|
||||
* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Respoonse.text`` will guess an encoding.
|
||||
* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Response.text`` will guess an encoding.
|
||||
* Default to ISO-8859-1 (Western) encoding for "text" subtypes.
|
||||
* Removal of `decode_unicode`. (*Backwards Incompatible*)
|
||||
* New multiple-hooks system.
|
||||
@@ -624,7 +881,7 @@ This is not a backwards compatible change.
|
||||
0.7.5 (2011-11-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Response.content = None if there was an invalid repsonse.
|
||||
* Response.content = None if there was an invalid response.
|
||||
* Redirection auth handling.
|
||||
|
||||
0.7.4 (2011-10-26)
|
||||
@@ -711,7 +968,7 @@ This is not a backwards compatible change.
|
||||
++++++++++++++++++
|
||||
|
||||
* New callback hook system
|
||||
* New persistient sessions object and context manager
|
||||
* New persistent sessions object and context manager
|
||||
* Transparent Dict-cookie handling
|
||||
* Status code reference object
|
||||
* Removed Response.cached
|
||||
@@ -745,7 +1002,7 @@ This is not a backwards compatible change.
|
||||
* Redirect Fixes
|
||||
* settings.verbose stream writing
|
||||
* Querystrings for all methods
|
||||
* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised
|
||||
* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicitly raised
|
||||
``r.requests.get('hwe://blah'); r.raise_for_status()``
|
||||
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2014 Kenneth Reitz
|
||||
Copyright 2015 Kenneth Reitz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
10
Makefile
10
Makefile
@@ -20,16 +20,10 @@ certs:
|
||||
deps: urllib3 chardet
|
||||
|
||||
urllib3:
|
||||
rm -fr requests/packages/urllib3
|
||||
git clone https://github.com/shazow/urllib3.git
|
||||
mv urllib3/urllib3 requests/packages/
|
||||
rm -fr urllib3
|
||||
git clone https://github.com/shazow/urllib3.git && rm -fr requests/packages/urllib3 && mv urllib3/urllib3 requests/packages/ && rm -fr urllib3
|
||||
|
||||
chardet:
|
||||
rm -fr requests/packages/chardet
|
||||
git clone https://github.com/chardet/chardet.git
|
||||
mv chardet/chardet requests/packages/
|
||||
rm -fr chardet
|
||||
git clone https://github.com/chardet/chardet.git && rm -fr requests/packages/chardet && mv chardet/chardet requests/packages/ && rm -fr chardet
|
||||
|
||||
publish:
|
||||
python setup.py register
|
||||
|
||||
13
README.rst
13
README.rst
@@ -1,11 +1,13 @@
|
||||
Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
.. image:: https://badge.fury.io/py/requests.png
|
||||
:target: http://badge.fury.io/py/requests
|
||||
.. image:: https://img.shields.io/pypi/v/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/requests.svg
|
||||
:target: https://pypi.python.org/pypi/requests
|
||||
|
||||
|
||||
.. image:: https://pypip.in/d/requests/badge.png
|
||||
:target: https://crate.io/packages/requests/
|
||||
|
||||
|
||||
Requests is an Apache2 Licensed HTTP library, written in Python, for human
|
||||
@@ -19,7 +21,7 @@ perform the simplest of tasks.
|
||||
|
||||
Things shouldn't be this way. Not in Python.
|
||||
|
||||
.. code-block:: pycon
|
||||
.. code-block:: python
|
||||
|
||||
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
|
||||
>>> r.status_code
|
||||
@@ -75,7 +77,6 @@ Contribute
|
||||
----------
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a `Contributor Friendly`_ tag for issues that should be ideal for people who are not very familiar with the codebase yet.
|
||||
#. If you feel uncomfortable or uncertain about an issue or your changes, feel free to email @sigmavirus24 and he will happily help you via email, Skype, remote pairing or whatever you are comfortable with.
|
||||
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
|
||||
#. 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_.
|
||||
|
||||
21
docs/_templates/sidebarintro.html
vendored
21
docs/_templates/sidebarintro.html
vendored
@@ -15,15 +15,9 @@
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Donate</h3>
|
||||
<p>
|
||||
If you love Requests, consider supporting the author <a href="https://www.gittip.com/kennethreitz/">on Gittip</a>:
|
||||
</p>
|
||||
<p>
|
||||
<iframe style="border: 0; margin: 0; padding: 0;"
|
||||
src="https://www.gittip.com/kennethreitz/widget.html"
|
||||
width="48pt" height="20pt"></iframe>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://gumroad.com/l/RRZc" class="gumroad-button">Buy Requests Pro</a>
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Get Updates</h3>
|
||||
@@ -40,13 +34,18 @@
|
||||
<li><a href="http://jp.python-requests.org/">Japanese</a></li>
|
||||
<li><a href="http://cn.python-requests.org/">Chinese</a></li>
|
||||
<li><a href="http://pt.python-requests.org/">Portuguese</a></li>
|
||||
|
||||
|
||||
<li><a href="http://it.python-requests.org/">Italian</a></li>
|
||||
<li><a href="http://es.python-requests.org/">Spanish</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://docs.python-requests.org/en/latest/community/recommended/">Recommended Packages and Extensions</a></li>
|
||||
|
||||
<p></p>
|
||||
|
||||
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
|
||||
<li><a href="http://docs.python-requests.org/en/latest/community/updates/#software-updates">Release History</a></li>
|
||||
</ul>
|
||||
|
||||
13
docs/_templates/sidebarlogo.html
vendored
13
docs/_templates/sidebarlogo.html
vendored
@@ -14,16 +14,9 @@
|
||||
development release.
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Donate</h3>
|
||||
<p>
|
||||
If you love Requests, consider supporting the author <a href="https://www.gittip.com/kennethreitz/">on Gittip</a>:
|
||||
</p>
|
||||
<p>
|
||||
<iframe style="border: 0; margin: 0; padding: 0;"
|
||||
src="https://www.gittip.com/kennethreitz/widget.html"
|
||||
width="48pt" height="20pt"></iframe>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://gumroad.com/l/RRZc" class="gumroad-button">Buy Requests Pro</a>
|
||||
</p>
|
||||
|
||||
<h3>Get Updates</h3>
|
||||
<p>Receive updates on new releases and upcoming projects.</p>
|
||||
|
||||
13
docs/_themes/kr/layout.html
vendored
13
docs/_themes/kr/layout.html
vendored
@@ -15,6 +15,9 @@
|
||||
<a href="https://github.com/kennethreitz/requests" class="github">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" class="github"/>
|
||||
</a>
|
||||
|
||||
<script type="text/javascript" src="https://gumroad.com/js/gumroad.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
/* <![CDATA[ */
|
||||
(function() {
|
||||
@@ -68,5 +71,15 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
window._pa = window._pa || {};
|
||||
_pa.productId = "requests-docs";
|
||||
var pa = document.createElement('script'); pa.type = 'text/javascript'; pa.async = true;
|
||||
pa.src = ('https:' == document.location.protocol ? 'https:' : 'http:') + "//tag.perfectaudience.com/serve/5226171f87bc6890da0000a0.js";
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(pa, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
@@ -81,6 +81,8 @@ Status Code Lookup
|
||||
>>> requests.codes['\o/']
|
||||
200
|
||||
|
||||
.. _api-cookies:
|
||||
|
||||
Cookies
|
||||
~~~~~~~
|
||||
|
||||
@@ -88,6 +90,12 @@ Cookies
|
||||
.. autofunction:: requests.utils.cookiejar_from_dict
|
||||
.. autofunction:: requests.utils.add_dict_to_cookiejar
|
||||
|
||||
.. autoclass:: requests.cookies.RequestsCookieJar
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: requests.cookies.CookieConflictError
|
||||
:inherited-members:
|
||||
|
||||
|
||||
Encodings
|
||||
~~~~~~~~~
|
||||
|
||||
@@ -79,9 +79,9 @@ when servers are using `Virtual Hosting`_. When such servers are hosting
|
||||
more than one SSL site they need to be able to return the appropriate
|
||||
certificate based on the hostname the client is connecting to.
|
||||
|
||||
Python3's SSL module includes native support for SNI. This support has not been
|
||||
back ported to Python2. For information on using SNI with Requests on Python2
|
||||
refer to this `Stack Overflow answer`_.
|
||||
Python3 and Python 2.7.9+ include native support for SNI in their SSL modules.
|
||||
For information on using SNI with Requests on Python < 2.7.9 refer to this
|
||||
`Stack Overflow answer`_.
|
||||
|
||||
.. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication
|
||||
.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting
|
||||
|
||||
@@ -30,3 +30,4 @@ Articles & Talks
|
||||
- `Issac Kelly's 'Consuming Web APIs' talk <http://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>`_
|
||||
|
||||
57
docs/community/recommended.rst
Normal file
57
docs/community/recommended.rst
Normal file
@@ -0,0 +1,57 @@
|
||||
.. _recommended:
|
||||
|
||||
Recommended Packages and Extensions
|
||||
===================================
|
||||
|
||||
Requests has a great variety of powerful and useful third-party extensions.
|
||||
This page provides an overview of some of the best of them.
|
||||
|
||||
Certifi CA Bundle
|
||||
-----------------
|
||||
|
||||
`Certifi`_ is a carefully curated collection of Root Certificates for
|
||||
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/
|
||||
|
||||
CacheControl
|
||||
------------
|
||||
|
||||
`CacheControl`_ is an extension that adds a full HTTP cache to Requests. This
|
||||
makes your web requests substantially more efficient, and should be used
|
||||
whenever you're making a lot of web requests.
|
||||
|
||||
.. _CacheControl: https://cachecontrol.readthedocs.org/en/latest/
|
||||
|
||||
Requests-Toolbelt
|
||||
-----------------
|
||||
|
||||
`Requests-Toolbelt`_ is a collection of utilities that some users of Requests may desire,
|
||||
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.org/en/latest/index.html
|
||||
|
||||
Requests-OAuthlib
|
||||
-----------------
|
||||
|
||||
`requests-oauthlib`_ makes it possible to do the OAuth dance from Requests
|
||||
automatically. This is useful for the large number of websites that use OAuth
|
||||
to provide authentication. It also provides a lot of tweaks that handle ways
|
||||
that specific OAuth providers differ from the standard specifications.
|
||||
|
||||
.. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/en/latest/
|
||||
|
||||
|
||||
Betamax
|
||||
-------
|
||||
|
||||
`Betamax`_ records your HTTP interactions so the NSA does not have to.
|
||||
A VCR imitation designed only for Python-Requests.
|
||||
|
||||
.. _betamax: https://github.com/sigmavirus24/betamax
|
||||
|
||||
|
||||
|
||||
54
docs/community/release-process.rst
Normal file
54
docs/community/release-process.rst
Normal file
@@ -0,0 +1,54 @@
|
||||
Release Process and Rules
|
||||
=========================
|
||||
|
||||
.. versionadded:: v2.6.2
|
||||
|
||||
Starting with the version to be released after ``v2.6.2``, the following rules
|
||||
will govern and describe how the Requests core team produces a new release.
|
||||
|
||||
Major Releases
|
||||
--------------
|
||||
|
||||
A major release will include breaking changes. When it is versioned, it will
|
||||
be versioned as ``vX.0.0``. For example, if the previous release was
|
||||
``v10.2.7`` the next version will be ``v11.0.0``.
|
||||
|
||||
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.
|
||||
|
||||
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``.
|
||||
|
||||
Minor releases will be backwards compatible with releases that have the same
|
||||
major version number. In other words, all versions that would start with
|
||||
``v10.`` should be compatible with each other.
|
||||
|
||||
Hotfix Releases
|
||||
---------------
|
||||
|
||||
A hotfix release will only include bug fixes that were missed when the project
|
||||
released the previous version. If the previous version of Requests released
|
||||
``v10.2.7`` the hotfix release would be versioned as ``v10.2.8``.
|
||||
|
||||
Hotfixes will **not** include upgrades to vendored dependences after
|
||||
``v2.6.2``
|
||||
|
||||
Reasoning
|
||||
---------
|
||||
|
||||
In the 2.5 and 2.6 release series, the Requests core team upgraded vendored
|
||||
dependencies and caused a great deal of headaches for both users and the core
|
||||
team. To reduce this pain, we're forming a concrete set of procedures so
|
||||
expectations will be properly set.
|
||||
@@ -5,12 +5,21 @@ Support
|
||||
|
||||
If you have questions or issues about Requests, there are several options:
|
||||
|
||||
StackOverflow
|
||||
-------------
|
||||
|
||||
If your question does not contain sensitive (possibly proprietary)
|
||||
information or can be properly anonymized, please ask a question on
|
||||
`StackOverflow <https://stackoverflow.com/questions/tagged/python-requests>`_
|
||||
and use the tag ``python-requests``.
|
||||
|
||||
Send a Tweet
|
||||
------------
|
||||
|
||||
If your question is less than 140 characters, feel free to send a tweet to
|
||||
`@kennethreitz <http://twitter.com/kennethreitz>`_.
|
||||
|
||||
`@kennethreitz <https://twitter.com/kennethreitz>`_,
|
||||
`@sigmavirus24 <https://twitter.com/sigmavirus24>`_, or
|
||||
`@lukasaoz <https://twitter.com/lukasaoz>`_.
|
||||
|
||||
File an Issue
|
||||
-------------
|
||||
@@ -34,4 +43,9 @@ IRC
|
||||
The official Freenode channel for Requests is
|
||||
`#python-requests <irc://irc.freenode.net/python-requests>`_
|
||||
|
||||
I'm also available as **kennethreitz** on Freenode.
|
||||
The core developers of requests are on IRC throughout the day.
|
||||
You can find them in ``#python-requests`` as:
|
||||
|
||||
- kennethreitz
|
||||
- lukasa
|
||||
- sigmavirus24
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
.. _updates:
|
||||
|
||||
|
||||
|
||||
|
||||
Community Updates
|
||||
=================
|
||||
|
||||
@@ -19,23 +17,14 @@ The best way to track the development of Requests is through
|
||||
Twitter
|
||||
-------
|
||||
|
||||
I often tweet about new features and releases of Requests.
|
||||
The author, Kenneth Reitz, often tweets about new features and releases of Requests.
|
||||
|
||||
Follow `@kennethreitz <https://twitter.com/kennethreitz>`_ for updates.
|
||||
|
||||
|
||||
Mailing List
|
||||
------------
|
||||
|
||||
There's a low-volume mailing list for Requests. To subscribe to the
|
||||
mailing list, send an email to
|
||||
`requests@librelist.org <mailto:requests@librelist.org>`_.
|
||||
|
||||
|
||||
|
||||
|
||||
Software Updates
|
||||
================
|
||||
Release and Version History
|
||||
===========================
|
||||
|
||||
.. include:: ../../HISTORY.rst
|
||||
|
||||
|
||||
105
docs/community/vulnerabilities.rst
Normal file
105
docs/community/vulnerabilities.rst
Normal file
@@ -0,0 +1,105 @@
|
||||
Vulnerability Disclosure
|
||||
========================
|
||||
|
||||
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.**
|
||||
|
||||
Our PGP Key fingerprints are:
|
||||
|
||||
- 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24)
|
||||
|
||||
- 90DC AE40 FEA7 4B14 9B70 662D F25F 2144 EEC1 373D (@lukasa)
|
||||
|
||||
If English is not your first language, please try to describe the problem and
|
||||
its impact to the best of your ability. For greater detail, please use your
|
||||
native language and we will try our best to translate it using online services.
|
||||
|
||||
Please also include the code you used to find the problem and the shortest
|
||||
amount of code necessary to reproduce it.
|
||||
|
||||
Please do not disclose this to anyone else. We will retrieve a CVE identifier
|
||||
if necessary and give you full credit under whatever name or alias you provide.
|
||||
We will only request an identifier when we have a fix and can publish it in a
|
||||
release.
|
||||
|
||||
We will respect your privacy and will only publicize your involvement if you
|
||||
grant us permission.
|
||||
|
||||
Process
|
||||
-------
|
||||
|
||||
This following information discusses the process the requests project follows
|
||||
in response to vulnerability disclosures. If you are disclosing a
|
||||
vulnerability, this section of the documentation lets you know how we will
|
||||
respond to your disclosure.
|
||||
|
||||
Timeline
|
||||
~~~~~~~~
|
||||
|
||||
When you report an issue, one of the project members will respond to you within
|
||||
two days *at the outside*. In most cases responses will be faster, usually
|
||||
within 12 hours. This initial response will at the very least confirm receipt
|
||||
of the report.
|
||||
|
||||
If we were able to rapidly reproduce the issue, the initial response will also
|
||||
contain confirmation of the issue. If we are not, we will often ask for more
|
||||
information about the reproduction scenario.
|
||||
|
||||
Our goal is to have a fix for any vulnerability released within two weeks of
|
||||
the initial disclosure. This may potentially involve shipping an interim
|
||||
release that simply disables function while a more mature fix can be prepared,
|
||||
but will in the vast majority of cases mean shipping a complete release as soon
|
||||
as possible.
|
||||
|
||||
Throughout the fix process we will keep you up to speed with how the fix is
|
||||
progressing. Once the fix is prepared, we will notify you that we believe we
|
||||
have a fix. Often we will ask you to confirm the fix resolves the problem in
|
||||
your environment, especially if we are not confident of our reproduction
|
||||
scenario.
|
||||
|
||||
At this point, we will prepare for the release. We will obtain a CVE number
|
||||
if one is required, providing you with full credit for the discovery. We will
|
||||
also decide on a planned release date, and let you know when it is. This
|
||||
release date will *always* be on a weekday.
|
||||
|
||||
At this point we will reach out to our major downstream packagers to notify
|
||||
them of an impending security-related patch so they can make arrangements. In
|
||||
addition, these packagers will be provided with the intended patch ahead of
|
||||
time, to ensure that they are able to promptly release their downstream
|
||||
packages. Currently the list of people we actively contact *ahead of a public
|
||||
release* is:
|
||||
|
||||
- Ralph Bean, Red Hat (@ralphbean)
|
||||
- Daniele Tricoli, Debian (@eriol)
|
||||
|
||||
We will notify these individuals at least a week ahead of our planned release
|
||||
date to ensure that they have sufficient time to prepare. If you believe you
|
||||
should be on this list, please let one of the maintainers know at one of the
|
||||
email addresses at the top of this article.
|
||||
|
||||
On release day, we will push the patch to our public repository, along with an
|
||||
updated changelog that describes the issue and credits you. We will then issue
|
||||
a PyPI release containing the patch.
|
||||
|
||||
At this point, we will publicise the release. This will involve mails to
|
||||
mailing lists, Tweets, and all other communication mechanisms available to the
|
||||
core team.
|
||||
|
||||
We will also explicitly mention which commits contain the fix to make it easier
|
||||
for other distributors and users to easily patch their own versions of requests
|
||||
if upgrading is not an option.
|
||||
|
||||
Previous CVEs
|
||||
-------------
|
||||
|
||||
- Fixed in 2.6.0
|
||||
|
||||
- `CVE 2015-2296 <http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2015-2296>`_,
|
||||
reported by Matthew Daley of `BugFuzz <https://bugfuzz.com/>`_.
|
||||
|
||||
- Fixed in 2.3.0
|
||||
|
||||
- `CVE 2014-1829 <http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2014-1829>`_
|
||||
|
||||
- `CVE 2014-1830 <http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2014-1830>`_
|
||||
@@ -46,7 +46,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'2014. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
copyright = u'2015. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
145
docs/dev/contributing.rst
Normal file
145
docs/dev/contributing.rst
Normal file
@@ -0,0 +1,145 @@
|
||||
.. _contributing:
|
||||
|
||||
Contributor's Guide
|
||||
===================
|
||||
|
||||
If you're reading this you're probably interested in contributing to
|
||||
Requests. First, We'd like to say: thank you! Open source projects
|
||||
live-and-die based on the support they receive from others, and the fact that
|
||||
you're even considering supporting Requests is very generous of
|
||||
you.
|
||||
|
||||
This document lays out guidelines and advice for contributing to Requests.
|
||||
If you're thinking of contributing, start by reading this thoroughly and
|
||||
getting a feel for how contributing to the project works. If you have any
|
||||
questions, feel free to reach out to either `Ian Cordasco`_ or `Cory Benfield`_,
|
||||
the primary maintainers.
|
||||
|
||||
The guide is split into sections based on the type of contribution you're
|
||||
thinking of making, with a section that covers general guidelines for all
|
||||
contributors.
|
||||
|
||||
.. _Ian Cordasco: http://www.coglib.com/~icordasc/
|
||||
.. _Cory Benfield: https://lukasa.co.uk/about
|
||||
|
||||
|
||||
All Contributions
|
||||
-----------------
|
||||
|
||||
Be Cordial
|
||||
~~~~~~~~~~
|
||||
|
||||
**Be cordial or be on your way.**
|
||||
|
||||
Requests has one very important rule governing all forms of contribution,
|
||||
including reporting bugs or requesting features. This golden rule is
|
||||
`be cordial or be on your way`_. **All contributions are welcome**, as long as
|
||||
everyone involved is treated with respect.
|
||||
|
||||
.. _be cordial or be on your way: http://kennethreitz.org/be-cordial-or-be-on-your-way/
|
||||
|
||||
.. _early-feedback:
|
||||
|
||||
Get Early Feedback
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are contributing, do not feel the need to sit on your contribution until
|
||||
it is perfectly polished and complete. It helps everyone involved for you to
|
||||
seek feedback as early as you possibly can. Submitting an early, unfinished
|
||||
version of your contribution for feedback in no way prejudices your chances of
|
||||
getting that contribution accepted, and can save you from putting a lot of work
|
||||
into a contribution that is not suitable for the project.
|
||||
|
||||
Contribution Suitability
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The project maintainer has the last word on whether or not a contribution is
|
||||
suitable for Requests. All contributions will be considered, but from time
|
||||
to time contributions will be rejected because they do not suit the project.
|
||||
|
||||
If your contribution is rejected, don't despair! So long as you followed these
|
||||
guidelines, you'll have a much better chance of getting your next contribution
|
||||
accepted.
|
||||
|
||||
|
||||
Code Contributions
|
||||
------------------
|
||||
|
||||
Steps
|
||||
~~~~~
|
||||
|
||||
When contributing code, you'll want to follow this checklist:
|
||||
|
||||
1. Fork the repository on GitHub.
|
||||
2. Run the tests to confirm they all pass on your system. If they don't, you'll
|
||||
need to investigate why they fail. If you're unable to diagnose this
|
||||
yourself, raise it as a bug report by following the guidelines in this
|
||||
document: :ref:`bug-reports`.
|
||||
3. Write tests that demonstrate your bug or feature. Ensure that they fail.
|
||||
4. Make your change.
|
||||
5. Run the entire test suite again, confirming that all tests pass *including
|
||||
the ones you just added*.
|
||||
6. Send a GitHub Pull Request to the main repository's ``master`` branch.
|
||||
GitHub Pull Requests are the expected method of code collaboration on this
|
||||
project.
|
||||
|
||||
The following sub-sections go into more detail on some of the points above.
|
||||
|
||||
Code Review
|
||||
~~~~~~~~~~~
|
||||
|
||||
Contributions will not be merged until they've been code reviewed. You should
|
||||
implement any code review feedback unless you strongly object to it. In the
|
||||
event that you object to the code review feedback, you should make your case
|
||||
clearly and calmly. If, after doing so, the feedback is judged to still apply,
|
||||
you must either apply the feedback or withdraw your contribution.
|
||||
|
||||
New Contributors
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are new or relatively new to Open Source, welcome! Requests aims to
|
||||
be a gentle introduction to the world of Open Source. If you're concerned about
|
||||
how best to contribute, please consider mailing a maintainer (listed above) and
|
||||
asking for help.
|
||||
|
||||
Please also check the :ref:`early-feedback` section.
|
||||
|
||||
Documentation Contributions
|
||||
---------------------------
|
||||
|
||||
Documentation improvements are always welcome! The documentation files live in
|
||||
the ``docs/`` directory of the codebase. They're written in
|
||||
`reStructuredText`_, and use `Sphinx`_ to generate the full suite of
|
||||
documentation.
|
||||
|
||||
When contributing documentation, please attempt to follow the style of the
|
||||
documentation files. This means a soft-limit of 79 characters wide in your text
|
||||
files and a semi-formal prose style.
|
||||
|
||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _Sphinx: http://sphinx-doc.org/index.html
|
||||
|
||||
|
||||
.. _bug-reports:
|
||||
|
||||
Bug Reports
|
||||
-----------
|
||||
|
||||
Bug reports are hugely important! Before you raise one, though, please check
|
||||
through the `GitHub issues`_, **both open and closed**, to confirm that the bug
|
||||
hasn't been reported before. Duplicate bug reports are a huge drain on the time
|
||||
of other contributors, and should be avoided as much as possible.
|
||||
|
||||
.. _GitHub issues: https://github.com/kennethreitz/requests/issues
|
||||
|
||||
|
||||
Feature Requests
|
||||
----------------
|
||||
|
||||
Requests is in a perpetual feature freeze. The maintainers believe that
|
||||
requests contains every major feature currently required by the vast majority
|
||||
of users.
|
||||
|
||||
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.
|
||||
@@ -48,7 +48,7 @@ Testimonials
|
||||
Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku,
|
||||
PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington
|
||||
Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed
|
||||
use Requests internally. It has been downloaded over 23,000,000 times from PyPI.
|
||||
use Requests internally. It has been downloaded over 60,000,000 times from PyPI.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
@@ -114,9 +114,12 @@ Requests ecosystem and community.
|
||||
:maxdepth: 1
|
||||
|
||||
community/faq
|
||||
community/out-there.rst
|
||||
community/recommended
|
||||
community/out-there
|
||||
community/support
|
||||
community/vulnerabilities
|
||||
community/updates
|
||||
community/release-process
|
||||
|
||||
API Documentation
|
||||
-----------------
|
||||
@@ -139,6 +142,7 @@ you.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
dev/contributing
|
||||
dev/philosophy
|
||||
dev/todo
|
||||
dev/authors
|
||||
|
||||
@@ -5,13 +5,17 @@ Advanced Usage
|
||||
|
||||
This document covers some of Requests more advanced features.
|
||||
|
||||
.. _session-objects:
|
||||
|
||||
Session Objects
|
||||
---------------
|
||||
|
||||
The Session object allows you to persist certain parameters across
|
||||
requests. It also persists cookies across all requests made from the
|
||||
Session instance.
|
||||
Session instance, and will use ``urllib3``'s `connection pooling`_. So if
|
||||
you're making several requests to the same host, the underlying TCP
|
||||
connection will be reused, which can result in a significant performance
|
||||
increase (see `HTTP persistent connection`_).
|
||||
|
||||
A Session object has all the methods of the main Requests API.
|
||||
|
||||
@@ -41,6 +45,33 @@ Any dictionaries that you pass to a request method will be merged with the
|
||||
session-level values that are set. The method-level parameters override session
|
||||
parameters.
|
||||
|
||||
Note, however, that method-level parameters will *not* be persisted across
|
||||
requests, even if using a session. This example will only send the cookies
|
||||
with the first request, but not the second::
|
||||
|
||||
s = requests.Session()
|
||||
r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
|
||||
print(r.text)
|
||||
# '{"cookies": {"from-my": "browser"}}'
|
||||
|
||||
r = s.get('http://httpbin.org/cookies')
|
||||
print(r.text)
|
||||
# '{"cookies": {}}'
|
||||
|
||||
|
||||
If you want to manually add cookies to your session, use the
|
||||
:ref:`Cookie utility functions <api-cookies>` to manipulate
|
||||
:attr:`Session.cookies <requests.Session.cookies>`.
|
||||
|
||||
Sessions can also be used as context managers::
|
||||
|
||||
with requests.Session() as s:
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
|
||||
This will make sure the session is closed as soon as the ``with`` block is
|
||||
exited, even if unhandled exceptions occurred.
|
||||
|
||||
|
||||
.. admonition:: Remove a Value From a Dict Parameter
|
||||
|
||||
Sometimes you'll want to omit session-level keys from a dict parameter. To
|
||||
@@ -50,6 +81,8 @@ parameters.
|
||||
All values that are contained within a session are directly available to you.
|
||||
See the :ref:`Session API Docs <sessionapi>` to learn more.
|
||||
|
||||
.. _request-and-response-objects:
|
||||
|
||||
Request and Response Objects
|
||||
----------------------------
|
||||
|
||||
@@ -82,6 +115,8 @@ request, and then the request's headers::
|
||||
{'Accept-Encoding': 'identity, deflate, compress, gzip',
|
||||
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}
|
||||
|
||||
.. _prepared-requests:
|
||||
|
||||
Prepared Requests
|
||||
-----------------
|
||||
|
||||
@@ -166,7 +201,11 @@ I don't have SSL setup on this domain, so it fails. Excellent. GitHub does thoug
|
||||
>>> requests.get('https://github.com', verify=True)
|
||||
<Response [200]>
|
||||
|
||||
You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs. This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable.
|
||||
You can pass ``verify`` the path to a CA_BUNDLE file or directory with certificates of trusted CAs::
|
||||
|
||||
>>> requests.get('https://github.com', verify='/path/to/certfile')
|
||||
|
||||
This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable.
|
||||
|
||||
Requests can also ignore verifying the SSL certificate if you set ``verify`` to False.
|
||||
|
||||
@@ -189,6 +228,29 @@ If you specify a wrong path or an invalid cert::
|
||||
>>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
|
||||
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
|
||||
|
||||
.. _ca-certificates:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
For the sake of security we recommend upgrading certifi frequently!
|
||||
|
||||
.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection
|
||||
.. _connection pooling: https://urllib3.readthedocs.org/en/latest/pools.html
|
||||
.. _certifi: http://certifi.io/
|
||||
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
|
||||
.. _body-content-workflow:
|
||||
|
||||
Body Content Workflow
|
||||
---------------------
|
||||
@@ -228,6 +290,7 @@ consider using ``contextlib.closing`` (`documented here`_), like this::
|
||||
|
||||
.. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing
|
||||
|
||||
.. _keep-alive:
|
||||
|
||||
Keep-Alive
|
||||
----------
|
||||
@@ -240,6 +303,7 @@ Note that connections are only released back to the pool for reuse once all body
|
||||
data has been read; be sure to either set ``stream`` to ``False`` or read the
|
||||
``content`` property of the ``Response`` object.
|
||||
|
||||
.. _streaming-uploads:
|
||||
|
||||
Streaming Uploads
|
||||
-----------------
|
||||
@@ -251,6 +315,16 @@ file-like object for your body::
|
||||
with open('massive-body', 'rb') as f:
|
||||
requests.post('http://some.url/streamed', data=f)
|
||||
|
||||
.. warning:: It is strongly recommended that you open files in `binary mode`_.
|
||||
This is because Requests may attempt to provide the
|
||||
``Content-Length`` header for you, and if it does this value will
|
||||
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
|
||||
|
||||
|
||||
.. _chunk-encoding:
|
||||
|
||||
Chunk-Encoded Requests
|
||||
----------------------
|
||||
@@ -259,15 +333,22 @@ Requests also supports Chunked transfer encoding for outgoing and incoming reque
|
||||
To send a chunk-encoded request, simply provide a generator (or any iterator without
|
||||
a length) for your body::
|
||||
|
||||
|
||||
def gen():
|
||||
yield 'hi'
|
||||
yield 'there'
|
||||
|
||||
requests.post('http://some.url/chunked', data=gen())
|
||||
|
||||
For chunked encoded responses, it's best to iterate over the data using
|
||||
:meth:`Response.iter_content() <requests.models.Response.iter_content>`. In
|
||||
an ideal situation you'll have set ``stream=True`` on the request, in which
|
||||
case you can iterate chunk-by-chunk by calling ``iter_content`` with a chunk
|
||||
size parameter of ``None``. If you want to set a maximum size of the chunk,
|
||||
you can set a chunk size parameter to any integer.
|
||||
|
||||
|
||||
.. _multipart:
|
||||
|
||||
POST Multiple Multipart-Encoded Files
|
||||
-------------------------------------
|
||||
|
||||
@@ -290,6 +371,16 @@ To do that, just set files to a list of tuples of (form_field_name, file_info):
|
||||
...
|
||||
}
|
||||
|
||||
.. warning:: It is strongly recommended that you open files in `binary mode`_.
|
||||
This is because Requests may attempt to provide the
|
||||
``Content-Length`` header for you, and if it does this value will
|
||||
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
|
||||
|
||||
|
||||
.. _event-hooks:
|
||||
|
||||
Event Hooks
|
||||
-----------
|
||||
@@ -329,6 +420,7 @@ Let's print some request method arguments at runtime::
|
||||
http://httpbin.org
|
||||
<Response [200]>
|
||||
|
||||
.. _custom-auth:
|
||||
|
||||
Custom Authentication
|
||||
---------------------
|
||||
@@ -372,7 +464,7 @@ Streaming Requests
|
||||
|
||||
With :class:`requests.Response.iter_lines()` you can easily
|
||||
iterate over streaming APIs such as the `Twitter Streaming
|
||||
API <https://dev.twitter.com/docs/streaming-api>`_. Simply
|
||||
API <https://dev.twitter.com/streaming/overview>`_. Simply
|
||||
set ``stream`` to ``True`` and iterate over the response with
|
||||
:class:`~requests.Response.iter_lines()`::
|
||||
|
||||
@@ -387,6 +479,20 @@ set ``stream`` to ``True`` and iterate over the response with
|
||||
if line:
|
||||
print(json.loads(line))
|
||||
|
||||
.. warning::
|
||||
|
||||
:class:`~requests.Response.iter_lines()` is not reentrant safe.
|
||||
Calling this method multiple times causes some of the received data
|
||||
being lost. In case you need to call it from multiple places, use
|
||||
the resulting iterator object instead::
|
||||
|
||||
lines = r.iter_lines()
|
||||
# Save the first line for later or just skip it
|
||||
first_line = next(lines)
|
||||
for line in lines:
|
||||
print(line)
|
||||
|
||||
.. _proxies:
|
||||
|
||||
Proxies
|
||||
-------
|
||||
@@ -420,8 +526,20 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn
|
||||
"http": "http://user:pass@10.10.1.10:3128/",
|
||||
}
|
||||
|
||||
To give a proxy for a specific scheme and host, use the
|
||||
`scheme://hostname` form for the key. This will match for
|
||||
any request to the given scheme and exact hostname.
|
||||
|
||||
::
|
||||
|
||||
proxies = {
|
||||
"http://10.20.1.128": "http://10.10.1.10:5323",
|
||||
}
|
||||
|
||||
Note that proxy URLs must include the scheme.
|
||||
|
||||
.. _compliance:
|
||||
|
||||
Compliance
|
||||
----------
|
||||
|
||||
@@ -448,6 +566,8 @@ specification in this case. If you require a different encoding, you can
|
||||
manually set the :attr:`Response.encoding <requests.Response.encoding>`
|
||||
property, or use the raw :attr:`Response.content <requests.Response.content>`.
|
||||
|
||||
.. _http-verbs:
|
||||
|
||||
HTTP Verbs
|
||||
----------
|
||||
|
||||
@@ -621,6 +741,8 @@ headers.
|
||||
Excellent. Time to write a Python program that abuses the GitHub API in all
|
||||
kinds of exciting ways, 4995 more times.
|
||||
|
||||
.. _link-headers:
|
||||
|
||||
Link Headers
|
||||
------------
|
||||
|
||||
@@ -643,6 +765,8 @@ Requests will automatically parse these link headers and make them easily consum
|
||||
>>> r.links["last"]
|
||||
{'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
|
||||
|
||||
.. _transport-adapters:
|
||||
|
||||
Transport Adapters
|
||||
------------------
|
||||
|
||||
@@ -711,6 +835,8 @@ SSLv3:
|
||||
.. _`described here`: http://www.kennethreitz.org/essays/the-future-of-python-http
|
||||
.. _`urllib3`: https://github.com/shazow/urllib3
|
||||
|
||||
.. _blocking-or-nonblocking:
|
||||
|
||||
Blocking Or Non-Blocking?
|
||||
-------------------------
|
||||
|
||||
@@ -728,6 +854,8 @@ Two excellent examples are `grequests`_ and `requests-futures`_.
|
||||
.. _`grequests`: https://github.com/kennethreitz/grequests
|
||||
.. _`requests-futures`: https://github.com/ross/requests-futures
|
||||
|
||||
.. _timeouts:
|
||||
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
@@ -765,21 +893,3 @@ coffee.
|
||||
r = requests.get('https://github.com', timeout=None)
|
||||
|
||||
.. _`connect()`: http://linux.die.net/man/2/connect
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
For the sake of security we recommend upgrading certifi frequently!
|
||||
|
||||
.. _certifi: http://certifi.io/
|
||||
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
||||
|
||||
@@ -37,12 +37,12 @@ get all the information we need from this object.
|
||||
Requests' simple API means that all forms of HTTP request are as obvious. For
|
||||
example, this is how you make an HTTP POST request::
|
||||
|
||||
>>> r = requests.post("http://httpbin.org/post")
|
||||
>>> r = requests.post("http://httpbin.org/post", data = {"key":"value"})
|
||||
|
||||
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
|
||||
OPTIONS? These are all just as simple::
|
||||
|
||||
>>> r = requests.put("http://httpbin.org/put")
|
||||
>>> r = requests.put("http://httpbin.org/put", data = {"key":"value"})
|
||||
>>> r = requests.delete("http://httpbin.org/delete")
|
||||
>>> r = requests.head("http://httpbin.org/get")
|
||||
>>> r = requests.options("http://httpbin.org/get")
|
||||
@@ -73,6 +73,12 @@ You can see that the URL has been correctly encoded by printing the URL::
|
||||
Note that any dictionary key whose value is ``None`` will not be added to the
|
||||
URL's query string.
|
||||
|
||||
You can also pass a list of items as a value::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
|
||||
>>> r = requests.get("http://httpbin.org/get", params=payload)
|
||||
>>> print(r.url)
|
||||
http://httpbin.org/get?key1=value1&key2=value2&key2=value3
|
||||
|
||||
Response Content
|
||||
----------------
|
||||
@@ -139,8 +145,14 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::
|
||||
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
|
||||
|
||||
In case the JSON decoding fails, ``r.json`` raises an exception. For example, if
|
||||
the response gets a 401 (Unauthorized), attempting ``r.json`` raises ``ValueError:
|
||||
No JSON object could be decoded``
|
||||
the response gets a 204 (No Content), or if the response contains invalid JSON,
|
||||
attempting ``r.json`` raises ``ValueError: No JSON object could be decoded``.
|
||||
|
||||
It should be noted that the success of the call to ``r.json`` does **not**
|
||||
indicate the success of the response. Some servers may return a JSON object in a
|
||||
failed response (e.g. error details with HTTP 500). Such JSON will be decoded
|
||||
and returned. To check that a request is successful, use
|
||||
``r.raise_for_status()`` or check ``r.status_code`` is what you expect.
|
||||
|
||||
|
||||
Raw Response Content
|
||||
@@ -175,14 +187,22 @@ Custom Headers
|
||||
If you'd like to add HTTP headers to a request, simply pass in a ``dict`` to the
|
||||
``headers`` parameter.
|
||||
|
||||
For example, we didn't specify our content-type in the previous example::
|
||||
For example, we didn't specify our user-agent in the previous example::
|
||||
|
||||
>>> import json
|
||||
>>> url = 'https://api.github.com/some/endpoint'
|
||||
>>> payload = {'some': 'data'}
|
||||
>>> headers = {'content-type': 'application/json'}
|
||||
>>> headers = {'user-agent': 'my-app/0.0.1'}
|
||||
|
||||
>>> r = requests.post(url, data=json.dumps(payload), headers=headers)
|
||||
>>> r = requests.get(url, headers=headers)
|
||||
|
||||
Note: Custom headers are given less precedence than more specific sources of information. For instance:
|
||||
|
||||
* Authorization headers will be overridden if credentials are passed via the ``auth`` parameter or are specified in a ``.netrc`` accessible in the environment.
|
||||
* Authorization headers will be removed if you get redirected off-host.
|
||||
* Proxy-Authorization headers will be overridden by proxy credentials provided in the URL.
|
||||
* Content-Length headers will be overridden when we can determine the length of the content.
|
||||
|
||||
Furthermore, Requests does not change its behavior at all based on which custom headers are specified. The headers are simply passed on into the final request.
|
||||
|
||||
|
||||
More complicated POST requests
|
||||
@@ -215,6 +235,14 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
|
||||
|
||||
>>> r = requests.post(url, data=json.dumps(payload))
|
||||
|
||||
Instead of encoding the ``dict`` yourself, you can also pass it directly using
|
||||
the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically::
|
||||
|
||||
>>> url = 'https://api.github.com/some/endpoint'
|
||||
>>> payload = {'some': 'data'}
|
||||
|
||||
>>> r = requests.post(url, json=payload)
|
||||
|
||||
|
||||
POST a Multipart-Encoded File
|
||||
-----------------------------
|
||||
@@ -234,7 +262,7 @@ Requests makes it simple to upload Multipart-encoded files::
|
||||
...
|
||||
}
|
||||
|
||||
You can set the filename, content_type and headers explicitly:
|
||||
You can set the filename, content_type and headers explicitly::
|
||||
|
||||
>>> url = 'http://httpbin.org/post'
|
||||
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
|
||||
@@ -268,11 +296,19 @@ In the event you are posting a very large file as a ``multipart/form-data``
|
||||
request, you may want to stream the request. By default, ``requests`` does not
|
||||
support this, but there is a separate package which does -
|
||||
``requests-toolbelt``. You should read `the toolbelt's documentation
|
||||
<https://toolbelt.rtfd.org>`_ for more details about how to use it.
|
||||
<https://toolbelt.readthedocs.org>`_ for more details about how to use it.
|
||||
|
||||
For sending multiple files in one request refer to the :ref:`advanced <advanced>`
|
||||
section.
|
||||
|
||||
.. warning:: It is strongly recommended that you open files in `binary mode`_.
|
||||
This is because Requests may attempt to provide the
|
||||
``Content-Length`` header for you, and if it does this value will
|
||||
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
|
||||
|
||||
|
||||
Response Status Codes
|
||||
---------------------
|
||||
@@ -340,6 +376,15 @@ So, we can access the headers using any capitalization we want::
|
||||
>>> r.headers.get('content-type')
|
||||
'application/json'
|
||||
|
||||
It is also special in that the server could have sent the same header multiple
|
||||
times with different values, but requests combines them so they can be
|
||||
represented in the dictionary within a single mapping, as per
|
||||
`RFC 7230 <http://tools.ietf.org/html/rfc7230#section-3.2>`_:
|
||||
|
||||
A recipient MAY combine multiple header fields with the same field name
|
||||
into one "field-name: field-value" pair, without changing the semantics
|
||||
of the message, by appending each subsequent field value to the combined
|
||||
field value in order, separated by a comma.
|
||||
|
||||
Cookies
|
||||
-------
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# /
|
||||
|
||||
"""
|
||||
requests HTTP library
|
||||
Requests HTTP library
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Requests is an HTTP library, written in Python, for human beings. Basic GET
|
||||
@@ -36,17 +36,17 @@ usage:
|
||||
The other HTTP methods are supported - see `requests.api`. Full documentation
|
||||
is at <http://python-requests.org>.
|
||||
|
||||
:copyright: (c) 2014 by Kenneth Reitz.
|
||||
:copyright: (c) 2015 by Kenneth Reitz.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '2.4.3'
|
||||
__build__ = 0x020403
|
||||
__version__ = '2.9.0'
|
||||
__build__ = 0x020900
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2014 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2015 Kenneth Reitz'
|
||||
|
||||
# Attempt to enable urllib3's SNI support, if possible
|
||||
try:
|
||||
@@ -62,7 +62,8 @@ from .sessions import session, Session
|
||||
from .status_codes import codes
|
||||
from .exceptions import (
|
||||
RequestException, Timeout, URLRequired,
|
||||
TooManyRedirects, HTTPError, ConnectionError
|
||||
TooManyRedirects, HTTPError, ConnectionError,
|
||||
FileModeWarning,
|
||||
)
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
@@ -75,3 +76,8 @@ except ImportError:
|
||||
pass
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
import warnings
|
||||
|
||||
# FileModeWarnings go off per the default.
|
||||
warnings.simplefilter('default', FileModeWarning, append=True)
|
||||
|
||||
@@ -8,32 +8,38 @@ This module contains the transport adapters that Requests uses to define
|
||||
and maintain connections.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
|
||||
from .models import Response
|
||||
from .packages.urllib3 import Retry
|
||||
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .packages.urllib3.util import Timeout as TimeoutSauce
|
||||
from .packages.urllib3.util.retry import Retry
|
||||
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)
|
||||
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
|
||||
select_proxy)
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .packages.urllib3.exceptions import ClosedPoolError
|
||||
from .packages.urllib3.exceptions import ConnectTimeoutError
|
||||
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
||||
from .packages.urllib3.exceptions import MaxRetryError
|
||||
from .packages.urllib3.exceptions import NewConnectionError
|
||||
from .packages.urllib3.exceptions import ProxyError as _ProxyError
|
||||
from .packages.urllib3.exceptions import ProtocolError
|
||||
from .packages.urllib3.exceptions import ReadTimeoutError
|
||||
from .packages.urllib3.exceptions import SSLError as _SSLError
|
||||
from .packages.urllib3.exceptions import ResponseError
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
||||
ProxyError)
|
||||
ProxyError, RetryError)
|
||||
from .auth import _basic_auth_str
|
||||
|
||||
DEFAULT_POOLBLOCK = False
|
||||
DEFAULT_POOLSIZE = 10
|
||||
DEFAULT_RETRIES = 0
|
||||
DEFAULT_POOL_TIMEOUT = None
|
||||
|
||||
|
||||
class BaseAdapter(object):
|
||||
@@ -60,8 +66,12 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param pool_connections: The number of urllib3 connection pools to cache.
|
||||
:param pool_maxsize: The maximum number of connections to save in the pool.
|
||||
:param int max_retries: The maximum number of retries each connection
|
||||
should attempt. Note, this applies only to failed connections and
|
||||
timeouts, never to requests where the server returns a response.
|
||||
should attempt. Note, this applies only to failed DNS lookups, socket
|
||||
connections and connection timeouts, never to requests where data has
|
||||
made it to the server. By default, Requests does not retry failed
|
||||
connections. If you need granular control over the conditions under
|
||||
which we retry a request, import urllib3's ``Retry`` class and pass
|
||||
that instead.
|
||||
:param pool_block: Whether the connection pool should block for connections.
|
||||
|
||||
Usage::
|
||||
@@ -77,7 +87,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
|
||||
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
|
||||
pool_block=DEFAULT_POOLBLOCK):
|
||||
self.max_retries = max_retries
|
||||
if max_retries == DEFAULT_RETRIES:
|
||||
self.max_retries = Retry(0, read=False)
|
||||
else:
|
||||
self.max_retries = Retry.from_int(max_retries)
|
||||
self.config = {}
|
||||
self.proxy_manager = {}
|
||||
|
||||
@@ -95,7 +108,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
|
||||
# because self.poolmanager uses a lambda function, which isn't pickleable.
|
||||
# self.poolmanager uses a lambda function, which isn't pickleable.
|
||||
self.proxy_manager = {}
|
||||
self.config = {}
|
||||
|
||||
@@ -123,7 +136,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
self._pool_block = block
|
||||
|
||||
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
|
||||
block=block, **pool_kwargs)
|
||||
block=block, strict=True, **pool_kwargs)
|
||||
|
||||
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
||||
"""Return urllib3 ProxyManager for the given proxy.
|
||||
@@ -173,10 +186,15 @@ class HTTPAdapter(BaseAdapter):
|
||||
raise Exception("Could not find a suitable SSL CA certificate bundle.")
|
||||
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
conn.ca_certs = cert_loc
|
||||
|
||||
if not os.path.isdir(cert_loc):
|
||||
conn.ca_certs = cert_loc
|
||||
else:
|
||||
conn.ca_cert_dir = cert_loc
|
||||
else:
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
conn.ca_cert_dir = None
|
||||
|
||||
if cert:
|
||||
if not isinstance(cert, basestring):
|
||||
@@ -229,8 +247,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param url: The URL to connect to.
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
"""
|
||||
proxies = proxies or {}
|
||||
proxy = proxies.get(urlparse(url.lower()).scheme)
|
||||
proxy = select_proxy(url, proxies)
|
||||
|
||||
if proxy:
|
||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||
@@ -263,12 +280,10 @@ class HTTPAdapter(BaseAdapter):
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
:param proxies: A dictionary of schemes to proxy URLs.
|
||||
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
|
||||
"""
|
||||
proxies = proxies or {}
|
||||
proxy = select_proxy(request.url, proxies)
|
||||
scheme = urlparse(request.url).scheme
|
||||
proxy = proxies.get(scheme)
|
||||
|
||||
if proxy and scheme != 'https':
|
||||
url = urldefragauth(request.url)
|
||||
else:
|
||||
@@ -301,7 +316,6 @@ class HTTPAdapter(BaseAdapter):
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param proxies: The url of the proxy being used for this request.
|
||||
:param kwargs: Optional additional keyword arguments.
|
||||
"""
|
||||
headers = {}
|
||||
username, password = get_auth_from_url(proxy)
|
||||
@@ -318,8 +332,8 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
:param stream: (optional) Whether to stream the request content.
|
||||
:param timeout: (optional) How long to wait for the server to send
|
||||
data before giving up, as a float, or a (`connect timeout, read
|
||||
timeout <user/advanced.html#timeouts>`_) tuple.
|
||||
data before giving up, as a float, or a :ref:`(connect timeout,
|
||||
read timeout) <timeouts>` tuple.
|
||||
:type timeout: float or tuple
|
||||
:param verify: (optional) Whether to verify SSL certificates.
|
||||
:param cert: (optional) Any user-provided SSL certificate to be trusted.
|
||||
@@ -358,7 +372,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
assert_same_host=False,
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=Retry(self.max_retries, read=False),
|
||||
retries=self.max_retries,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
@@ -367,7 +381,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
if hasattr(conn, 'proxy_pool'):
|
||||
conn = conn.proxy_pool
|
||||
|
||||
low_conn = conn._get_conn(timeout=timeout)
|
||||
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
|
||||
|
||||
try:
|
||||
low_conn.putrequest(request.method,
|
||||
@@ -386,7 +400,15 @@ class HTTPAdapter(BaseAdapter):
|
||||
low_conn.send(b'\r\n')
|
||||
low_conn.send(b'0\r\n\r\n')
|
||||
|
||||
r = low_conn.getresponse()
|
||||
# Receive the response from the server
|
||||
try:
|
||||
# For Python 2.7+ versions, use buffering of HTTP
|
||||
# responses
|
||||
r = low_conn.getresponse(buffering=True)
|
||||
except TypeError:
|
||||
# For compatibility with Python 2.6 versions and back
|
||||
r = low_conn.getresponse()
|
||||
|
||||
resp = HTTPResponse.from_httplib(
|
||||
r,
|
||||
pool=conn,
|
||||
@@ -399,17 +421,22 @@ class HTTPAdapter(BaseAdapter):
|
||||
# Then, reraise so that we can handle the actual exception.
|
||||
low_conn.close()
|
||||
raise
|
||||
else:
|
||||
# All is well, return the connection to the pool.
|
||||
conn._put_conn(low_conn)
|
||||
|
||||
except (ProtocolError, socket.error) as err:
|
||||
raise ConnectionError(err, request=request)
|
||||
|
||||
except MaxRetryError as e:
|
||||
if isinstance(e.reason, ConnectTimeoutError):
|
||||
raise ConnectTimeout(e, request=request)
|
||||
# TODO: Remove this in 3.0.0: see #2811
|
||||
if not isinstance(e.reason, NewConnectionError):
|
||||
raise ConnectTimeout(e, request=request)
|
||||
|
||||
if isinstance(e.reason, ResponseError):
|
||||
raise RetryError(e, request=request)
|
||||
|
||||
raise ConnectionError(e, request=request)
|
||||
|
||||
except ClosedPoolError as e:
|
||||
raise ConnectionError(e, request=request)
|
||||
|
||||
except _ProxyError as e:
|
||||
|
||||
@@ -16,7 +16,6 @@ from . import sessions
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -28,15 +27,17 @@ def request(method, url, **kwargs):
|
||||
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload.
|
||||
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) How long to wait for the server to send data
|
||||
before giving up, as a float, or a (`connect timeout, read timeout
|
||||
<user/advanced.html#timeouts>`_) tuple.
|
||||
before giving up, as a float, or a :ref:`(connect timeout, read
|
||||
timeout) <timeouts>` tuple.
|
||||
:type timeout: float or tuple
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:type allow_redirects: bool
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``.
|
||||
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
|
||||
Usage::
|
||||
|
||||
@@ -45,26 +46,34 @@ def request(method, url, **kwargs):
|
||||
<Response [200]>
|
||||
"""
|
||||
|
||||
session = sessions.Session()
|
||||
return session.request(method=method, url=url, **kwargs)
|
||||
# By using the 'with' statement we are sure the session is closed, thus we
|
||||
# avoid leaving sockets open which can trigger a ResourceWarning in some
|
||||
# cases, and look like a memory leak in others.
|
||||
with sessions.Session() as session:
|
||||
return session.request(method=method, url=url, **kwargs)
|
||||
|
||||
|
||||
def get(url, **kwargs):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
def get(url, params=None, **kwargs):
|
||||
"""Sends a GET request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('get', url, **kwargs)
|
||||
return request('get', url, params=params, **kwargs)
|
||||
|
||||
|
||||
def options(url, **kwargs):
|
||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||
"""Sends a OPTIONS request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -72,10 +81,12 @@ def options(url, **kwargs):
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
"""Sends a HEAD request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
@@ -83,44 +94,52 @@ def head(url, **kwargs):
|
||||
|
||||
|
||||
def post(url, data=None, json=None, **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
"""Sends a POST request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, 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 \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, json=json, **kwargs)
|
||||
|
||||
|
||||
def put(url, data=None, **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
"""Sends a PUT request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(url, data=None, **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
"""Sends a PATCH request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
"""Sends a DELETE request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
return request('delete', url, **kwargs)
|
||||
|
||||
@@ -11,12 +11,14 @@ import os
|
||||
import re
|
||||
import time
|
||||
import hashlib
|
||||
import threading
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
from .compat import urlparse, str
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .utils import parse_dict_header, to_native_string
|
||||
from .status_codes import codes
|
||||
|
||||
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
||||
CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
|
||||
@@ -62,18 +64,26 @@ class HTTPDigestAuth(AuthBase):
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.last_nonce = ''
|
||||
self.nonce_count = 0
|
||||
self.chal = {}
|
||||
self.pos = None
|
||||
# Keep state in per-thread local storage
|
||||
self._thread_local = threading.local()
|
||||
|
||||
def init_per_thread_state(self):
|
||||
# Ensure state is initialized just once per-thread
|
||||
if not hasattr(self._thread_local, 'init'):
|
||||
self._thread_local.init = True
|
||||
self._thread_local.last_nonce = ''
|
||||
self._thread_local.nonce_count = 0
|
||||
self._thread_local.chal = {}
|
||||
self._thread_local.pos = None
|
||||
self._thread_local.num_401_calls = None
|
||||
|
||||
def build_digest_header(self, method, url):
|
||||
|
||||
realm = self.chal['realm']
|
||||
nonce = self.chal['nonce']
|
||||
qop = self.chal.get('qop')
|
||||
algorithm = self.chal.get('algorithm')
|
||||
opaque = self.chal.get('opaque')
|
||||
realm = self._thread_local.chal['realm']
|
||||
nonce = self._thread_local.chal['nonce']
|
||||
qop = self._thread_local.chal.get('qop')
|
||||
algorithm = self._thread_local.chal.get('algorithm')
|
||||
opaque = self._thread_local.chal.get('opaque')
|
||||
|
||||
if algorithm is None:
|
||||
_algorithm = 'MD5'
|
||||
@@ -101,7 +111,8 @@ class HTTPDigestAuth(AuthBase):
|
||||
# XXX not implemented yet
|
||||
entdig = None
|
||||
p_parsed = urlparse(url)
|
||||
path = p_parsed.path
|
||||
#: path is request-uri defined in RFC 2616 which should not be empty
|
||||
path = p_parsed.path or "/"
|
||||
if p_parsed.query:
|
||||
path += '?' + p_parsed.query
|
||||
|
||||
@@ -111,30 +122,32 @@ class HTTPDigestAuth(AuthBase):
|
||||
HA1 = hash_utf8(A1)
|
||||
HA2 = hash_utf8(A2)
|
||||
|
||||
if nonce == self.last_nonce:
|
||||
self.nonce_count += 1
|
||||
if nonce == self._thread_local.last_nonce:
|
||||
self._thread_local.nonce_count += 1
|
||||
else:
|
||||
self.nonce_count = 1
|
||||
ncvalue = '%08x' % self.nonce_count
|
||||
s = str(self.nonce_count).encode('utf-8')
|
||||
self._thread_local.nonce_count = 1
|
||||
ncvalue = '%08x' % self._thread_local.nonce_count
|
||||
s = str(self._thread_local.nonce_count).encode('utf-8')
|
||||
s += nonce.encode('utf-8')
|
||||
s += time.ctime().encode('utf-8')
|
||||
s += os.urandom(8)
|
||||
|
||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, HA2)
|
||||
if _algorithm == 'MD5-SESS':
|
||||
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
|
||||
|
||||
if qop is None:
|
||||
if not qop:
|
||||
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
|
||||
elif qop == 'auth' or 'auth' in qop.split(','):
|
||||
noncebit = "%s:%s:%s:%s:%s" % (
|
||||
nonce, ncvalue, cnonce, 'auth', HA2
|
||||
)
|
||||
respdig = KD(HA1, noncebit)
|
||||
else:
|
||||
# XXX handle auth-int.
|
||||
return None
|
||||
|
||||
self.last_nonce = nonce
|
||||
self._thread_local.last_nonce = nonce
|
||||
|
||||
# XXX should the partial digests be encoded too?
|
||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||
@@ -150,26 +163,30 @@ class HTTPDigestAuth(AuthBase):
|
||||
|
||||
return 'Digest %s' % (base)
|
||||
|
||||
def handle_redirect(self, r, **kwargs):
|
||||
"""Reset num_401_calls counter on redirects."""
|
||||
if r.is_redirect:
|
||||
self._thread_local.num_401_calls = 1
|
||||
|
||||
def handle_401(self, r, **kwargs):
|
||||
"""Takes the given response and tries digest-auth, if needed."""
|
||||
|
||||
if self.pos is not None:
|
||||
if self._thread_local.pos is not None:
|
||||
# Rewind the file position indicator of the body to where
|
||||
# it was to resend the request.
|
||||
r.request.body.seek(self.pos)
|
||||
num_401_calls = getattr(self, 'num_401_calls', 1)
|
||||
r.request.body.seek(self._thread_local.pos)
|
||||
s_auth = r.headers.get('www-authenticate', '')
|
||||
|
||||
if 'digest' in s_auth.lower() and num_401_calls < 2:
|
||||
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
|
||||
|
||||
setattr(self, 'num_401_calls', num_401_calls + 1)
|
||||
self._thread_local.num_401_calls += 1
|
||||
pat = re.compile(r'digest ', flags=re.IGNORECASE)
|
||||
self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
||||
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
||||
|
||||
# Consume content and release the original connection
|
||||
# to allow our new request to reuse the same one.
|
||||
r.content
|
||||
r.raw.release_conn()
|
||||
r.close()
|
||||
prep = r.request.copy()
|
||||
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
|
||||
prep.prepare_cookies(prep._cookies)
|
||||
@@ -182,16 +199,25 @@ class HTTPDigestAuth(AuthBase):
|
||||
|
||||
return _r
|
||||
|
||||
setattr(self, 'num_401_calls', 1)
|
||||
self._thread_local.num_401_calls = 1
|
||||
return r
|
||||
|
||||
def __call__(self, r):
|
||||
# Initialize per-thread state, if needed
|
||||
self.init_per_thread_state()
|
||||
# If we have a saved nonce, skip the 401
|
||||
if self.last_nonce:
|
||||
if self._thread_local.last_nonce:
|
||||
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
|
||||
try:
|
||||
self.pos = r.body.tell()
|
||||
self._thread_local.pos = r.body.tell()
|
||||
except AttributeError:
|
||||
pass
|
||||
# In the case of HTTPDigestAuth being reused and the body of
|
||||
# the previous request was a file-like object, pos has the
|
||||
# file position of the previous body. Ensure it's set to
|
||||
# None.
|
||||
self._thread_local.pos = None
|
||||
r.register_hook('response', self.handle_401)
|
||||
r.register_hook('response', self.handle_redirect)
|
||||
self._thread_local.num_401_calls = 1
|
||||
|
||||
return r
|
||||
|
||||
2180
requests/cacert.pem
2180
requests/cacert.pem
File diff suppressed because it is too large
Load Diff
@@ -21,62 +21,10 @@ is_py2 = (_ver[0] == 2)
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
#: Python 3.0.x
|
||||
is_py30 = (is_py3 and _ver[1] == 0)
|
||||
|
||||
#: Python 3.1.x
|
||||
is_py31 = (is_py3 and _ver[1] == 1)
|
||||
|
||||
#: Python 3.2.x
|
||||
is_py32 = (is_py3 and _ver[1] == 2)
|
||||
|
||||
#: Python 3.3.x
|
||||
is_py33 = (is_py3 and _ver[1] == 3)
|
||||
|
||||
#: Python 3.4.x
|
||||
is_py34 = (is_py3 and _ver[1] == 4)
|
||||
|
||||
#: Python 2.7.x
|
||||
is_py27 = (is_py2 and _ver[1] == 7)
|
||||
|
||||
#: Python 2.6.x
|
||||
is_py26 = (is_py2 and _ver[1] == 6)
|
||||
|
||||
#: Python 2.5.x
|
||||
is_py25 = (is_py2 and _ver[1] == 5)
|
||||
|
||||
#: Python 2.4.x
|
||||
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
|
||||
|
||||
|
||||
# ---------
|
||||
# Platforms
|
||||
# ---------
|
||||
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version.lower()
|
||||
|
||||
is_pypy = ('pypy' in _ver)
|
||||
is_jython = ('jython' in _ver)
|
||||
is_ironpython = ('iron' in _ver)
|
||||
|
||||
# Assume CPython, if nothing else.
|
||||
is_cpython = not any((is_pypy, is_jython, is_ironpython))
|
||||
|
||||
# Windows-based system.
|
||||
is_windows = 'win32' in str(sys.platform).lower()
|
||||
|
||||
# Standard Linux 2+ system.
|
||||
is_linux = ('linux' in str(sys.platform).lower())
|
||||
is_osx = ('darwin' in str(sys.platform).lower())
|
||||
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
|
||||
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except (ImportError, SyntaxError):
|
||||
# simplejson does not support Python 3.2, it thows a SyntaxError
|
||||
# simplejson does not support Python 3.2, it throws a SyntaxError
|
||||
# because of u'...' Unicode literals.
|
||||
import json
|
||||
|
||||
@@ -99,7 +47,6 @@ if is_py2:
|
||||
basestring = basestring
|
||||
numeric_types = (int, long, float)
|
||||
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
|
||||
from urllib.request import parse_http_list, getproxies, proxy_bypass
|
||||
|
||||
@@ -6,6 +6,7 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests.
|
||||
requests.utils imports from here, so be careful with imports.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import time
|
||||
import collections
|
||||
from .compat import cookielib, urlparse, urlunparse, Morsel
|
||||
@@ -142,10 +143,13 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
||||
"""
|
||||
clearables = []
|
||||
for cookie in cookiejar:
|
||||
if cookie.name == name:
|
||||
if domain is None or domain == cookie.domain:
|
||||
if path is None or path == cookie.path:
|
||||
clearables.append((cookie.domain, cookie.path, cookie.name))
|
||||
if cookie.name != name:
|
||||
continue
|
||||
if domain is not None and domain != cookie.domain:
|
||||
continue
|
||||
if path is not None and path != cookie.path:
|
||||
continue
|
||||
clearables.append((cookie.domain, cookie.path, cookie.name))
|
||||
|
||||
for domain, path, name in clearables:
|
||||
cookiejar.clear(domain, path, name)
|
||||
@@ -157,26 +161,28 @@ class CookieConflictError(RuntimeError):
|
||||
|
||||
|
||||
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
|
||||
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
|
||||
interface.
|
||||
|
||||
This is the CookieJar we create by default for requests and sessions that
|
||||
don't specify one, since some clients may expect response.cookies and
|
||||
session.cookies to support dict operations.
|
||||
|
||||
Don't use the dict interface internally; it's just for compatibility with
|
||||
with external client code. All `requests` code should work out of the box
|
||||
with externally provided instances of CookieJar, e.g., LWPCookieJar and
|
||||
FileCookieJar.
|
||||
|
||||
Caution: dictionary operations that are normally O(1) may be O(n).
|
||||
Requests does not use the dict interface internally; it's just for
|
||||
compatibility with external client code. All requests code should work
|
||||
out of the box with externally provided instances of ``CookieJar``, e.g.
|
||||
``LWPCookieJar`` and ``FileCookieJar``.
|
||||
|
||||
Unlike a regular CookieJar, this class is pickleable.
|
||||
"""
|
||||
|
||||
.. warning:: dictionary operations that are normally O(1) may be O(n).
|
||||
"""
|
||||
def get(self, name, default=None, domain=None, path=None):
|
||||
"""Dict-like get() that also supports optional domain and path args in
|
||||
order to resolve naming collisions from using one cookie jar over
|
||||
multiple domains. Caution: operation is O(n), not O(1)."""
|
||||
multiple domains.
|
||||
|
||||
.. warning:: operation is O(n), not O(1)."""
|
||||
try:
|
||||
return self._find_no_duplicates(name, domain, path)
|
||||
except KeyError:
|
||||
@@ -199,37 +205,38 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
return c
|
||||
|
||||
def iterkeys(self):
|
||||
"""Dict-like iterkeys() that returns an iterator of names of cookies from the jar.
|
||||
See itervalues() and iteritems()."""
|
||||
"""Dict-like iterkeys() that returns an iterator of names of cookies
|
||||
from the jar. See itervalues() and iteritems()."""
|
||||
for cookie in iter(self):
|
||||
yield cookie.name
|
||||
|
||||
def keys(self):
|
||||
"""Dict-like keys() that returns a list of names of cookies from the jar.
|
||||
See values() and items()."""
|
||||
"""Dict-like keys() that returns a list of names of cookies from the
|
||||
jar. See values() and items()."""
|
||||
return list(self.iterkeys())
|
||||
|
||||
def itervalues(self):
|
||||
"""Dict-like itervalues() that returns an iterator of values of cookies from the jar.
|
||||
See iterkeys() and iteritems()."""
|
||||
"""Dict-like itervalues() that returns an iterator of values of cookies
|
||||
from the jar. See iterkeys() and iteritems()."""
|
||||
for cookie in iter(self):
|
||||
yield cookie.value
|
||||
|
||||
def values(self):
|
||||
"""Dict-like values() that returns a list of values of cookies from the jar.
|
||||
See keys() and items()."""
|
||||
"""Dict-like values() that returns a list of values of cookies from the
|
||||
jar. See keys() and items()."""
|
||||
return list(self.itervalues())
|
||||
|
||||
def iteritems(self):
|
||||
"""Dict-like iteritems() that returns an iterator of name-value tuples from the jar.
|
||||
See iterkeys() and itervalues()."""
|
||||
"""Dict-like iteritems() that returns an iterator of name-value tuples
|
||||
from the jar. See iterkeys() and itervalues()."""
|
||||
for cookie in iter(self):
|
||||
yield cookie.name, cookie.value
|
||||
|
||||
def items(self):
|
||||
"""Dict-like items() that returns a list of name-value tuples from the jar.
|
||||
See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
|
||||
and get a vanilla python dict of key value pairs."""
|
||||
"""Dict-like items() that returns a list of name-value tuples from the
|
||||
jar. See keys() and values(). Allows client-code to call
|
||||
``dict(RequestsCookieJar)`` and get a vanilla python dict of key value
|
||||
pairs."""
|
||||
return list(self.iteritems())
|
||||
|
||||
def list_domains(self):
|
||||
@@ -259,8 +266,9 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
return False # there is only one domain in jar
|
||||
|
||||
def get_dict(self, domain=None, path=None):
|
||||
"""Takes as an argument an optional domain and path and returns a plain old
|
||||
Python dict of name-value pairs of cookies that meet the requirements."""
|
||||
"""Takes as an argument an optional domain and path and returns a plain
|
||||
old Python dict of name-value pairs of cookies that meet the
|
||||
requirements."""
|
||||
dictionary = {}
|
||||
for cookie in iter(self):
|
||||
if (domain is None or cookie.domain == domain) and (path is None
|
||||
@@ -269,21 +277,24 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
return dictionary
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""Dict-like __getitem__() for compatibility with client code. Throws exception
|
||||
if there are more than one cookie with name. In that case, use the more
|
||||
explicit get() method instead. Caution: operation is O(n), not O(1)."""
|
||||
"""Dict-like __getitem__() for compatibility with client code. Throws
|
||||
exception if there are more than one cookie with name. In that case,
|
||||
use the more explicit get() method instead.
|
||||
|
||||
.. warning:: operation is O(n), not O(1)."""
|
||||
|
||||
return self._find_no_duplicates(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
"""Dict-like __setitem__ for compatibility with client code. Throws exception
|
||||
if there is already a cookie of that name in the jar. In that case, use the more
|
||||
explicit set() method instead."""
|
||||
"""Dict-like __setitem__ for compatibility with client code. Throws
|
||||
exception if there is already a cookie of that name in the jar. In that
|
||||
case, use the more explicit set() method instead."""
|
||||
|
||||
self.set(name, value)
|
||||
|
||||
def __delitem__(self, name):
|
||||
"""Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
|
||||
"""Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
|
||||
``remove_cookie_by_name()``."""
|
||||
remove_cookie_by_name(self, name)
|
||||
|
||||
def set_cookie(self, cookie, *args, **kwargs):
|
||||
@@ -295,15 +306,16 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""Updates this jar with cookies from another CookieJar or dict-like"""
|
||||
if isinstance(other, cookielib.CookieJar):
|
||||
for cookie in other:
|
||||
self.set_cookie(cookie)
|
||||
self.set_cookie(copy.copy(cookie))
|
||||
else:
|
||||
super(RequestsCookieJar, self).update(other)
|
||||
|
||||
def _find(self, name, domain=None, path=None):
|
||||
"""Requests uses this method internally to get cookie values. Takes as args name
|
||||
and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
|
||||
_find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
|
||||
if there are conflicting cookies."""
|
||||
"""Requests uses this method internally to get cookie values. Takes as
|
||||
args name and optional domain and path. Returns a cookie.value. If
|
||||
there are conflicting cookies, _find arbitrarily chooses one. See
|
||||
_find_no_duplicates if you want an exception thrown if there are
|
||||
conflicting cookies."""
|
||||
for cookie in iter(self):
|
||||
if cookie.name == name:
|
||||
if domain is None or cookie.domain == domain:
|
||||
@@ -313,10 +325,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
|
||||
|
||||
def _find_no_duplicates(self, name, domain=None, path=None):
|
||||
"""__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
|
||||
Takes as args name and optional domain and path. Returns a cookie.value.
|
||||
Throws KeyError if cookie is not found and CookieConflictError if there are
|
||||
multiple cookies that match name and optionally domain and path."""
|
||||
"""Both ``__get_item__`` and ``get`` call this function: it's never
|
||||
used elsewhere in Requests. Takes as args name and optional domain and
|
||||
path. Returns a cookie.value. Throws KeyError if cookie is not found
|
||||
and CookieConflictError if there are multiple cookies that match name
|
||||
and optionally domain and path."""
|
||||
toReturn = None
|
||||
for cookie in iter(self):
|
||||
if cookie.name == name:
|
||||
@@ -350,6 +363,21 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
return new_cj
|
||||
|
||||
|
||||
def _copy_cookie_jar(jar):
|
||||
if jar is None:
|
||||
return None
|
||||
|
||||
if hasattr(jar, 'copy'):
|
||||
# We're dealing with an instance of RequestsCookieJar
|
||||
return jar.copy()
|
||||
# We're dealing with a generic CookieJar instance
|
||||
new_jar = copy.copy(jar)
|
||||
new_jar.clear()
|
||||
for cookie in jar:
|
||||
new_jar.set_cookie(copy.copy(cookie))
|
||||
return new_jar
|
||||
|
||||
|
||||
def create_cookie(name, value, **kwargs):
|
||||
"""Make a cookie from underspecified parameters.
|
||||
|
||||
@@ -390,11 +418,14 @@ def morsel_to_cookie(morsel):
|
||||
|
||||
expires = None
|
||||
if morsel['max-age']:
|
||||
expires = time.time() + morsel['max-age']
|
||||
try:
|
||||
expires = int(time.time() + int(morsel['max-age']))
|
||||
except ValueError:
|
||||
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
|
||||
elif morsel['expires']:
|
||||
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
|
||||
expires = time.mktime(
|
||||
time.strptime(morsel['expires'], time_template)) - time.timezone
|
||||
expires = int(time.mktime(
|
||||
time.strptime(morsel['expires'], time_template)) - time.timezone)
|
||||
return create_cookie(
|
||||
comment=morsel['comment'],
|
||||
comment_url=bool(morsel['comment']),
|
||||
@@ -440,7 +471,7 @@ def merge_cookies(cookiejar, cookies):
|
||||
"""
|
||||
if not isinstance(cookiejar, cookielib.CookieJar):
|
||||
raise ValueError('You can only merge into CookieJar')
|
||||
|
||||
|
||||
if isinstance(cookies, dict):
|
||||
cookiejar = cookiejar_from_dict(
|
||||
cookies, cookiejar=cookiejar, overwrite=False)
|
||||
|
||||
@@ -90,5 +90,25 @@ class ChunkedEncodingError(RequestException):
|
||||
class ContentDecodingError(RequestException, BaseHTTPError):
|
||||
"""Failed to decode response content"""
|
||||
|
||||
|
||||
class StreamConsumedError(RequestException, TypeError):
|
||||
"""The content for this response was already consumed"""
|
||||
|
||||
|
||||
class RetryError(RequestException):
|
||||
"""Custom retries logic failed"""
|
||||
|
||||
|
||||
# Warnings
|
||||
|
||||
|
||||
class RequestsWarning(Warning):
|
||||
"""Base warning for Requests."""
|
||||
pass
|
||||
|
||||
|
||||
class FileModeWarning(RequestsWarning, DeprecationWarning):
|
||||
"""
|
||||
A file was opened in text mode, but Requests determined its binary length.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -12,34 +12,23 @@ Available hooks:
|
||||
The response generated from a Request.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
HOOKS = ['response']
|
||||
|
||||
|
||||
def default_hooks():
|
||||
hooks = {}
|
||||
for event in HOOKS:
|
||||
hooks[event] = []
|
||||
return hooks
|
||||
return dict((event, []) for event in HOOKS)
|
||||
|
||||
# TODO: response is the only one
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data, **kwargs):
|
||||
"""Dispatches a hook dictionary on a given piece of data."""
|
||||
|
||||
hooks = hooks or dict()
|
||||
|
||||
if key in hooks:
|
||||
hooks = hooks.get(key)
|
||||
|
||||
hooks = hooks.get(key)
|
||||
if hooks:
|
||||
if hasattr(hooks, '__call__'):
|
||||
hooks = [hooks]
|
||||
|
||||
for hook in hooks:
|
||||
_hook_data = hook(hook_data, **kwargs)
|
||||
if _hook_data is not None:
|
||||
hook_data = _hook_data
|
||||
|
||||
return hook_data
|
||||
|
||||
@@ -15,23 +15,23 @@ from .hooks import default_hooks
|
||||
from .structures import CaseInsensitiveDict
|
||||
|
||||
from .auth import HTTPBasicAuth
|
||||
from .cookies import cookiejar_from_dict, get_cookie_header
|
||||
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
|
||||
from .packages.urllib3.fields import RequestField
|
||||
from .packages.urllib3.filepost import encode_multipart_formdata
|
||||
from .packages.urllib3.util import parse_url
|
||||
from .packages.urllib3.exceptions import (
|
||||
DecodeError, ReadTimeoutError, ProtocolError)
|
||||
DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
|
||||
from .exceptions import (
|
||||
HTTPError, RequestException, MissingSchema, InvalidURL,
|
||||
ChunkedEncodingError, ContentDecodingError, ConnectionError,
|
||||
StreamConsumedError)
|
||||
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
|
||||
ContentDecodingError, ConnectionError, StreamConsumedError)
|
||||
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, to_native_string)
|
||||
from .compat import (
|
||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
|
||||
is_py2, chardet, json, builtin_str, basestring)
|
||||
is_py2, chardet, builtin_str, basestring)
|
||||
from .compat import json as complexjson
|
||||
from .status_codes import codes
|
||||
|
||||
#: The set of HTTP status codes that indicate an automatically
|
||||
@@ -43,12 +43,11 @@ REDIRECT_STATI = (
|
||||
codes.temporary_redirect, # 307
|
||||
codes.permanent_redirect, # 308
|
||||
)
|
||||
|
||||
DEFAULT_REDIRECT_LIMIT = 30
|
||||
CONTENT_CHUNK_SIZE = 10 * 1024
|
||||
ITER_CHUNK_SIZE = 512
|
||||
|
||||
json_dumps = json.dumps
|
||||
|
||||
|
||||
class RequestEncodingMixin(object):
|
||||
@property
|
||||
@@ -82,7 +81,7 @@ class RequestEncodingMixin(object):
|
||||
"""
|
||||
|
||||
if isinstance(data, (str, bytes)):
|
||||
return data
|
||||
return to_native_string(data)
|
||||
elif hasattr(data, 'read'):
|
||||
return data
|
||||
elif hasattr(data, '__iter__'):
|
||||
@@ -144,13 +143,13 @@ class RequestEncodingMixin(object):
|
||||
else:
|
||||
fn = guess_filename(v) or k
|
||||
fp = v
|
||||
if isinstance(fp, str):
|
||||
fp = StringIO(fp)
|
||||
if isinstance(fp, bytes):
|
||||
fp = BytesIO(fp)
|
||||
|
||||
rf = RequestField(name=k, data=fp.read(),
|
||||
filename=fn, headers=fh)
|
||||
if isinstance(fp, (str, bytes, bytearray)):
|
||||
fdata = fp
|
||||
else:
|
||||
fdata = fp.read()
|
||||
|
||||
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
|
||||
rf.make_multipart(content_type=ft)
|
||||
new_fields.append(rf)
|
||||
|
||||
@@ -193,7 +192,7 @@ class Request(RequestHooksMixin):
|
||||
:param headers: dictionary of headers to send.
|
||||
:param files: dictionary of {filename: fileobject} files to multipart upload.
|
||||
:param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place.
|
||||
:param json: json for the body to attach to the request (if data is not specified).
|
||||
:param json: json for the body to attach to the request (if files or data is not specified).
|
||||
:param params: dictionary of URL parameters to append to the URL.
|
||||
:param auth: Auth handler or (user, pass) tuple.
|
||||
:param cookies: dictionary or CookieJar of cookies to attach to this request.
|
||||
@@ -207,17 +206,8 @@ class Request(RequestHooksMixin):
|
||||
<PreparedRequest [GET]>
|
||||
|
||||
"""
|
||||
def __init__(self,
|
||||
method=None,
|
||||
url=None,
|
||||
headers=None,
|
||||
files=None,
|
||||
data=None,
|
||||
params=None,
|
||||
auth=None,
|
||||
cookies=None,
|
||||
hooks=None,
|
||||
json=None):
|
||||
def __init__(self, method=None, url=None, headers=None, files=None,
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||
|
||||
# Default empty dicts for dict params.
|
||||
data = [] if data is None else data
|
||||
@@ -296,8 +286,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
self.hooks = default_hooks()
|
||||
|
||||
def prepare(self, method=None, url=None, headers=None, files=None,
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None,
|
||||
json=None):
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||
"""Prepares the entire request with the given parameters."""
|
||||
|
||||
self.prepare_method(method)
|
||||
@@ -306,6 +295,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
self.prepare_cookies(cookies)
|
||||
self.prepare_body(data, files, json)
|
||||
self.prepare_auth(auth, url)
|
||||
|
||||
# Note that prepare_auth must be last to enable authentication schemes
|
||||
# such as OAuth to work on a fully prepared request.
|
||||
|
||||
@@ -320,7 +310,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
p.method = self.method
|
||||
p.url = self.url
|
||||
p.headers = self.headers.copy() if self.headers is not None else None
|
||||
p._cookies = self._cookies.copy() if self._cookies is not None else None
|
||||
p._cookies = _copy_cookie_jar(self._cookies)
|
||||
p.body = self.body
|
||||
p.hooks = self.hooks
|
||||
return p
|
||||
@@ -329,12 +319,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
"""Prepares the given HTTP method."""
|
||||
self.method = method
|
||||
if self.method is not None:
|
||||
self.method = self.method.upper()
|
||||
self.method = to_native_string(self.method.upper())
|
||||
|
||||
def prepare_url(self, url, params):
|
||||
"""Prepares the given HTTP URL."""
|
||||
#: Accept objects that have string representations.
|
||||
#: We're unable to blindy call unicode/str functions
|
||||
#: We're unable to blindly call unicode/str functions
|
||||
#: as this will include the bytestring indicator (b'')
|
||||
#: on python 3.x.
|
||||
#: https://github.com/kennethreitz/requests/pull/2238
|
||||
@@ -351,11 +341,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
return
|
||||
|
||||
# Support for unicode domain names and paths.
|
||||
scheme, auth, host, port, path, query, fragment = parse_url(url)
|
||||
try:
|
||||
scheme, auth, host, port, path, query, fragment = parse_url(url)
|
||||
except LocationParseError as e:
|
||||
raise InvalidURL(*e.args)
|
||||
|
||||
if not scheme:
|
||||
raise MissingSchema("Invalid URL {0!r}: No schema supplied. "
|
||||
"Perhaps you meant http://{0}?".format(url))
|
||||
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
|
||||
error = error.format(to_native_string(url, 'utf8'))
|
||||
|
||||
raise MissingSchema(error)
|
||||
|
||||
if not host:
|
||||
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
||||
@@ -419,9 +414,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
content_type = None
|
||||
length = None
|
||||
|
||||
if json is not None:
|
||||
if not data and json is not None:
|
||||
content_type = 'application/json'
|
||||
body = json_dumps(json)
|
||||
body = complexjson.dumps(json)
|
||||
|
||||
is_stream = all([
|
||||
hasattr(data, '__iter__'),
|
||||
@@ -439,7 +434,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if files:
|
||||
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
|
||||
|
||||
if length is not None:
|
||||
if length:
|
||||
self.headers['Content-Length'] = builtin_str(length)
|
||||
else:
|
||||
self.headers['Transfer-Encoding'] = 'chunked'
|
||||
@@ -448,7 +443,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if files:
|
||||
(body, content_type) = self._encode_files(files, data)
|
||||
else:
|
||||
if data and json is None:
|
||||
if data:
|
||||
body = self._encode_params(data)
|
||||
if isinstance(data, basestring) or hasattr(data, 'read'):
|
||||
content_type = None
|
||||
@@ -472,7 +467,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
l = super_len(body)
|
||||
if l:
|
||||
self.headers['Content-Length'] = builtin_str(l)
|
||||
elif self.method not in ('GET', 'HEAD'):
|
||||
elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None):
|
||||
self.headers['Content-Length'] = '0'
|
||||
|
||||
def prepare_auth(self, auth, url=''):
|
||||
@@ -498,7 +493,15 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
self.prepare_content_length(self.body)
|
||||
|
||||
def prepare_cookies(self, cookies):
|
||||
"""Prepares the given HTTP cookie data."""
|
||||
"""Prepares the given HTTP cookie data.
|
||||
|
||||
This function eventually generates a ``Cookie`` header from the
|
||||
given cookies using cookielib. Due to cookielib's design, the header
|
||||
will not be regenerated if it already exists, meaning this function
|
||||
can only be called once for the life of the
|
||||
:class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
|
||||
to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
|
||||
header is removed beforehand."""
|
||||
|
||||
if isinstance(cookies, cookielib.CookieJar):
|
||||
self._cookies = cookies
|
||||
@@ -511,6 +514,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
|
||||
def prepare_hooks(self, hooks):
|
||||
"""Prepares the given hooks."""
|
||||
# hooks can be passed as None to the prepare method and to this
|
||||
# method. To prevent iterating over None, simply use an empty list
|
||||
# if hooks is False-y
|
||||
hooks = hooks or []
|
||||
for event in hooks:
|
||||
self.register_hook(event, hooks[event])
|
||||
|
||||
@@ -521,16 +528,8 @@ class Response(object):
|
||||
"""
|
||||
|
||||
__attrs__ = [
|
||||
'_content',
|
||||
'status_code',
|
||||
'headers',
|
||||
'url',
|
||||
'history',
|
||||
'encoding',
|
||||
'reason',
|
||||
'cookies',
|
||||
'elapsed',
|
||||
'request',
|
||||
'_content', 'status_code', 'headers', 'url', 'history',
|
||||
'encoding', 'reason', 'cookies', 'elapsed', 'request'
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
@@ -570,7 +569,11 @@ class Response(object):
|
||||
self.cookies = cookiejar_from_dict({})
|
||||
|
||||
#: The amount of time elapsed between sending the request
|
||||
#: and the arrival of the response (as a timedelta)
|
||||
#: and the arrival of the response (as a timedelta).
|
||||
#: This property specifically measures the time taken between sending
|
||||
#: the first byte of the request and finishing parsing the headers. It
|
||||
#: is therefore unaffected by consuming the response content or the
|
||||
#: value of the ``stream`` keyword argument.
|
||||
self.elapsed = datetime.timedelta(0)
|
||||
|
||||
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
|
||||
@@ -615,7 +618,7 @@ class Response(object):
|
||||
def ok(self):
|
||||
try:
|
||||
self.raise_for_status()
|
||||
except RequestException:
|
||||
except HTTPError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -628,7 +631,7 @@ class Response(object):
|
||||
|
||||
@property
|
||||
def is_permanent_redirect(self):
|
||||
"""True if this Response one of the permanant versions of redirect"""
|
||||
"""True if this Response one of the permanent versions of redirect"""
|
||||
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
|
||||
|
||||
@property
|
||||
@@ -646,9 +649,10 @@ class Response(object):
|
||||
If decode_unicode is True, content will be decoded using the best
|
||||
available encoding based on the response.
|
||||
"""
|
||||
|
||||
def generate():
|
||||
try:
|
||||
# Special case for urllib3.
|
||||
# Special case for urllib3.
|
||||
if hasattr(self.raw, 'stream'):
|
||||
try:
|
||||
for chunk in self.raw.stream(chunk_size, decode_content=True):
|
||||
yield chunk
|
||||
@@ -658,7 +662,7 @@ class Response(object):
|
||||
raise ContentDecodingError(e)
|
||||
except ReadTimeoutError as e:
|
||||
raise ConnectionError(e)
|
||||
except AttributeError:
|
||||
else:
|
||||
# Standard file-like object.
|
||||
while True:
|
||||
chunk = self.raw.read(chunk_size)
|
||||
@@ -682,10 +686,12 @@ class Response(object):
|
||||
|
||||
return chunks
|
||||
|
||||
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None):
|
||||
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None):
|
||||
"""Iterates over the response data, one line at a time. When
|
||||
stream=True is set on the request, this avoids reading the
|
||||
content at once into memory for large responses.
|
||||
|
||||
.. note:: This method is not reentrant safe.
|
||||
"""
|
||||
|
||||
pending = None
|
||||
@@ -694,7 +700,11 @@ class Response(object):
|
||||
|
||||
if pending is not None:
|
||||
chunk = pending + chunk
|
||||
lines = chunk.splitlines()
|
||||
|
||||
if delimiter:
|
||||
lines = chunk.split(delimiter)
|
||||
else:
|
||||
lines = chunk.splitlines()
|
||||
|
||||
if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
|
||||
pending = lines.pop()
|
||||
@@ -783,14 +793,16 @@ class Response(object):
|
||||
encoding = guess_json_utf(self.content)
|
||||
if encoding is not None:
|
||||
try:
|
||||
return json.loads(self.content.decode(encoding), **kwargs)
|
||||
return complexjson.loads(
|
||||
self.content.decode(encoding), **kwargs
|
||||
)
|
||||
except UnicodeDecodeError:
|
||||
# Wrong UTF codec detected; usually because it's not UTF-8
|
||||
# but some other 8-bit codec. This is an RFC violation,
|
||||
# and the server didn't bother to tell us what codec *was*
|
||||
# used.
|
||||
pass
|
||||
return json.loads(self.text, **kwargs)
|
||||
return complexjson.loads(self.text, **kwargs)
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
@@ -816,10 +828,10 @@ class Response(object):
|
||||
http_error_msg = ''
|
||||
|
||||
if 400 <= self.status_code < 500:
|
||||
http_error_msg = '%s Client Error: %s' % (self.status_code, self.reason)
|
||||
http_error_msg = '%s Client Error: %s for url: %s' % (self.status_code, self.reason, self.url)
|
||||
|
||||
elif 500 <= self.status_code < 600:
|
||||
http_error_msg = '%s Server Error: %s' % (self.status_code, self.reason)
|
||||
http_error_msg = '%s Server Error: %s for url: %s' % (self.status_code, self.reason, self.url)
|
||||
|
||||
if http_error_msg:
|
||||
raise HTTPError(http_error_msg, response=self)
|
||||
@@ -830,4 +842,7 @@ class Response(object):
|
||||
|
||||
*Note: Should not normally need to be called explicitly.*
|
||||
"""
|
||||
if not self._content_consumed:
|
||||
return self.raw.close()
|
||||
|
||||
return self.raw.release_conn()
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
If you are planning to submit a pull request to requests with any changes in
|
||||
this library do not go any further. These are independent libraries which we
|
||||
vendor into requests. Any changes necessary to these libraries must be made in
|
||||
this library do not go any further. These are independent libraries which we
|
||||
vendor into requests. Any changes necessary to these libraries must be made in
|
||||
them and submitted as separate pull requests to those libraries.
|
||||
|
||||
urllib3 pull requests go here: https://github.com/shazow/urllib3
|
||||
|
||||
chardet pull requests go here: https://github.com/chardet/chardet
|
||||
|
||||
See https://github.com/kennethreitz/requests/pull/1812#issuecomment-30854316
|
||||
for the reasoning behind this.
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
from __future__ import absolute_import
|
||||
'''
|
||||
Debian and other distributions "unbundle" requests' vendored dependencies, and
|
||||
rewrite all imports to use the global versions of ``urllib3`` and ``chardet``.
|
||||
The problem with this is that not only requests itself imports those
|
||||
dependencies, but third-party code outside of the distros' control too.
|
||||
|
||||
from . import urllib3
|
||||
In reaction to these problems, the distro maintainers replaced
|
||||
``requests.packages`` with a magical "stub module" that imports the correct
|
||||
modules. The implementations were varying in quality and all had severe
|
||||
problems. For example, a symlink (or hardlink) that links the correct modules
|
||||
into place introduces problems regarding object identity, since you now have
|
||||
two modules in `sys.modules` with the same API, but different identities::
|
||||
|
||||
requests.packages.urllib3 is not urllib3
|
||||
|
||||
With version ``2.5.2``, requests started to maintain its own stub, so that
|
||||
distro-specific breakage would be reduced to a minimum, even though the whole
|
||||
issue is not requests' fault in the first place. See
|
||||
https://github.com/kennethreitz/requests/pull/2375 for the corresponding pull
|
||||
request.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
|
||||
try:
|
||||
from . import urllib3
|
||||
except ImportError:
|
||||
import urllib3
|
||||
sys.modules['%s.urllib3' % __name__] = urllib3
|
||||
|
||||
try:
|
||||
from . import chardet
|
||||
except ImportError:
|
||||
import chardet
|
||||
sys.modules['%s.chardet' % __name__] = chardet
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
__version__ = "2.2.1"
|
||||
__version__ = "2.3.0"
|
||||
from sys import version_info
|
||||
|
||||
|
||||
|
||||
@@ -12,34 +12,68 @@ Example::
|
||||
If no paths are provided, it takes its input from stdin.
|
||||
|
||||
"""
|
||||
from io import open
|
||||
from sys import argv, stdin
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from io import open
|
||||
|
||||
from chardet import __version__
|
||||
from chardet.universaldetector import UniversalDetector
|
||||
|
||||
|
||||
def description_of(file, name='stdin'):
|
||||
"""Return a string describing the probable encoding of a file."""
|
||||
def description_of(lines, name='stdin'):
|
||||
"""
|
||||
Return a string describing the probable encoding of a file or
|
||||
list of strings.
|
||||
|
||||
:param lines: The lines to get the encoding of.
|
||||
:type lines: Iterable of bytes
|
||||
:param name: Name of file or collection of lines
|
||||
:type name: str
|
||||
"""
|
||||
u = UniversalDetector()
|
||||
for line in file:
|
||||
for line in lines:
|
||||
u.feed(line)
|
||||
u.close()
|
||||
result = u.result
|
||||
if result['encoding']:
|
||||
return '%s: %s with confidence %s' % (name,
|
||||
result['encoding'],
|
||||
result['confidence'])
|
||||
return '{0}: {1} with confidence {2}'.format(name, result['encoding'],
|
||||
result['confidence'])
|
||||
else:
|
||||
return '%s: no result' % name
|
||||
return '{0}: no result'.format(name)
|
||||
|
||||
|
||||
def main():
|
||||
if len(argv) <= 1:
|
||||
print(description_of(stdin))
|
||||
else:
|
||||
for path in argv[1:]:
|
||||
with open(path, 'rb') as f:
|
||||
print(description_of(f, path))
|
||||
def main(argv=None):
|
||||
'''
|
||||
Handles command line arguments and gets things started.
|
||||
|
||||
:param argv: List of arguments, as if specified on the command-line.
|
||||
If None, ``sys.argv[1:]`` is used instead.
|
||||
:type argv: list of str
|
||||
'''
|
||||
# Get command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Takes one or more file paths and reports their detected \
|
||||
encodings",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
conflict_handler='resolve')
|
||||
parser.add_argument('input',
|
||||
help='File whose encoding we would like to determine.',
|
||||
type=argparse.FileType('rb'), nargs='*',
|
||||
default=[sys.stdin])
|
||||
parser.add_argument('--version', action='version',
|
||||
version='%(prog)s {0}'.format(__version__))
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
for f in args.input:
|
||||
if f.isatty():
|
||||
print("You are running chardetect interactively. Press " +
|
||||
"CTRL-D twice at the start of a blank line to signal the " +
|
||||
"end of your input. If you want help, run chardetect " +
|
||||
"--help\n", file=sys.stderr)
|
||||
print(description_of(f, f.name))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -177,6 +177,12 @@ class JapaneseContextAnalysis:
|
||||
return -1, 1
|
||||
|
||||
class SJISContextAnalysis(JapaneseContextAnalysis):
|
||||
def __init__(self):
|
||||
self.charset_name = "SHIFT_JIS"
|
||||
|
||||
def get_charset_name(self):
|
||||
return self.charset_name
|
||||
|
||||
def get_order(self, aBuf):
|
||||
if not aBuf:
|
||||
return -1, 1
|
||||
@@ -184,6 +190,8 @@ class SJISContextAnalysis(JapaneseContextAnalysis):
|
||||
first_char = wrap_ord(aBuf[0])
|
||||
if ((0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC)):
|
||||
charLen = 2
|
||||
if (first_char == 0x87) or (0xFA <= first_char <= 0xFC):
|
||||
self.charset_name = "CP932"
|
||||
else:
|
||||
charLen = 1
|
||||
|
||||
|
||||
@@ -129,11 +129,11 @@ class Latin1Prober(CharSetProber):
|
||||
if total < 0.01:
|
||||
confidence = 0.0
|
||||
else:
|
||||
confidence = ((self._mFreqCounter[3] / total)
|
||||
- (self._mFreqCounter[1] * 20.0 / total))
|
||||
confidence = ((self._mFreqCounter[3] - self._mFreqCounter[1] * 20.0)
|
||||
/ total)
|
||||
if confidence < 0.0:
|
||||
confidence = 0.0
|
||||
# lower the confidence of latin1 so that other more accurate
|
||||
# detector can take priority.
|
||||
confidence = confidence * 0.5
|
||||
confidence = confidence * 0.73
|
||||
return confidence
|
||||
|
||||
@@ -353,7 +353,7 @@ SJIS_cls = (
|
||||
2,2,2,2,2,2,2,2, # 68 - 6f
|
||||
2,2,2,2,2,2,2,2, # 70 - 77
|
||||
2,2,2,2,2,2,2,1, # 78 - 7f
|
||||
3,3,3,3,3,3,3,3, # 80 - 87
|
||||
3,3,3,3,3,2,2,3, # 80 - 87
|
||||
3,3,3,3,3,3,3,3, # 88 - 8f
|
||||
3,3,3,3,3,3,3,3, # 90 - 97
|
||||
3,3,3,3,3,3,3,3, # 98 - 9f
|
||||
@@ -369,9 +369,8 @@ SJIS_cls = (
|
||||
2,2,2,2,2,2,2,2, # d8 - df
|
||||
3,3,3,3,3,3,3,3, # e0 - e7
|
||||
3,3,3,3,3,4,4,4, # e8 - ef
|
||||
4,4,4,4,4,4,4,4, # f0 - f7
|
||||
4,4,4,4,4,0,0,0 # f8 - ff
|
||||
)
|
||||
3,3,3,3,3,3,3,3, # f0 - f7
|
||||
3,3,3,3,3,0,0,0) # f8 - ff
|
||||
|
||||
|
||||
SJIS_st = (
|
||||
@@ -571,5 +570,3 @@ UTF8SMModel = {'classTable': UTF8_cls,
|
||||
'stateTable': UTF8_st,
|
||||
'charLenTable': UTF8CharLenTable,
|
||||
'name': 'UTF-8'}
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
@@ -47,7 +47,7 @@ class SJISProber(MultiByteCharSetProber):
|
||||
self._mContextAnalyzer.reset()
|
||||
|
||||
def get_charset_name(self):
|
||||
return "SHIFT_JIS"
|
||||
return self._mContextAnalyzer.get_charset_name()
|
||||
|
||||
def feed(self, aBuf):
|
||||
aLen = len(aBuf)
|
||||
|
||||
@@ -71,9 +71,9 @@ class UniversalDetector:
|
||||
|
||||
if not self._mGotData:
|
||||
# If the data starts with BOM, we know it is UTF
|
||||
if aBuf[:3] == codecs.BOM:
|
||||
if aBuf[:3] == codecs.BOM_UTF8:
|
||||
# EF BB BF UTF-8 with BOM
|
||||
self.result = {'encoding': "UTF-8", 'confidence': 1.0}
|
||||
self.result = {'encoding': "UTF-8-SIG", 'confidence': 1.0}
|
||||
elif aBuf[:4] == codecs.BOM_UTF32_LE:
|
||||
# FF FE 00 00 UTF-32, little-endian BOM
|
||||
self.result = {'encoding': "UTF-32LE", 'confidence': 1.0}
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
urllib3 - Thread-safe connection pooling and re-using.
|
||||
"""
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = 'dev'
|
||||
|
||||
from __future__ import absolute_import
|
||||
import warnings
|
||||
|
||||
from .connectionpool import (
|
||||
HTTPConnectionPool,
|
||||
@@ -32,8 +30,30 @@ except ImportError:
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = '1.13'
|
||||
|
||||
__all__ = (
|
||||
'HTTPConnectionPool',
|
||||
'HTTPSConnectionPool',
|
||||
'PoolManager',
|
||||
'ProxyManager',
|
||||
'HTTPResponse',
|
||||
'Retry',
|
||||
'Timeout',
|
||||
'add_stderr_logger',
|
||||
'connection_from_url',
|
||||
'disable_warnings',
|
||||
'encode_multipart_formdata',
|
||||
'get_host',
|
||||
'make_headers',
|
||||
'proxy_from_url',
|
||||
)
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
def add_stderr_logger(level=logging.DEBUG):
|
||||
"""
|
||||
Helper for quickly adding a StreamHandler to the logger. Useful for
|
||||
@@ -55,9 +75,16 @@ def add_stderr_logger(level=logging.DEBUG):
|
||||
del NullHandler
|
||||
|
||||
|
||||
# Set security warning to only go off once by default.
|
||||
import warnings
|
||||
warnings.simplefilter('module', exceptions.SecurityWarning)
|
||||
# SecurityWarning's always go off by default.
|
||||
warnings.simplefilter('always', exceptions.SecurityWarning, append=True)
|
||||
# SubjectAltNameWarning's should go off once per host
|
||||
warnings.simplefilter('default', exceptions.SubjectAltNameWarning)
|
||||
# InsecurePlatformWarning's don't vary between requests, so we keep it default.
|
||||
warnings.simplefilter('default', exceptions.InsecurePlatformWarning,
|
||||
append=True)
|
||||
# SNIMissingWarnings should go off only once.
|
||||
warnings.simplefilter('default', exceptions.SNIMissingWarning)
|
||||
|
||||
|
||||
def disable_warnings(category=exceptions.HTTPWarning):
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from __future__ import absolute_import
|
||||
from collections import Mapping, MutableMapping
|
||||
try:
|
||||
from threading import RLock
|
||||
except ImportError: # Platform-specific: No threads available
|
||||
except ImportError: # Platform-specific: No threads available
|
||||
class RLock:
|
||||
def __enter__(self):
|
||||
pass
|
||||
@@ -10,11 +11,11 @@ except ImportError: # Platform-specific: No threads available
|
||||
pass
|
||||
|
||||
|
||||
try: # Python 2.7+
|
||||
try: # Python 2.7+
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from .packages.ordered_dict import OrderedDict
|
||||
from .packages.six import itervalues
|
||||
from .packages.six import iterkeys, itervalues, PY3
|
||||
|
||||
|
||||
__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict']
|
||||
@@ -85,8 +86,7 @@ class RecentlyUsedContainer(MutableMapping):
|
||||
def clear(self):
|
||||
with self.lock:
|
||||
# Copy pointers to all values, then wipe the mapping
|
||||
# under Python 2, this copies the list of values twice :-|
|
||||
values = list(self._container.values())
|
||||
values = list(itervalues(self._container))
|
||||
self._container.clear()
|
||||
|
||||
if self.dispose_func:
|
||||
@@ -95,7 +95,7 @@ class RecentlyUsedContainer(MutableMapping):
|
||||
|
||||
def keys(self):
|
||||
with self.lock:
|
||||
return self._container.keys()
|
||||
return list(iterkeys(self._container))
|
||||
|
||||
|
||||
class HTTPHeaderDict(MutableMapping):
|
||||
@@ -130,25 +130,82 @@ class HTTPHeaderDict(MutableMapping):
|
||||
'foo=bar, baz=quxx'
|
||||
>>> headers['Content-Length']
|
||||
'7'
|
||||
|
||||
If you want to access the raw headers with their original casing
|
||||
for debugging purposes you can access the private ``._data`` attribute
|
||||
which is a normal python ``dict`` that maps the case-insensitive key to a
|
||||
list of tuples stored as (case-sensitive-original-name, value). Using the
|
||||
structure from above as our example:
|
||||
|
||||
>>> headers._data
|
||||
{'set-cookie': [('Set-Cookie', 'foo=bar'), ('set-cookie', 'baz=quxx')],
|
||||
'content-length': [('content-length', '7')]}
|
||||
"""
|
||||
|
||||
def __init__(self, headers=None, **kwargs):
|
||||
self._data = {}
|
||||
if headers is None:
|
||||
headers = {}
|
||||
self.update(headers, **kwargs)
|
||||
super(HTTPHeaderDict, self).__init__()
|
||||
self._container = {}
|
||||
if headers is not None:
|
||||
if isinstance(headers, HTTPHeaderDict):
|
||||
self._copy_from(headers)
|
||||
else:
|
||||
self.extend(headers)
|
||||
if kwargs:
|
||||
self.extend(kwargs)
|
||||
|
||||
def add(self, key, value):
|
||||
def __setitem__(self, key, val):
|
||||
self._container[key.lower()] = (key, val)
|
||||
return self._container[key.lower()]
|
||||
|
||||
def __getitem__(self, key):
|
||||
val = self._container[key.lower()]
|
||||
return ', '.join(val[1:])
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._container[key.lower()]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key.lower() in self._container
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
|
||||
return False
|
||||
if not isinstance(other, type(self)):
|
||||
other = type(self)(other)
|
||||
return (dict((k.lower(), v) for k, v in self.itermerged()) ==
|
||||
dict((k.lower(), v) for k, v in other.itermerged()))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
if not PY3: # Python 2
|
||||
iterkeys = MutableMapping.iterkeys
|
||||
itervalues = MutableMapping.itervalues
|
||||
|
||||
__marker = object()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._container)
|
||||
|
||||
def __iter__(self):
|
||||
# Only provide the originally cased names
|
||||
for vals in self._container.values():
|
||||
yield vals[0]
|
||||
|
||||
def pop(self, key, default=__marker):
|
||||
'''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
||||
If key is not found, d is returned if given, otherwise KeyError is raised.
|
||||
'''
|
||||
# Using the MutableMapping function directly fails due to the private marker.
|
||||
# Using ordinary dict.pop would expose the internal structures.
|
||||
# So let's reinvent the wheel.
|
||||
try:
|
||||
value = self[key]
|
||||
except KeyError:
|
||||
if default is self.__marker:
|
||||
raise
|
||||
return default
|
||||
else:
|
||||
del self[key]
|
||||
return value
|
||||
|
||||
def discard(self, key):
|
||||
try:
|
||||
del self[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def add(self, key, val):
|
||||
"""Adds a (name, value) pair, doesn't overwrite the value if it already
|
||||
exists.
|
||||
|
||||
@@ -157,43 +214,111 @@ class HTTPHeaderDict(MutableMapping):
|
||||
>>> headers['foo']
|
||||
'bar, baz'
|
||||
"""
|
||||
self._data.setdefault(key.lower(), []).append((key, value))
|
||||
key_lower = key.lower()
|
||||
new_vals = key, val
|
||||
# Keep the common case aka no item present as fast as possible
|
||||
vals = self._container.setdefault(key_lower, new_vals)
|
||||
if new_vals is not vals:
|
||||
# new_vals was not inserted, as there was a previous one
|
||||
if isinstance(vals, list):
|
||||
# If already several items got inserted, we have a list
|
||||
vals.append(val)
|
||||
else:
|
||||
# vals should be a tuple then, i.e. only one item so far
|
||||
# Need to convert the tuple to list for further extension
|
||||
self._container[key_lower] = [vals[0], vals[1], val]
|
||||
|
||||
def extend(self, *args, **kwargs):
|
||||
"""Generic import function for any type of header-like object.
|
||||
Adapted version of MutableMapping.update in order to insert items
|
||||
with self.add instead of self.__setitem__
|
||||
"""
|
||||
if len(args) > 1:
|
||||
raise TypeError("extend() takes at most 1 positional "
|
||||
"arguments ({0} given)".format(len(args)))
|
||||
other = args[0] if len(args) >= 1 else ()
|
||||
|
||||
if isinstance(other, HTTPHeaderDict):
|
||||
for key, val in other.iteritems():
|
||||
self.add(key, val)
|
||||
elif isinstance(other, Mapping):
|
||||
for key in other:
|
||||
self.add(key, other[key])
|
||||
elif hasattr(other, "keys"):
|
||||
for key in other.keys():
|
||||
self.add(key, other[key])
|
||||
else:
|
||||
for key, value in other:
|
||||
self.add(key, value)
|
||||
|
||||
for key, value in kwargs.items():
|
||||
self.add(key, value)
|
||||
|
||||
def getlist(self, key):
|
||||
"""Returns a list of all the values for the named field. Returns an
|
||||
empty list if the key doesn't exist."""
|
||||
return self[key].split(', ') if key in self else []
|
||||
try:
|
||||
vals = self._container[key.lower()]
|
||||
except KeyError:
|
||||
return []
|
||||
else:
|
||||
if isinstance(vals, tuple):
|
||||
return [vals[1]]
|
||||
else:
|
||||
return vals[1:]
|
||||
|
||||
def copy(self):
|
||||
h = HTTPHeaderDict()
|
||||
for key in self._data:
|
||||
for rawkey, value in self._data[key]:
|
||||
h.add(rawkey, value)
|
||||
return h
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Mapping):
|
||||
return False
|
||||
other = HTTPHeaderDict(other)
|
||||
return dict((k1, self[k1]) for k1 in self._data) == \
|
||||
dict((k2, other[k2]) for k2 in other._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
values = self._data[key.lower()]
|
||||
return ', '.join(value[1] for value in values)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._data[key.lower()] = [(key, value)]
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._data[key.lower()]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __iter__(self):
|
||||
for headers in itervalues(self._data):
|
||||
yield headers[0][0]
|
||||
# Backwards compatibility for httplib
|
||||
getheaders = getlist
|
||||
getallmatchingheaders = getlist
|
||||
iget = getlist
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
|
||||
return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
|
||||
|
||||
def _copy_from(self, other):
|
||||
for key in other:
|
||||
val = other.getlist(key)
|
||||
if isinstance(val, list):
|
||||
# Don't need to convert tuples
|
||||
val = list(val)
|
||||
self._container[key.lower()] = [key] + val
|
||||
|
||||
def copy(self):
|
||||
clone = type(self)()
|
||||
clone._copy_from(self)
|
||||
return clone
|
||||
|
||||
def iteritems(self):
|
||||
"""Iterate over all header lines, including duplicate ones."""
|
||||
for key in self:
|
||||
vals = self._container[key.lower()]
|
||||
for val in vals[1:]:
|
||||
yield vals[0], val
|
||||
|
||||
def itermerged(self):
|
||||
"""Iterate over all headers, merging duplicate ones together."""
|
||||
for key in self:
|
||||
val = self._container[key.lower()]
|
||||
yield val[0], ', '.join(val[1:])
|
||||
|
||||
def items(self):
|
||||
return list(self.iteritems())
|
||||
|
||||
@classmethod
|
||||
def from_httplib(cls, message): # Python 2
|
||||
"""Read headers from a Python 2 httplib message object."""
|
||||
# python2.7 does not expose a proper API for exporting multiheaders
|
||||
# efficiently. This function re-reads raw lines from the message
|
||||
# object and extracts the multiheaders properly.
|
||||
headers = []
|
||||
|
||||
for line in message.headers:
|
||||
if line.startswith((' ', '\t')):
|
||||
key, value = headers[-1]
|
||||
headers[-1] = (key, value + '\r\n' + line.rstrip())
|
||||
continue
|
||||
|
||||
key, value = line.split(':', 1)
|
||||
headers.append((key, value.strip()))
|
||||
|
||||
return cls(headers)
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
from __future__ import absolute_import
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
from socket import timeout as SocketTimeout
|
||||
from socket import error as SocketError, timeout as SocketTimeout
|
||||
import warnings
|
||||
from .packages import six
|
||||
|
||||
try: # Python 3
|
||||
from http.client import HTTPConnection as _HTTPConnection, HTTPException
|
||||
from http.client import HTTPConnection as _HTTPConnection
|
||||
from http.client import HTTPException # noqa: unused in this module
|
||||
except ImportError:
|
||||
from httplib import HTTPConnection as _HTTPConnection, HTTPException
|
||||
|
||||
|
||||
class DummyConnection(object):
|
||||
"Used to detect a failed ConnectionCls import."
|
||||
pass
|
||||
|
||||
from httplib import HTTPConnection as _HTTPConnection
|
||||
from httplib import HTTPException # noqa: unused in this module
|
||||
|
||||
try: # Compiled with SSL?
|
||||
HTTPSConnection = DummyConnection
|
||||
import ssl
|
||||
BaseSSLError = ssl.SSLError
|
||||
except (ImportError, AttributeError): # Platform-specific: No SSL.
|
||||
@@ -26,12 +24,21 @@ except (ImportError, AttributeError): # Platform-specific: No SSL.
|
||||
pass
|
||||
|
||||
|
||||
try: # Python 3:
|
||||
# Not a no-op, we're adding this to the namespace so it can be imported.
|
||||
ConnectionError = ConnectionError
|
||||
except NameError: # Python 2:
|
||||
class ConnectionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
from .exceptions import (
|
||||
NewConnectionError,
|
||||
ConnectTimeoutError,
|
||||
SubjectAltNameWarning,
|
||||
SystemTimeWarning,
|
||||
)
|
||||
from .packages.ssl_match_hostname import match_hostname
|
||||
from .packages import six
|
||||
|
||||
from .util.ssl_ import (
|
||||
resolve_cert_reqs,
|
||||
@@ -40,8 +47,8 @@ from .util.ssl_ import (
|
||||
assert_fingerprint,
|
||||
)
|
||||
|
||||
from .util import connection
|
||||
|
||||
from .util import connection
|
||||
|
||||
port_by_scheme = {
|
||||
'http': 80,
|
||||
@@ -51,6 +58,11 @@ port_by_scheme = {
|
||||
RECENT_DATE = datetime.date(2014, 1, 1)
|
||||
|
||||
|
||||
class DummyConnection(object):
|
||||
"""Used to detect a failed ConnectionCls import."""
|
||||
pass
|
||||
|
||||
|
||||
class HTTPConnection(_HTTPConnection, object):
|
||||
"""
|
||||
Based on httplib.HTTPConnection but provides an extra constructor
|
||||
@@ -124,11 +136,15 @@ class HTTPConnection(_HTTPConnection, object):
|
||||
conn = connection.create_connection(
|
||||
(self.host, self.port), self.timeout, **extra_kw)
|
||||
|
||||
except SocketTimeout:
|
||||
except SocketTimeout as e:
|
||||
raise ConnectTimeoutError(
|
||||
self, "Connection to %s timed out. (connect timeout=%s)" %
|
||||
(self.host, self.timeout))
|
||||
|
||||
except SocketError as e:
|
||||
raise NewConnectionError(
|
||||
self, "Failed to establish a new connection: %s" % e)
|
||||
|
||||
return conn
|
||||
|
||||
def _prepare_conn(self, conn):
|
||||
@@ -176,19 +192,25 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
"""
|
||||
cert_reqs = None
|
||||
ca_certs = None
|
||||
ca_cert_dir = None
|
||||
ssl_version = None
|
||||
assert_fingerprint = None
|
||||
|
||||
def set_cert(self, key_file=None, cert_file=None,
|
||||
cert_reqs=None, ca_certs=None,
|
||||
assert_hostname=None, assert_fingerprint=None):
|
||||
assert_hostname=None, assert_fingerprint=None,
|
||||
ca_cert_dir=None):
|
||||
|
||||
if (ca_certs or ca_cert_dir) and cert_reqs is None:
|
||||
cert_reqs = 'CERT_REQUIRED'
|
||||
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.cert_reqs = cert_reqs
|
||||
self.ca_certs = ca_certs
|
||||
self.assert_hostname = assert_hostname
|
||||
self.assert_fingerprint = assert_fingerprint
|
||||
self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
|
||||
self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
|
||||
|
||||
def connect(self):
|
||||
# Add certificate verification
|
||||
@@ -225,6 +247,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file,
|
||||
cert_reqs=resolved_cert_reqs,
|
||||
ca_certs=self.ca_certs,
|
||||
ca_cert_dir=self.ca_cert_dir,
|
||||
server_hostname=hostname,
|
||||
ssl_version=resolved_ssl_version)
|
||||
|
||||
@@ -233,14 +256,24 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
self.assert_fingerprint)
|
||||
elif resolved_cert_reqs != ssl.CERT_NONE \
|
||||
and self.assert_hostname is not False:
|
||||
match_hostname(self.sock.getpeercert(),
|
||||
self.assert_hostname or hostname)
|
||||
cert = self.sock.getpeercert()
|
||||
if not cert.get('subjectAltName', ()):
|
||||
warnings.warn((
|
||||
'Certificate for {0} has no `subjectAltName`, falling back to check for a '
|
||||
'`commonName` for now. This feature is being removed by major browsers and '
|
||||
'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 '
|
||||
'for details.)'.format(hostname)),
|
||||
SubjectAltNameWarning
|
||||
)
|
||||
match_hostname(cert, self.assert_hostname or hostname)
|
||||
|
||||
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED
|
||||
or self.assert_fingerprint is not None)
|
||||
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or
|
||||
self.assert_fingerprint is not None)
|
||||
|
||||
|
||||
if ssl:
|
||||
# Make a copy for testing.
|
||||
UnverifiedHTTPSConnection = HTTPSConnection
|
||||
HTTPSConnection = VerifiedHTTPSConnection
|
||||
else:
|
||||
HTTPSConnection = DummyConnection
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
import errno
|
||||
import logging
|
||||
import sys
|
||||
@@ -10,13 +11,15 @@ try: # Python 3
|
||||
from queue import LifoQueue, Empty, Full
|
||||
except ImportError:
|
||||
from Queue import LifoQueue, Empty, Full
|
||||
import Queue as _ # Platform-specific: Windows
|
||||
# Queue is imported for side effects on MS Windows
|
||||
import Queue as _unused_module_Queue # noqa: unused
|
||||
|
||||
|
||||
from .exceptions import (
|
||||
ClosedPoolError,
|
||||
ProtocolError,
|
||||
EmptyPoolError,
|
||||
HeaderParsingError,
|
||||
HostChangedError,
|
||||
LocationValueError,
|
||||
MaxRetryError,
|
||||
@@ -25,6 +28,7 @@ from .exceptions import (
|
||||
SSLError,
|
||||
TimeoutError,
|
||||
InsecureRequestWarning,
|
||||
NewConnectionError,
|
||||
)
|
||||
from .packages.ssl_match_hostname import CertificateError
|
||||
from .packages import six
|
||||
@@ -38,9 +42,10 @@ from .request import RequestMethods
|
||||
from .response import HTTPResponse
|
||||
|
||||
from .util.connection import is_connection_dropped
|
||||
from .util.response import assert_header_parsing
|
||||
from .util.retry import Retry
|
||||
from .util.timeout import Timeout
|
||||
from .util.url import get_host
|
||||
from .util.url import get_host, Url
|
||||
|
||||
|
||||
xrange = six.moves.xrange
|
||||
@@ -50,7 +55,7 @@ log = logging.getLogger(__name__)
|
||||
_Default = object()
|
||||
|
||||
|
||||
## Pool objects
|
||||
# Pool objects
|
||||
class ConnectionPool(object):
|
||||
"""
|
||||
Base class for all connection pools, such as
|
||||
@@ -64,14 +69,28 @@ class ConnectionPool(object):
|
||||
if not host:
|
||||
raise LocationValueError("No host specified.")
|
||||
|
||||
# httplib doesn't like it when we include brackets in ipv6 addresses
|
||||
self.host = host.strip('[]')
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def __str__(self):
|
||||
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
||||
self.host, self.port)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
# Return False to re-raise any potential exceptions
|
||||
return False
|
||||
|
||||
def close():
|
||||
"""
|
||||
Close all pooled connections and disable the pool.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
|
||||
_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK])
|
||||
|
||||
@@ -105,7 +124,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
:param maxsize:
|
||||
Number of connections to save that can be reused. More than 1 is useful
|
||||
in multithreaded situations. If ``block`` is set to false, more
|
||||
in multithreaded situations. If ``block`` is set to False, more
|
||||
connections will be created but they will not be saved once they've
|
||||
been used.
|
||||
|
||||
@@ -266,6 +285,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
"""
|
||||
pass
|
||||
|
||||
def _prepare_proxy(self, conn):
|
||||
# Nothing to do for HTTP connections.
|
||||
pass
|
||||
|
||||
def _get_timeout(self, timeout):
|
||||
""" Helper that always returns a :class:`urllib3.util.Timeout` """
|
||||
if timeout is _Default:
|
||||
@@ -278,6 +301,23 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
# can be removed later
|
||||
return Timeout.from_float(timeout)
|
||||
|
||||
def _raise_timeout(self, err, url, timeout_value):
|
||||
"""Is the error actually a timeout? Will raise a ReadTimeout or pass"""
|
||||
|
||||
if isinstance(err, SocketTimeout):
|
||||
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
|
||||
|
||||
# See the above comment about EAGAIN in Python 3. In Python 2 we have
|
||||
# to specifically catch it and throw the timeout error
|
||||
if hasattr(err, 'errno') and err.errno in _blocking_errnos:
|
||||
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
|
||||
|
||||
# Catch possible read timeouts thrown as SSL errors. If not the
|
||||
# case, rethrow the original. We need to do this because of:
|
||||
# http://bugs.python.org/issue10272
|
||||
if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python 2.6
|
||||
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
|
||||
|
||||
def _make_request(self, conn, method, url, timeout=_Default,
|
||||
**httplib_request_kw):
|
||||
"""
|
||||
@@ -301,7 +341,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
conn.timeout = timeout_obj.connect_timeout
|
||||
|
||||
# Trigger any extra validation we need to do.
|
||||
self._validate_conn(conn)
|
||||
try:
|
||||
self._validate_conn(conn)
|
||||
except (SocketTimeout, BaseSSLError) as e:
|
||||
# Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.
|
||||
self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
|
||||
raise
|
||||
|
||||
# conn.request() calls httplib.*.request, not the method in
|
||||
# urllib3.request. It also calls makefile (recv) on the socket.
|
||||
@@ -327,32 +372,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
# Receive the response from the server
|
||||
try:
|
||||
try: # Python 2.7+, use buffering of HTTP responses
|
||||
try: # Python 2.7, use buffering of HTTP responses
|
||||
httplib_response = conn.getresponse(buffering=True)
|
||||
except TypeError: # Python 2.6 and older
|
||||
httplib_response = conn.getresponse()
|
||||
except SocketTimeout:
|
||||
raise ReadTimeoutError(
|
||||
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
|
||||
|
||||
except BaseSSLError as e:
|
||||
# Catch possible read timeouts thrown as SSL errors. If not the
|
||||
# case, rethrow the original. We need to do this because of:
|
||||
# http://bugs.python.org/issue10272
|
||||
if 'timed out' in str(e) or \
|
||||
'did not complete (read)' in str(e): # Python 2.6
|
||||
raise ReadTimeoutError(
|
||||
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
|
||||
|
||||
raise
|
||||
|
||||
except SocketError as e: # Platform-specific: Python 2
|
||||
# See the above comment about EAGAIN in Python 3. In Python 2 we
|
||||
# have to specifically catch it and throw the timeout error
|
||||
if e.errno in _blocking_errnos:
|
||||
raise ReadTimeoutError(
|
||||
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
|
||||
|
||||
except (SocketTimeout, BaseSSLError, SocketError) as e:
|
||||
self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
|
||||
raise
|
||||
|
||||
# AppEngine doesn't have a version attr.
|
||||
@@ -360,8 +385,19 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
log.debug("\"%s %s %s\" %s %s" % (method, url, http_version,
|
||||
httplib_response.status,
|
||||
httplib_response.length))
|
||||
|
||||
try:
|
||||
assert_header_parsing(httplib_response.msg)
|
||||
except HeaderParsingError as hpe: # Platform-specific: Python 3
|
||||
log.warning(
|
||||
'Failed to parse headers (url=%s): %s',
|
||||
self._absolute_url(url), hpe, exc_info=True)
|
||||
|
||||
return httplib_response
|
||||
|
||||
def _absolute_url(self, path):
|
||||
return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close all pooled connections and disable the pool.
|
||||
@@ -508,11 +544,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
try:
|
||||
# Request a connection from the queue.
|
||||
timeout_obj = self._get_timeout(timeout)
|
||||
conn = self._get_conn(timeout=pool_timeout)
|
||||
|
||||
conn.timeout = timeout_obj.connect_timeout
|
||||
|
||||
is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)
|
||||
if is_new_proxy_conn:
|
||||
self._prepare_proxy(conn)
|
||||
|
||||
# Make the request on the httplib connection object.
|
||||
httplib_response = self._make_request(conn, method, url,
|
||||
timeout=timeout,
|
||||
timeout=timeout_obj,
|
||||
body=body, headers=headers)
|
||||
|
||||
# If we're going to release the connection in ``finally:``, then
|
||||
@@ -537,26 +580,33 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
raise EmptyPoolError(self, "No pool connections are available.")
|
||||
|
||||
except (BaseSSLError, CertificateError) as e:
|
||||
# Release connection unconditionally because there is no way to
|
||||
# close it externally in case of exception.
|
||||
# Close the connection. If a connection is reused on which there
|
||||
# was a Certificate error, the next request will certainly raise
|
||||
# another Certificate error.
|
||||
conn = conn and conn.close()
|
||||
release_conn = True
|
||||
raise SSLError(e)
|
||||
|
||||
except (TimeoutError, HTTPException, SocketError) as e:
|
||||
if conn:
|
||||
# Discard the connection for these exceptions. It will be
|
||||
# be replaced during the next _get_conn() call.
|
||||
conn.close()
|
||||
conn = None
|
||||
except SSLError:
|
||||
# Treat SSLError separately from BaseSSLError to preserve
|
||||
# traceback.
|
||||
conn = conn and conn.close()
|
||||
release_conn = True
|
||||
raise
|
||||
|
||||
stacktrace = sys.exc_info()[2]
|
||||
if isinstance(e, SocketError) and self.proxy:
|
||||
except (TimeoutError, HTTPException, SocketError, ProtocolError) as e:
|
||||
# Discard the connection for these exceptions. It will be
|
||||
# be replaced during the next _get_conn() call.
|
||||
conn = conn and conn.close()
|
||||
release_conn = True
|
||||
|
||||
if isinstance(e, (SocketError, NewConnectionError)) and self.proxy:
|
||||
e = ProxyError('Cannot connect to proxy.', e)
|
||||
elif isinstance(e, (SocketError, HTTPException)):
|
||||
e = ProtocolError('Connection aborted.', e)
|
||||
|
||||
retries = retries.increment(method, url, error=e,
|
||||
_pool=self, _stacktrace=stacktrace)
|
||||
retries = retries.increment(method, url, error=e, _pool=self,
|
||||
_stacktrace=sys.exc_info()[2])
|
||||
retries.sleep()
|
||||
|
||||
# Keep track of the error for the retry warning.
|
||||
@@ -588,26 +638,31 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
retries = retries.increment(method, url, response=response, _pool=self)
|
||||
except MaxRetryError:
|
||||
if retries.raise_on_redirect:
|
||||
# Release the connection for this response, since we're not
|
||||
# returning it to be released manually.
|
||||
response.release_conn()
|
||||
raise
|
||||
return response
|
||||
|
||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
||||
return self.urlopen(method, redirect_location, body, headers,
|
||||
retries=retries, redirect=redirect,
|
||||
assert_same_host=assert_same_host,
|
||||
timeout=timeout, pool_timeout=pool_timeout,
|
||||
release_conn=release_conn, **response_kw)
|
||||
return self.urlopen(
|
||||
method, redirect_location, body, headers,
|
||||
retries=retries, redirect=redirect,
|
||||
assert_same_host=assert_same_host,
|
||||
timeout=timeout, pool_timeout=pool_timeout,
|
||||
release_conn=release_conn, **response_kw)
|
||||
|
||||
# Check if we should retry the HTTP response.
|
||||
if retries.is_forced_retry(method, status_code=response.status):
|
||||
retries = retries.increment(method, url, response=response, _pool=self)
|
||||
retries.sleep()
|
||||
log.info("Forced retry: %s" % url)
|
||||
return self.urlopen(method, url, body, headers,
|
||||
retries=retries, redirect=redirect,
|
||||
assert_same_host=assert_same_host,
|
||||
timeout=timeout, pool_timeout=pool_timeout,
|
||||
release_conn=release_conn, **response_kw)
|
||||
return self.urlopen(
|
||||
method, url, body, headers,
|
||||
retries=retries, redirect=redirect,
|
||||
assert_same_host=assert_same_host,
|
||||
timeout=timeout, pool_timeout=pool_timeout,
|
||||
release_conn=release_conn, **response_kw)
|
||||
|
||||
return response
|
||||
|
||||
@@ -624,10 +679,10 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
``assert_hostname`` and ``host`` in this order to verify connections.
|
||||
If ``assert_hostname`` is False, no verification is done.
|
||||
|
||||
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs`` and
|
||||
``ssl_version`` are only used if :mod:`ssl` is available and are fed into
|
||||
:meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket
|
||||
into an SSL socket.
|
||||
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``,
|
||||
``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is
|
||||
available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade
|
||||
the connection socket into an SSL socket.
|
||||
"""
|
||||
|
||||
scheme = 'https'
|
||||
@@ -640,15 +695,20 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
key_file=None, cert_file=None, cert_reqs=None,
|
||||
ca_certs=None, ssl_version=None,
|
||||
assert_hostname=None, assert_fingerprint=None,
|
||||
**conn_kw):
|
||||
ca_cert_dir=None, **conn_kw):
|
||||
|
||||
HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize,
|
||||
block, headers, retries, _proxy, _proxy_headers,
|
||||
**conn_kw)
|
||||
|
||||
if ca_certs and cert_reqs is None:
|
||||
cert_reqs = 'CERT_REQUIRED'
|
||||
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.cert_reqs = cert_reqs
|
||||
self.ca_certs = ca_certs
|
||||
self.ca_cert_dir = ca_cert_dir
|
||||
self.ssl_version = ssl_version
|
||||
self.assert_hostname = assert_hostname
|
||||
self.assert_fingerprint = assert_fingerprint
|
||||
@@ -664,28 +724,31 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
cert_file=self.cert_file,
|
||||
cert_reqs=self.cert_reqs,
|
||||
ca_certs=self.ca_certs,
|
||||
ca_cert_dir=self.ca_cert_dir,
|
||||
assert_hostname=self.assert_hostname,
|
||||
assert_fingerprint=self.assert_fingerprint)
|
||||
conn.ssl_version = self.ssl_version
|
||||
|
||||
if self.proxy is not None:
|
||||
# Python 2.7+
|
||||
try:
|
||||
set_tunnel = conn.set_tunnel
|
||||
except AttributeError: # Platform-specific: Python 2.6
|
||||
set_tunnel = conn._set_tunnel
|
||||
|
||||
if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older
|
||||
set_tunnel(self.host, self.port)
|
||||
else:
|
||||
set_tunnel(self.host, self.port, self.proxy_headers)
|
||||
|
||||
# Establish tunnel connection early, because otherwise httplib
|
||||
# would improperly set Host: header to proxy's IP:port.
|
||||
conn.connect()
|
||||
|
||||
return conn
|
||||
|
||||
def _prepare_proxy(self, conn):
|
||||
"""
|
||||
Establish tunnel connection early, because otherwise httplib
|
||||
would improperly set Host: header to proxy's IP:port.
|
||||
"""
|
||||
# Python 2.7+
|
||||
try:
|
||||
set_tunnel = conn.set_tunnel
|
||||
except AttributeError: # Platform-specific: Python 2.6
|
||||
set_tunnel = conn._set_tunnel
|
||||
|
||||
if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older
|
||||
set_tunnel(self.host, self.port)
|
||||
else:
|
||||
set_tunnel(self.host, self.port, self.proxy_headers)
|
||||
|
||||
conn.connect()
|
||||
|
||||
def _new_conn(self):
|
||||
"""
|
||||
Return a fresh :class:`httplib.HTTPSConnection`.
|
||||
@@ -695,7 +758,6 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
% (self.num_connections, self.host))
|
||||
|
||||
if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
|
||||
# Platform-specific: Python without ssl
|
||||
raise SSLError("Can't connect to HTTPS URL because the SSL "
|
||||
"module is not available.")
|
||||
|
||||
@@ -725,8 +787,7 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
warnings.warn((
|
||||
'Unverified HTTPS request is being made. '
|
||||
'Adding certificate verification is strongly advised. See: '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html '
|
||||
'(This warning will only appear once by default.)'),
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'),
|
||||
InsecureRequestWarning)
|
||||
|
||||
|
||||
|
||||
223
requests/packages/urllib3/contrib/appengine.py
Normal file
223
requests/packages/urllib3/contrib/appengine.py
Normal file
@@ -0,0 +1,223 @@
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from ..exceptions import (
|
||||
HTTPError,
|
||||
HTTPWarning,
|
||||
MaxRetryError,
|
||||
ProtocolError,
|
||||
TimeoutError,
|
||||
SSLError
|
||||
)
|
||||
|
||||
from ..packages.six import BytesIO
|
||||
from ..request import RequestMethods
|
||||
from ..response import HTTPResponse
|
||||
from ..util.timeout import Timeout
|
||||
from ..util.retry import Retry
|
||||
|
||||
try:
|
||||
from google.appengine.api import urlfetch
|
||||
except ImportError:
|
||||
urlfetch = None
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AppEnginePlatformWarning(HTTPWarning):
|
||||
pass
|
||||
|
||||
|
||||
class AppEnginePlatformError(HTTPError):
|
||||
pass
|
||||
|
||||
|
||||
class AppEngineManager(RequestMethods):
|
||||
"""
|
||||
Connection manager for Google App Engine sandbox applications.
|
||||
|
||||
This manager uses the URLFetch service directly instead of using the
|
||||
emulated httplib, and is subject to URLFetch limitations as described in
|
||||
the App Engine documentation here:
|
||||
|
||||
https://cloud.google.com/appengine/docs/python/urlfetch
|
||||
|
||||
Notably it will raise an AppEnginePlatformError if:
|
||||
* URLFetch is not available.
|
||||
* If you attempt to use this on GAEv2 (Managed VMs), as full socket
|
||||
support is available.
|
||||
* If a request size is more than 10 megabytes.
|
||||
* If a response size is more than 32 megabtyes.
|
||||
* If you use an unsupported request method such as OPTIONS.
|
||||
|
||||
Beyond those cases, it will raise normal urllib3 errors.
|
||||
"""
|
||||
|
||||
def __init__(self, headers=None, retries=None, validate_certificate=True):
|
||||
if not urlfetch:
|
||||
raise AppEnginePlatformError(
|
||||
"URLFetch is not available in this environment.")
|
||||
|
||||
if is_prod_appengine_mvms():
|
||||
raise AppEnginePlatformError(
|
||||
"Use normal urllib3.PoolManager instead of AppEngineManager"
|
||||
"on Managed VMs, as using URLFetch is not necessary in "
|
||||
"this environment.")
|
||||
|
||||
warnings.warn(
|
||||
"urllib3 is using URLFetch on Google App Engine sandbox instead "
|
||||
"of sockets. To use sockets directly instead of URLFetch see "
|
||||
"https://urllib3.readthedocs.org/en/latest/contrib.html.",
|
||||
AppEnginePlatformWarning)
|
||||
|
||||
RequestMethods.__init__(self, headers)
|
||||
self.validate_certificate = validate_certificate
|
||||
|
||||
self.retries = retries or Retry.DEFAULT
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
# Return False to re-raise any potential exceptions
|
||||
return False
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None,
|
||||
retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT,
|
||||
**response_kw):
|
||||
|
||||
retries = self._get_retries(retries, redirect)
|
||||
|
||||
try:
|
||||
response = urlfetch.fetch(
|
||||
url,
|
||||
payload=body,
|
||||
method=method,
|
||||
headers=headers or {},
|
||||
allow_truncated=False,
|
||||
follow_redirects=(
|
||||
redirect and
|
||||
retries.redirect != 0 and
|
||||
retries.total),
|
||||
deadline=self._get_absolute_timeout(timeout),
|
||||
validate_certificate=self.validate_certificate,
|
||||
)
|
||||
except urlfetch.DeadlineExceededError as e:
|
||||
raise TimeoutError(self, e)
|
||||
|
||||
except urlfetch.InvalidURLError as e:
|
||||
if 'too large' in str(e):
|
||||
raise AppEnginePlatformError(
|
||||
"URLFetch request too large, URLFetch only "
|
||||
"supports requests up to 10mb in size.", e)
|
||||
raise ProtocolError(e)
|
||||
|
||||
except urlfetch.DownloadError as e:
|
||||
if 'Too many redirects' in str(e):
|
||||
raise MaxRetryError(self, url, reason=e)
|
||||
raise ProtocolError(e)
|
||||
|
||||
except urlfetch.ResponseTooLargeError as e:
|
||||
raise AppEnginePlatformError(
|
||||
"URLFetch response too large, URLFetch only supports"
|
||||
"responses up to 32mb in size.", e)
|
||||
|
||||
except urlfetch.SSLCertificateError as e:
|
||||
raise SSLError(e)
|
||||
|
||||
except urlfetch.InvalidMethodError as e:
|
||||
raise AppEnginePlatformError(
|
||||
"URLFetch does not support method: %s" % method, e)
|
||||
|
||||
http_response = self._urlfetch_response_to_http_response(
|
||||
response, **response_kw)
|
||||
|
||||
# Check for redirect response
|
||||
if (http_response.get_redirect_location() and
|
||||
retries.raise_on_redirect and redirect):
|
||||
raise MaxRetryError(self, url, "too many redirects")
|
||||
|
||||
# Check if we should retry the HTTP response.
|
||||
if retries.is_forced_retry(method, status_code=http_response.status):
|
||||
retries = retries.increment(
|
||||
method, url, response=http_response, _pool=self)
|
||||
log.info("Forced retry: %s" % url)
|
||||
retries.sleep()
|
||||
return self.urlopen(
|
||||
method, url,
|
||||
body=body, headers=headers,
|
||||
retries=retries, redirect=redirect,
|
||||
timeout=timeout, **response_kw)
|
||||
|
||||
return http_response
|
||||
|
||||
def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):
|
||||
|
||||
if is_prod_appengine():
|
||||
# Production GAE handles deflate encoding automatically, but does
|
||||
# not remove the encoding header.
|
||||
content_encoding = urlfetch_resp.headers.get('content-encoding')
|
||||
|
||||
if content_encoding == 'deflate':
|
||||
del urlfetch_resp.headers['content-encoding']
|
||||
|
||||
return HTTPResponse(
|
||||
# In order for decoding to work, we must present the content as
|
||||
# a file-like object.
|
||||
body=BytesIO(urlfetch_resp.content),
|
||||
headers=urlfetch_resp.headers,
|
||||
status=urlfetch_resp.status_code,
|
||||
**response_kw
|
||||
)
|
||||
|
||||
def _get_absolute_timeout(self, timeout):
|
||||
if timeout is Timeout.DEFAULT_TIMEOUT:
|
||||
return 5 # 5s is the default timeout for URLFetch.
|
||||
if isinstance(timeout, Timeout):
|
||||
if timeout.read is not timeout.connect:
|
||||
warnings.warn(
|
||||
"URLFetch does not support granular timeout settings, "
|
||||
"reverting to total timeout.", AppEnginePlatformWarning)
|
||||
return timeout.total
|
||||
return timeout
|
||||
|
||||
def _get_retries(self, retries, redirect):
|
||||
if not isinstance(retries, Retry):
|
||||
retries = Retry.from_int(
|
||||
retries, redirect=redirect, default=self.retries)
|
||||
|
||||
if retries.connect or retries.read or retries.redirect:
|
||||
warnings.warn(
|
||||
"URLFetch only supports total retries and does not "
|
||||
"recognize connect, read, or redirect retry parameters.",
|
||||
AppEnginePlatformWarning)
|
||||
|
||||
return retries
|
||||
|
||||
|
||||
def is_appengine():
|
||||
return (is_local_appengine() or
|
||||
is_prod_appengine() or
|
||||
is_prod_appengine_mvms())
|
||||
|
||||
|
||||
def is_appengine_sandbox():
|
||||
return is_appengine() and not is_prod_appengine_mvms()
|
||||
|
||||
|
||||
def is_local_appengine():
|
||||
return ('APPENGINE_RUNTIME' in os.environ and
|
||||
'Development/' in os.environ['SERVER_SOFTWARE'])
|
||||
|
||||
|
||||
def is_prod_appengine():
|
||||
return ('APPENGINE_RUNTIME' in os.environ and
|
||||
'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and
|
||||
not is_prod_appengine_mvms())
|
||||
|
||||
|
||||
def is_prod_appengine_mvms():
|
||||
return os.environ.get('GAE_VM', False) == 'true'
|
||||
@@ -3,6 +3,7 @@ NTLM authenticating pool, contributed by erikcederstran
|
||||
|
||||
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
from http.client import HTTPSConnection
|
||||
|
||||
@@ -29,7 +29,7 @@ Now you can use :mod:`urllib3` as you normally would, and it will support SNI
|
||||
when the required modules are installed.
|
||||
|
||||
Activating this module also has the positive side effect of disabling SSL/TLS
|
||||
encryption in Python 2 (see `CRIME attack`_).
|
||||
compression in Python 2 (see `CRIME attack`_).
|
||||
|
||||
If you want to configure the default list of supported cipher suites, you can
|
||||
set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
|
||||
@@ -38,13 +38,12 @@ Module Variables
|
||||
----------------
|
||||
|
||||
:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
|
||||
Default: ``ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:
|
||||
ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS``
|
||||
|
||||
.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
|
||||
.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
|
||||
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
|
||||
@@ -55,7 +54,7 @@ except SyntaxError as e:
|
||||
import OpenSSL.SSL
|
||||
from pyasn1.codec.der import decoder as der_decoder
|
||||
from pyasn1.type import univ, constraint
|
||||
from socket import _fileobject, timeout
|
||||
from socket import _fileobject, timeout, error as SocketError
|
||||
import ssl
|
||||
import select
|
||||
|
||||
@@ -70,33 +69,31 @@ HAS_SNI = SUBJ_ALT_NAME_SUPPORT
|
||||
# Map from urllib3 to PyOpenSSL compatible parameter-values.
|
||||
_openssl_versions = {
|
||||
ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
|
||||
ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD,
|
||||
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
|
||||
}
|
||||
|
||||
if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
|
||||
_openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
|
||||
|
||||
if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
|
||||
_openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
|
||||
|
||||
try:
|
||||
_openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
_openssl_verify = {
|
||||
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
|
||||
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
|
||||
ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
|
||||
+ OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
ssl.CERT_REQUIRED:
|
||||
OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
}
|
||||
|
||||
# A secure default.
|
||||
# Sources for more information on TLS ciphers:
|
||||
#
|
||||
# - https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
# - https://www.ssllabs.com/projects/best-practices/index.html
|
||||
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
||||
#
|
||||
# The general intent is:
|
||||
# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
|
||||
# - prefer ECDHE over DHE for better performance,
|
||||
# - prefer any AES-GCM over any AES-CBC for better performance and security,
|
||||
# - use 3DES as fallback which is secure but slow,
|
||||
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
|
||||
DEFAULT_SSL_CIPHER_LIST = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:" + \
|
||||
"ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:" + \
|
||||
"!aNULL:!MD5:!DSS"
|
||||
DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS
|
||||
|
||||
# OpenSSL will only write 16K at a time
|
||||
SSL_WRITE_BLOCKSIZE = 16384
|
||||
|
||||
orig_util_HAS_SNI = util.HAS_SNI
|
||||
orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
|
||||
@@ -116,7 +113,7 @@ def extract_from_urllib3():
|
||||
util.HAS_SNI = orig_util_HAS_SNI
|
||||
|
||||
|
||||
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||
class SubjectAltName(BaseSubjectAltName):
|
||||
'''ASN.1 implementation for subjectAltNames support'''
|
||||
|
||||
@@ -127,7 +124,7 @@ class SubjectAltName(BaseSubjectAltName):
|
||||
constraint.ValueSizeConstraint(1, 1024)
|
||||
|
||||
|
||||
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||
def get_subj_alt_name(peer_cert):
|
||||
# Search through extensions
|
||||
dns_name = []
|
||||
@@ -184,6 +181,11 @@ class WrappedSocket(object):
|
||||
except OpenSSL.SSL.SysCallError as e:
|
||||
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
|
||||
return b''
|
||||
else:
|
||||
raise SocketError(e)
|
||||
except OpenSSL.SSL.ZeroReturnError as e:
|
||||
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
|
||||
return b''
|
||||
else:
|
||||
raise
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
@@ -199,12 +201,33 @@ class WrappedSocket(object):
|
||||
def settimeout(self, timeout):
|
||||
return self.socket.settimeout(timeout)
|
||||
|
||||
def _send_until_done(self, data):
|
||||
while True:
|
||||
try:
|
||||
return self.connection.send(data)
|
||||
except OpenSSL.SSL.WantWriteError:
|
||||
_, wlist, _ = select.select([], [self.socket], [],
|
||||
self.socket.gettimeout())
|
||||
if not wlist:
|
||||
raise timeout()
|
||||
continue
|
||||
|
||||
def sendall(self, data):
|
||||
return self.connection.sendall(data)
|
||||
total_sent = 0
|
||||
while total_sent < len(data):
|
||||
sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
|
||||
total_sent += sent
|
||||
|
||||
def shutdown(self):
|
||||
# FIXME rethrow compatible exceptions should we ever use this
|
||||
self.connection.shutdown()
|
||||
|
||||
def close(self):
|
||||
if self._makefile_refs < 1:
|
||||
return self.connection.shutdown()
|
||||
try:
|
||||
return self.connection.close()
|
||||
except OpenSSL.SSL.Error:
|
||||
return
|
||||
else:
|
||||
self._makefile_refs -= 1
|
||||
|
||||
@@ -245,17 +268,18 @@ def _verify_callback(cnx, x509, err_no, err_depth, return_code):
|
||||
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=None):
|
||||
ssl_version=None, ca_cert_dir=None):
|
||||
ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
|
||||
if certfile:
|
||||
keyfile = keyfile or certfile # Match behaviour of the normal python ssl library
|
||||
ctx.use_certificate_file(certfile)
|
||||
if keyfile:
|
||||
ctx.use_privatekey_file(keyfile)
|
||||
if cert_reqs != ssl.CERT_NONE:
|
||||
ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
|
||||
if ca_certs:
|
||||
if ca_certs or ca_cert_dir:
|
||||
try:
|
||||
ctx.load_verify_locations(ca_certs, None)
|
||||
ctx.load_verify_locations(ca_certs, ca_cert_dir)
|
||||
except OpenSSL.SSL.Error as e:
|
||||
raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
|
||||
else:
|
||||
@@ -275,10 +299,12 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
try:
|
||||
cnx.do_handshake()
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
select.select([sock], [], [])
|
||||
rd, _, _ = select.select([sock], [], [], sock.gettimeout())
|
||||
if not rd:
|
||||
raise timeout('select timed out')
|
||||
continue
|
||||
except OpenSSL.SSL.Error as e:
|
||||
raise ssl.SSLError('bad handshake', e)
|
||||
raise ssl.SSLError('bad handshake: %r' % e)
|
||||
break
|
||||
|
||||
return WrappedSocket(cnx, sock)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
from __future__ import absolute_import
|
||||
# Base Exceptions
|
||||
|
||||
## Base Exceptions
|
||||
|
||||
class HTTPError(Exception):
|
||||
"Base exception used by this module."
|
||||
pass
|
||||
|
||||
|
||||
class HTTPWarning(Warning):
|
||||
"Base warning used by this module."
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class PoolError(HTTPError):
|
||||
"Base exception for errors caused within a pool."
|
||||
def __init__(self, pool, message):
|
||||
@@ -57,7 +58,7 @@ class ProtocolError(HTTPError):
|
||||
ConnectionError = ProtocolError
|
||||
|
||||
|
||||
## Leaf Exceptions
|
||||
# Leaf Exceptions
|
||||
|
||||
class MaxRetryError(RequestError):
|
||||
"""Raised when the maximum number of retries is exceeded.
|
||||
@@ -72,11 +73,8 @@ class MaxRetryError(RequestError):
|
||||
def __init__(self, pool, url, reason=None):
|
||||
self.reason = reason
|
||||
|
||||
message = "Max retries exceeded with url: %s" % url
|
||||
if reason:
|
||||
message += " (Caused by %r)" % reason
|
||||
else:
|
||||
message += " (Caused by redirect)"
|
||||
message = "Max retries exceeded with url: %s (Caused by %r)" % (
|
||||
url, reason)
|
||||
|
||||
RequestError.__init__(self, pool, url, message)
|
||||
|
||||
@@ -116,6 +114,11 @@ class ConnectTimeoutError(TimeoutError):
|
||||
pass
|
||||
|
||||
|
||||
class NewConnectionError(ConnectTimeoutError, PoolError):
|
||||
"Raised when we fail to establish a new connection. Usually ECONNREFUSED."
|
||||
pass
|
||||
|
||||
|
||||
class EmptyPoolError(PoolError):
|
||||
"Raised when a pool runs out of connections and no more are allowed."
|
||||
pass
|
||||
@@ -141,11 +144,22 @@ class LocationParseError(LocationValueError):
|
||||
self.location = location
|
||||
|
||||
|
||||
class ResponseError(HTTPError):
|
||||
"Used as a container for an error reason supplied in a MaxRetryError."
|
||||
GENERIC_ERROR = 'too many error responses'
|
||||
SPECIFIC_ERROR = 'too many {status_code} error responses'
|
||||
|
||||
|
||||
class SecurityWarning(HTTPWarning):
|
||||
"Warned when perfoming security reducing actions"
|
||||
pass
|
||||
|
||||
|
||||
class SubjectAltNameWarning(SecurityWarning):
|
||||
"Warned when connecting to a host with a certificate missing a SAN."
|
||||
pass
|
||||
|
||||
|
||||
class InsecureRequestWarning(SecurityWarning):
|
||||
"Warned when making an unverified HTTPS request."
|
||||
pass
|
||||
@@ -154,3 +168,34 @@ class InsecureRequestWarning(SecurityWarning):
|
||||
class SystemTimeWarning(SecurityWarning):
|
||||
"Warned when system time is suspected to be wrong"
|
||||
pass
|
||||
|
||||
|
||||
class InsecurePlatformWarning(SecurityWarning):
|
||||
"Warned when certain SSL configuration is not available on a platform."
|
||||
pass
|
||||
|
||||
|
||||
class SNIMissingWarning(HTTPWarning):
|
||||
"Warned when making a HTTPS request without SNI available."
|
||||
pass
|
||||
|
||||
|
||||
class ResponseNotChunked(ProtocolError, ValueError):
|
||||
"Response needs to be chunked in order to read it as chunks."
|
||||
pass
|
||||
|
||||
|
||||
class ProxySchemeUnknown(AssertionError, ValueError):
|
||||
"ProxyManager does not support the supplied scheme"
|
||||
# TODO(t-8ch): Stop inheriting from AssertionError in v2.0.
|
||||
|
||||
def __init__(self, scheme):
|
||||
message = "Not supported proxy scheme %s" % scheme
|
||||
super(ProxySchemeUnknown, self).__init__(message)
|
||||
|
||||
|
||||
class HeaderParsingError(HTTPError):
|
||||
"Raised by assert_header_parsing, but we convert it to a log.warning statement."
|
||||
def __init__(self, defects, unparsed_data):
|
||||
message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data)
|
||||
super(HeaderParsingError, self).__init__(message)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
import email.utils
|
||||
import mimetypes
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
import codecs
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -2,3 +2,4 @@ from __future__ import absolute_import
|
||||
|
||||
from . import ssl_match_hostname
|
||||
|
||||
__all__ = ('ssl_match_hostname', )
|
||||
|
||||
1
requests/packages/urllib3/packages/ssl_match_hostname/.gitignore
vendored
Normal file
1
requests/packages/urllib3/packages/ssl_match_hostname/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
env
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
try: # Python 3
|
||||
@@ -8,7 +9,7 @@ except ImportError:
|
||||
from ._collections import RecentlyUsedContainer
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
from .connectionpool import port_by_scheme
|
||||
from .exceptions import LocationValueError
|
||||
from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown
|
||||
from .request import RequestMethods
|
||||
from .util.url import parse_url
|
||||
from .util.retry import Retry
|
||||
@@ -25,7 +26,7 @@ pool_classes_by_scheme = {
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs',
|
||||
'ssl_version')
|
||||
'ssl_version', 'ca_cert_dir')
|
||||
|
||||
|
||||
class PoolManager(RequestMethods):
|
||||
@@ -64,6 +65,14 @@ class PoolManager(RequestMethods):
|
||||
self.pools = RecentlyUsedContainer(num_pools,
|
||||
dispose_func=lambda p: p.close())
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.clear()
|
||||
# Return False to re-raise any potential exceptions
|
||||
return False
|
||||
|
||||
def _new_pool(self, scheme, host, port):
|
||||
"""
|
||||
Create a new :class:`ConnectionPool` based on host, port and scheme.
|
||||
@@ -167,7 +176,14 @@ class PoolManager(RequestMethods):
|
||||
if not isinstance(retries, Retry):
|
||||
retries = Retry.from_int(retries, redirect=redirect)
|
||||
|
||||
kw['retries'] = retries.increment(method, redirect_location)
|
||||
try:
|
||||
retries = retries.increment(method, url, response=response, _pool=conn)
|
||||
except MaxRetryError:
|
||||
if retries.raise_on_redirect:
|
||||
raise
|
||||
return response
|
||||
|
||||
kw['retries'] = retries
|
||||
kw['redirect'] = redirect
|
||||
|
||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
||||
@@ -212,8 +228,8 @@ class ProxyManager(PoolManager):
|
||||
port = port_by_scheme.get(proxy.scheme, 80)
|
||||
proxy = proxy._replace(port=port)
|
||||
|
||||
assert proxy.scheme in ("http", "https"), \
|
||||
'Not supported proxy scheme %s' % proxy.scheme
|
||||
if proxy.scheme not in ("http", "https"):
|
||||
raise ProxySchemeUnknown(proxy.scheme)
|
||||
|
||||
self.proxy = proxy
|
||||
self.proxy_headers = proxy_headers or {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
try:
|
||||
from urllib.parse import urlencode
|
||||
except ImportError:
|
||||
@@ -71,14 +72,22 @@ class RequestMethods(object):
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
|
||||
def request_encode_url(self, method, url, fields=None, **urlopen_kw):
|
||||
def request_encode_url(self, method, url, fields=None, headers=None,
|
||||
**urlopen_kw):
|
||||
"""
|
||||
Make a request using :meth:`urlopen` with the ``fields`` encoded in
|
||||
the url. This is useful for request methods like GET, HEAD, DELETE, etc.
|
||||
"""
|
||||
if headers is None:
|
||||
headers = self.headers
|
||||
|
||||
extra_kw = {'headers': headers}
|
||||
extra_kw.update(urlopen_kw)
|
||||
|
||||
if fields:
|
||||
url += '?' + urlencode(fields)
|
||||
return self.urlopen(method, url, **urlopen_kw)
|
||||
|
||||
return self.urlopen(method, url, **extra_kw)
|
||||
|
||||
def request_encode_body(self, method, url, fields=None, headers=None,
|
||||
encode_multipart=True, multipart_boundary=None,
|
||||
@@ -118,18 +127,25 @@ class RequestMethods(object):
|
||||
which is used to compose the body of the request. The random boundary
|
||||
string can be explicitly set with the ``multipart_boundary`` parameter.
|
||||
"""
|
||||
if encode_multipart:
|
||||
body, content_type = encode_multipart_formdata(
|
||||
fields or {}, boundary=multipart_boundary)
|
||||
else:
|
||||
body, content_type = (urlencode(fields or {}),
|
||||
'application/x-www-form-urlencoded')
|
||||
|
||||
if headers is None:
|
||||
headers = self.headers
|
||||
|
||||
headers_ = {'Content-Type': content_type}
|
||||
headers_.update(headers)
|
||||
extra_kw = {'headers': {}}
|
||||
|
||||
return self.urlopen(method, url, body=body, headers=headers_,
|
||||
**urlopen_kw)
|
||||
if fields:
|
||||
if 'body' in urlopen_kw:
|
||||
raise TypeError(
|
||||
"request got values for both 'fields' and 'body', can only specify one.")
|
||||
|
||||
if encode_multipart:
|
||||
body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)
|
||||
else:
|
||||
body, content_type = urlencode(fields), 'application/x-www-form-urlencoded'
|
||||
|
||||
extra_kw['body'] = body
|
||||
extra_kw['headers'] = {'Content-Type': content_type}
|
||||
|
||||
extra_kw['headers'].update(headers)
|
||||
extra_kw.update(urlopen_kw)
|
||||
|
||||
return self.urlopen(method, url, **extra_kw)
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
from __future__ import absolute_import
|
||||
from contextlib import contextmanager
|
||||
import zlib
|
||||
import io
|
||||
from socket import timeout as SocketTimeout
|
||||
from socket import error as SocketError
|
||||
|
||||
from ._collections import HTTPHeaderDict
|
||||
from .exceptions import ProtocolError, DecodeError, ReadTimeoutError
|
||||
from .packages.six import string_types as basestring, binary_type
|
||||
from .exceptions import (
|
||||
ProtocolError, DecodeError, ReadTimeoutError, ResponseNotChunked
|
||||
)
|
||||
from .packages.six import string_types as basestring, binary_type, PY3
|
||||
from .packages.six.moves import http_client as httplib
|
||||
from .connection import HTTPException, BaseSSLError
|
||||
from .util.response import is_fp_closed
|
||||
|
||||
from .util.response import is_fp_closed, is_response_to_head
|
||||
|
||||
|
||||
class DeflateDecoder(object):
|
||||
@@ -21,6 +26,9 @@ class DeflateDecoder(object):
|
||||
return getattr(self._obj, name)
|
||||
|
||||
def decompress(self, data):
|
||||
if not data:
|
||||
return data
|
||||
|
||||
if not self._first_try:
|
||||
return self._obj.decompress(data)
|
||||
|
||||
@@ -36,9 +44,23 @@ class DeflateDecoder(object):
|
||||
self._data = None
|
||||
|
||||
|
||||
class GzipDecoder(object):
|
||||
|
||||
def __init__(self):
|
||||
self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._obj, name)
|
||||
|
||||
def decompress(self, data):
|
||||
if not data:
|
||||
return data
|
||||
return self._obj.decompress(data)
|
||||
|
||||
|
||||
def _get_decoder(mode):
|
||||
if mode == 'gzip':
|
||||
return zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
return GzipDecoder()
|
||||
|
||||
return DeflateDecoder()
|
||||
|
||||
@@ -76,9 +98,10 @@ class HTTPResponse(io.IOBase):
|
||||
strict=0, preload_content=True, decode_content=True,
|
||||
original_response=None, pool=None, connection=None):
|
||||
|
||||
self.headers = HTTPHeaderDict()
|
||||
if headers:
|
||||
self.headers.update(headers)
|
||||
if isinstance(headers, HTTPHeaderDict):
|
||||
self.headers = headers
|
||||
else:
|
||||
self.headers = HTTPHeaderDict(headers)
|
||||
self.status = status
|
||||
self.version = version
|
||||
self.reason = reason
|
||||
@@ -100,6 +123,16 @@ class HTTPResponse(io.IOBase):
|
||||
if hasattr(body, 'read'):
|
||||
self._fp = body
|
||||
|
||||
# Are we using the chunked-style of transfer encoding?
|
||||
self.chunked = False
|
||||
self.chunk_left = None
|
||||
tr_enc = self.headers.get('transfer-encoding', '').lower()
|
||||
# Don't incur the penalty of creating a list and then discarding it
|
||||
encodings = (enc.strip() for enc in tr_enc.split(","))
|
||||
if "chunked" in encodings:
|
||||
self.chunked = True
|
||||
|
||||
# If requested, preload the body.
|
||||
if preload_content and not self._body:
|
||||
self._body = self.read(decode_content=decode_content)
|
||||
|
||||
@@ -140,6 +173,93 @@ class HTTPResponse(io.IOBase):
|
||||
"""
|
||||
return self._fp_bytes_read
|
||||
|
||||
def _init_decoder(self):
|
||||
"""
|
||||
Set-up the _decoder attribute if necessar.
|
||||
"""
|
||||
# Note: content-encoding value should be case-insensitive, per RFC 7230
|
||||
# Section 3.2
|
||||
content_encoding = self.headers.get('content-encoding', '').lower()
|
||||
if self._decoder is None and content_encoding in self.CONTENT_DECODERS:
|
||||
self._decoder = _get_decoder(content_encoding)
|
||||
|
||||
def _decode(self, data, decode_content, flush_decoder):
|
||||
"""
|
||||
Decode the data passed in and potentially flush the decoder.
|
||||
"""
|
||||
try:
|
||||
if decode_content and self._decoder:
|
||||
data = self._decoder.decompress(data)
|
||||
except (IOError, zlib.error) as e:
|
||||
content_encoding = self.headers.get('content-encoding', '').lower()
|
||||
raise DecodeError(
|
||||
"Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding, e)
|
||||
|
||||
if flush_decoder and decode_content:
|
||||
data += self._flush_decoder()
|
||||
|
||||
return data
|
||||
|
||||
def _flush_decoder(self):
|
||||
"""
|
||||
Flushes the decoder. Should only be called if the decoder is actually
|
||||
being used.
|
||||
"""
|
||||
if self._decoder:
|
||||
buf = self._decoder.decompress(b'')
|
||||
return buf + self._decoder.flush()
|
||||
|
||||
return b''
|
||||
|
||||
@contextmanager
|
||||
def _error_catcher(self):
|
||||
"""
|
||||
Catch low-level python exceptions, instead re-raising urllib3
|
||||
variants, so that low-level exceptions are not leaked in the
|
||||
high-level api.
|
||||
|
||||
On exit, release the connection back to the pool.
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
yield
|
||||
|
||||
except SocketTimeout:
|
||||
# FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
|
||||
# there is yet no clean way to get at it from this context.
|
||||
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
||||
|
||||
except BaseSSLError as e:
|
||||
# FIXME: Is there a better way to differentiate between SSLErrors?
|
||||
if 'read operation timed out' not in str(e): # Defensive:
|
||||
# This shouldn't happen but just in case we're missing an edge
|
||||
# case, let's avoid swallowing SSL errors.
|
||||
raise
|
||||
|
||||
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
||||
|
||||
except (HTTPException, SocketError) as e:
|
||||
# This includes IncompleteRead.
|
||||
raise ProtocolError('Connection broken: %r' % e, e)
|
||||
|
||||
except Exception:
|
||||
# The response may not be closed but we're not going to use it anymore
|
||||
# so close it now to ensure that the connection is released back to the pool.
|
||||
if self._original_response and not self._original_response.isclosed():
|
||||
self._original_response.close()
|
||||
|
||||
# Closing the response may not actually be sufficient to close
|
||||
# everything, so if we have a hold of the connection close that
|
||||
# too.
|
||||
if self._connection is not None:
|
||||
self._connection.close()
|
||||
|
||||
raise
|
||||
finally:
|
||||
if self._original_response and self._original_response.isclosed():
|
||||
self.release_conn()
|
||||
|
||||
def read(self, amt=None, decode_content=None, cache_content=False):
|
||||
"""
|
||||
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
|
||||
@@ -161,12 +281,7 @@ class HTTPResponse(io.IOBase):
|
||||
after having ``.read()`` the file object. (Overridden if ``amt`` is
|
||||
set.)
|
||||
"""
|
||||
# Note: content-encoding value should be case-insensitive, per RFC 7230
|
||||
# Section 3.2
|
||||
content_encoding = self.headers.get('content-encoding', '').lower()
|
||||
if self._decoder is None:
|
||||
if content_encoding in self.CONTENT_DECODERS:
|
||||
self._decoder = _get_decoder(content_encoding)
|
||||
self._init_decoder()
|
||||
if decode_content is None:
|
||||
decode_content = self.decode_content
|
||||
|
||||
@@ -174,67 +289,36 @@ class HTTPResponse(io.IOBase):
|
||||
return
|
||||
|
||||
flush_decoder = False
|
||||
data = None
|
||||
|
||||
try:
|
||||
try:
|
||||
if amt is None:
|
||||
# cStringIO doesn't like amt=None
|
||||
data = self._fp.read()
|
||||
with self._error_catcher():
|
||||
if amt is None:
|
||||
# cStringIO doesn't like amt=None
|
||||
data = self._fp.read()
|
||||
flush_decoder = True
|
||||
else:
|
||||
cache_content = False
|
||||
data = self._fp.read(amt)
|
||||
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
|
||||
# Close the connection when no data is returned
|
||||
#
|
||||
# This is redundant to what httplib/http.client _should_
|
||||
# already do. However, versions of python released before
|
||||
# December 15, 2012 (http://bugs.python.org/issue16298) do
|
||||
# not properly close the connection in all cases. There is
|
||||
# no harm in redundantly calling close.
|
||||
self._fp.close()
|
||||
flush_decoder = True
|
||||
else:
|
||||
cache_content = False
|
||||
data = self._fp.read(amt)
|
||||
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
|
||||
# Close the connection when no data is returned
|
||||
#
|
||||
# This is redundant to what httplib/http.client _should_
|
||||
# already do. However, versions of python released before
|
||||
# December 15, 2012 (http://bugs.python.org/issue16298) do
|
||||
# not properly close the connection in all cases. There is
|
||||
# no harm in redundantly calling close.
|
||||
self._fp.close()
|
||||
flush_decoder = True
|
||||
|
||||
except SocketTimeout:
|
||||
# FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
|
||||
# there is yet no clean way to get at it from this context.
|
||||
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
||||
|
||||
except BaseSSLError as e:
|
||||
# FIXME: Is there a better way to differentiate between SSLErrors?
|
||||
if not 'read operation timed out' in str(e): # Defensive:
|
||||
# This shouldn't happen but just in case we're missing an edge
|
||||
# case, let's avoid swallowing SSL errors.
|
||||
raise
|
||||
|
||||
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
||||
|
||||
except HTTPException as e:
|
||||
# This includes IncompleteRead.
|
||||
raise ProtocolError('Connection broken: %r' % e, e)
|
||||
|
||||
if data:
|
||||
self._fp_bytes_read += len(data)
|
||||
|
||||
try:
|
||||
if decode_content and self._decoder:
|
||||
data = self._decoder.decompress(data)
|
||||
except (IOError, zlib.error) as e:
|
||||
raise DecodeError(
|
||||
"Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding, e)
|
||||
|
||||
if flush_decoder and decode_content and self._decoder:
|
||||
buf = self._decoder.decompress(binary_type())
|
||||
data += buf + self._decoder.flush()
|
||||
data = self._decode(data, decode_content, flush_decoder)
|
||||
|
||||
if cache_content:
|
||||
self._body = data
|
||||
|
||||
return data
|
||||
|
||||
finally:
|
||||
if self._original_response and self._original_response.isclosed():
|
||||
self.release_conn()
|
||||
return data
|
||||
|
||||
def stream(self, amt=2**16, decode_content=None):
|
||||
"""
|
||||
@@ -252,11 +336,15 @@ class HTTPResponse(io.IOBase):
|
||||
If True, will attempt to decode the body based on the
|
||||
'content-encoding' header.
|
||||
"""
|
||||
while not is_fp_closed(self._fp):
|
||||
data = self.read(amt=amt, decode_content=decode_content)
|
||||
if self.chunked:
|
||||
for line in self.read_chunked(amt, decode_content=decode_content):
|
||||
yield line
|
||||
else:
|
||||
while not is_fp_closed(self._fp):
|
||||
data = self.read(amt=amt, decode_content=decode_content)
|
||||
|
||||
if data:
|
||||
yield data
|
||||
if data:
|
||||
yield data
|
||||
|
||||
@classmethod
|
||||
def from_httplib(ResponseCls, r, **response_kw):
|
||||
@@ -267,14 +355,17 @@ class HTTPResponse(io.IOBase):
|
||||
Remaining parameters are passed to the HTTPResponse constructor, along
|
||||
with ``original_response=r``.
|
||||
"""
|
||||
headers = r.msg
|
||||
|
||||
headers = HTTPHeaderDict()
|
||||
for k, v in r.getheaders():
|
||||
headers.add(k, v)
|
||||
if not isinstance(headers, HTTPHeaderDict):
|
||||
if PY3: # Python 3
|
||||
headers = HTTPHeaderDict(headers.items())
|
||||
else: # Python 2
|
||||
headers = HTTPHeaderDict.from_httplib(headers)
|
||||
|
||||
# HTTPResponse objects in Python 3 don't have a .strict attribute
|
||||
strict = getattr(r, 'strict', 0)
|
||||
return ResponseCls(body=r,
|
||||
resp = ResponseCls(body=r,
|
||||
headers=headers,
|
||||
status=r.status,
|
||||
version=r.version,
|
||||
@@ -282,6 +373,7 @@ class HTTPResponse(io.IOBase):
|
||||
strict=strict,
|
||||
original_response=r,
|
||||
**response_kw)
|
||||
return resp
|
||||
|
||||
# Backwards-compatibility methods for httplib.HTTPResponse
|
||||
def getheaders(self):
|
||||
@@ -331,3 +423,92 @@ class HTTPResponse(io.IOBase):
|
||||
else:
|
||||
b[:len(temp)] = temp
|
||||
return len(temp)
|
||||
|
||||
def _update_chunk_length(self):
|
||||
# First, we'll figure out length of a chunk and then
|
||||
# we'll try to read it from socket.
|
||||
if self.chunk_left is not None:
|
||||
return
|
||||
line = self._fp.fp.readline()
|
||||
line = line.split(b';', 1)[0]
|
||||
try:
|
||||
self.chunk_left = int(line, 16)
|
||||
except ValueError:
|
||||
# Invalid chunked protocol response, abort.
|
||||
self.close()
|
||||
raise httplib.IncompleteRead(line)
|
||||
|
||||
def _handle_chunk(self, amt):
|
||||
returned_chunk = None
|
||||
if amt is None:
|
||||
chunk = self._fp._safe_read(self.chunk_left)
|
||||
returned_chunk = chunk
|
||||
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
|
||||
self.chunk_left = None
|
||||
elif amt < self.chunk_left:
|
||||
value = self._fp._safe_read(amt)
|
||||
self.chunk_left = self.chunk_left - amt
|
||||
returned_chunk = value
|
||||
elif amt == self.chunk_left:
|
||||
value = self._fp._safe_read(amt)
|
||||
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
|
||||
self.chunk_left = None
|
||||
returned_chunk = value
|
||||
else: # amt > self.chunk_left
|
||||
returned_chunk = self._fp._safe_read(self.chunk_left)
|
||||
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
|
||||
self.chunk_left = None
|
||||
return returned_chunk
|
||||
|
||||
def read_chunked(self, amt=None, decode_content=None):
|
||||
"""
|
||||
Similar to :meth:`HTTPResponse.read`, but with an additional
|
||||
parameter: ``decode_content``.
|
||||
|
||||
:param decode_content:
|
||||
If True, will attempt to decode the body based on the
|
||||
'content-encoding' header.
|
||||
"""
|
||||
self._init_decoder()
|
||||
# FIXME: Rewrite this method and make it a class with a better structured logic.
|
||||
if not self.chunked:
|
||||
raise ResponseNotChunked(
|
||||
"Response is not chunked. "
|
||||
"Header 'transfer-encoding: chunked' is missing.")
|
||||
|
||||
# Don't bother reading the body of a HEAD request.
|
||||
if self._original_response and is_response_to_head(self._original_response):
|
||||
self._original_response.close()
|
||||
return
|
||||
|
||||
with self._error_catcher():
|
||||
while True:
|
||||
self._update_chunk_length()
|
||||
if self.chunk_left == 0:
|
||||
break
|
||||
chunk = self._handle_chunk(amt)
|
||||
decoded = self._decode(chunk, decode_content=decode_content,
|
||||
flush_decoder=False)
|
||||
if decoded:
|
||||
yield decoded
|
||||
|
||||
if decode_content:
|
||||
# On CPython and PyPy, we should never need to flush the
|
||||
# decoder. However, on Jython we *might* need to, so
|
||||
# lets defensively do it anyway.
|
||||
decoded = self._flush_decoder()
|
||||
if decoded: # Platform-specific: Jython.
|
||||
yield decoded
|
||||
|
||||
# Chunk content ends with \r\n: discard it.
|
||||
while True:
|
||||
line = self._fp.fp.readline()
|
||||
if not line:
|
||||
# Some sites may not end with '\r\n'.
|
||||
break
|
||||
if line == b'\r\n':
|
||||
break
|
||||
|
||||
# We read everything; close the "file".
|
||||
if self._original_response:
|
||||
self._original_response.close()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
# For backwards compatibility, provide imports that used to be here.
|
||||
from .connection import is_connection_dropped
|
||||
from .request import make_headers
|
||||
@@ -22,3 +23,22 @@ from .url import (
|
||||
split_first,
|
||||
Url,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
'HAS_SNI',
|
||||
'SSLContext',
|
||||
'Retry',
|
||||
'Timeout',
|
||||
'Url',
|
||||
'assert_fingerprint',
|
||||
'current_time',
|
||||
'is_connection_dropped',
|
||||
'is_fp_closed',
|
||||
'get_host',
|
||||
'parse_url',
|
||||
'make_headers',
|
||||
'resolve_cert_reqs',
|
||||
'resolve_ssl_version',
|
||||
'split_first',
|
||||
'ssl_wrap_socket',
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
import socket
|
||||
try:
|
||||
from select import poll, POLLIN
|
||||
@@ -60,6 +61,8 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
"""
|
||||
|
||||
host, port = address
|
||||
if host.startswith('['):
|
||||
host = host.strip('[]')
|
||||
err = None
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
@@ -78,15 +81,16 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||
sock.connect(sa)
|
||||
return sock
|
||||
|
||||
except socket.error as _:
|
||||
err = _
|
||||
except socket.error as e:
|
||||
err = e
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
sock = None
|
||||
|
||||
if err is not None:
|
||||
raise err
|
||||
else:
|
||||
raise socket.error("getaddrinfo returns an empty list")
|
||||
|
||||
raise socket.error("getaddrinfo returns an empty list")
|
||||
|
||||
|
||||
def _set_socket_options(sock, options):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
from base64 import b64encode
|
||||
|
||||
from ..packages.six import b
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
from __future__ import absolute_import
|
||||
from ..packages.six.moves import http_client as httplib
|
||||
|
||||
from ..exceptions import HeaderParsingError
|
||||
|
||||
|
||||
def is_fp_closed(obj):
|
||||
"""
|
||||
Checks whether a given file-like object is closed.
|
||||
@@ -20,3 +26,49 @@ def is_fp_closed(obj):
|
||||
pass
|
||||
|
||||
raise ValueError("Unable to determine whether fp is closed.")
|
||||
|
||||
|
||||
def assert_header_parsing(headers):
|
||||
"""
|
||||
Asserts whether all headers have been successfully parsed.
|
||||
Extracts encountered errors from the result of parsing headers.
|
||||
|
||||
Only works on Python 3.
|
||||
|
||||
:param headers: Headers to verify.
|
||||
:type headers: `httplib.HTTPMessage`.
|
||||
|
||||
:raises urllib3.exceptions.HeaderParsingError:
|
||||
If parsing errors are found.
|
||||
"""
|
||||
|
||||
# This will fail silently if we pass in the wrong kind of parameter.
|
||||
# To make debugging easier add an explicit check.
|
||||
if not isinstance(headers, httplib.HTTPMessage):
|
||||
raise TypeError('expected httplib.Message, got {0}.'.format(
|
||||
type(headers)))
|
||||
|
||||
defects = getattr(headers, 'defects', None)
|
||||
get_payload = getattr(headers, 'get_payload', None)
|
||||
|
||||
unparsed_data = None
|
||||
if get_payload: # Platform-specific: Python 3.
|
||||
unparsed_data = get_payload()
|
||||
|
||||
if defects or unparsed_data:
|
||||
raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data)
|
||||
|
||||
|
||||
def is_response_to_head(response):
|
||||
"""
|
||||
Checks, wether a the request of a response has been a HEAD-request.
|
||||
Handles the quirks of AppEngine.
|
||||
|
||||
:param conn:
|
||||
:type conn: :class:`httplib.HTTPResponse`
|
||||
"""
|
||||
# FIXME: Can we do this somehow without accessing private httplib _method?
|
||||
method = response._method
|
||||
if isinstance(method, int): # Platform-specific: Appengine
|
||||
return method == 3
|
||||
return method.upper() == 'HEAD'
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from __future__ import absolute_import
|
||||
import time
|
||||
import logging
|
||||
|
||||
from ..exceptions import (
|
||||
ProtocolError,
|
||||
ConnectTimeoutError,
|
||||
ReadTimeoutError,
|
||||
MaxRetryError,
|
||||
ProtocolError,
|
||||
ReadTimeoutError,
|
||||
ResponseError,
|
||||
)
|
||||
from ..packages import six
|
||||
|
||||
@@ -36,7 +38,6 @@ class Retry(object):
|
||||
Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless
|
||||
retries are disabled, in which case the causing exception will be raised.
|
||||
|
||||
|
||||
:param int total:
|
||||
Total number of retries to allow. Takes precedence over other counts.
|
||||
|
||||
@@ -94,7 +95,7 @@ class Retry(object):
|
||||
|
||||
seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
|
||||
for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer
|
||||
than :attr:`Retry.MAX_BACKOFF`.
|
||||
than :attr:`Retry.BACKOFF_MAX`.
|
||||
|
||||
By default, backoff is disabled (set to 0).
|
||||
|
||||
@@ -126,7 +127,7 @@ class Retry(object):
|
||||
self.method_whitelist = method_whitelist
|
||||
self.backoff_factor = backoff_factor
|
||||
self.raise_on_redirect = raise_on_redirect
|
||||
self._observed_errors = _observed_errors # TODO: use .history instead?
|
||||
self._observed_errors = _observed_errors # TODO: use .history instead?
|
||||
|
||||
def new(self, **kw):
|
||||
params = dict(
|
||||
@@ -184,13 +185,13 @@ class Retry(object):
|
||||
return isinstance(err, ConnectTimeoutError)
|
||||
|
||||
def _is_read_error(self, err):
|
||||
""" Errors that occur after the request has been started, so we can't
|
||||
assume that the server did not process any of it.
|
||||
""" Errors that occur after the request has been started, so we should
|
||||
assume that the server began processing it.
|
||||
"""
|
||||
return isinstance(err, (ReadTimeoutError, ProtocolError))
|
||||
|
||||
def is_forced_retry(self, method, status_code):
|
||||
""" Is this method/response retryable? (Based on method/codes whitelists)
|
||||
""" Is this method/status code retryable? (Based on method/codes whitelists)
|
||||
"""
|
||||
if self.method_whitelist and method.upper() not in self.method_whitelist:
|
||||
return False
|
||||
@@ -198,8 +199,7 @@ class Retry(object):
|
||||
return self.status_forcelist and status_code in self.status_forcelist
|
||||
|
||||
def is_exhausted(self):
|
||||
""" Are we out of retries?
|
||||
"""
|
||||
""" Are we out of retries? """
|
||||
retry_counts = (self.total, self.connect, self.read, self.redirect)
|
||||
retry_counts = list(filter(None, retry_counts))
|
||||
if not retry_counts:
|
||||
@@ -207,7 +207,8 @@ class Retry(object):
|
||||
|
||||
return min(retry_counts) < 0
|
||||
|
||||
def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None):
|
||||
def increment(self, method=None, url=None, response=None, error=None,
|
||||
_pool=None, _stacktrace=None):
|
||||
""" Return a new Retry object with incremented retry counters.
|
||||
|
||||
:param response: A response object, or None, if the server did not
|
||||
@@ -230,6 +231,7 @@ class Retry(object):
|
||||
connect = self.connect
|
||||
read = self.read
|
||||
redirect = self.redirect
|
||||
cause = 'unknown'
|
||||
|
||||
if error and self._is_connection_error(error):
|
||||
# Connect retry?
|
||||
@@ -251,10 +253,16 @@ class Retry(object):
|
||||
# Redirect retry?
|
||||
if redirect is not None:
|
||||
redirect -= 1
|
||||
cause = 'too many redirects'
|
||||
|
||||
else:
|
||||
# FIXME: Nothing changed, scenario doesn't make sense.
|
||||
# Incrementing because of a server error like a 500 in
|
||||
# status_forcelist and a the given method is in the whitelist
|
||||
_observed_errors += 1
|
||||
cause = ResponseError.GENERIC_ERROR
|
||||
if response and response.status:
|
||||
cause = ResponseError.SPECIFIC_ERROR.format(
|
||||
status_code=response.status)
|
||||
|
||||
new_retry = self.new(
|
||||
total=total,
|
||||
@@ -262,13 +270,12 @@ class Retry(object):
|
||||
_observed_errors=_observed_errors)
|
||||
|
||||
if new_retry.is_exhausted():
|
||||
raise MaxRetryError(_pool, url, error)
|
||||
raise MaxRetryError(_pool, url, error or ResponseError(cause))
|
||||
|
||||
log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry))
|
||||
|
||||
return new_retry
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return ('{cls.__name__}(total={self.total}, connect={self.connect}, '
|
||||
'read={self.read}, redirect={self.redirect})').format(
|
||||
|
||||
@@ -1,21 +1,137 @@
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import md5, sha1
|
||||
from __future__ import absolute_import
|
||||
import errno
|
||||
import warnings
|
||||
import hmac
|
||||
|
||||
from ..exceptions import SSLError
|
||||
from binascii import hexlify, unhexlify
|
||||
from hashlib import md5, sha1, sha256
|
||||
|
||||
from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
|
||||
|
||||
|
||||
SSLContext = None
|
||||
HAS_SNI = False
|
||||
create_default_context = None
|
||||
|
||||
# Maps the length of a digest to a possible hash function producing this digest
|
||||
HASHFUNC_MAP = {
|
||||
32: md5,
|
||||
40: sha1,
|
||||
64: sha256,
|
||||
}
|
||||
|
||||
|
||||
def _const_compare_digest_backport(a, b):
|
||||
"""
|
||||
Compare two digests of equal length in constant time.
|
||||
|
||||
The digests must be of type str/bytes.
|
||||
Returns True if the digests match, and False otherwise.
|
||||
"""
|
||||
result = abs(len(a) - len(b))
|
||||
for l, r in zip(bytearray(a), bytearray(b)):
|
||||
result |= l ^ r
|
||||
return result == 0
|
||||
|
||||
|
||||
_const_compare_digest = getattr(hmac, 'compare_digest',
|
||||
_const_compare_digest_backport)
|
||||
|
||||
|
||||
try: # Test for SSL features
|
||||
SSLContext = None
|
||||
HAS_SNI = False
|
||||
|
||||
import ssl
|
||||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
|
||||
from ssl import SSLContext # Modern SSL?
|
||||
from ssl import HAS_SNI # Has SNI?
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION
|
||||
except ImportError:
|
||||
OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
|
||||
OP_NO_COMPRESSION = 0x20000
|
||||
|
||||
# A secure default.
|
||||
# Sources for more information on TLS ciphers:
|
||||
#
|
||||
# - https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
# - https://www.ssllabs.com/projects/best-practices/index.html
|
||||
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
||||
#
|
||||
# The general intent is:
|
||||
# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
|
||||
# - prefer ECDHE over DHE for better performance,
|
||||
# - prefer any AES-GCM over any AES-CBC for better performance and security,
|
||||
# - use 3DES as fallback which is secure but slow,
|
||||
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
|
||||
DEFAULT_CIPHERS = (
|
||||
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
|
||||
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
|
||||
'!eNULL:!MD5'
|
||||
)
|
||||
|
||||
try:
|
||||
from ssl import SSLContext # Modern SSL?
|
||||
except ImportError:
|
||||
import sys
|
||||
|
||||
class SSLContext(object): # Platform-specific: Python 2 & 3.1
|
||||
supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or
|
||||
(3, 2) <= sys.version_info)
|
||||
|
||||
def __init__(self, protocol_version):
|
||||
self.protocol = protocol_version
|
||||
# Use default values from a real SSLContext
|
||||
self.check_hostname = False
|
||||
self.verify_mode = ssl.CERT_NONE
|
||||
self.ca_certs = None
|
||||
self.options = 0
|
||||
self.certfile = None
|
||||
self.keyfile = None
|
||||
self.ciphers = None
|
||||
|
||||
def load_cert_chain(self, certfile, keyfile):
|
||||
self.certfile = certfile
|
||||
self.keyfile = keyfile
|
||||
|
||||
def load_verify_locations(self, cafile=None, capath=None):
|
||||
self.ca_certs = cafile
|
||||
|
||||
if capath is not None:
|
||||
raise SSLError("CA directories not supported in older Pythons")
|
||||
|
||||
def set_ciphers(self, cipher_suite):
|
||||
if not self.supports_set_ciphers:
|
||||
raise TypeError(
|
||||
'Your version of Python does not support setting '
|
||||
'a custom cipher suite. Please upgrade to Python '
|
||||
'2.7, 3.2, or later if you need this functionality.'
|
||||
)
|
||||
self.ciphers = cipher_suite
|
||||
|
||||
def wrap_socket(self, socket, server_hostname=None):
|
||||
warnings.warn(
|
||||
'A true SSLContext object is not available. This prevents '
|
||||
'urllib3 from configuring SSL appropriately and may cause '
|
||||
'certain SSL connections to fail. For more information, see '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||
'#insecureplatformwarning.',
|
||||
InsecurePlatformWarning
|
||||
)
|
||||
kwargs = {
|
||||
'keyfile': self.keyfile,
|
||||
'certfile': self.certfile,
|
||||
'ca_certs': self.ca_certs,
|
||||
'cert_reqs': self.verify_mode,
|
||||
'ssl_version': self.protocol,
|
||||
}
|
||||
if self.supports_set_ciphers: # Platform-specific: Python 2.7+
|
||||
return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
|
||||
else: # Platform-specific: Python 2.6
|
||||
return wrap_socket(socket, **kwargs)
|
||||
|
||||
|
||||
def assert_fingerprint(cert, fingerprint):
|
||||
"""
|
||||
Checks if given fingerprint matches the supplied certificate.
|
||||
@@ -26,30 +142,21 @@ def assert_fingerprint(cert, fingerprint):
|
||||
Fingerprint as string of hexdigits, can be interspersed by colons.
|
||||
"""
|
||||
|
||||
# Maps the length of a digest to a possible hash function producing
|
||||
# this digest.
|
||||
hashfunc_map = {
|
||||
16: md5,
|
||||
20: sha1
|
||||
}
|
||||
|
||||
fingerprint = fingerprint.replace(':', '').lower()
|
||||
digest_length, odd = divmod(len(fingerprint), 2)
|
||||
|
||||
if odd or digest_length not in hashfunc_map:
|
||||
raise SSLError('Fingerprint is of invalid length.')
|
||||
digest_length = len(fingerprint)
|
||||
hashfunc = HASHFUNC_MAP.get(digest_length)
|
||||
if not hashfunc:
|
||||
raise SSLError(
|
||||
'Fingerprint of invalid length: {0}'.format(fingerprint))
|
||||
|
||||
# We need encode() here for py32; works on py2 and p33.
|
||||
fingerprint_bytes = unhexlify(fingerprint.encode())
|
||||
|
||||
hashfunc = hashfunc_map[digest_length]
|
||||
|
||||
cert_digest = hashfunc(cert).digest()
|
||||
|
||||
if not cert_digest == fingerprint_bytes:
|
||||
if not _const_compare_digest(cert_digest, fingerprint_bytes):
|
||||
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
|
||||
.format(hexlify(fingerprint_bytes),
|
||||
hexlify(cert_digest)))
|
||||
.format(fingerprint, hexlify(cert_digest)))
|
||||
|
||||
|
||||
def resolve_cert_reqs(candidate):
|
||||
@@ -91,42 +198,120 @@ def resolve_ssl_version(candidate):
|
||||
return candidate
|
||||
|
||||
|
||||
if SSLContext is not None: # Python 3.2+
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=None):
|
||||
"""
|
||||
All arguments except `server_hostname` have the same meaning as for
|
||||
:func:`ssl.wrap_socket`
|
||||
def create_urllib3_context(ssl_version=None, cert_reqs=None,
|
||||
options=None, ciphers=None):
|
||||
"""All arguments have the same meaning as ``ssl_wrap_socket``.
|
||||
|
||||
:param server_hostname:
|
||||
Hostname of the expected certificate
|
||||
"""
|
||||
context = SSLContext(ssl_version)
|
||||
context.verify_mode = cert_reqs
|
||||
By default, this function does a lot of the same work that
|
||||
``ssl.create_default_context`` does on Python 3.4+. It:
|
||||
|
||||
# Disable TLS compression to migitate CRIME attack (issue #309)
|
||||
OP_NO_COMPRESSION = 0x20000
|
||||
context.options |= OP_NO_COMPRESSION
|
||||
- Disables SSLv2, SSLv3, and compression
|
||||
- Sets a restricted set of server ciphers
|
||||
|
||||
if ca_certs:
|
||||
try:
|
||||
context.load_verify_locations(ca_certs)
|
||||
# Py32 raises IOError
|
||||
# Py33 raises FileNotFoundError
|
||||
except Exception as e: # Reraise as SSLError
|
||||
If you wish to enable SSLv3, you can do::
|
||||
|
||||
from urllib3.util import ssl_
|
||||
context = ssl_.create_urllib3_context()
|
||||
context.options &= ~ssl_.OP_NO_SSLv3
|
||||
|
||||
You can do the same to enable compression (substituting ``COMPRESSION``
|
||||
for ``SSLv3`` in the last line above).
|
||||
|
||||
:param ssl_version:
|
||||
The desired protocol version to use. This will default to
|
||||
PROTOCOL_SSLv23 which will negotiate the highest protocol that both
|
||||
the server and your installation of OpenSSL support.
|
||||
:param cert_reqs:
|
||||
Whether to require the certificate verification. This defaults to
|
||||
``ssl.CERT_REQUIRED``.
|
||||
:param options:
|
||||
Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
|
||||
``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``.
|
||||
:param ciphers:
|
||||
Which cipher suites to allow the server to select.
|
||||
:returns:
|
||||
Constructed SSLContext object with specified options
|
||||
:rtype: SSLContext
|
||||
"""
|
||||
context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23)
|
||||
|
||||
# Setting the default here, as we may have no ssl module on import
|
||||
cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
|
||||
|
||||
if options is None:
|
||||
options = 0
|
||||
# SSLv2 is easily broken and is considered harmful and dangerous
|
||||
options |= OP_NO_SSLv2
|
||||
# SSLv3 has several problems and is now dangerous
|
||||
options |= OP_NO_SSLv3
|
||||
# Disable compression to prevent CRIME attacks for OpenSSL 1.0+
|
||||
# (issue #309)
|
||||
options |= OP_NO_COMPRESSION
|
||||
|
||||
context.options |= options
|
||||
|
||||
if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6
|
||||
context.set_ciphers(ciphers or DEFAULT_CIPHERS)
|
||||
|
||||
context.verify_mode = cert_reqs
|
||||
if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2
|
||||
# We do our own verification, including fingerprints and alternative
|
||||
# hostnames. So disable it here
|
||||
context.check_hostname = False
|
||||
return context
|
||||
|
||||
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=None, ciphers=None, ssl_context=None,
|
||||
ca_cert_dir=None):
|
||||
"""
|
||||
All arguments except for server_hostname, ssl_context, and ca_cert_dir have
|
||||
the same meaning as they do when using :func:`ssl.wrap_socket`.
|
||||
|
||||
:param server_hostname:
|
||||
When SNI is supported, the expected hostname of the certificate
|
||||
:param ssl_context:
|
||||
A pre-made :class:`SSLContext` object. If none is provided, one will
|
||||
be created using :func:`create_urllib3_context`.
|
||||
:param ciphers:
|
||||
A string of ciphers we wish the client to support. This is not
|
||||
supported on Python 2.6 as the ssl module does not support it.
|
||||
:param ca_cert_dir:
|
||||
A directory containing CA certificates in multiple separate files, as
|
||||
supported by OpenSSL's -CApath flag or the capath argument to
|
||||
SSLContext.load_verify_locations().
|
||||
"""
|
||||
context = ssl_context
|
||||
if context is None:
|
||||
context = create_urllib3_context(ssl_version, cert_reqs,
|
||||
ciphers=ciphers)
|
||||
|
||||
if ca_certs or ca_cert_dir:
|
||||
try:
|
||||
context.load_verify_locations(ca_certs, ca_cert_dir)
|
||||
except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2
|
||||
raise SSLError(e)
|
||||
# Py33 raises FileNotFoundError which subclasses OSError
|
||||
# These are not equivalent unless we check the errno attribute
|
||||
except OSError as e: # Platform-specific: Python 3.3 and beyond
|
||||
if e.errno == errno.ENOENT:
|
||||
raise SSLError(e)
|
||||
if certfile:
|
||||
# FIXME: This block needs a test.
|
||||
context.load_cert_chain(certfile, keyfile)
|
||||
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
|
||||
return context.wrap_socket(sock, server_hostname=server_hostname)
|
||||
return context.wrap_socket(sock)
|
||||
raise
|
||||
|
||||
else: # Python 3.1 and earlier
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=None):
|
||||
return wrap_socket(sock, keyfile=keyfile, certfile=certfile,
|
||||
ca_certs=ca_certs, cert_reqs=cert_reqs,
|
||||
ssl_version=ssl_version)
|
||||
if certfile:
|
||||
context.load_cert_chain(certfile, keyfile)
|
||||
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
|
||||
return context.wrap_socket(sock, server_hostname=server_hostname)
|
||||
|
||||
warnings.warn(
|
||||
'An HTTPS request has been made, but the SNI (Subject Name '
|
||||
'Indication) extension to TLS is not available on this platform. '
|
||||
'This may cause the server to present an incorrect TLS '
|
||||
'certificate, which can cause validation failures. For more '
|
||||
'information, see '
|
||||
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||
'#snimissingwarning.',
|
||||
SNIMissingWarning
|
||||
)
|
||||
return context.wrap_socket(sock)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
# The default socket timeout, used by httplib to indicate that no timeout was
|
||||
# specified by the user
|
||||
from socket import _GLOBAL_DEFAULT_TIMEOUT
|
||||
@@ -9,6 +10,7 @@ from ..exceptions import TimeoutStateError
|
||||
# urllib3
|
||||
_Default = object()
|
||||
|
||||
|
||||
def current_time():
|
||||
"""
|
||||
Retrieve the current time. This function is mocked out in unit testing.
|
||||
@@ -226,9 +228,9 @@ class Timeout(object):
|
||||
has not yet been called on this object.
|
||||
"""
|
||||
if (self.total is not None and
|
||||
self.total is not self.DEFAULT_TIMEOUT and
|
||||
self._read is not None and
|
||||
self._read is not self.DEFAULT_TIMEOUT):
|
||||
self.total is not self.DEFAULT_TIMEOUT and
|
||||
self._read is not None and
|
||||
self._read is not self.DEFAULT_TIMEOUT):
|
||||
# In case the connect timeout has not yet been established.
|
||||
if self._start_connect is None:
|
||||
return self._read
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
from collections import namedtuple
|
||||
|
||||
from ..exceptions import LocationParseError
|
||||
@@ -15,6 +16,8 @@ class Url(namedtuple('Url', url_attrs)):
|
||||
|
||||
def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None,
|
||||
query=None, fragment=None):
|
||||
if path and not path.startswith('/'):
|
||||
path = '/' + path
|
||||
return super(Url, cls).__new__(cls, scheme, auth, host, port, path,
|
||||
query, fragment)
|
||||
|
||||
@@ -40,6 +43,49 @@ class Url(namedtuple('Url', url_attrs)):
|
||||
return '%s:%d' % (self.host, self.port)
|
||||
return self.host
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
Convert self into a url
|
||||
|
||||
This function should more or less round-trip with :func:`.parse_url`. The
|
||||
returned url may not be exactly the same as the url inputted to
|
||||
:func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
|
||||
with a blank port will have : removed).
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> U = parse_url('http://google.com/mail/')
|
||||
>>> U.url
|
||||
'http://google.com/mail/'
|
||||
>>> Url('http', 'username:password', 'host.com', 80,
|
||||
... '/path', 'query', 'fragment').url
|
||||
'http://username:password@host.com:80/path?query#fragment'
|
||||
"""
|
||||
scheme, auth, host, port, path, query, fragment = self
|
||||
url = ''
|
||||
|
||||
# We use "is not None" we want things to happen with empty strings (or 0 port)
|
||||
if scheme is not None:
|
||||
url += scheme + '://'
|
||||
if auth is not None:
|
||||
url += auth + '@'
|
||||
if host is not None:
|
||||
url += host
|
||||
if port is not None:
|
||||
url += ':' + str(port)
|
||||
if path is not None:
|
||||
url += path
|
||||
if query is not None:
|
||||
url += '?' + query
|
||||
if fragment is not None:
|
||||
url += '#' + fragment
|
||||
|
||||
return url
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
||||
|
||||
def split_first(s, delims):
|
||||
"""
|
||||
@@ -71,7 +117,7 @@ def split_first(s, delims):
|
||||
if min_idx is None or min_idx < 0:
|
||||
return s, '', None
|
||||
|
||||
return s[:min_idx], s[min_idx+1:], min_delim
|
||||
return s[:min_idx], s[min_idx + 1:], min_delim
|
||||
|
||||
|
||||
def parse_url(url):
|
||||
@@ -84,7 +130,7 @@ def parse_url(url):
|
||||
Example::
|
||||
|
||||
>>> parse_url('http://google.com/mail/')
|
||||
Url(scheme='http', host='google.com', port=None, path='/', ...)
|
||||
Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
|
||||
>>> parse_url('google.com:80')
|
||||
Url(scheme=None, host='google.com', port=80, path=None, ...)
|
||||
>>> parse_url('/foo?bar')
|
||||
|
||||
@@ -13,7 +13,7 @@ from collections import Mapping
|
||||
from datetime import datetime
|
||||
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import cookielib, OrderedDict, urljoin, urlparse, builtin_str
|
||||
from .compat import cookielib, OrderedDict, urljoin, urlparse
|
||||
from .cookies import (
|
||||
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
|
||||
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
|
||||
@@ -21,6 +21,7 @@ from .hooks import default_hooks, dispatch_hook
|
||||
from .utils import to_key_val_list, default_headers, to_native_string
|
||||
from .exceptions import (
|
||||
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
|
||||
from .packages.urllib3._collections import RecentlyUsedContainer
|
||||
from .structures import CaseInsensitiveDict
|
||||
|
||||
from .adapters import HTTPAdapter
|
||||
@@ -35,6 +36,8 @@ from .status_codes import codes
|
||||
# formerly defined here, reexposed here for backward compatibility
|
||||
from .models import REDIRECT_STATI
|
||||
|
||||
REDIRECT_CACHE_SIZE = 1000
|
||||
|
||||
|
||||
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||
"""
|
||||
@@ -59,12 +62,11 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||
merged_setting = dict_class(to_key_val_list(session_setting))
|
||||
merged_setting.update(to_key_val_list(request_setting))
|
||||
|
||||
# Remove keys that are set to None.
|
||||
for (k, v) in request_setting.items():
|
||||
if v is None:
|
||||
del merged_setting[k]
|
||||
|
||||
merged_setting = dict((k, v) for (k, v) in merged_setting.items() if v is not None)
|
||||
# Remove keys that are set to None. Extract keys first to avoid altering
|
||||
# the dictionary during iteration.
|
||||
none_keys = [k for (k, v) in merged_setting.items() if v is None]
|
||||
for key in none_keys:
|
||||
del merged_setting[key]
|
||||
|
||||
return merged_setting
|
||||
|
||||
@@ -87,7 +89,7 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
|
||||
|
||||
class SessionRedirectMixin(object):
|
||||
def resolve_redirects(self, resp, req, stream=False, timeout=None,
|
||||
verify=True, cert=None, proxies=None):
|
||||
verify=True, cert=None, proxies=None, **adapter_kwargs):
|
||||
"""Receives a Response. Returns a generator of Responses."""
|
||||
|
||||
i = 0
|
||||
@@ -128,7 +130,7 @@ class SessionRedirectMixin(object):
|
||||
# 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 urlparse(url).netloc:
|
||||
if not parsed.netloc:
|
||||
url = urljoin(resp.url, requote_uri(url))
|
||||
else:
|
||||
url = requote_uri(url)
|
||||
@@ -168,7 +170,10 @@ class SessionRedirectMixin(object):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
extract_cookies_to_jar(prepared_request._cookies, prepared_request, resp.raw)
|
||||
# Extract any cookies sent on the response to the cookiejar
|
||||
# in the new request. Because we've mutated our copied prepared
|
||||
# request, use the old one that we haven't yet touched.
|
||||
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
|
||||
prepared_request._cookies.update(self.cookies)
|
||||
prepared_request.prepare_cookies(prepared_request._cookies)
|
||||
|
||||
@@ -187,6 +192,7 @@ class SessionRedirectMixin(object):
|
||||
cert=cert,
|
||||
proxies=proxies,
|
||||
allow_redirects=False,
|
||||
**adapter_kwargs
|
||||
)
|
||||
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
|
||||
@@ -267,13 +273,19 @@ class Session(SessionRedirectMixin):
|
||||
>>> import requests
|
||||
>>> s = requests.Session()
|
||||
>>> s.get('http://httpbin.org/get')
|
||||
200
|
||||
<Response [200]>
|
||||
|
||||
Or as a context manager::
|
||||
|
||||
>>> with requests.Session() as s:
|
||||
>>> s.get('http://httpbin.org/get')
|
||||
<Response [200]>
|
||||
"""
|
||||
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
|
||||
'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
|
||||
'max_redirects', 'redirect_cache'
|
||||
'max_redirects',
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
@@ -287,9 +299,9 @@ class Session(SessionRedirectMixin):
|
||||
#: :class:`Request <Request>`.
|
||||
self.auth = None
|
||||
|
||||
#: Dictionary mapping protocol to the URL of the proxy (e.g.
|
||||
#: {'http': 'foo.bar:3128'}) to be used on each
|
||||
#: :class:`Request <Request>`.
|
||||
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
|
||||
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
|
||||
#: be used on each :class:`Request <Request>`.
|
||||
self.proxies = {}
|
||||
|
||||
#: Event-handling hooks.
|
||||
@@ -313,7 +325,8 @@ class Session(SessionRedirectMixin):
|
||||
#: limit, a :class:`TooManyRedirects` exception is raised.
|
||||
self.max_redirects = DEFAULT_REDIRECT_LIMIT
|
||||
|
||||
#: Should we trust the environment?
|
||||
#: Trust environment settings for proxy configuration, default
|
||||
#: authentication and similar.
|
||||
self.trust_env = True
|
||||
|
||||
#: A CookieJar containing all currently outstanding cookies set on this
|
||||
@@ -327,7 +340,8 @@ class Session(SessionRedirectMixin):
|
||||
self.mount('https://', HTTPAdapter())
|
||||
self.mount('http://', HTTPAdapter())
|
||||
|
||||
self.redirect_cache = {}
|
||||
# Only store 1000 redirects to prevent using infinite memory
|
||||
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -397,8 +411,8 @@ class Session(SessionRedirectMixin):
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query
|
||||
string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the
|
||||
:class:`Request`.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send
|
||||
in the body of the :class:`Request`.
|
||||
:param json: (optional) json to send in the body of the
|
||||
:class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the
|
||||
@@ -410,23 +424,20 @@ class Session(SessionRedirectMixin):
|
||||
:param auth: (optional) Auth tuple or callable to enable
|
||||
Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) How long to wait for the server to send
|
||||
data before giving up, as a float, or a (`connect timeout, read
|
||||
timeout <user/advanced.html#timeouts>`_) tuple.
|
||||
data before giving up, as a float, or a :ref:`(connect timeout,
|
||||
read timeout) <timeouts>` tuple.
|
||||
:type timeout: float or tuple
|
||||
:param allow_redirects: (optional) Set to True by default.
|
||||
:type allow_redirects: bool
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of
|
||||
the proxy.
|
||||
:param proxies: (optional) Dictionary mapping protocol or protocol and
|
||||
hostname to the URL of the proxy.
|
||||
:param stream: (optional) whether to immediately download the response
|
||||
content. Defaults to ``False``.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified.
|
||||
A CA_BUNDLE path can also be provided.
|
||||
:param verify: (optional) whether the SSL cert will be verified.
|
||||
A CA_BUNDLE path can also be provided. Defaults to ``True``.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem).
|
||||
If Tuple, ('cert', 'key') pair.
|
||||
"""
|
||||
|
||||
method = builtin_str(method)
|
||||
|
||||
# Create the Request.
|
||||
req = Request(
|
||||
method = method.upper(),
|
||||
@@ -553,10 +564,6 @@ class Session(SessionRedirectMixin):
|
||||
# Set up variables needed for resolve_redirects and dispatching of hooks
|
||||
allow_redirects = kwargs.pop('allow_redirects', True)
|
||||
stream = kwargs.get('stream')
|
||||
timeout = kwargs.get('timeout')
|
||||
verify = kwargs.get('verify')
|
||||
cert = kwargs.get('cert')
|
||||
proxies = kwargs.get('proxies')
|
||||
hooks = request.hooks
|
||||
|
||||
# Get the appropriate adapter to use
|
||||
@@ -584,12 +591,7 @@ class Session(SessionRedirectMixin):
|
||||
extract_cookies_to_jar(self.cookies, request, r.raw)
|
||||
|
||||
# Redirect resolving generator.
|
||||
gen = self.resolve_redirects(r, request,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
proxies=proxies)
|
||||
gen = self.resolve_redirects(r, request, **kwargs)
|
||||
|
||||
# Resolve redirects if allowed.
|
||||
history = [resp for resp in gen] if allow_redirects else []
|
||||
@@ -632,7 +634,7 @@ class Session(SessionRedirectMixin):
|
||||
'cert': cert}
|
||||
|
||||
def get_adapter(self, url):
|
||||
"""Returns the appropriate connnection adapter for the given URL."""
|
||||
"""Returns the appropriate connection adapter for the given URL."""
|
||||
for (prefix, adapter) in self.adapters.items():
|
||||
|
||||
if url.lower().startswith(prefix):
|
||||
@@ -658,12 +660,19 @@ class Session(SessionRedirectMixin):
|
||||
self.adapters[key] = self.adapters.pop(key)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
state['redirect_cache'] = dict(self.redirect_cache)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
redirect_cache = state.pop('redirect_cache', {})
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
|
||||
for redirect, to in redirect_cache.items():
|
||||
self.redirect_cache[redirect] = to
|
||||
|
||||
|
||||
def session():
|
||||
"""Returns a :class:`Session` for context-management."""
|
||||
|
||||
@@ -78,11 +78,12 @@ _codes = {
|
||||
507: ('insufficient_storage',),
|
||||
509: ('bandwidth_limit_exceeded', 'bandwidth'),
|
||||
510: ('not_extended',),
|
||||
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
|
||||
}
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for (code, titles) in list(_codes.items()):
|
||||
for code, titles in _codes.items():
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith('\\'):
|
||||
|
||||
@@ -19,15 +19,17 @@ import re
|
||||
import sys
|
||||
import socket
|
||||
import struct
|
||||
import warnings
|
||||
|
||||
from . import __version__
|
||||
from . import certs
|
||||
from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
|
||||
builtin_str, getproxies, proxy_bypass, urlunparse)
|
||||
builtin_str, getproxies, proxy_bypass, urlunparse,
|
||||
basestring)
|
||||
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .exceptions import InvalidURL
|
||||
from .exceptions import InvalidURL, FileModeWarning
|
||||
|
||||
_hush_pyflakes = (RequestsCookieJar,)
|
||||
|
||||
@@ -46,26 +48,47 @@ def dict_to_sequence(d):
|
||||
|
||||
|
||||
def super_len(o):
|
||||
total_length = 0
|
||||
current_position = 0
|
||||
|
||||
if hasattr(o, '__len__'):
|
||||
return len(o)
|
||||
total_length = len(o)
|
||||
|
||||
if hasattr(o, 'len'):
|
||||
return o.len
|
||||
elif hasattr(o, 'len'):
|
||||
total_length = o.len
|
||||
|
||||
if hasattr(o, 'fileno'):
|
||||
elif hasattr(o, 'getvalue'):
|
||||
# e.g. BytesIO, cStringIO.StringIO
|
||||
total_length = len(o.getvalue())
|
||||
|
||||
elif hasattr(o, 'fileno'):
|
||||
try:
|
||||
fileno = o.fileno()
|
||||
except io.UnsupportedOperation:
|
||||
pass
|
||||
else:
|
||||
return os.fstat(fileno).st_size
|
||||
total_length = os.fstat(fileno).st_size
|
||||
|
||||
if hasattr(o, 'getvalue'):
|
||||
# e.g. BytesIO, cStringIO.StringIO
|
||||
return len(o.getvalue())
|
||||
# Having used fstat to determine the file length, we need to
|
||||
# confirm that this file was opened up in binary mode.
|
||||
if 'b' not in o.mode:
|
||||
warnings.warn((
|
||||
"Requests has determined the content-length for this "
|
||||
"request using the binary size of the file: however, the "
|
||||
"file has been opened in text mode (i.e. without the 'b' "
|
||||
"flag in the mode). This may lead to an incorrect "
|
||||
"content-length. In Requests 3.0, support will be removed "
|
||||
"for files in text mode."),
|
||||
FileModeWarning
|
||||
)
|
||||
|
||||
if hasattr(o, 'tell'):
|
||||
current_position = o.tell()
|
||||
|
||||
return max(0, total_length - current_position)
|
||||
|
||||
|
||||
def get_netrc_auth(url):
|
||||
def get_netrc_auth(url, raise_errors=False):
|
||||
"""Returns the Requests tuple auth for a given url from netrc."""
|
||||
|
||||
try:
|
||||
@@ -103,8 +126,9 @@ def get_netrc_auth(url):
|
||||
return (_netrc[login_i], _netrc[2])
|
||||
except (NetrcParseError, IOError):
|
||||
# If there was a parsing error or a permissions issue reading the file,
|
||||
# we'll just skip netrc auth
|
||||
pass
|
||||
# we'll just skip netrc auth unless explicitly asked to raise errors.
|
||||
if raise_errors:
|
||||
raise
|
||||
|
||||
# AppEngine hackiness.
|
||||
except (ImportError, AttributeError):
|
||||
@@ -114,7 +138,8 @@ def get_netrc_auth(url):
|
||||
def guess_filename(obj):
|
||||
"""Tries to guess the filename of the given object."""
|
||||
name = getattr(obj, 'name', None)
|
||||
if name and name[0] != '<' and name[-1] != '>':
|
||||
if (name and isinstance(name, basestring) and name[0] != '<' and
|
||||
name[-1] != '>'):
|
||||
return os.path.basename(name)
|
||||
|
||||
|
||||
@@ -287,6 +312,11 @@ def get_encodings_from_content(content):
|
||||
|
||||
:param content: bytestring to extract encodings from.
|
||||
"""
|
||||
warnings.warn((
|
||||
'In requests 3.0, get_encodings_from_content will be removed. For '
|
||||
'more information, please see the discussion on issue #2266. (This'
|
||||
' warning should only appear once.)'),
|
||||
DeprecationWarning)
|
||||
|
||||
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
|
||||
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
|
||||
@@ -354,6 +384,11 @@ def get_unicode_from_response(r):
|
||||
2. fall back and replace all unicode characters
|
||||
|
||||
"""
|
||||
warnings.warn((
|
||||
'In requests 3.0, get_unicode_from_response will be removed. For '
|
||||
'more information, please see the discussion on issue #2266. (This'
|
||||
' warning should only appear once.)'),
|
||||
DeprecationWarning)
|
||||
|
||||
tried_encodings = []
|
||||
|
||||
@@ -407,10 +442,18 @@ def requote_uri(uri):
|
||||
This function passes the given URI through an unquote/quote cycle to
|
||||
ensure that it is fully and consistently quoted.
|
||||
"""
|
||||
# Unquote only the unreserved characters
|
||||
# Then quote only illegal characters (do not quote reserved, unreserved,
|
||||
# or '%')
|
||||
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
|
||||
safe_with_percent = "!#$%&'()*+,/:;=?@[]~"
|
||||
safe_without_percent = "!#$&'()*+,/:;=?@[]~"
|
||||
try:
|
||||
# Unquote only the unreserved characters
|
||||
# Then quote only illegal characters (do not quote reserved,
|
||||
# unreserved, or '%')
|
||||
return quote(unquote_unreserved(uri), safe=safe_with_percent)
|
||||
except InvalidURL:
|
||||
# We couldn't unquote the given URI, so let's try quoting it, but
|
||||
# there may be unquoted '%'s in the URI. We need to make sure they're
|
||||
# properly quoted so they do not cause issues elsewhere.
|
||||
return quote(uri, safe=safe_without_percent)
|
||||
|
||||
|
||||
def address_in_network(ip, net):
|
||||
@@ -477,7 +520,9 @@ def should_bypass_proxies(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.
|
||||
no_proxy = no_proxy.replace(' ', '').split(',')
|
||||
no_proxy = (
|
||||
host for host in no_proxy.replace(' ', '').split(',') if host
|
||||
)
|
||||
|
||||
ip = netloc.split(':')[0]
|
||||
if is_ipv4_address(ip):
|
||||
@@ -515,36 +560,22 @@ def get_environ_proxies(url):
|
||||
else:
|
||||
return getproxies()
|
||||
|
||||
def select_proxy(url, proxies):
|
||||
"""Select a proxy for the url, if applicable.
|
||||
|
||||
:param url: The url being for the request
|
||||
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
|
||||
"""
|
||||
proxies = proxies or {}
|
||||
urlparts = urlparse(url)
|
||||
proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname)
|
||||
if proxy is None:
|
||||
proxy = proxies.get(urlparts.scheme)
|
||||
return proxy
|
||||
|
||||
def default_user_agent(name="python-requests"):
|
||||
"""Return a string representing the default user agent."""
|
||||
_implementation = platform.python_implementation()
|
||||
|
||||
if _implementation == 'CPython':
|
||||
_implementation_version = platform.python_version()
|
||||
elif _implementation == 'PyPy':
|
||||
_implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
|
||||
sys.pypy_version_info.minor,
|
||||
sys.pypy_version_info.micro)
|
||||
if sys.pypy_version_info.releaselevel != 'final':
|
||||
_implementation_version = ''.join([_implementation_version, sys.pypy_version_info.releaselevel])
|
||||
elif _implementation == 'Jython':
|
||||
_implementation_version = platform.python_version() # Complete Guess
|
||||
elif _implementation == 'IronPython':
|
||||
_implementation_version = platform.python_version() # Complete Guess
|
||||
else:
|
||||
_implementation_version = 'Unknown'
|
||||
|
||||
try:
|
||||
p_system = platform.system()
|
||||
p_release = platform.release()
|
||||
except IOError:
|
||||
p_system = 'Unknown'
|
||||
p_release = 'Unknown'
|
||||
|
||||
return " ".join(['%s/%s' % (name, __version__),
|
||||
'%s/%s' % (_implementation, _implementation_version),
|
||||
'%s/%s' % (p_system, p_release)])
|
||||
return '%s/%s' % (name, __version__)
|
||||
|
||||
|
||||
def default_headers():
|
||||
@@ -567,7 +598,7 @@ def parse_header_links(value):
|
||||
|
||||
replace_chars = " '\""
|
||||
|
||||
for val in value.split(","):
|
||||
for val in re.split(", *<", value):
|
||||
try:
|
||||
url, params = val.split(";", 1)
|
||||
except ValueError:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
py==1.4.12
|
||||
pytest==2.3.4
|
||||
pytest-cov==1.6
|
||||
py==1.4.30
|
||||
pytest==2.8.1
|
||||
pytest-cov==2.1.0
|
||||
pytest-httpbin==0.0.7
|
||||
httpbin==0.4.0
|
||||
wheel
|
||||
|
||||
20
setup.py
20
setup.py
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
from codecs import open
|
||||
|
||||
try:
|
||||
@@ -29,6 +28,14 @@ packages = [
|
||||
|
||||
requires = []
|
||||
|
||||
version = ''
|
||||
with open('requests/__init__.py', 'r') as fd:
|
||||
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
|
||||
fd.read(), re.MULTILINE).group(1)
|
||||
|
||||
if not version:
|
||||
raise RuntimeError('Cannot find version information')
|
||||
|
||||
with open('README.rst', 'r', 'utf-8') as f:
|
||||
readme = f.read()
|
||||
with open('HISTORY.rst', 'r', 'utf-8') as f:
|
||||
@@ -36,7 +43,7 @@ with open('HISTORY.rst', 'r', 'utf-8') as f:
|
||||
|
||||
setup(
|
||||
name='requests',
|
||||
version=requests.__version__,
|
||||
version=version,
|
||||
description='Python HTTP for Humans.',
|
||||
long_description=readme + '\n\n' + history,
|
||||
author='Kenneth Reitz',
|
||||
@@ -55,14 +62,13 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4'
|
||||
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
),
|
||||
extras_require={
|
||||
'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'],
|
||||
'security': ['pyOpenSSL>=0.13', 'ndg-httpsclient', 'pyasn1'],
|
||||
},
|
||||
)
|
||||
|
||||
456
test_requests.py
456
test_requests.py
@@ -9,6 +9,7 @@ import os
|
||||
import pickle
|
||||
import unittest
|
||||
import collections
|
||||
import contextlib
|
||||
|
||||
import io
|
||||
import requests
|
||||
@@ -16,11 +17,13 @@ import pytest
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import HTTPDigestAuth, _basic_auth_str
|
||||
from requests.compat import (
|
||||
Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str)
|
||||
Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3,
|
||||
builtin_str, OrderedDict
|
||||
)
|
||||
from requests.cookies import cookiejar_from_dict, morsel_to_cookie
|
||||
from requests.exceptions import (ConnectionError, ConnectTimeout,
|
||||
InvalidSchema, InvalidURL, MissingSchema,
|
||||
ReadTimeout, Timeout)
|
||||
ReadTimeout, Timeout, RetryError)
|
||||
from requests.models import PreparedRequest
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
from requests.sessions import SessionRedirectMixin
|
||||
@@ -32,6 +35,11 @@ try:
|
||||
except ImportError:
|
||||
import io as StringIO
|
||||
|
||||
try:
|
||||
from multiprocessing.pool import ThreadPool
|
||||
except ImportError:
|
||||
ThreadPool = None
|
||||
|
||||
if is_py3:
|
||||
def u(s):
|
||||
return s
|
||||
@@ -40,20 +48,33 @@ else:
|
||||
return s.decode('unicode-escape')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def httpbin(httpbin):
|
||||
# Issue #1483: Make sure the URL always has a trailing slash
|
||||
httpbin_url = httpbin.url.rstrip('/') + '/'
|
||||
|
||||
def inner(*suffix):
|
||||
return urljoin(httpbin_url, '/'.join(suffix))
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def httpsbin_url(httpbin_secure):
|
||||
# Issue #1483: Make sure the URL always has a trailing slash
|
||||
httpbin_url = httpbin_secure.url.rstrip('/') + '/'
|
||||
|
||||
def inner(*suffix):
|
||||
return urljoin(httpbin_url, '/'.join(suffix))
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# Requests to this URL should always fail with a connection timeout (nothing
|
||||
# listening on that port)
|
||||
TARPIT = "http://10.255.255.1"
|
||||
HTTPBIN = os.environ.get('HTTPBIN_URL', 'http://httpbin.org/')
|
||||
# Issue #1483: Make sure the URL always has a trailing slash
|
||||
HTTPBIN = HTTPBIN.rstrip('/') + '/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
return urljoin(HTTPBIN, '/'.join(suffix))
|
||||
|
||||
|
||||
class RequestsTestCase(unittest.TestCase):
|
||||
class TestRequests(object):
|
||||
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
@@ -97,12 +118,20 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert pr.url == req.url
|
||||
assert pr.body == 'life=42'
|
||||
|
||||
def test_no_content_length(self):
|
||||
def test_no_content_length(self, httpbin):
|
||||
get_req = requests.Request('GET', httpbin('get')).prepare()
|
||||
assert 'Content-Length' not in get_req.headers
|
||||
head_req = requests.Request('HEAD', httpbin('head')).prepare()
|
||||
assert 'Content-Length' not in head_req.headers
|
||||
|
||||
def test_override_content_length(self, httpbin):
|
||||
headers = {
|
||||
'Content-Length': 'not zero'
|
||||
}
|
||||
r = requests.Request('POST', httpbin('post'), headers=headers).prepare()
|
||||
assert 'Content-Length' in r.headers
|
||||
assert r.headers['Content-Length'] == 'not zero'
|
||||
|
||||
def test_path_is_not_double_encoded(self):
|
||||
request = requests.Request('GET', "http://0.0.0.0/get/test case").prepare()
|
||||
|
||||
@@ -116,19 +145,30 @@ class RequestsTestCase(unittest.TestCase):
|
||||
"http://example.com/path?key=value#fragment", params={"a": "b"}).prepare()
|
||||
assert request.url == "http://example.com/path?key=value&a=b#fragment"
|
||||
|
||||
def test_mixed_case_scheme_acceptable(self):
|
||||
def test_params_original_order_is_preserved_by_default(self):
|
||||
param_ordered_dict = OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1)))
|
||||
session = requests.Session()
|
||||
request = requests.Request('GET', 'http://example.com/', params=param_ordered_dict)
|
||||
prep = session.prepare_request(request)
|
||||
assert prep.url == 'http://example.com/?z=1&a=1&k=1&d=1'
|
||||
|
||||
def test_params_bytes_are_encoded(self):
|
||||
request = requests.Request('GET', 'http://example.com',
|
||||
params=b'test=foo').prepare()
|
||||
assert request.url == 'http://example.com/?test=foo'
|
||||
|
||||
def test_mixed_case_scheme_acceptable(self, httpbin):
|
||||
s = requests.Session()
|
||||
s.proxies = getproxies()
|
||||
parts = urlparse(httpbin('get'))
|
||||
schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://',
|
||||
'https://', 'HTTPS://', 'hTTps://', 'HttPs://']
|
||||
schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://']
|
||||
for scheme in schemes:
|
||||
url = scheme + parts.netloc + parts.path
|
||||
r = requests.Request('GET', url)
|
||||
r = s.send(r.prepare())
|
||||
assert r.status_code == 200, 'failed for scheme {0}'.format(scheme)
|
||||
|
||||
def test_HTTP_200_OK_GET_ALTERNATIVE(self):
|
||||
def test_HTTP_200_OK_GET_ALTERNATIVE(self, httpbin):
|
||||
r = requests.Request('GET', httpbin('get'))
|
||||
s = requests.Session()
|
||||
s.proxies = getproxies()
|
||||
@@ -137,7 +177,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self, httpbin):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
assert r.status_code == 200
|
||||
assert r.history[0].status_code == 302
|
||||
@@ -147,7 +187,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
# r = requests.post(httpbin('status', '302'), data={'some': 'data'})
|
||||
# self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self, httpbin):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
@@ -155,25 +195,25 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert heads['User-agent'] in r.text
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self, httpbin):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_set_cookie_on_301(self):
|
||||
def test_set_cookie_on_301(self, httpbin):
|
||||
s = requests.session()
|
||||
url = httpbin('cookies/set?foo=bar')
|
||||
s.get(url)
|
||||
assert s.cookies['foo'] == 'bar'
|
||||
|
||||
def test_cookie_sent_on_redirect(self):
|
||||
def test_cookie_sent_on_redirect(self, httpbin):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies/set?foo=bar'))
|
||||
r = s.get(httpbin('redirect/1')) # redirects to httpbin('get')
|
||||
assert 'Cookie' in r.json()['headers']
|
||||
|
||||
def test_cookie_removed_on_expire(self):
|
||||
def test_cookie_removed_on_expire(self, httpbin):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies/set?foo=bar'))
|
||||
assert s.cookies['foo'] == 'bar'
|
||||
@@ -186,18 +226,18 @@ class RequestsTestCase(unittest.TestCase):
|
||||
)
|
||||
assert 'foo' not in s.cookies
|
||||
|
||||
def test_cookie_quote_wrapped(self):
|
||||
def test_cookie_quote_wrapped(self, httpbin):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies/set?foo="bar:baz"'))
|
||||
assert s.cookies['foo'] == '"bar:baz"'
|
||||
|
||||
def test_cookie_persists_via_api(self):
|
||||
def test_cookie_persists_via_api(self, httpbin):
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('redirect/1'), cookies={'foo': 'bar'})
|
||||
assert 'foo' in r.request.headers['Cookie']
|
||||
assert 'foo' in r.history[0].request.headers['Cookie']
|
||||
|
||||
def test_request_cookie_overrides_session_cookie(self):
|
||||
def test_request_cookie_overrides_session_cookie(self, httpbin):
|
||||
s = requests.session()
|
||||
s.cookies['foo'] = 'bar'
|
||||
r = s.get(httpbin('cookies'), cookies={'foo': 'baz'})
|
||||
@@ -205,13 +245,13 @@ class RequestsTestCase(unittest.TestCase):
|
||||
# Session cookie should not be modified
|
||||
assert s.cookies['foo'] == 'bar'
|
||||
|
||||
def test_request_cookies_not_persisted(self):
|
||||
def test_request_cookies_not_persisted(self, httpbin):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies'), cookies={'foo': 'baz'})
|
||||
# Sending a request with cookies should not add cookies to the session
|
||||
assert not s.cookies
|
||||
|
||||
def test_generic_cookiejar_works(self):
|
||||
def test_generic_cookiejar_works(self, httpbin):
|
||||
cj = cookielib.CookieJar()
|
||||
cookiejar_from_dict({'foo': 'bar'}, cj)
|
||||
s = requests.session()
|
||||
@@ -222,7 +262,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
# Make sure the session cj is still the custom one
|
||||
assert s.cookies is cj
|
||||
|
||||
def test_param_cookiejar_works(self):
|
||||
def test_param_cookiejar_works(self, httpbin):
|
||||
cj = cookielib.CookieJar()
|
||||
cookiejar_from_dict({'foo': 'bar'}, cj)
|
||||
s = requests.session()
|
||||
@@ -230,13 +270,13 @@ class RequestsTestCase(unittest.TestCase):
|
||||
# Make sure the cookie was sent
|
||||
assert r.json()['cookies']['foo'] == 'bar'
|
||||
|
||||
def test_requests_in_history_are_not_overridden(self):
|
||||
def test_requests_in_history_are_not_overridden(self, httpbin):
|
||||
resp = requests.get(httpbin('redirect/3'))
|
||||
urls = [r.url for r in resp.history]
|
||||
req_urls = [r.request.url for r in resp.history]
|
||||
assert urls == req_urls
|
||||
|
||||
def test_history_is_always_a_list(self):
|
||||
def test_history_is_always_a_list(self, httpbin):
|
||||
"""
|
||||
Show that even with redirects, Response.history is always a list.
|
||||
"""
|
||||
@@ -246,15 +286,15 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert isinstance(resp.history, list)
|
||||
assert not isinstance(resp.history, tuple)
|
||||
|
||||
def test_headers_on_session_with_None_are_not_sent(self):
|
||||
def test_headers_on_session_with_None_are_not_sent(self, httpbin):
|
||||
"""Do not send headers in Session.headers with None values."""
|
||||
ses = requests.Session()
|
||||
ses.headers['Accept-Encoding'] = None
|
||||
req = requests.Request('GET', 'http://httpbin.org/get')
|
||||
req = requests.Request('GET', httpbin('get'))
|
||||
prep = ses.prepare_request(req)
|
||||
assert 'Accept-Encoding' not in prep.headers
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
def test_user_agent_transfers(self, httpbin):
|
||||
|
||||
heads = {
|
||||
'User-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
@@ -270,15 +310,15 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
assert heads['user-agent'] in r.text
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
def test_HTTP_200_OK_HEAD(self, httpbin):
|
||||
r = requests.head(httpbin('get'))
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
def test_HTTP_200_OK_PUT(self, httpbin):
|
||||
r = requests.put(httpbin('put'))
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self, httpbin):
|
||||
auth = ('user', 'pass')
|
||||
url = httpbin('basic-auth', 'user', 'pass')
|
||||
|
||||
@@ -293,43 +333,55 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_connection_error(self):
|
||||
def test_connection_error_invalid_domain(self):
|
||||
"""Connecting to an unknown domain should raise a ConnectionError"""
|
||||
with pytest.raises(ConnectionError):
|
||||
requests.get("http://fooobarbangbazbing.httpbin.org")
|
||||
requests.get("http://doesnotexist.google.com")
|
||||
|
||||
def test_connection_error_invalid_port(self):
|
||||
"""Connecting to an invalid port should raise a ConnectionError"""
|
||||
with pytest.raises(ConnectionError):
|
||||
requests.get("http://httpbin.org:1")
|
||||
requests.get("http://localhost:1", timeout=1)
|
||||
|
||||
def test_basicauth_with_netrc(self):
|
||||
def test_LocationParseError(self):
|
||||
"""Inputing a URL that cannot be parsed should raise an InvalidURL error"""
|
||||
with pytest.raises(InvalidURL):
|
||||
requests.get("http://fe80::5054:ff:fe5a:fc0")
|
||||
|
||||
def test_basicauth_with_netrc(self, httpbin):
|
||||
auth = ('user', 'pass')
|
||||
wrong_auth = ('wronguser', 'wrongpass')
|
||||
url = httpbin('basic-auth', 'user', 'pass')
|
||||
|
||||
def get_netrc_auth_mock(url):
|
||||
return auth
|
||||
requests.sessions.get_netrc_auth = get_netrc_auth_mock
|
||||
old_auth = requests.sessions.get_netrc_auth
|
||||
|
||||
# Should use netrc and work.
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 200
|
||||
try:
|
||||
def get_netrc_auth_mock(url):
|
||||
return auth
|
||||
requests.sessions.get_netrc_auth = get_netrc_auth_mock
|
||||
|
||||
# Given auth should override and fail.
|
||||
r = requests.get(url, auth=wrong_auth)
|
||||
assert r.status_code == 401
|
||||
# Should use netrc and work.
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
s = requests.session()
|
||||
# Given auth should override and fail.
|
||||
r = requests.get(url, auth=wrong_auth)
|
||||
assert r.status_code == 401
|
||||
|
||||
# Should use netrc and work.
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
s = requests.session()
|
||||
|
||||
# Given auth should override and fail.
|
||||
s.auth = wrong_auth
|
||||
r = s.get(url)
|
||||
assert r.status_code == 401
|
||||
# Should use netrc and work.
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_DIGEST_HTTP_200_OK_GET(self):
|
||||
# Given auth should override and fail.
|
||||
s.auth = wrong_auth
|
||||
r = s.get(url)
|
||||
assert r.status_code == 401
|
||||
finally:
|
||||
requests.sessions.get_netrc_auth = old_auth
|
||||
|
||||
def test_DIGEST_HTTP_200_OK_GET(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
@@ -345,7 +397,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = s.get(url)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_DIGEST_AUTH_RETURNS_COOKIE(self):
|
||||
def test_DIGEST_AUTH_RETURNS_COOKIE(self, httpbin):
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
r = requests.get(url)
|
||||
@@ -354,14 +406,14 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self):
|
||||
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'
|
||||
|
||||
def test_DIGEST_STREAM(self):
|
||||
def test_DIGEST_STREAM(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
@@ -372,7 +424,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(url, auth=auth, stream=False)
|
||||
assert r.raw.read() == b''
|
||||
|
||||
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self):
|
||||
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'wrongpass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
@@ -388,7 +440,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = s.get(url)
|
||||
assert r.status_code == 401
|
||||
|
||||
def test_DIGESTAUTH_QUOTES_QOP_VALUE(self):
|
||||
def test_DIGESTAUTH_QUOTES_QOP_VALUE(self, httpbin):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
@@ -396,7 +448,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(url, auth=auth)
|
||||
assert '"auth"' in r.request.headers['Authorization']
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
def test_POSTBIN_GET_POST_FILES(self, httpbin):
|
||||
|
||||
url = httpbin('post')
|
||||
post1 = requests.post(url).raise_for_status()
|
||||
@@ -414,7 +466,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
with pytest.raises(ValueError):
|
||||
requests.post(url, files=['bad file data'])
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_DATA(self):
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_DATA(self, httpbin):
|
||||
|
||||
url = httpbin('post')
|
||||
post1 = requests.post(url).raise_for_status()
|
||||
@@ -433,17 +485,17 @@ class RequestsTestCase(unittest.TestCase):
|
||||
with pytest.raises(ValueError):
|
||||
requests.post(url, files=['bad file data'])
|
||||
|
||||
def test_conflicting_post_params(self):
|
||||
def test_conflicting_post_params(self, httpbin):
|
||||
url = httpbin('post')
|
||||
with open('requirements.txt') as f:
|
||||
pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})")
|
||||
pytest.raises(ValueError, "requests.post(url, data=u('[{\"some\": \"data\"}]'), files={'some': f})")
|
||||
|
||||
def test_request_ok_set(self):
|
||||
def test_request_ok_set(self, httpbin):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
assert not r.ok
|
||||
|
||||
def test_status_raising(self):
|
||||
def test_status_raising(self, httpbin):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
r.raise_for_status()
|
||||
@@ -451,11 +503,11 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(httpbin('status', '500'))
|
||||
assert not r.ok
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
def test_decompress_gzip(self, httpbin):
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
def test_unicode_get(self):
|
||||
def test_unicode_get(self, httpbin):
|
||||
url = httpbin('/get')
|
||||
requests.get(url, params={'foo': 'føø'})
|
||||
requests.get(url, params={'føø': 'føø'})
|
||||
@@ -463,29 +515,29 @@ class RequestsTestCase(unittest.TestCase):
|
||||
requests.get(url, params={'foo': 'foo'})
|
||||
requests.get(httpbin('ø'), params={'foo': 'foo'})
|
||||
|
||||
def test_unicode_header_name(self):
|
||||
def test_unicode_header_name(self, httpbin):
|
||||
requests.put(
|
||||
httpbin('put'),
|
||||
headers={str('Content-Type'): 'application/octet-stream'},
|
||||
data='\xff') # compat.str is unicode.
|
||||
|
||||
def test_pyopenssl_redirect(self):
|
||||
requests.get('https://httpbin.org/status/301')
|
||||
def test_pyopenssl_redirect(self, httpsbin_url, httpbin_ca_bundle):
|
||||
requests.get(httpsbin_url('status', '301'), verify=httpbin_ca_bundle)
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
def test_urlencoded_get_query_multivalued_param(self, httpbin):
|
||||
|
||||
r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz']))
|
||||
assert r.status_code == 200
|
||||
assert r.url == httpbin('get?test=foo&test=baz')
|
||||
|
||||
def test_different_encodings_dont_break_post(self):
|
||||
def test_different_encodings_dont_break_post(self, httpbin):
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': json.dumps({'a': 123})},
|
||||
params={'blah': 'asdf1234'},
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_unicode_multipart_post(self):
|
||||
def test_unicode_multipart_post(self, httpbin):
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': u('ëlïxr')},
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
@@ -506,7 +558,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_unicode_multipart_post_fieldnames(self):
|
||||
def test_unicode_multipart_post_fieldnames(self, httpbin):
|
||||
filename = os.path.splitext(__file__)[0] + '.py'
|
||||
r = requests.Request(method='POST',
|
||||
url=httpbin('post'),
|
||||
@@ -517,13 +569,24 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert b'name="stuff"' in prep.body
|
||||
assert b'name="b\'stuff\'"' not in prep.body
|
||||
|
||||
def test_unicode_method_name(self):
|
||||
def test_unicode_method_name(self, httpbin):
|
||||
files = {'file': open('test_requests.py', 'rb')}
|
||||
r = requests.request(
|
||||
method=u('POST'), url=httpbin('post'), files=files)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_custom_content_type(self):
|
||||
def test_unicode_method_name_with_request_object(self, httpbin):
|
||||
files = {'file': open('test_requests.py', 'rb')}
|
||||
s = requests.Session()
|
||||
req = requests.Request(u("POST"), httpbin('post'), files=files)
|
||||
prep = s.prepare_request(req)
|
||||
assert isinstance(prep.method, builtin_str)
|
||||
assert prep.method == "POST"
|
||||
|
||||
resp = s.send(prep)
|
||||
assert resp.status_code == 200
|
||||
|
||||
def test_custom_content_type(self, httpbin):
|
||||
r = requests.post(
|
||||
httpbin('post'),
|
||||
data={'stuff': json.dumps({'a': 123})},
|
||||
@@ -533,38 +596,38 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert r.status_code == 200
|
||||
assert b"text/py-content-type" in r.request.body
|
||||
|
||||
def test_hook_receives_request_arguments(self):
|
||||
def test_hook_receives_request_arguments(self, httpbin):
|
||||
def hook(resp, **kwargs):
|
||||
assert resp is not None
|
||||
assert kwargs != {}
|
||||
|
||||
requests.Request('GET', HTTPBIN, hooks={'response': hook})
|
||||
requests.Request('GET', httpbin(), hooks={'response': hook})
|
||||
|
||||
def test_session_hooks_are_used_with_no_request_hooks(self):
|
||||
def test_session_hooks_are_used_with_no_request_hooks(self, httpbin):
|
||||
hook = lambda x, *args, **kwargs: x
|
||||
s = requests.Session()
|
||||
s.hooks['response'].append(hook)
|
||||
r = requests.Request('GET', HTTPBIN)
|
||||
r = requests.Request('GET', httpbin())
|
||||
prep = s.prepare_request(r)
|
||||
assert prep.hooks['response'] != []
|
||||
assert prep.hooks['response'] == [hook]
|
||||
|
||||
def test_session_hooks_are_overriden_by_request_hooks(self):
|
||||
def test_session_hooks_are_overridden_by_request_hooks(self, httpbin):
|
||||
hook1 = lambda x, *args, **kwargs: x
|
||||
hook2 = lambda x, *args, **kwargs: x
|
||||
assert hook1 is not hook2
|
||||
s = requests.Session()
|
||||
s.hooks['response'].append(hook2)
|
||||
r = requests.Request('GET', HTTPBIN, hooks={'response': [hook1]})
|
||||
r = requests.Request('GET', httpbin(), hooks={'response': [hook1]})
|
||||
prep = s.prepare_request(r)
|
||||
assert prep.hooks['response'] == [hook1]
|
||||
|
||||
def test_prepared_request_hook(self):
|
||||
def test_prepared_request_hook(self, httpbin):
|
||||
def hook(resp, **kwargs):
|
||||
resp.hook_working = True
|
||||
return resp
|
||||
|
||||
req = requests.Request('GET', HTTPBIN, hooks={'response': hook})
|
||||
req = requests.Request('GET', httpbin(), hooks={'response': hook})
|
||||
prep = req.prepare()
|
||||
|
||||
s = requests.Session()
|
||||
@@ -573,7 +636,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
|
||||
assert hasattr(resp, 'hook_working')
|
||||
|
||||
def test_prepared_from_session(self):
|
||||
def test_prepared_from_session(self, httpbin):
|
||||
class DummyAuth(requests.auth.AuthBase):
|
||||
def __call__(self, r):
|
||||
r.headers['Dummy-Auth-Test'] = 'dummy-auth-test-ok'
|
||||
@@ -726,7 +789,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
# make sure one can use items multiple times
|
||||
assert list(items) == list(items)
|
||||
|
||||
def test_time_elapsed_blank(self):
|
||||
def test_time_elapsed_blank(self, httpbin):
|
||||
r = requests.get(httpbin('get'))
|
||||
td = r.elapsed
|
||||
total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600)
|
||||
@@ -765,7 +828,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
chunks = r.iter_content(decode_unicode=True)
|
||||
assert all(isinstance(chunk, str) for chunk in chunks)
|
||||
|
||||
def test_request_and_response_are_pickleable(self):
|
||||
def test_request_and_response_are_pickleable(self, httpbin):
|
||||
r = requests.get(httpbin('get'))
|
||||
|
||||
# verify we can pickle the original request
|
||||
@@ -797,8 +860,8 @@ class RequestsTestCase(unittest.TestCase):
|
||||
url = 'http://user:pass%23pass@complex.url.com/path?query=yes'
|
||||
assert ('user', 'pass#pass') == requests.utils.get_auth_from_url(url)
|
||||
|
||||
def test_cannot_send_unprepared_requests(self):
|
||||
r = requests.Request(url=HTTPBIN)
|
||||
def test_cannot_send_unprepared_requests(self, httpbin):
|
||||
r = requests.Request(url=httpbin())
|
||||
with pytest.raises(ValueError):
|
||||
requests.Session().send(r)
|
||||
|
||||
@@ -812,7 +875,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert str(error) == 'message'
|
||||
assert error.response == response
|
||||
|
||||
def test_session_pickling(self):
|
||||
def test_session_pickling(self, httpbin):
|
||||
r = requests.Request('GET', httpbin('get'))
|
||||
s = requests.Session()
|
||||
|
||||
@@ -822,7 +885,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = s.send(r.prepare())
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_fixes_1329(self):
|
||||
def test_fixes_1329(self, httpbin):
|
||||
"""
|
||||
Ensure that header updates are done case-insensitively.
|
||||
"""
|
||||
@@ -835,7 +898,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert headers['Accept'] == 'application/json'
|
||||
assert headers['ACCEPT'] == 'application/json'
|
||||
|
||||
def test_uppercase_scheme_redirect(self):
|
||||
def test_uppercase_scheme_redirect(self, httpbin):
|
||||
parts = urlparse(httpbin('html'))
|
||||
url = "HTTP://" + parts.netloc + parts.path
|
||||
r = requests.get(httpbin('redirect-to'), params={'url': url})
|
||||
@@ -880,14 +943,14 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert 'http://' in s2.adapters
|
||||
assert 'https://' in s2.adapters
|
||||
|
||||
def test_header_remove_is_case_insensitive(self):
|
||||
def test_header_remove_is_case_insensitive(self, httpbin):
|
||||
# From issue #1321
|
||||
s = requests.Session()
|
||||
s.headers['foo'] = 'bar'
|
||||
r = s.get(httpbin('get'), headers={'FOO': None})
|
||||
assert 'foo' not in r.request.headers
|
||||
|
||||
def test_params_are_merged_case_sensitive(self):
|
||||
def test_params_are_merged_case_sensitive(self, httpbin):
|
||||
s = requests.Session()
|
||||
s.params['foo'] = 'bar'
|
||||
r = s.get(httpbin('get'), params={'FOO': 'bar'})
|
||||
@@ -902,7 +965,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.Request('GET', url).prepare()
|
||||
assert r.url == url
|
||||
|
||||
def test_header_keys_are_native(self):
|
||||
def test_header_keys_are_native(self, httpbin):
|
||||
headers = {u('unicode'): 'blah', 'byte'.encode('ascii'): 'blah'}
|
||||
r = requests.Request('GET', httpbin('get'), headers=headers)
|
||||
p = r.prepare()
|
||||
@@ -912,7 +975,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert 'unicode' in p.headers.keys()
|
||||
assert 'byte' in p.headers.keys()
|
||||
|
||||
def test_can_send_nonstring_objects_with_files(self):
|
||||
def test_can_send_nonstring_objects_with_files(self, httpbin):
|
||||
data = {'a': 0.0}
|
||||
files = {'b': 'foo'}
|
||||
r = requests.Request('POST', httpbin('post'), data=data, files=files)
|
||||
@@ -920,7 +983,28 @@ class RequestsTestCase(unittest.TestCase):
|
||||
|
||||
assert 'multipart/form-data' in p.headers['Content-Type']
|
||||
|
||||
def test_autoset_header_values_are_native(self):
|
||||
def test_can_send_bytes_bytearray_objects_with_files(self, httpbin):
|
||||
# Test bytes:
|
||||
data = {'a': 'this is a string'}
|
||||
files = {'b': b'foo'}
|
||||
r = requests.Request('POST', httpbin('post'), data=data, files=files)
|
||||
p = r.prepare()
|
||||
assert 'multipart/form-data' in p.headers['Content-Type']
|
||||
# Test bytearrays:
|
||||
files = {'b': bytearray(b'foo')}
|
||||
r = requests.Request('POST', httpbin('post'), data=data, files=files)
|
||||
p = r.prepare()
|
||||
assert 'multipart/form-data' in p.headers['Content-Type']
|
||||
|
||||
def test_can_send_file_object_with_non_string_filename(self, httpbin):
|
||||
f = io.BytesIO()
|
||||
f.name = 2
|
||||
r = requests.Request('POST', httpbin('post'), files={'f': f})
|
||||
p = r.prepare()
|
||||
|
||||
assert 'multipart/form-data' in p.headers['Content-Type']
|
||||
|
||||
def test_autoset_header_values_are_native(self, httpbin):
|
||||
data = 'this is a string'
|
||||
length = '16'
|
||||
req = requests.Request('POST', httpbin('post'), data=data)
|
||||
@@ -939,7 +1023,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
preq = req.prepare()
|
||||
assert test_url == preq.url
|
||||
|
||||
def test_auth_is_stripped_on_redirect_off_host(self):
|
||||
def test_auth_is_stripped_on_redirect_off_host(self, httpbin):
|
||||
r = requests.get(
|
||||
httpbin('redirect-to'),
|
||||
params={'url': 'http://www.google.co.uk'},
|
||||
@@ -948,14 +1032,14 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert r.history[0].request.headers['Authorization']
|
||||
assert not r.request.headers.get('Authorization', '')
|
||||
|
||||
def test_auth_is_retained_for_redirect_on_host(self):
|
||||
def test_auth_is_retained_for_redirect_on_host(self, httpbin):
|
||||
r = requests.get(httpbin('redirect/1'), auth=('user', 'pass'))
|
||||
h1 = r.history[0].request.headers['Authorization']
|
||||
h2 = r.request.headers['Authorization']
|
||||
|
||||
assert h1 == h2
|
||||
|
||||
def test_manual_redirect_with_partial_body_read(self):
|
||||
def test_manual_redirect_with_partial_body_read(self, httpbin):
|
||||
s = requests.Session()
|
||||
r1 = s.get(httpbin('redirect/2'), allow_redirects=False, stream=True)
|
||||
assert r1.is_redirect
|
||||
@@ -988,7 +1072,7 @@ class RequestsTestCase(unittest.TestCase):
|
||||
|
||||
adapter.build_response = build_response
|
||||
|
||||
def test_redirect_with_wrong_gzipped_header(self):
|
||||
def test_redirect_with_wrong_gzipped_header(self, httpbin):
|
||||
s = requests.Session()
|
||||
url = httpbin('redirect/1')
|
||||
self._patch_adapter_gzipped_redirect(s, url)
|
||||
@@ -999,15 +1083,15 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert isinstance(s, builtin_str)
|
||||
assert s == "Basic dGVzdDp0ZXN0"
|
||||
|
||||
def test_requests_history_is_saved(self):
|
||||
r = requests.get('https://httpbin.org/redirect/5')
|
||||
def test_requests_history_is_saved(self, httpbin):
|
||||
r = requests.get(httpbin('redirect/5'))
|
||||
total = r.history[-1].history
|
||||
i = 0
|
||||
for item in r.history:
|
||||
assert item.history == total[0:i]
|
||||
i=i+1
|
||||
i = i + 1
|
||||
|
||||
def test_json_param_post_content_type_works(self):
|
||||
def test_json_param_post_content_type_works(self, httpbin):
|
||||
r = requests.post(
|
||||
httpbin('post'),
|
||||
json={'life': 42}
|
||||
@@ -1016,6 +1100,39 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert 'application/json' in r.request.headers['Content-Type']
|
||||
assert {'life': 42} == r.json()['json']
|
||||
|
||||
def test_json_param_post_should_not_override_data_param(self, httpbin):
|
||||
r = requests.Request(method='POST', url=httpbin('post'),
|
||||
data={'stuff': 'elixr'},
|
||||
json={'music': 'flute'})
|
||||
prep = r.prepare()
|
||||
assert 'stuff=elixr' == prep.body
|
||||
|
||||
def test_response_iter_lines(self, httpbin):
|
||||
r = requests.get(httpbin('stream/4'), stream=True)
|
||||
assert r.status_code == 200
|
||||
|
||||
it = r.iter_lines()
|
||||
next(it)
|
||||
assert len(list(it)) == 3
|
||||
|
||||
def test_unconsumed_session_response_closes_connection(self, httpbin):
|
||||
s = requests.session()
|
||||
|
||||
with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response:
|
||||
pass
|
||||
|
||||
assert response._content_consumed is False
|
||||
assert response.raw.closed
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_response_iter_lines_reentrant(self, httpbin):
|
||||
"""Response.iter_lines() is not reentrant safe"""
|
||||
r = requests.get(httpbin('stream/4'), stream=True)
|
||||
assert r.status_code == 200
|
||||
|
||||
next(r.iter_lines())
|
||||
assert len(list(r.iter_lines())) == 3
|
||||
|
||||
|
||||
class TestContentEncodingDetection(unittest.TestCase):
|
||||
|
||||
@@ -1161,6 +1278,7 @@ class TestCaseInsensitiveDict(unittest.TestCase):
|
||||
del othercid['spam']
|
||||
assert cid != othercid
|
||||
assert cid == {'spam': 'blueval', 'eggs': 'redval'}
|
||||
assert cid != object()
|
||||
|
||||
def test_setdefault(self):
|
||||
cid = CaseInsensitiveDict({'Spam': 'blueval'})
|
||||
@@ -1198,6 +1316,16 @@ class TestCaseInsensitiveDict(unittest.TestCase):
|
||||
assert frozenset(cid.keys()) == keyset
|
||||
assert frozenset(cid) == keyset
|
||||
|
||||
def test_copy(self):
|
||||
cid = CaseInsensitiveDict({
|
||||
'Accept': 'application/json',
|
||||
'user-Agent': 'requests',
|
||||
})
|
||||
cid_copy = cid.copy()
|
||||
assert cid == cid_copy
|
||||
cid['changed'] = True
|
||||
assert cid != cid_copy
|
||||
|
||||
|
||||
class UtilsTestCase(unittest.TestCase):
|
||||
|
||||
@@ -1223,6 +1351,13 @@ class UtilsTestCase(unittest.TestCase):
|
||||
assert super_len(
|
||||
cStringIO.StringIO('but some how, some way...')) == 25
|
||||
|
||||
def test_super_len_correctly_calculates_len_of_partially_read_file(self):
|
||||
"""Ensure that we handle partially consumed file like objects."""
|
||||
from requests.utils import super_len
|
||||
s = StringIO.StringIO()
|
||||
s.write('foobarbogus')
|
||||
assert super_len(s) == 0
|
||||
|
||||
def test_get_environ_proxies_ip_ranges(self):
|
||||
"""Ensures that IP addresses are correctly matches with ranges
|
||||
in no_proxy variable."""
|
||||
@@ -1244,6 +1379,41 @@ class UtilsTestCase(unittest.TestCase):
|
||||
'http://localhost.localdomain:5000/v1.0/') == {}
|
||||
assert get_environ_proxies('http://www.requests.com/') != {}
|
||||
|
||||
def test_select_proxies(self):
|
||||
"""Make sure we can select per-host proxies correctly."""
|
||||
from requests.utils import select_proxy
|
||||
proxies = {'http': 'http://http.proxy',
|
||||
'http://some.host': 'http://some.host.proxy'}
|
||||
assert select_proxy('hTTp://u:p@Some.Host/path', proxies) == 'http://some.host.proxy'
|
||||
assert select_proxy('hTTp://u:p@Other.Host/path', proxies) == 'http://http.proxy'
|
||||
assert select_proxy('hTTps://Other.Host', proxies) is None
|
||||
|
||||
def test_guess_filename_when_int(self):
|
||||
from requests.utils import guess_filename
|
||||
assert None is guess_filename(1)
|
||||
|
||||
def test_guess_filename_when_filename_is_an_int(self):
|
||||
from requests.utils import guess_filename
|
||||
fake = type('Fake', (object,), {'name': 1})()
|
||||
assert None is guess_filename(fake)
|
||||
|
||||
def test_guess_filename_with_file_like_obj(self):
|
||||
from requests.utils import guess_filename
|
||||
from requests import compat
|
||||
fake = type('Fake', (object,), {'name': b'value'})()
|
||||
guessed_name = guess_filename(fake)
|
||||
assert b'value' == guessed_name
|
||||
assert isinstance(guessed_name, compat.bytes)
|
||||
|
||||
def test_guess_filename_with_unicode_name(self):
|
||||
from requests.utils import guess_filename
|
||||
from requests import compat
|
||||
filename = b'value'.decode('utf-8')
|
||||
fake = type('Fake', (object,), {'name': filename})()
|
||||
guessed_name = guess_filename(fake)
|
||||
assert filename == guessed_name
|
||||
assert isinstance(guessed_name, compat.str)
|
||||
|
||||
def test_is_ipv4_address(self):
|
||||
from requests.utils import is_ipv4_address
|
||||
assert is_ipv4_address('8.8.8.8')
|
||||
@@ -1280,6 +1450,22 @@ class UtilsTestCase(unittest.TestCase):
|
||||
assert username == percent_encoding_test_chars
|
||||
assert password == percent_encoding_test_chars
|
||||
|
||||
def test_requote_uri_with_unquoted_percents(self):
|
||||
"""Ensure we handle unquoted percent signs in redirects.
|
||||
|
||||
See: https://github.com/kennethreitz/requests/issues/2356
|
||||
"""
|
||||
from requests.utils import requote_uri
|
||||
bad_uri = 'http://example.com/fiz?buz=%ppicture'
|
||||
quoted = 'http://example.com/fiz?buz=%25ppicture'
|
||||
assert quoted == requote_uri(bad_uri)
|
||||
|
||||
def test_requote_uri_properly_requotes(self):
|
||||
"""Ensure requoting doesn't break expectations."""
|
||||
from requests.utils import requote_uri
|
||||
quoted = 'http://example.com/fiz?buz=%25ppicture'
|
||||
assert quoted == requote_uri(quoted)
|
||||
|
||||
|
||||
class TestMorselToCookieExpires(unittest.TestCase):
|
||||
|
||||
@@ -1340,13 +1526,13 @@ class TestMorselToCookieMaxAge(unittest.TestCase):
|
||||
|
||||
|
||||
class TestTimeout:
|
||||
def test_stream_timeout(self):
|
||||
def test_stream_timeout(self, httpbin):
|
||||
try:
|
||||
requests.get('https://httpbin.org/delay/10', timeout=2.0)
|
||||
requests.get(httpbin('delay/10'), timeout=2.0)
|
||||
except requests.exceptions.Timeout as e:
|
||||
assert 'Read timed out' in e.args[0].args[0]
|
||||
|
||||
def test_invalid_timeout(self):
|
||||
def test_invalid_timeout(self, httpbin):
|
||||
with pytest.raises(ValueError) as e:
|
||||
requests.get(httpbin('get'), timeout=(3, 4, 5))
|
||||
assert '(connect, read)' in str(e)
|
||||
@@ -1355,7 +1541,7 @@ class TestTimeout:
|
||||
requests.get(httpbin('get'), timeout="foo")
|
||||
assert 'must be an int or float' in str(e)
|
||||
|
||||
def test_none_timeout(self):
|
||||
def test_none_timeout(self, httpbin):
|
||||
""" Check that you can set None as a valid timeout value.
|
||||
|
||||
To actually test this behavior, we'd want to check that setting the
|
||||
@@ -1367,7 +1553,7 @@ class TestTimeout:
|
||||
r = requests.get(httpbin('get'), timeout=None)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_read_timeout(self):
|
||||
def test_read_timeout(self, httpbin):
|
||||
try:
|
||||
requests.get(httpbin('delay/10'), timeout=(None, 0.1))
|
||||
assert False, "The recv() request should time out."
|
||||
@@ -1389,6 +1575,11 @@ class TestTimeout:
|
||||
except ConnectTimeout:
|
||||
pass
|
||||
|
||||
def test_encoded_methods(self, httpbin):
|
||||
"""See: https://github.com/kennethreitz/requests/issues/2316"""
|
||||
r = requests.request(b'GET', httpbin('get'))
|
||||
assert r.ok
|
||||
|
||||
|
||||
SendCall = collections.namedtuple('SendCall', ('args', 'kwargs'))
|
||||
|
||||
@@ -1435,9 +1626,9 @@ class TestRedirects:
|
||||
'proxies': {},
|
||||
}
|
||||
|
||||
def test_requests_are_updated_each_time(self):
|
||||
def test_requests_are_updated_each_time(self, httpbin):
|
||||
session = RedirectSession([303, 307])
|
||||
prep = requests.Request('POST', 'http://httpbin.org/post').prepare()
|
||||
prep = requests.Request('POST', httpbin('post')).prepare()
|
||||
r0 = session.send(prep)
|
||||
assert r0.request.method == 'POST'
|
||||
assert session.calls[-1] == SendCall((r0.request,), {})
|
||||
@@ -1507,13 +1698,44 @@ def test_prepared_request_complete_copy():
|
||||
)
|
||||
assert_copy(p, p.copy())
|
||||
|
||||
|
||||
def test_prepare_unicode_url():
|
||||
p = PreparedRequest()
|
||||
p.prepare(
|
||||
method='GET',
|
||||
url=u('http://www.example.com/üniçø∂é')
|
||||
url=u('http://www.example.com/üniçø∂é'),
|
||||
)
|
||||
assert_copy(p, p.copy())
|
||||
|
||||
|
||||
def test_urllib3_retries(httpbin):
|
||||
from requests.packages.urllib3.util import Retry
|
||||
s = requests.Session()
|
||||
s.mount('http://', HTTPAdapter(max_retries=Retry(
|
||||
total=2, status_forcelist=[500]
|
||||
)))
|
||||
|
||||
with pytest.raises(RetryError):
|
||||
s.get(httpbin('status/500'))
|
||||
|
||||
|
||||
def test_urllib3_pool_connection_closed(httpbin):
|
||||
s = requests.Session()
|
||||
s.mount('http://', HTTPAdapter(pool_connections=0, pool_maxsize=0))
|
||||
|
||||
try:
|
||||
s.get(httpbin('status/200'))
|
||||
except ConnectionError as e:
|
||||
assert u"Pool is closed." in str(e)
|
||||
|
||||
|
||||
def test_vendor_aliases():
|
||||
from requests.packages import urllib3
|
||||
from requests.packages import chardet
|
||||
|
||||
with pytest.raises(ImportError):
|
||||
from requests.packages import webbrowser
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user