Compare commits
1070 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d2398173 | ||
|
|
32941fdd20 | ||
|
|
7c2306baec | ||
|
|
404585ce8c | ||
|
|
78eecd7d7e | ||
|
|
fadcf799de | ||
|
|
8bfbfdfc79 | ||
|
|
843672f4fe | ||
|
|
e8f3fc33bb | ||
|
|
89266f9062 | ||
|
|
c589d8a251 | ||
|
|
f0e327f979 | ||
|
|
c0ae3dd31e | ||
|
|
dda9689ca4 | ||
|
|
f968fe0136 | ||
|
|
1164450dac | ||
|
|
aa42318395 | ||
|
|
488a51d619 | ||
|
|
12e5f3855c | ||
|
|
62a23bfbde | ||
|
|
b026def961 | ||
|
|
662c3edacc | ||
|
|
355b97165c | ||
|
|
d81dfe266e | ||
|
|
77a57c0e7e | ||
|
|
a291d0d0f8 | ||
|
|
617091e747 | ||
|
|
cb15310d6a | ||
|
|
1c0abbd74b | ||
|
|
7813bd8ac0 | ||
|
|
ad8205f930 | ||
|
|
f9dd485582 | ||
|
|
d0f309b947 | ||
|
|
400b4567f0 | ||
|
|
ba4cc326c2 | ||
|
|
cb8f9756e3 | ||
|
|
b8561cda5e | ||
|
|
7125fe5554 | ||
|
|
4a30979622 | ||
|
|
b28673368b | ||
|
|
de1637c3de | ||
|
|
0eb0b89431 | ||
|
|
96cd8e9ca0 | ||
|
|
9f26d0ced3 | ||
|
|
b7d0925d6b | ||
|
|
2b8e01bff7 | ||
|
|
686db5d452 | ||
|
|
36fd8da1c0 | ||
|
|
45506d1f39 | ||
|
|
2d9746621a | ||
|
|
3c14435109 | ||
|
|
4cf31b0b86 | ||
|
|
c501784541 | ||
|
|
4b7c5dab0e | ||
|
|
c477f6eb6f | ||
|
|
f25827c5b6 | ||
|
|
355003b400 | ||
|
|
6a9d59ea97 | ||
|
|
2f50cb92b6 | ||
|
|
c24665b593 | ||
|
|
973e9cb548 | ||
|
|
763393f70a | ||
|
|
2c11473816 | ||
|
|
40d325366b | ||
|
|
51f7628078 | ||
|
|
4161a7ec6a | ||
|
|
ed767cb703 | ||
|
|
0442122d34 | ||
|
|
327b9eab87 | ||
|
|
3ee594140b | ||
|
|
15694fa2f7 | ||
|
|
07f6675376 | ||
|
|
32327f8f7c | ||
|
|
a050faf084 | ||
|
|
0a37d82444 | ||
|
|
3d2d072927 | ||
|
|
44663b5e8e | ||
|
|
db43eb4cb9 | ||
|
|
cff70e75b7 | ||
|
|
2d328d8a7f | ||
|
|
c010f5895a | ||
|
|
33f1087584 | ||
|
|
975430d5e6 | ||
|
|
56ef079e6f | ||
|
|
0308a2806a | ||
|
|
175945b0bd | ||
|
|
2ee67441c8 | ||
|
|
6fd03d5f20 | ||
|
|
a3a928c7f1 | ||
|
|
bca920c1c1 | ||
|
|
6b09e6ee34 | ||
|
|
2a7811bf2f | ||
|
|
e627c1f8a8 | ||
|
|
fe7348cc53 | ||
|
|
b066902705 | ||
|
|
64eb415ec0 | ||
|
|
e912a6f5da | ||
|
|
3288426f2b | ||
|
|
0a7d98871c | ||
|
|
ed7b14899f | ||
|
|
7b6338c4b6 | ||
|
|
a0f991dbad | ||
|
|
c84012edec | ||
|
|
7f571f5e5a | ||
|
|
d4227051a9 | ||
|
|
3d373e2a16 | ||
|
|
38e6695627 | ||
|
|
8bd6515564 | ||
|
|
8fedec785a | ||
|
|
7cf6ca80a6 | ||
|
|
abc38cf17f | ||
|
|
0ba8c44260 | ||
|
|
1adaba3865 | ||
|
|
35ec6bb613 | ||
|
|
3d4d5ab0df | ||
|
|
bcc4125a18 | ||
|
|
7f9ea04286 | ||
|
|
317f64a11f | ||
|
|
a3e55634d6 | ||
|
|
9bf53676b8 | ||
|
|
41d1a427b0 | ||
|
|
853a07112b | ||
|
|
a8555d811d | ||
|
|
538de55607 | ||
|
|
57ccd7e65c | ||
|
|
a9033912b3 | ||
|
|
373a34a144 | ||
|
|
9324c2a9b4 | ||
|
|
fb2931801e | ||
|
|
8ac6b266c9 | ||
|
|
efe5d13ed6 | ||
|
|
a9963e3841 | ||
|
|
f1d104e004 | ||
|
|
996f99b267 | ||
|
|
62d2ea8034 | ||
|
|
ed15c70cd0 | ||
|
|
cf6b20429a | ||
|
|
324336e7f6 | ||
|
|
d240b1fe15 | ||
|
|
c333bd144e | ||
|
|
53d60cac4f | ||
|
|
6271046a27 | ||
|
|
8a81a437d2 | ||
|
|
a453a78c7c | ||
|
|
bdd7f8f8a2 | ||
|
|
d232948188 | ||
|
|
35d5ac6e8f | ||
|
|
fdd08215ea | ||
|
|
ff7cfa732f | ||
|
|
45be14ed6f | ||
|
|
11a3eaec26 | ||
|
|
7832f2ae7e | ||
|
|
6eb349f5d2 | ||
|
|
ff169d32fe | ||
|
|
404ecaf95b | ||
|
|
1de14e6a93 | ||
|
|
492bda1343 | ||
|
|
4d6871d917 | ||
|
|
c4ab96b638 | ||
|
|
fed6cfbf4d | ||
|
|
2a27b123fd | ||
|
|
7c5151e8e7 | ||
|
|
a189c238cc | ||
|
|
d427bb8603 | ||
|
|
7eed89a0df | ||
|
|
5d9699bbdc | ||
|
|
e6cce7a5bd | ||
|
|
d79e1bbaad | ||
|
|
bf1744eea2 | ||
|
|
9427e1efe8 | ||
|
|
661af1dc1a | ||
|
|
a5bc99d53e | ||
|
|
14fae695bf | ||
|
|
be04142723 | ||
|
|
748c65f7a7 | ||
|
|
2fc9f6bbc9 | ||
|
|
65bf3ab201 | ||
|
|
906fcd7328 | ||
|
|
7b819852b9 | ||
|
|
9217a2523d | ||
|
|
0aedef07c3 | ||
|
|
3e60a9eb9c | ||
|
|
798dc4ae1d | ||
|
|
632d9893b0 | ||
|
|
809d9863bf | ||
|
|
742cbeaee1 | ||
|
|
2159c80270 | ||
|
|
8e9446d3fb | ||
|
|
a7da521a62 | ||
|
|
2621fe2bff | ||
|
|
3843a40f7c | ||
|
|
f647352d01 | ||
|
|
1631f0f5ae | ||
|
|
b4eb8663af | ||
|
|
e1528ce3be | ||
|
|
1360e77cb2 | ||
|
|
9921099546 | ||
|
|
2f6b5feb1a | ||
|
|
d569a2d6bc | ||
|
|
e3ba495ed9 | ||
|
|
d4a77cf2af | ||
|
|
3a2eaa67a2 | ||
|
|
f3ad56f103 | ||
|
|
c3e6c41fc1 | ||
|
|
8a055c5d42 | ||
|
|
06e4971fe7 | ||
|
|
e28c1c9bfa | ||
|
|
3550ac7473 | ||
|
|
51800bf36f | ||
|
|
c89a3e207e | ||
|
|
365c64fe3b | ||
|
|
6b652f766d | ||
|
|
5a8a8f2bd3 | ||
|
|
821cbb3e08 | ||
|
|
f8798aee28 | ||
|
|
11cd2b8253 | ||
|
|
1677c688cb | ||
|
|
b92feb3f0a | ||
|
|
7d4d91f013 | ||
|
|
3279906737 | ||
|
|
c7b1bc8197 | ||
|
|
da288ad57a | ||
|
|
03ae9bee74 | ||
|
|
a364395612 | ||
|
|
37f3a29d49 | ||
|
|
48396f97a9 | ||
|
|
ccd19c53c8 | ||
|
|
2bae0d295b | ||
|
|
8755969629 | ||
|
|
e12d053ab8 | ||
|
|
13b07cb5d8 | ||
|
|
d3adc9d334 | ||
|
|
751e2ea2d4 | ||
|
|
dab3df7619 | ||
|
|
171b370b0e | ||
|
|
bcbc1be1c8 | ||
|
|
04c329d703 | ||
|
|
9a62aa6380 | ||
|
|
11e5c8eab7 | ||
|
|
775b6f6f00 | ||
|
|
dacc1d8aaf | ||
|
|
18591920f3 | ||
|
|
193eb42891 | ||
|
|
b20d171c1c | ||
|
|
590211e13e | ||
|
|
fff5269d1a | ||
|
|
e831947be4 | ||
|
|
c205ce658f | ||
|
|
13060f89c7 | ||
|
|
f25901874a | ||
|
|
3cc6acaca4 | ||
|
|
21ccf8f8c0 | ||
|
|
b973d08b0e | ||
|
|
d24ee0e1dc | ||
|
|
afb1bd103a | ||
|
|
4c1852f928 | ||
|
|
6e26f5754b | ||
|
|
82d1b340dc | ||
|
|
eab2ab9faa | ||
|
|
12427e3434 | ||
|
|
76bf523a1f | ||
|
|
613816dd9d | ||
|
|
f8ec2544ce | ||
|
|
3c2f52d681 | ||
|
|
3baf3ab069 | ||
|
|
5c575b30c2 | ||
|
|
e65f52aae4 | ||
|
|
44277441d6 | ||
|
|
7b68c124d5 | ||
|
|
f2172922c5 | ||
|
|
079a825ac3 | ||
|
|
9cf9d76eb5 | ||
|
|
dc7029ec98 | ||
|
|
f0019c06d7 | ||
|
|
a094ad8f31 | ||
|
|
d6827a37d0 | ||
|
|
5b7a79b0ff | ||
|
|
ee861f4287 | ||
|
|
0c0bab3ad0 | ||
|
|
1c73ec22e9 | ||
|
|
4677ba5cd9 | ||
|
|
093709be4d | ||
|
|
f6ed16bbbe | ||
|
|
fc618aa78a | ||
|
|
82e69de44f | ||
|
|
8f8062723a | ||
|
|
cc4381b91e | ||
|
|
eae641485b | ||
|
|
29309201b6 | ||
|
|
0dd6c40205 | ||
|
|
84d9a1b577 | ||
|
|
17170a2ca7 | ||
|
|
8d34538f4c | ||
|
|
8d1a85a8d0 | ||
|
|
06f048e507 | ||
|
|
83a9f2c740 | ||
|
|
bc5cc0dc92 | ||
|
|
6562137731 | ||
|
|
d078b69aef | ||
|
|
0a1e527aec | ||
|
|
9f55f503b1 | ||
|
|
253cba373b | ||
|
|
9451ae2e98 | ||
|
|
d203349740 | ||
|
|
4aad69594d | ||
|
|
c7f165ae6f | ||
|
|
98cbde1324 | ||
|
|
77e5443f71 | ||
|
|
e15ae8f9a7 | ||
|
|
0eb2509449 | ||
|
|
bd0e002804 | ||
|
|
0e65dff008 | ||
|
|
fba77fc65f | ||
|
|
1cf5ec872c | ||
|
|
21c2146be4 | ||
|
|
2f91d8e773 | ||
|
|
946f7ec579 | ||
|
|
658d609e9f | ||
|
|
abca7f0ad0 | ||
|
|
cf5ddecc4c | ||
|
|
c1a44259d6 | ||
|
|
54b2cb0b27 | ||
|
|
09b6aef835 | ||
|
|
228c5e68ae | ||
|
|
6bb0529bed | ||
|
|
1532effb68 | ||
|
|
65eaebb177 | ||
|
|
ab0714ce9c | ||
|
|
a3a7c504ad | ||
|
|
ca45da39fa | ||
|
|
313ff95ee4 | ||
|
|
97815c9930 | ||
|
|
e2645826be | ||
|
|
7cc898f9ed | ||
|
|
ec979dc60a | ||
|
|
32a6d07036 | ||
|
|
d6c2d8f1dc | ||
|
|
4a5c94f410 | ||
|
|
0eb5b633aa | ||
|
|
56c36ac27d | ||
|
|
508bd26741 | ||
|
|
835053c55e | ||
|
|
f0e0a62bbe | ||
|
|
bdcbabb758 | ||
|
|
7cc2d468c7 | ||
|
|
83f4c01564 | ||
|
|
cfeb485434 | ||
|
|
d3b7f98814 | ||
|
|
f5b0043c08 | ||
|
|
6ce27f7d55 | ||
|
|
531dc35126 | ||
|
|
d974b46dbe | ||
|
|
ea1d5f3648 | ||
|
|
83b4081e85 | ||
|
|
e18e79d820 | ||
|
|
7234c7240a | ||
|
|
7fa6276eb7 | ||
|
|
bbdf81dc1c | ||
|
|
4b0d53dcb7 | ||
|
|
746d2cad5d | ||
|
|
14f57f6585 | ||
|
|
e63dff0a3a | ||
|
|
09c0365255 | ||
|
|
1661bf6543 | ||
|
|
2ef0b1b51d | ||
|
|
3c58600187 | ||
|
|
572c15d049 | ||
|
|
99a6074816 | ||
|
|
8b750ee182 | ||
|
|
c9b4920230 | ||
|
|
793d314c8d | ||
|
|
8c873712fe | ||
|
|
faae6b87b1 | ||
|
|
fd7610dd7a | ||
|
|
b0e1865073 | ||
|
|
de0658f7e8 | ||
|
|
79bb9ee141 | ||
|
|
2f33679cb3 | ||
|
|
6f5735274b | ||
|
|
709828204a | ||
|
|
b355b678a9 | ||
|
|
124c172c72 | ||
|
|
709c8bdcbf | ||
|
|
2c55d269ce | ||
|
|
e0d1b09ffe | ||
|
|
ceb42dd5de | ||
|
|
80fe5dc147 | ||
|
|
d806ffc1cd | ||
|
|
4f75f1f41d | ||
|
|
fe10eeb5be | ||
|
|
cf37f63ccb | ||
|
|
e7efce91fb | ||
|
|
5f834715d0 | ||
|
|
dd89df314d | ||
|
|
fdbb53db19 | ||
|
|
590ce29743 | ||
|
|
7548bc7ccc | ||
|
|
544ebf402f | ||
|
|
640538adcb | ||
|
|
1ded0ad3d5 | ||
|
|
d339b0f64f | ||
|
|
8e2696b385 | ||
|
|
b5212ddc3d | ||
|
|
dacaae520a | ||
|
|
e663c77e70 | ||
|
|
ec109c9ac8 | ||
|
|
0b9a2a1581 | ||
|
|
2759060559 | ||
|
|
00b13673e3 | ||
|
|
91ac5ba110 | ||
|
|
0d8c08cd22 | ||
|
|
ab4c03adc3 | ||
|
|
75b0c4be1b | ||
|
|
4a8e6415c7 | ||
|
|
58bf158840 | ||
|
|
c0f066626e | ||
|
|
69c7a8638e | ||
|
|
1b6f1b091c | ||
|
|
d39cdc45d1 | ||
|
|
7f761f3adf | ||
|
|
5e76943f02 | ||
|
|
b887ea3b60 | ||
|
|
690426ac86 | ||
|
|
b8298b0710 | ||
|
|
3bd5406c06 | ||
|
|
3ae0ff3685 | ||
|
|
80e29bfcab | ||
|
|
d7107c2cdc | ||
|
|
658da765ec | ||
|
|
9e2f5dd0ed | ||
|
|
1ffce4f7dc | ||
|
|
fcac1c3746 | ||
|
|
c0763bb8d5 | ||
|
|
75bd9d0e94 | ||
|
|
08bc1198d3 | ||
|
|
cf54f60367 | ||
|
|
329efcae6e | ||
|
|
5a2a78f7df | ||
|
|
f72c13ffda | ||
|
|
0a17badb21 | ||
|
|
12f9aa3669 | ||
|
|
afa2e86a4d | ||
|
|
b7842ab34b | ||
|
|
f8b3141204 | ||
|
|
c40d6d0509 | ||
|
|
a74481a1d4 | ||
|
|
b898415d0d | ||
|
|
b47effe5d8 | ||
|
|
4f10605389 | ||
|
|
120e62900b | ||
|
|
986704dc62 | ||
|
|
2f13956357 | ||
|
|
8d700dd1e5 | ||
|
|
5be412db13 | ||
|
|
a61a861cfb | ||
|
|
c363ca476f | ||
|
|
319b1ed551 | ||
|
|
67f50a0fe5 | ||
|
|
b98cc8b4e9 | ||
|
|
495646bb6a | ||
|
|
4516bd45b2 | ||
|
|
730c0e2e2b | ||
|
|
3020d0e56a | ||
|
|
49bed18a83 | ||
|
|
e6959412aa | ||
|
|
04c0bcc9b4 | ||
|
|
ec4edae977 | ||
|
|
f478c34671 | ||
|
|
a0ef4f2358 | ||
|
|
ca57e93ebf | ||
|
|
5ca5b7ec65 | ||
|
|
2b202aabd7 | ||
|
|
cb64d31171 | ||
|
|
80eafe3b63 | ||
|
|
16aa444261 | ||
|
|
0bbdbac17f | ||
|
|
c430b60e38 | ||
|
|
02f6555536 | ||
|
|
4293e3fdb2 | ||
|
|
50298bbf97 | ||
|
|
ac82fa47a0 | ||
|
|
976bfbf687 | ||
|
|
1bc377b188 | ||
|
|
a89530aef5 | ||
|
|
c32b1cd6d6 | ||
|
|
90f5c8bd85 | ||
|
|
878bb61d24 | ||
|
|
8493d8329e | ||
|
|
57aeafa115 | ||
|
|
a29e862adf | ||
|
|
5ed62e1137 | ||
|
|
f99a2534c9 | ||
|
|
a939423006 | ||
|
|
4493acc5ee | ||
|
|
c4eb9301bb | ||
|
|
57ba337e79 | ||
|
|
3d542693bc | ||
|
|
5f2607ffca | ||
|
|
ab916a8a8d | ||
|
|
1fb4bd82f7 | ||
|
|
d892e93589 | ||
|
|
38c2532c5f | ||
|
|
18b5ee7d5b | ||
|
|
9fd2d07c1d | ||
|
|
e7bcc3b332 | ||
|
|
b001fcf968 | ||
|
|
125619c783 | ||
|
|
fe6882623c | ||
|
|
11c4315da3 | ||
|
|
672e66ba5f | ||
|
|
bcfddb3d65 | ||
|
|
9cae98c167 | ||
|
|
b0e2e4f284 | ||
|
|
36842db26f | ||
|
|
e84a7c7e6f | ||
|
|
2109afc144 | ||
|
|
786dfdd7e5 | ||
|
|
62d05f842d | ||
|
|
fc3b03a5f5 | ||
|
|
f4f2bd9fab | ||
|
|
b0555b68e7 | ||
|
|
497695864b | ||
|
|
128b49a1fb | ||
|
|
0dbda6665d | ||
|
|
464279f48d | ||
|
|
b14a244ab4 | ||
|
|
2c7e920298 | ||
|
|
b725bfd3fb | ||
|
|
47e96b8040 | ||
|
|
9a1348707c | ||
|
|
ada98899f8 | ||
|
|
16a7a26452 | ||
|
|
2c04f9d199 | ||
|
|
70e98e47cf | ||
|
|
b525dc29f7 | ||
|
|
a3fb5ac326 | ||
|
|
8e6f98f44e | ||
|
|
9ba93b885c | ||
|
|
5f44d79729 | ||
|
|
a3b26c7e5b | ||
|
|
e9be7c1a58 | ||
|
|
6c359b2457 | ||
|
|
7647e52943 | ||
|
|
a0ae2e6c7b | ||
|
|
efd2b57b92 | ||
|
|
47aba0cc20 | ||
|
|
f5a2b75924 | ||
|
|
49a38ac89a | ||
|
|
6d071f4331 | ||
|
|
3873121c01 | ||
|
|
5bff8e362f | ||
|
|
62733146be | ||
|
|
99f6581770 | ||
|
|
b37fbf30a3 | ||
|
|
e65d0ac9e3 | ||
|
|
61a0e85672 | ||
|
|
c18e6459a8 | ||
|
|
6db255d17d | ||
|
|
49d79546e8 | ||
|
|
f3ba022f46 | ||
|
|
17f2002839 | ||
|
|
a6ee9757a7 | ||
|
|
d9cb18ad2a | ||
|
|
557480cfe8 | ||
|
|
5dae0bb9bf | ||
|
|
e38046e6c6 | ||
|
|
97be9ee4cd | ||
|
|
c658b363e3 | ||
|
|
7f9cef0114 | ||
|
|
8efb6cc870 | ||
|
|
1c8a141711 | ||
|
|
4d38790b5b | ||
|
|
5cfbb0aaf5 | ||
|
|
29271a4100 | ||
|
|
df0dc2b67a | ||
|
|
ff286f351c | ||
|
|
430125b396 | ||
|
|
0536e5b708 | ||
|
|
e6990162e5 | ||
|
|
d9b304b906 | ||
|
|
79f5d532c6 | ||
|
|
6af6a08b7a | ||
|
|
96d5044722 | ||
|
|
4258201900 | ||
|
|
11e5126454 | ||
|
|
9e953f3320 | ||
|
|
bbbeedb0a2 | ||
|
|
de17f4e5a4 | ||
|
|
86eba21b8a | ||
|
|
53d679edba | ||
|
|
89c90add2c | ||
|
|
a869be1e5a | ||
|
|
68619b8088 | ||
|
|
534cdd7587 | ||
|
|
e185aec920 | ||
|
|
14e8eea0ee | ||
|
|
2b849545ea | ||
|
|
15396fb2f9 | ||
|
|
1acef81ed3 | ||
|
|
a25141f9f4 | ||
|
|
d339ffe9d9 | ||
|
|
c5ba5888b5 | ||
|
|
3415e6de0b | ||
|
|
638532dd9a | ||
|
|
c6fc7d99b5 | ||
|
|
281e0598bc | ||
|
|
aa39fb0c80 | ||
|
|
459a91c54f | ||
|
|
115b51e68c | ||
|
|
4441697e49 | ||
|
|
5174ab4325 | ||
|
|
95911f059a | ||
|
|
e3288d5e33 | ||
|
|
49140d6880 | ||
|
|
9c6ffc5303 | ||
|
|
f80984f377 | ||
|
|
9c0a0fbd01 | ||
|
|
57dbab2932 | ||
|
|
7fdb541e2d | ||
|
|
082f4d8948 | ||
|
|
8aa015ffc9 | ||
|
|
cfb1a478ac | ||
|
|
28db6c3f53 | ||
|
|
5f768b191d | ||
|
|
d9a9a981bc | ||
|
|
9fcfec18da | ||
|
|
a83268b01b | ||
|
|
a1afbfbaa3 | ||
|
|
4114b04521 | ||
|
|
f7e67c01ea | ||
|
|
544a5fea59 | ||
|
|
0a8bb2e1b3 | ||
|
|
5184f0c5a9 | ||
|
|
9a1a4136ac | ||
|
|
ca26c1a253 | ||
|
|
a91ac242f3 | ||
|
|
31d495fb02 | ||
|
|
bdda99c0c1 | ||
|
|
9ed93044e0 | ||
|
|
0bfc029b5e | ||
|
|
4eb2759400 | ||
|
|
0001f5b651 | ||
|
|
e1615e0f1e | ||
|
|
3483bae24f | ||
|
|
716d29c49c | ||
|
|
a7223f86a9 | ||
|
|
2c0ff95d4d | ||
|
|
91766548d1 | ||
|
|
12f3c65e5a | ||
|
|
f5e421e880 | ||
|
|
9d88ac7b50 | ||
|
|
75dc012f41 | ||
|
|
f214b91d77 | ||
|
|
f80fbad4ff | ||
|
|
2e38f54b39 | ||
|
|
2c153c1556 | ||
|
|
70fbd27d5c | ||
|
|
c910b958f0 | ||
|
|
6ce4ece8aa | ||
|
|
5be6c21b14 | ||
|
|
8207ed074e | ||
|
|
f8cf713f41 | ||
|
|
8f58c095c5 | ||
|
|
5937282618 | ||
|
|
a3b8ef15db | ||
|
|
4e2ecd6987 | ||
|
|
814e0520ad | ||
|
|
19a2a6c6d1 | ||
|
|
0e711f2f9d | ||
|
|
5e60279a19 | ||
|
|
6fb2674446 | ||
|
|
5a3aae73ba | ||
|
|
47510486ec | ||
|
|
c3bba1543b | ||
|
|
cef1811415 | ||
|
|
21acc6b732 | ||
|
|
ba71e2a3cb | ||
|
|
baf3dcb6c7 | ||
|
|
8b92b6513c | ||
|
|
03ddf26596 | ||
|
|
8c1fd5db95 | ||
|
|
10eed6344c | ||
|
|
06eee390dd | ||
|
|
fad9abfd77 | ||
|
|
a5f79712bb | ||
|
|
c71ac7917a | ||
|
|
4e59f9ec93 | ||
|
|
a6412b02ba | ||
|
|
961f78ab5f | ||
|
|
463ca76cc4 | ||
|
|
f58ab8bc03 | ||
|
|
3a06c35da3 | ||
|
|
37070ac30e | ||
|
|
e56d05d230 | ||
|
|
f1fa8f0064 | ||
|
|
56d6d703f9 | ||
|
|
7fbbdeb324 | ||
|
|
f7e4832472 | ||
|
|
4ed16d99c2 | ||
|
|
02f422d132 | ||
|
|
a76f4925b5 | ||
|
|
c2e942f230 | ||
|
|
c7b99f7819 | ||
|
|
6bb4c4ebe6 | ||
|
|
d1dfa02070 | ||
|
|
ee36baf4f3 | ||
|
|
e85b857a35 | ||
|
|
e7ed1224d2 | ||
|
|
9f29a31f66 | ||
|
|
356bf42373 | ||
|
|
ba00c4411c | ||
|
|
c74b66a53e | ||
|
|
a7a619ba5c | ||
|
|
65c06579b4 | ||
|
|
a5ff4be7b0 | ||
|
|
52ac24120c | ||
|
|
a77b91671c | ||
|
|
a350ceffb9 | ||
|
|
5c72601599 | ||
|
|
99b6e35940 | ||
|
|
8ae4b440e3 | ||
|
|
1933d3c707 | ||
|
|
9d8d1d14f1 | ||
|
|
42b33ae1a1 | ||
|
|
ab23536b1b | ||
|
|
b51640b051 | ||
|
|
6421ec1784 | ||
|
|
296d8cc097 | ||
|
|
bbf1021156 | ||
|
|
8ffe17cb09 | ||
|
|
e17155151b | ||
|
|
1da54de5dc | ||
|
|
8693fc746f | ||
|
|
5309ad005f | ||
|
|
43854067d4 | ||
|
|
5a7fbbb00d | ||
|
|
04ee583822 | ||
|
|
2a7223585e | ||
|
|
58e018af13 | ||
|
|
c165255e98 | ||
|
|
cf60062df1 | ||
|
|
82a5f1dd9b | ||
|
|
c70b7bb012 | ||
|
|
16aa01420b | ||
|
|
e98e61da12 | ||
|
|
cfefcab8e0 | ||
|
|
eb5d191a57 | ||
|
|
3ea9c53826 | ||
|
|
3900243297 | ||
|
|
7fdb09b766 | ||
|
|
81efe1fa88 | ||
|
|
f7fdfe8b9a | ||
|
|
76dacaf231 | ||
|
|
89241c525e | ||
|
|
bf499a80de | ||
|
|
6b129ec0c9 | ||
|
|
1165bec773 | ||
|
|
b79dea97fd | ||
|
|
4504953212 | ||
|
|
2ccc68c4b4 | ||
|
|
7c66e6f267 | ||
|
|
bec201c5ca | ||
|
|
35b0c5e16e | ||
|
|
fc0565fa5d | ||
|
|
289e813ab6 | ||
|
|
8a1b0e1852 | ||
|
|
f0edd4a3e0 | ||
|
|
a64a62088a | ||
|
|
588b6bbf8a | ||
|
|
d3cbf37352 | ||
|
|
6b377cc919 | ||
|
|
e91fe9b2b8 | ||
|
|
45c9ecc82a | ||
|
|
3d49eb40f8 | ||
|
|
9f6246a7b8 | ||
|
|
8f20058d0d | ||
|
|
6e9cb09fff | ||
|
|
9aa62d5622 | ||
|
|
bcc48a9607 | ||
|
|
3458e6a905 | ||
|
|
0086f97b65 | ||
|
|
06b9d154cc | ||
|
|
e8668fb512 | ||
|
|
b40128219f | ||
|
|
14a798882c | ||
|
|
37bbdbe8c3 | ||
|
|
4ab76943ef | ||
|
|
30377ca912 | ||
|
|
0a24de2ef9 | ||
|
|
3462d9c87b | ||
|
|
2584c48e8d | ||
|
|
0a4b7356ac | ||
|
|
ec926a4958 | ||
|
|
e84d84b5b8 | ||
|
|
cfc1a76381 | ||
|
|
6730818ddc | ||
|
|
7ee2b79127 | ||
|
|
58c77bac27 | ||
|
|
40c54800a6 | ||
|
|
3fccf63f49 | ||
|
|
6a23625c11 | ||
|
|
474e17b0af | ||
|
|
f1e8700e9a | ||
|
|
c05c64d21b | ||
|
|
c0d8e8e846 | ||
|
|
58e3dd9de8 | ||
|
|
02408973c6 | ||
|
|
df2c80e28e | ||
|
|
60558fdcc4 | ||
|
|
52b9f01e6e | ||
|
|
b7bb1c7b61 | ||
|
|
7c206c74ba | ||
|
|
50279dd83c | ||
|
|
cdd0613aec | ||
|
|
69e7e94c4c | ||
|
|
f5f2ddfa74 | ||
|
|
ff0f9613f2 | ||
|
|
30ed6643d7 | ||
|
|
d9798f4a3c | ||
|
|
82f2ad6f99 | ||
|
|
6833b326f8 | ||
|
|
afc6561fdb | ||
|
|
81817852ad | ||
|
|
2d8440ea98 | ||
|
|
a348fd7ae3 | ||
|
|
1588ad7c46 | ||
|
|
ae9cdf1a47 | ||
|
|
220f687e6f | ||
|
|
f4296ba758 | ||
|
|
3d6937af7d | ||
|
|
fad1c665a6 | ||
|
|
b17ec8e1df | ||
|
|
d1b6db05a4 | ||
|
|
2999c71e46 | ||
|
|
0b424e1637 | ||
|
|
87239802c1 | ||
|
|
0c4342a3a6 | ||
|
|
bd45bc716a | ||
|
|
dfbfb2303f | ||
|
|
4002efb56a | ||
|
|
f7257eada6 | ||
|
|
02ea563a06 | ||
|
|
341ac9fe8a | ||
|
|
5bc919a24f | ||
|
|
a0c3570640 | ||
|
|
ce42b338f4 | ||
|
|
17088763e7 | ||
|
|
53bf544050 | ||
|
|
e725f30625 | ||
|
|
4e8b680865 | ||
|
|
92a8db6da0 | ||
|
|
2131ba4dda | ||
|
|
7a1a40e445 | ||
|
|
52a95cb93a | ||
|
|
0c4b973dcf | ||
|
|
7192b8022e | ||
|
|
147c24154e | ||
|
|
99f0ef4766 | ||
|
|
35ed9b5f38 | ||
|
|
0f2dcff725 | ||
|
|
5b73f2ea61 | ||
|
|
1d2abea94b | ||
|
|
6a51f6b2f8 | ||
|
|
c48f0d990f | ||
|
|
f9141f51ec | ||
|
|
e649003974 | ||
|
|
358478eb88 | ||
|
|
498b446f14 | ||
|
|
f5bbb9714d | ||
|
|
1031ec4942 | ||
|
|
827b2340a2 | ||
|
|
8de59f03a0 | ||
|
|
73ba48be2e | ||
|
|
72943d0247 | ||
|
|
6fefffd03e | ||
|
|
d0b96f8e3b | ||
|
|
b8c55da48d | ||
|
|
5b54b3e53f | ||
|
|
3326030862 | ||
|
|
a957bcfd7b | ||
|
|
8b9f689ecc | ||
|
|
428fc9c0bd | ||
|
|
d04f31b302 | ||
|
|
4673d3dfa7 | ||
|
|
a548b6249d | ||
|
|
a9822b0bb6 | ||
|
|
3d3f05884f | ||
|
|
50c39fd95a | ||
|
|
28a9534383 | ||
|
|
c60618ae01 | ||
|
|
dac050bf40 | ||
|
|
e7a7615e0d | ||
|
|
7c271a466f | ||
|
|
95336a0542 | ||
|
|
b908559ac8 | ||
|
|
a0e9b21c9d | ||
|
|
0cfd801f09 | ||
|
|
c4f013a6f9 | ||
|
|
20c930e48e | ||
|
|
abe33f0ef4 | ||
|
|
3cfc83b23b | ||
|
|
e4c85b318f | ||
|
|
f524fe4934 | ||
|
|
fedbafc79b | ||
|
|
4dc48aea63 | ||
|
|
c8f2b3a722 | ||
|
|
e7b31af784 | ||
|
|
8fbd6d85e0 | ||
|
|
8d4afcb5b8 | ||
|
|
a166dd0ad7 | ||
|
|
5871291956 | ||
|
|
24ffc3610a | ||
|
|
85507dd042 | ||
|
|
4ada2e5664 | ||
|
|
0cf9ede580 | ||
|
|
975a183796 | ||
|
|
7671e3878c | ||
|
|
372e4d94c5 | ||
|
|
e9f94e698d | ||
|
|
9697290111 | ||
|
|
03babafc7b | ||
|
|
78b5d9ed45 | ||
|
|
ba93593711 | ||
|
|
a8695ecd32 | ||
|
|
b970d468d1 | ||
|
|
608d057066 | ||
|
|
04bcb74970 | ||
|
|
6e90aa7d65 | ||
|
|
2361a6fc79 | ||
|
|
7077272baa | ||
|
|
0b475dd654 | ||
|
|
1997dee3d9 | ||
|
|
e1c0646f95 | ||
|
|
c9f5614417 | ||
|
|
7875a12617 | ||
|
|
5afe11ba1c | ||
|
|
47b98bc90f | ||
|
|
b58433fb74 | ||
|
|
e228cf1cc4 | ||
|
|
3092483433 | ||
|
|
30d937f299 | ||
|
|
e8c2fc1585 | ||
|
|
2e391fa44c | ||
|
|
2f5008107c | ||
|
|
4cd54d2f87 | ||
|
|
87e8a12a9f | ||
|
|
ffde764a91 | ||
|
|
62d29d5106 | ||
|
|
5c132c98ca | ||
|
|
14bd9ebb45 | ||
|
|
2f9e287f7d | ||
|
|
8ac2fe6eb1 | ||
|
|
1c5ce723d8 | ||
|
|
a7d280cb3d | ||
|
|
111336459e | ||
|
|
cff3b7f9bd | ||
|
|
cfc19b4dde | ||
|
|
f52c0032a3 | ||
|
|
dfe0e9382d | ||
|
|
a699ddd14a | ||
|
|
ae4e85f11c | ||
|
|
e2af50b7c9 | ||
|
|
1758070cdc | ||
|
|
7881aaa759 | ||
|
|
ed160f2d2a | ||
|
|
802bcc9ba3 | ||
|
|
351a711ce9 | ||
|
|
0cf0bde158 | ||
|
|
dc7b4e7d32 | ||
|
|
9488af5a42 | ||
|
|
bd2b0c45ba | ||
|
|
8565053c93 | ||
|
|
5287b8cd9f | ||
|
|
0b22bc9b42 | ||
|
|
182f92ab26 | ||
|
|
55cea74b56 | ||
|
|
33df9d1bf2 | ||
|
|
37ed9116b7 | ||
|
|
3af8af5730 | ||
|
|
0db4dab03e | ||
|
|
df66fa7975 | ||
|
|
d2894b3c58 | ||
|
|
cd01aa4666 | ||
|
|
74ce263d69 | ||
|
|
ac9f9b653c | ||
|
|
c48cf2bba8 | ||
|
|
dcebab4c35 | ||
|
|
5345a89dc5 | ||
|
|
95b0549c75 | ||
|
|
04bcab8057 | ||
|
|
332f55e552 | ||
|
|
7f3f641fb8 | ||
|
|
18270e1059 | ||
|
|
645933a5da | ||
|
|
2a2e7bd190 | ||
|
|
2d38f51834 | ||
|
|
a3cc97128b | ||
|
|
71d3490bcd | ||
|
|
aa5e477236 | ||
|
|
3127a34298 | ||
|
|
0cb7376db0 | ||
|
|
e69a3a41f6 | ||
|
|
608c61dcd9 | ||
|
|
3dd2a0443d | ||
|
|
84434fddee | ||
|
|
784b671559 | ||
|
|
edfa15558b | ||
|
|
1481e9ae6a | ||
|
|
1d1927bb2b | ||
|
|
42c45df84d | ||
|
|
29f7a5bcfa | ||
|
|
5c5c13a28f | ||
|
|
cb2e87210c | ||
|
|
14081bed46 | ||
|
|
2826ca4362 | ||
|
|
f996313aab | ||
|
|
7127bcda97 | ||
|
|
3443ede52a | ||
|
|
4b93bb4082 | ||
|
|
2a9ae4b367 | ||
|
|
a8e77688f0 | ||
|
|
34e3c7b68f | ||
|
|
c8914bd4d4 | ||
|
|
1b2533f92d | ||
|
|
37d269c104 | ||
|
|
d697c37d3e | ||
|
|
4df5aadb01 | ||
|
|
058ef27178 | ||
|
|
ccb9eb28e1 | ||
|
|
d5a0384630 | ||
|
|
fa47067802 | ||
|
|
45fabf4b96 | ||
|
|
46e01db785 | ||
|
|
8da0abfa1a | ||
|
|
dd5a39731a | ||
|
|
b6aa868c28 | ||
|
|
724ff7e4fa | ||
|
|
1a64a29c84 | ||
|
|
2aa32c16dc | ||
|
|
824d54f131 | ||
|
|
07dd324e2f | ||
|
|
e46351cfbd | ||
|
|
58d63d55bc | ||
|
|
3f98034c2a | ||
|
|
3d448f843e | ||
|
|
d8c923c385 | ||
|
|
3bf6c63d52 | ||
|
|
335bf7018d | ||
|
|
d8fb6d2a5a | ||
|
|
c243d09398 | ||
|
|
7fa4fdacc9 | ||
|
|
71195bb219 | ||
|
|
ec0b708b18 | ||
|
|
cfee830877 | ||
|
|
3dd2235ac5 | ||
|
|
cc2c54093e | ||
|
|
2dfcc0bb7b | ||
|
|
256c86f8f7 | ||
|
|
a276ebd240 | ||
|
|
0bf60c8876 | ||
|
|
84d9ff0c5c | ||
|
|
68a2954c67 | ||
|
|
c56acf0e32 | ||
|
|
2a17e5237c | ||
|
|
56406b3442 | ||
|
|
660e090315 | ||
|
|
d12eac8669 | ||
|
|
2dc556e082 | ||
|
|
aad27218d5 | ||
|
|
430e87d0fd |
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,10 +1,14 @@
|
||||
.idea
|
||||
.coverage
|
||||
MANIFEST
|
||||
coverage.xml
|
||||
nosetests.xml
|
||||
junit-report.xml
|
||||
pylint.txt
|
||||
*.pyc
|
||||
docs/_build
|
||||
toy.py
|
||||
.gitignore
|
||||
junit-report.xml
|
||||
violations.pyflakes.txt
|
||||
cover/
|
||||
docs/_build
|
||||
requests.egg-info/
|
||||
*.pyc
|
||||
*.swp
|
||||
env/
|
||||
|
||||
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.2
|
||||
env: HTTPBIN_URL=http://httpbin.org/
|
||||
script: make travis
|
||||
install:
|
||||
- pip install nose
|
||||
- pip install . --use-mirrors
|
||||
52
AUTHORS
52
AUTHORS
@@ -1,52 +0,0 @@
|
||||
Requests is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
|
||||
Development Lead
|
||||
````````````````
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.com>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Various Pocoo Members
|
||||
- Chris Adams
|
||||
- Flavio Percoco Premoli
|
||||
- Dj Gilcrease
|
||||
- Justin Murphy
|
||||
- Rob Madole
|
||||
- Aram Dulyan
|
||||
- Johannes Gorset
|
||||
- 村山めがね (Megane Murayama)
|
||||
- James Rowe
|
||||
- Daniel Schauenberg
|
||||
- Zbigniew Siciarz
|
||||
- Daniele Tricoli 'Eriol'
|
||||
- Richard Boulton
|
||||
- Miguel Olivares <miguel@moliware.com>
|
||||
- Alberto Paro
|
||||
- Jérémy Bethmont
|
||||
- 潘旭 (Xu Pan)
|
||||
- Tamás Gulácsi
|
||||
- Rubén Abad
|
||||
- Peter Manser
|
||||
- Jeremy Selier
|
||||
- Jens Diemer
|
||||
- Alex <@alopatin>
|
||||
- Tom Hogans <tomhsx@gmail.com>
|
||||
- Armin Ronacher
|
||||
- Shrikant Sharat Kandula
|
||||
- Mikko Ohtamaa
|
||||
- Den Shabalin
|
||||
- Daniel Miller <danielm@vs-networks.com>
|
||||
- Alejandro Giacometti
|
||||
- Rick Mak
|
||||
- Johan Bergström
|
||||
- Josselin Jacquard
|
||||
- Travis N. Vaught
|
||||
- Fredrik Möllerstrand
|
||||
- Daniel Hengeveld
|
||||
- Dan Head
|
||||
- Bruno Renié
|
||||
- David Fischer
|
||||
106
AUTHORS.rst
Normal file
106
AUTHORS.rst
Normal file
@@ -0,0 +1,106 @@
|
||||
Requests is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
|
||||
Development Lead
|
||||
````````````````
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.com>
|
||||
|
||||
|
||||
Urllib3
|
||||
```````
|
||||
|
||||
- Andrey Petrov <andrey.petrov@shazow.net>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Various Pocoo Members
|
||||
- Chris Adams
|
||||
- Flavio Percoco Premoli
|
||||
- Dj Gilcrease
|
||||
- Justin Murphy
|
||||
- Rob Madole
|
||||
- Aram Dulyan
|
||||
- Johannes Gorset
|
||||
- 村山めがね (Megane Murayama)
|
||||
- James Rowe
|
||||
- Daniel Schauenberg
|
||||
- Zbigniew Siciarz
|
||||
- Daniele Tricoli 'Eriol'
|
||||
- Richard Boulton
|
||||
- Miguel Olivares <miguel@moliware.com>
|
||||
- Alberto Paro
|
||||
- Jérémy Bethmont
|
||||
- 潘旭 (Xu Pan)
|
||||
- Tamás Gulácsi
|
||||
- Rubén Abad
|
||||
- Peter Manser
|
||||
- Jeremy Selier
|
||||
- Jens Diemer
|
||||
- Alex <@alopatin>
|
||||
- Tom Hogans <tomhsx@gmail.com>
|
||||
- Armin Ronacher
|
||||
- Shrikant Sharat Kandula
|
||||
- Mikko Ohtamaa
|
||||
- Den Shabalin
|
||||
- Daniel Miller <danielm@vs-networks.com>
|
||||
- Alejandro Giacometti
|
||||
- Rick Mak
|
||||
- Johan Bergström
|
||||
- Josselin Jacquard
|
||||
- Travis N. Vaught
|
||||
- Fredrik Möllerstrand
|
||||
- Daniel Hengeveld
|
||||
- Dan Head
|
||||
- Bruno Renié
|
||||
- David Fischer
|
||||
- Joseph McCullough
|
||||
- Juergen Brendel
|
||||
- Juan Riaza
|
||||
- Ryan Kelly
|
||||
- Rolando Espinoza La fuente
|
||||
- Robert Gieseke
|
||||
- Idan Gazit
|
||||
- Ed Summers
|
||||
- Chris Van Horne
|
||||
- Christopher Davis
|
||||
- Ori Livneh
|
||||
- Jason Emerick
|
||||
- Bryan Helmig
|
||||
- Jonas Obrist
|
||||
- Lucian Ursu
|
||||
- Tom Moertel
|
||||
- Frank Kumro Jr
|
||||
- Chase Sterling
|
||||
- Marty Alchin
|
||||
- takluyver
|
||||
- Ben Toews (mastahyeti)
|
||||
- David Kemp
|
||||
- Brendon Crawford
|
||||
- Denis (Telofy)
|
||||
- Cory Benfield (Lukasa)
|
||||
- Matt Giuca
|
||||
- Adam Tauber
|
||||
- Honza Javorek
|
||||
- Brendan Maguire <maguire.brendan@gmail.com>
|
||||
- Chris Dary
|
||||
- Danver Braganza <danverbraganza@gmail.com>
|
||||
- Max Countryman
|
||||
- Nick Chadwick
|
||||
- Jonathan Drosdeck
|
||||
- Jiri Machalek
|
||||
- Steve Pulec
|
||||
- Michael Kelly
|
||||
- Michael Newman <newmaniese@gmail.com>
|
||||
- Jonty Wareing <jonty@jonty.co.uk>
|
||||
- Shivaram Lingamneni
|
||||
- Miguel Turner
|
||||
- Rohan Jain (crodjer)
|
||||
- Justin Barber <barber.justin@gmail.com>
|
||||
- Roman Haritonov <@reclosedev>
|
||||
- Josh Imhoff <joshimhoff13@gmail.com>
|
||||
- Arup Malakar <amalakar@gmail.com>
|
||||
- Danilo Bargen (gwrtheyrn)
|
||||
- Torsten Landschoff
|
||||
245
HISTORY.rst
245
HISTORY.rst
@@ -1,6 +1,249 @@
|
||||
.. :changelog:
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
0.13.1 (2012-06-07)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Allow passing a file or file-like object as data.
|
||||
- Allow hooks to return responses that indicate errors.
|
||||
- Fix Response.text and Response.json for body-less responses.
|
||||
|
||||
0.13.0 (2012-05-29)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Removal of Requests.async in favor of `grequests <https://github.com/kennethreitz/grequests>`_
|
||||
- Allow disabling of cookie persistiance.
|
||||
- New implimentation 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.
|
||||
- RequestsCookieJar improvements.
|
||||
- Various bug fixes.
|
||||
|
||||
0.12.1 (2012-05-08)
|
||||
+++++++++++++++++++
|
||||
|
||||
- New ``Response.json`` property.
|
||||
- Ability to add string file uploads.
|
||||
- Fix out-of-range issue with iter_lines.
|
||||
- Fix iter_content default size.
|
||||
- Fix POST redirects containing files.
|
||||
|
||||
0.12.0 (2012-05-02)
|
||||
+++++++++++++++++++
|
||||
|
||||
- EXPERIMENTAL OAUTH SUPPORT!
|
||||
- Proper CookieJar-backed cookies interface with awesome dict-like interface.
|
||||
- Speed fix for non-iterated content chunks.
|
||||
- Move ``pre_request`` to a more usable place.
|
||||
- New ``pre_send`` hook.
|
||||
- Lazily encode data, params, files.
|
||||
- Load system Certificate Bundle if ``certify`` isn't available.
|
||||
- Cleanups, fixes.
|
||||
|
||||
0.11.2 (2012-04-22)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Attempt to use the OS's certificate bundle if ``certifi`` isn't available.
|
||||
- Infinite digest auth redirect fix.
|
||||
- Multi-part file upload improvements.
|
||||
- Fix decoding of invalid %encodings in URLs.
|
||||
- If there is no content in a response don't throw an error the second time that content is attempted to be read.
|
||||
- Upload data on redirects.
|
||||
|
||||
0.11.1 (2012-03-30)
|
||||
+++++++++++++++++++
|
||||
|
||||
* POST redirects now break RFC to do what browsers do: Follow up with a GET.
|
||||
* New ``strict_mode`` configuration to disable new redirect behavior.
|
||||
|
||||
|
||||
0.11.0 (2012-03-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Private SSL Certificate support
|
||||
* Remove select.poll from Gevent monkeypatching
|
||||
* Remove redundant generator for chunked transfer encoding
|
||||
* Fix: Response.ok raises Timeout Exception in safe_mode
|
||||
|
||||
0.10.8 (2012-03-09)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Generate chunked ValueError fix
|
||||
* Proxy configuration by environment variables
|
||||
* Simplification of iter_lines.
|
||||
* New `trust_env` configuration for disabling system/environment hints.
|
||||
* Suppress cookie errors.
|
||||
|
||||
0.10.7 (2012-03-07)
|
||||
+++++++++++++++++++
|
||||
|
||||
* `encode_uri` = False
|
||||
|
||||
0.10.6 (2012-02-25)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Allow '=' in cookies.
|
||||
|
||||
0.10.5 (2012-02-25)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Response body with 0 content-length fix.
|
||||
* New async.imap.
|
||||
* Don't fail on netrc.
|
||||
|
||||
|
||||
0.10.4 (2012-02-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Honor netrc.
|
||||
|
||||
0.10.3 (2012-02-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
* HEAD requests don't follow redirects anymore.
|
||||
* raise_for_status() doesn't raise for 3xx anymore.
|
||||
* Make Session objects picklable.
|
||||
* ValueError for invalid schema URLs.
|
||||
|
||||
0.10.2 (2012-01-15)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Vastly improved URL quoting.
|
||||
* Additional allowed cookie key values.
|
||||
* Attempted fix for "Too many open files" Error
|
||||
* Replace unicode errors on first pass, no need for second pass.
|
||||
* Append '/' to bare-domain urls before query insertion.
|
||||
* Exceptions now inherit from RuntimeError.
|
||||
* Binary uploads + auth fix.
|
||||
* Bugfixes.
|
||||
|
||||
|
||||
0.10.1 (2012-01-23)
|
||||
+++++++++++++++++++
|
||||
|
||||
* PYTHON 3 SUPPORT!
|
||||
* Dropped 2.5 Support. (*Backwards Incompatible*)
|
||||
|
||||
0.10.0 (2012-01-21)
|
||||
+++++++++++++++++++
|
||||
|
||||
* ``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.
|
||||
* Default to ISO-8859-1 (Western) encoding for "text" subtypes.
|
||||
* Removal of `decode_unicode`. (*Backwards Incompatible*)
|
||||
* New multiple-hooks system.
|
||||
* New ``Response.register_hook`` for registering hooks within the pipeline.
|
||||
* ``Response.url`` is now Unicode.
|
||||
|
||||
0.9.3 (2012-01-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* SSL verify=False bugfix (apparent on windows machines).
|
||||
|
||||
0.9.2 (2012-01-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Asynchronous async.send method.
|
||||
* Support for proper chunk streams with boundaries.
|
||||
* session argument for Session classes.
|
||||
* Print entire hook tracebacks, not just exception instance.
|
||||
* Fix response.iter_lines from pending next line.
|
||||
* Fix but in HTTP-digest auth w/ URI having query strings.
|
||||
* Fix in Event Hooks section.
|
||||
* Urllib3 update.
|
||||
|
||||
|
||||
0.9.1 (2012-01-06)
|
||||
++++++++++++++++++
|
||||
|
||||
* danger_mode for automatic Response.raise_for_status()
|
||||
* Response.iter_lines refactor
|
||||
|
||||
0.9.0 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* verify ssl is default.
|
||||
|
||||
|
||||
0.8.9 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* Packaging fix.
|
||||
|
||||
|
||||
0.8.8 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* SSL CERT VERIFICATION!
|
||||
* Release of Cerifi: Mozilla's cert list.
|
||||
* New 'verify' argument for SSL requests.
|
||||
* Urllib3 update.
|
||||
|
||||
0.8.7 (2011-12-24)
|
||||
++++++++++++++++++
|
||||
|
||||
* iter_lines last-line truncation fix
|
||||
* Force safe_mode for async requests
|
||||
* Handle safe_mode exceptions more consistently
|
||||
* Fix iteration on null responses in safe_mode
|
||||
|
||||
0.8.6 (2011-12-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Socket timeout fixes.
|
||||
* Proxy Authorization support.
|
||||
|
||||
0.8.5 (2011-12-14)
|
||||
++++++++++++++++++
|
||||
|
||||
* Response.iter_lines!
|
||||
|
||||
0.8.4 (2011-12-11)
|
||||
++++++++++++++++++
|
||||
|
||||
* Prefetch bugfix.
|
||||
* Added license to installed version.
|
||||
|
||||
0.8.3 (2011-11-27)
|
||||
++++++++++++++++++
|
||||
|
||||
* Converted auth system to use simpler callable objects.
|
||||
* New session parameter to API methods.
|
||||
* Display full URL while logging.
|
||||
|
||||
0.8.2 (2011-11-19)
|
||||
++++++++++++++++++
|
||||
|
||||
* New Unicode decoding system, based on over-ridable `Response.encoding`.
|
||||
* Proper URL slash-quote handling.
|
||||
* Cookies with ``[``, ``]``, and ``_`` allowed.
|
||||
|
||||
0.8.1 (2011-11-15)
|
||||
++++++++++++++++++
|
||||
|
||||
* URL Request path fix
|
||||
* Proxy fix.
|
||||
* Timeouts fix.
|
||||
|
||||
0.8.0 (2011-11-13)
|
||||
++++++++++++++++++
|
||||
|
||||
* Keep-alive support!
|
||||
* Complete removal of Urllib2
|
||||
* Complete removal of Poster
|
||||
* Complete removal of CookieJars
|
||||
* New ConnectionError raising
|
||||
* Safe_mode for error catching
|
||||
* prefetch parameter for request methods
|
||||
* OPTION method
|
||||
* Async pool size throttling
|
||||
* File uploads send real names
|
||||
* Vendored in urllib3
|
||||
|
||||
0.7.6 (2011-11-07)
|
||||
++++++++++++++++++
|
||||
|
||||
@@ -15,7 +258,7 @@ History
|
||||
0.7.4 (2011-10-26)
|
||||
++++++++++++++++++
|
||||
|
||||
* Sesion Hooks fix.
|
||||
* Session Hooks fix.
|
||||
|
||||
0.7.3 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
Copyright (c) 2012 Kenneth Reitz.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
@@ -1 +1 @@
|
||||
include README.rst LICENSE HISTORY.rst test_requests.py
|
||||
include README.rst LICENSE NOTICE HISTORY.rst tests/*.py
|
||||
|
||||
64
Makefile
64
Makefile
@@ -1,16 +1,68 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
# these files should pass pyflakes
|
||||
# exclude ./env/, which may contain virtualenv packages
|
||||
PYFLAKES_WHITELIST=$(shell find . -name "*.py" ! -path "./docs/*" ! -path "./tests/*" \
|
||||
! -path "./requests/packages/*" ! -path "./env/*" \
|
||||
! -path "./requests/__init__.py" ! -path "./requests/compat.py")
|
||||
|
||||
# hack: if pyflakes is available, set this to the location of pyflakes
|
||||
# if it's not, e.g., in the Python 3 or PyPy Jenkins environments, set it to
|
||||
# the location of the no-op `true` command.
|
||||
PYFLAKES_IF_AVAILABLE=$(shell if which pyflakes > /dev/null ; \
|
||||
then which pyflakes; \
|
||||
else which true; fi )
|
||||
|
||||
# test_requests_ext.py depends on external services, and async doesn't work under Python 3
|
||||
# Travis/Jenkins should be ensuring that all other tests pass on all supported versions
|
||||
CI_TESTS=$(shell find tests/ -name "*.py" ! -name "test_requests_ext.py" ! -name "test_requests_async.py")
|
||||
|
||||
init:
|
||||
pip install -r reqs.txt
|
||||
python setup.py develop
|
||||
pip install -r requirements.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py
|
||||
nosetests ./tests/*
|
||||
|
||||
ci: init
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
lazy:
|
||||
nosetests --with-color tests/test_requests.py
|
||||
|
||||
stats:
|
||||
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
|
||||
simple:
|
||||
nosetests tests/test_requests.py
|
||||
|
||||
pyflakes:
|
||||
pyflakes ${PYFLAKES_WHITELIST}
|
||||
|
||||
cipyflakes:
|
||||
${PYFLAKES_IF_AVAILABLE} ${PYFLAKES_WHITELIST}
|
||||
|
||||
citests:
|
||||
nosetests ${CI_TESTS} --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
ci: citests cipyflakes
|
||||
|
||||
travis: citests
|
||||
|
||||
server:
|
||||
gunicorn httpbin:app --bind=0.0.0.0:7077 &
|
||||
|
||||
# compute statistics of various kinds
|
||||
lemonade:
|
||||
-pyflakes requests > violations.pyflakes.txt
|
||||
# HTML output will be available in the default location, ./cover/
|
||||
nosetests --with-coverage --cover-html --cover-package=requests ${CI_TESTS} ./tests/test_requests_async.py
|
||||
|
||||
site:
|
||||
cd docs; make dirhtml
|
||||
|
||||
clean:
|
||||
git clean -Xfd
|
||||
|
||||
deps:
|
||||
rm -fr requests/packages/urllib3
|
||||
git clone https://github.com/shazow/urllib3.git
|
||||
cd urllib3 && git checkout master && cd ..
|
||||
mv urllib3/urllib3 requests/packages/
|
||||
rm -fr urllib3
|
||||
|
||||
docs: site
|
||||
|
||||
25
NOTICE
25
NOTICE
@@ -1,12 +1,25 @@
|
||||
Request includes some vendorized python libraries to ease installation.
|
||||
|
||||
Poster License
|
||||
==============
|
||||
Urllib3 License
|
||||
===============
|
||||
|
||||
Copyright (c) 2010 Chris AtLee
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt),
|
||||
Modifications copyright 2022 Kenneth Reitz.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
65
README.rst
65
README.rst
@@ -1,6 +1,9 @@
|
||||
Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
|
||||
.. image:: https://secure.travis-ci.org/kennethreitz/requests.png?branch=develop
|
||||
|
||||
Requests is an ISC Licensed HTTP library, written in Python, for human
|
||||
beings.
|
||||
|
||||
@@ -19,60 +22,32 @@ Things shouldn't be this way. Not in Python.
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
...
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
|
||||
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
|
||||
Requests allow you to send HTTP/1.1 requests. You can add headers, form data,
|
||||
multipart files, and parameters with simple Python dictionaries, and access the
|
||||
response data in the same way. It's powered by urllib2, but it does
|
||||
all the hard work and crazy hacks for you.
|
||||
response data in the same way. It's powered by httplib and `urllib3
|
||||
<https://github.com/shazow/urllib3>`_, but it does all the hard work and crazy
|
||||
hacks for you.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests.
|
||||
- Gevent support for Asyncronous Requests.
|
||||
- Sessions with cookie persistience.
|
||||
- Basic, Digest, and Custom Authentication support.
|
||||
- Automatic form-encoding of dictionaries
|
||||
- A simple dictionary interface for request/response cookies.
|
||||
- Multipart file uploads.
|
||||
- Automatc decoding of Unicode, gzip, and deflate responses.
|
||||
- Full support for unicode URLs and domain names.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
It couldn't be simpler. ::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('http://google.com')
|
||||
|
||||
|
||||
HTTPS? Basic Authentication? ::
|
||||
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass')
|
||||
>>> r.status_code
|
||||
401
|
||||
|
||||
|
||||
Uh oh, we're not authorized! Let's add authentication. ::
|
||||
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass', auth=('user', 'pass'))
|
||||
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
|
||||
>>> r.content
|
||||
'{"authenticated": true, "user": "user"}'
|
||||
- International Domains and URLs
|
||||
- Keep-Alive & Connection Pooling
|
||||
- Sessions with Cookie Persistence
|
||||
- Browser-style SSL Verification
|
||||
- Basic/Digest Authentication
|
||||
- Elegant Key/Value Cookies
|
||||
- Automatic Decompression
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
- Thread-safety
|
||||
|
||||
|
||||
Installation
|
||||
@@ -99,4 +74,4 @@ Contribute
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/requests
|
||||
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS
|
||||
.. _AUTHORS: https://github.com/kennethreitz/requests/blob/develop/AUTHORS.rst
|
||||
|
||||
18
docs/_templates/sidebarintro.html
vendored
18
docs/_templates/sidebarintro.html
vendored
@@ -4,12 +4,28 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Requests is an elegant and simple HTTP library for Python, built for
|
||||
human beings. You are currently looking at the documentation of the
|
||||
development release.
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Donate</h3>
|
||||
<p>
|
||||
If you love Requests, consider making a small donation <a href="https://flattr.com/thing/442264/Requests">on Flattr</a>:
|
||||
</p>
|
||||
<p>
|
||||
<a class="FlattrButton" style="display:none;" rev="flattr;button:compact;" href="http://docs.python-requests.org/"></a>
|
||||
<noscript><a href="http://flattr.com/thing/442264/Requests" target="_blank">
|
||||
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a></noscript>
|
||||
</p>
|
||||
|
||||
<h3>Feedback</h3>
|
||||
<p>
|
||||
Feedback is greatly appreciated. If you have any questions, comments,
|
||||
@@ -20,8 +36,8 @@
|
||||
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://python-requests.org/">The Requests Website</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="https://flattr.com/thing/442264/Requests">Requests @ Flattr</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
|
||||
14
docs/_templates/sidebarlogo.html
vendored
14
docs/_templates/sidebarlogo.html
vendored
@@ -3,9 +3,23 @@
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Logo"/>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Requests is an elegant and simple HTTP library for Python, built for
|
||||
human beings. You are currently looking at the documentation of the
|
||||
development release.
|
||||
</p>
|
||||
|
||||
<h3>Support Requests</h3>
|
||||
<p>
|
||||
If you love Requests, consider making a small donation <a href="https://flattr.com/thing/442264/Requests">on Flattr</a>:
|
||||
</p>
|
||||
<p>
|
||||
<a class="FlattrButton" style="display:none;" rev="flattr;button:compact;" href="http://docs.python-requests.org/"></a>
|
||||
<noscript><a href="http://flattr.com/thing/442264/Requests" target="_blank">
|
||||
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a></noscript>
|
||||
</p>
|
||||
2
docs/_themes/README.rst
vendored
2
docs/_themes/README.rst
vendored
@@ -2,7 +2,7 @@ krTheme Sphinx Style
|
||||
====================
|
||||
|
||||
This repository contains sphinx styles Kenneth Reitz uses in most of
|
||||
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
|
||||
his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
|
||||
21
docs/_themes/kr/layout.html
vendored
21
docs/_themes/kr/layout.html
vendored
@@ -4,8 +4,6 @@
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9">
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
@@ -16,6 +14,23 @@
|
||||
<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">
|
||||
/* <![CDATA[ */
|
||||
(function() {
|
||||
var s = document.createElement('script'), t = document.getElementsByTagName('script')[0];
|
||||
s.type = 'text/javascript';
|
||||
s.async = true;
|
||||
s.src = 'http://api.flattr.com/js/0.6/load.js?mode=auto';
|
||||
t.parentNode.insertBefore(s, t);
|
||||
})();
|
||||
/* ]]> */
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
new HelloBar(36402,48802);
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
@@ -45,4 +60,6 @@
|
||||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
92
docs/_themes/kr/static/flasky.css_t
vendored
92
docs/_themes/kr/static/flasky.css_t
vendored
@@ -387,7 +387,7 @@ a:hover tt {
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
@media screen and (max-width: 870px) {
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: none;
|
||||
@@ -436,6 +436,96 @@ a:hover tt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 875px) {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.rtd_doc_footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.document {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.github {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
90
docs/_themes/kr/static/small_flask.css
vendored
90
docs/_themes/kr/static/small_flask.css
vendored
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* small_flask.css_t
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.rtd_doc_footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.document {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.github {
|
||||
display: none;
|
||||
}
|
||||
75
docs/api.rst
75
docs/api.rst
@@ -17,6 +17,15 @@ All of Request's functionality can be accessed by these 7 methods.
|
||||
They all return an instance of the :class:`Response <Response>` object.
|
||||
|
||||
.. autofunction:: request
|
||||
|
||||
---------------------
|
||||
|
||||
|
||||
.. autoclass:: Response
|
||||
:inherited-members:
|
||||
|
||||
---------------------
|
||||
|
||||
.. autofunction:: head
|
||||
.. autofunction:: get
|
||||
.. autofunction:: post
|
||||
@@ -25,11 +34,33 @@ They all return an instance of the :class:`Response <Response>` object.
|
||||
.. autofunction:: delete
|
||||
|
||||
|
||||
-----------
|
||||
-----------------
|
||||
|
||||
.. autofunction:: session
|
||||
|
||||
|
||||
.. autoclass:: Response
|
||||
:inherited-members:
|
||||
|
||||
Exceptions
|
||||
~~~~~~~~~~
|
||||
|
||||
.. module:: requests
|
||||
|
||||
.. autoexception:: RequestException
|
||||
.. autoexception:: ConnectionError
|
||||
.. autoexception:: HTTPError
|
||||
.. autoexception:: URLRequired
|
||||
.. autoexception:: TooManyRedirects
|
||||
|
||||
|
||||
.. _configurations:
|
||||
|
||||
Configurations
|
||||
--------------
|
||||
|
||||
.. automodule:: requests.defaults
|
||||
|
||||
|
||||
.. _async:
|
||||
|
||||
Async
|
||||
-----
|
||||
@@ -56,6 +87,22 @@ Requests.
|
||||
|
||||
.. module:: requests.utils
|
||||
|
||||
Status Code Lookup
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: requests.codes
|
||||
|
||||
::
|
||||
|
||||
>>> requests.codes['temporary_redirect']
|
||||
307
|
||||
|
||||
>>> requests.codes.teapot
|
||||
418
|
||||
|
||||
>>> requests.codes['\o/']
|
||||
200
|
||||
|
||||
Cookies
|
||||
~~~~~~~
|
||||
|
||||
@@ -80,26 +127,18 @@ These items are an internal component to Requests, and should never be
|
||||
seen by the end user (developer). This part of the API documentation
|
||||
exists for those who are extending the functionality of Requests.
|
||||
|
||||
Exceptions
|
||||
~~~~~~~~~~
|
||||
|
||||
.. module:: requests
|
||||
|
||||
.. autoexception:: HTTPError
|
||||
|
||||
.. autoexception:: RequestException
|
||||
|
||||
.. autoexception:: AuthenticationError
|
||||
.. autoexception:: URLRequired
|
||||
.. autoexception:: InvalidMethod
|
||||
.. autoexception:: TooManyRedirects
|
||||
|
||||
|
||||
|
||||
Classes
|
||||
~~~~~~~
|
||||
|
||||
.. autoclass:: requests.Response
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: requests.Request
|
||||
:inherited-members:
|
||||
|
||||
.. _sessionapi:
|
||||
|
||||
.. autoclass:: requests.Session
|
||||
:inherited-members:
|
||||
|
||||
|
||||
@@ -51,29 +51,23 @@ Chris Adams gave an excellent summary on
|
||||
Python 3 Support?
|
||||
-----------------
|
||||
|
||||
It's on the way. Here's a list of Python platforms that are officially
|
||||
Yes! Here's a list of Python platforms that are officially
|
||||
supported:
|
||||
|
||||
* cPython 2.5
|
||||
* cPython 2.5.5
|
||||
* cPython 2.5.6
|
||||
* cPython 2.6
|
||||
* cPython 2.6.6
|
||||
* cPython 2.6.7
|
||||
* cPython 2.7
|
||||
* cPython 2.7.1
|
||||
* cPython 2.7.2
|
||||
* cPython 3.1
|
||||
* cPython 3.2
|
||||
* PyPy-c 1.4
|
||||
* PyPy-c 1.5
|
||||
|
||||
|
||||
Support for Python 3.x is coming *very* soon.
|
||||
* PyPy-c 1.6
|
||||
* PyPy-c 1.7
|
||||
|
||||
|
||||
Keep-alive Support?
|
||||
-------------------
|
||||
|
||||
It's on the way.
|
||||
Yep!
|
||||
|
||||
|
||||
Proxy Support?
|
||||
@@ -81,3 +75,9 @@ Proxy Support?
|
||||
|
||||
You bet!
|
||||
|
||||
|
||||
SSL Verification?
|
||||
-----------------
|
||||
|
||||
Absolutely.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
- `requests-oauth-hook <https://github.com/maraujop/requests-oauth-hook>`_, adds OAuth support to Requests.
|
||||
- `requests-oauth <https://github.com/maraujop/requests-oauth>`_, adds OAuth support to Requests.
|
||||
- `FacePy <https://github.com/jgorset/facepy>`_, a Python wrapper to the Facebook API.
|
||||
- `robotframework-requests <https://github.com/bulkan/robotframework-requests>`_, a Robot Framework API wrapper.
|
||||
- `fullerene <https://github.com/bitprophet/fullerene>`_, a Graphite Dashboard.
|
||||
@@ -50,9 +50,6 @@ Requests is available installed as a Debian package! Debian Etch Ubuntu, since O
|
||||
|
||||
$ apt-get install python-requests
|
||||
|
||||
Unfortunately, the most recent version available is v0.5.0. If you're on the
|
||||
Debian Python Package team, I'd love an update of that :)
|
||||
|
||||
|
||||
Fedora and RedHat
|
||||
-----------------
|
||||
|
||||
@@ -43,7 +43,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'2011. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
copyright = u'2012. 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
|
||||
|
||||
@@ -2,4 +2,4 @@ Authors
|
||||
=======
|
||||
|
||||
|
||||
.. include:: ../../AUTHORS
|
||||
.. include:: ../../AUTHORS.rst
|
||||
@@ -3,13 +3,29 @@ How to Help
|
||||
|
||||
Requests is under active development, and contributions are more than welcome!
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a 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.
|
||||
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ on Github to start making your
|
||||
changes to the **develop** 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 <https://github.com/kennethreitz/requests/blob/develop/AUTHORS.rst>`_.
|
||||
|
||||
Development dependencies
|
||||
------------------------
|
||||
|
||||
You'll need to install ``gunicorn`` and ``httpbin`` and various other dependencies in
|
||||
order to run requests' test suite::
|
||||
|
||||
$ virtualenv env
|
||||
$ . env/bin/activate
|
||||
$ make
|
||||
$ make test
|
||||
|
||||
The ``Makefile`` has various useful targets for testing.
|
||||
|
||||
What Needs to be Done
|
||||
---------------------
|
||||
|
||||
- OAuth Support (`#65 <https://github.com/kennethreitz/requests/issues/65>`_)
|
||||
- HTTP Cert Checking (`#30 <https://github.com/kennethreitz/requests/issues/30>`_)
|
||||
- Removal of Poster (`#68 <https://github.com/kennethreitz/requests/issues/68>`_)
|
||||
- Python 3.x Support (`#70 <https://github.com/kennethreitz/requests/issues/70>`_)
|
||||
- Kerberos Support (`#47 <https://github.com/kennethreitz/requests/issues/47>`_)
|
||||
- More Flexible Context Manager (`#69 <https://github.com/kennethreitz/requests/issues/69>`_)
|
||||
- Documentation needs a roadmap.
|
||||
|
||||
@@ -10,46 +10,50 @@ Release v\ |version|. (:ref:`Installation <install>`)
|
||||
|
||||
Requests is an :ref:`ISC Licensed <isc>` HTTP library, written in Python, for human beings.
|
||||
|
||||
Most existing Python modules for sending HTTP requests are extremely verbose
|
||||
and cumbersome. Python's builtin **urllib2** module provides most of
|
||||
the HTTP capabilities you should need, but the api is thoroughly **broken**.
|
||||
It requires an *enormous* amount of work (even method overrides) to perform
|
||||
the simplest of tasks.
|
||||
Python's standard **urllib2** module provides most of
|
||||
the HTTP capabilities you need, but the API is thoroughly **broken**.
|
||||
It was built for a different time — and a different web. It requires an *enormous* amount of work (even method overrides) to perform the simplest of tasks.
|
||||
|
||||
Things shouldn’t be this way. Not in Python.
|
||||
|
||||
::
|
||||
|
||||
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
|
||||
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
|
||||
>>> r.status_code
|
||||
204
|
||||
200
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
...
|
||||
'application/json; charset=utf8'
|
||||
>>> r.encoding
|
||||
'utf-8'
|
||||
>>> r.text
|
||||
u'{"type":"User"...'
|
||||
>>> r.json
|
||||
{u'private_gists': 419, u'total_private_repos': 77, ...}
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
See `similar code, without Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
Requests takes all of the work out of Python HTTP/1.1 — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by `urllib3 <https://github.com/shazow/urllib3>`_, which is embedded within Requests.
|
||||
|
||||
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
|
||||
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
|
||||
multipart files, and parameters with simple Python dictionaries, and access the
|
||||
response data in the same way. It's powered by :py:class:`urllib2`, but it does
|
||||
all the hard work and crazy hacks for you.
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`The Washington Post <http://www.washingtonpost.com/>`_, `Twitter, Inc <http://twitter.com>`_,
|
||||
a U.S. Federal Institution,
|
||||
NIH,
|
||||
`Heroku <http://heroku.com>`_, `PayPal <https://www.paypal.com/>`_,
|
||||
`Transifex <https://www.transifex.net/>`_,
|
||||
`Native Instruments <http://www.native-instruments.com/>`_, `The Washington Post <http://www.washingtonpost.com/>`_,
|
||||
`Twitter, Inc <http://twitter.com>`_,
|
||||
`Readability <http://readability.com>`_, and
|
||||
`Work for Pie <http://workforpie.com>`_
|
||||
use Requests internally.
|
||||
Federal US Institutions
|
||||
use Requests internally. It has been installed over 100,000 times from PyPI.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
right level of abstraction.
|
||||
|
||||
**Matt DeBoard**
|
||||
I'm going to get @kennethreitz's Python requests module tattooed
|
||||
on my body, somehow. The whole thing.
|
||||
|
||||
**Daniel Greenfeld**
|
||||
Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to
|
||||
@kennethreitz's request library. Today has been AWESOME.
|
||||
@@ -58,8 +62,24 @@ use Requests internally.
|
||||
Python HTTP: When in doubt, or when not in doubt, use Requests. Beautiful,
|
||||
simple, Pythonic.
|
||||
|
||||
**Rich Leland**
|
||||
Requests is awesome. That is all.
|
||||
|
||||
Feature Support
|
||||
---------------
|
||||
|
||||
Requests is ready for today's web.
|
||||
|
||||
- International Domains and URLs
|
||||
- Keep-Alive & Connection Pooling
|
||||
- Sessions with Cookie Persistence
|
||||
- Browser-style SSL Verification
|
||||
- Basic/Digest Authentication
|
||||
- Elegant Key/Value Cookies
|
||||
- Automatic Decompression
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
- ``.netrc`` support
|
||||
- Thread-safe.
|
||||
|
||||
|
||||
User Guide
|
||||
@@ -85,7 +105,7 @@ This part of the documentation, which is mostly prose, details the
|
||||
Requests ecosystem and community.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
community/faq
|
||||
community/out-there.rst
|
||||
@@ -111,7 +131,7 @@ If you want to contribute to the project, this part of the documentation is for
|
||||
you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
dev/internals
|
||||
dev/todo
|
||||
|
||||
@@ -10,8 +10,8 @@ Session Objects
|
||||
---------------
|
||||
|
||||
The Session object allows you to persist certain parameters across
|
||||
requests. It also establishes a CookieJar and passes it along
|
||||
to any requests made from the Session instance.
|
||||
requests. It also persists cookies across all requests made from the
|
||||
Session instance.
|
||||
|
||||
A session object has all the methods of the main Requests API.
|
||||
|
||||
@@ -22,7 +22,7 @@ Let's persist some cookies across requests::
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
|
||||
print r.content
|
||||
print r.text
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
|
||||
|
||||
@@ -37,45 +37,78 @@ Sessions can also be used to provide default data to the request methods::
|
||||
c.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
|
||||
|
||||
|
||||
.. admonition:: Global Settings
|
||||
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.
|
||||
|
||||
Certain parameters are best set in the ``config`` dictionary
|
||||
(e.g. user agent header).
|
||||
.. admonition:: Remove a Value From a Dict Parameter
|
||||
|
||||
Sometimes you'll want to omit session-level keys from a dict parameter. To do this, you simply set that key's value to ``None`` in the method-level parameter. It will automatically be omitted.
|
||||
|
||||
All values that are contained within a session are directly available to you. See the :ref:`Session API Docs <sessionapi>` to learn more.
|
||||
|
||||
|
||||
SSL Cert Verification
|
||||
---------------------
|
||||
|
||||
Requests can verify SSL certificates for HTTPS requests, just like a web browser. To check a host's SSL certificate, you can use the ``verify`` argument::
|
||||
|
||||
>>> requests.get('https://kennethreitz.com', verify=True)
|
||||
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
|
||||
|
||||
I don't have SSL setup on this domain, so it fails. Excellent. Github does though::
|
||||
|
||||
>>> requests.get('https://github.com', verify=True)
|
||||
<Response [200]>
|
||||
|
||||
You can also pass ``verify`` the path to a CA_BUNDLE file for private certs. You can also set the ``REQUESTS_CA_BUNDLE`` environment variable.
|
||||
|
||||
|
||||
Body Content Workflow
|
||||
---------------------
|
||||
|
||||
By default, when you make a request, the body of the response isn't downloaded immediately. The response headers are downloaded when you make a request, but the content isn't downloaded until you access the :class:`Response.content` attribute.
|
||||
|
||||
Let's walk through it::
|
||||
|
||||
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
|
||||
r = requests.get(tarball_url)
|
||||
|
||||
The request has been made, but the connection is still open. The response body has not been downloaded yet. ::
|
||||
|
||||
r.content
|
||||
|
||||
The content has been downloaded and cached.
|
||||
|
||||
You can override this default behavior with the ``prefetch`` parameter::
|
||||
|
||||
r = requests.get(tarball_url, prefetch=True)
|
||||
# Blocks until all of request body has been downloaded.
|
||||
|
||||
|
||||
Configuring Requests
|
||||
--------------------
|
||||
|
||||
Sometimes you may want to configure a request to customize its behavior. To do
|
||||
this, you can pass in a ``config`` dictionary to a request or session. See the :ref:`Configuration API Docs <configurations>` to learn more.
|
||||
|
||||
|
||||
Keep-Alive
|
||||
----------
|
||||
|
||||
Excellent news — thanks to urllib3, keep-alive is 100% automatic within a session! Any requests that you make within a session will automatically reuse the appropriate connection!
|
||||
|
||||
Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``prefetch`` to ``True`` or read the ``content`` property of the ``Response`` object.
|
||||
|
||||
If you'd like to disable keep-alive, you can simply set the ``keep_alive`` configuration to ``False``::
|
||||
|
||||
s = requests.session()
|
||||
s.config['keep_alive'] = False
|
||||
|
||||
|
||||
Asynchronous Requests
|
||||
----------------------
|
||||
|
||||
Requests has first-class support for non-blocking i/o requests, powered
|
||||
by gevent. This allows you to send a bunch of HTTP requests at the same
|
||||
|
||||
First, let's import the async module. Heads up — if you don't have
|
||||
`gevent <gevent>`_ this will fail::
|
||||
|
||||
from requests import async
|
||||
|
||||
The ``async`` module has the exact same api as ``requests``, except it
|
||||
doesn't send the request immediately. Instead, it returns the ``Request``
|
||||
object.
|
||||
|
||||
We can build a list of ``Request`` objects easily::
|
||||
|
||||
urls = [
|
||||
'http://python-requests.org',
|
||||
'http://httpbin.org',
|
||||
'http://python-guide.org',
|
||||
'http://kennethreitz.com'
|
||||
]
|
||||
|
||||
rs = [async.get(u) for u in urls]
|
||||
|
||||
Now we have a list of ``Request`` objects, ready to be sent. We could send them
|
||||
one at a time with ``Request.send()``, but that would take a while. Instead,
|
||||
we'll send them all at the same time with ``async.map()``. Using ``async.map()``
|
||||
will also guarantee execution of the ``response`` hook, described below. ::
|
||||
|
||||
>>> async.map(rs)
|
||||
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
|
||||
``requests.async`` has been removed from requests and is now its own repository named `GRequests <https://github.com/kennethreitz/grequests>`_.
|
||||
|
||||
|
||||
Event Hooks
|
||||
@@ -121,14 +154,14 @@ anything, nothing else is effected.
|
||||
|
||||
Let's print some request method arguments at runtime::
|
||||
|
||||
>>> requests.get('http://httpbin', hooks=dict(args=print_url))
|
||||
http://httpbin
|
||||
>>> requests.get('http://httpbin.org', hooks=dict(args=print_url))
|
||||
http://httpbin.org
|
||||
<Response [200]>
|
||||
|
||||
Let's hijack some arguments this time with a new callback::
|
||||
|
||||
def hack_headers(args):
|
||||
if not args[headers]:
|
||||
if args.get('headers') is None:
|
||||
args['headers'] = dict()
|
||||
|
||||
args['headers'].update({'X-Testing': 'True'})
|
||||
@@ -140,7 +173,7 @@ Let's hijack some arguments this time with a new callback::
|
||||
|
||||
And give it a try::
|
||||
|
||||
>>> requests.get('http://httpbin/headers', hooks=hooks, headers=headers)
|
||||
>>> requests.get('http://httpbin.org/headers', hooks=hooks, headers=headers)
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "",
|
||||
@@ -162,35 +195,57 @@ Custom Authentication
|
||||
|
||||
Requests allows you to use specify your own authentication mechanism.
|
||||
|
||||
When you pass our authentication tuple to a request method, the first
|
||||
string is the type of authentication. 'basic' is inferred if none is
|
||||
provided.
|
||||
Any callable which is passed as the ``auth`` argument to a request method will
|
||||
have the opportunity to modify the request before it is dispatched.
|
||||
|
||||
You can pass in a callable object instead of a string for the first item
|
||||
in the tuple, and it will be used in place of the built in authentication
|
||||
callbacks.
|
||||
Authentication implementations are subclasses of ``requests.auth.AuthBase``,
|
||||
and are easy to define. Requests provides two common authentication scheme
|
||||
implementations in ``requests.auth``: ``HTTPBasicAuth`` and ``HTTPDigestAuth``.
|
||||
|
||||
Let's pretend that we have a web service that will only respond if the
|
||||
``X-Pizza`` header is set to a password value. Unlikely, but just go with it.
|
||||
|
||||
We simply need to define a callback function that will be used to update the
|
||||
Request object, right before it is dispatched.
|
||||
|
||||
::
|
||||
|
||||
def pizza_auth(r, username):
|
||||
"""Attaches HTTP Pizza Authentication to the given Request object.
|
||||
"""
|
||||
r.headers['X-Pizza'] = username
|
||||
from requests.auth import AuthBase
|
||||
class PizzaAuth(AuthBase):
|
||||
"""Attaches HTTP Pizza Authentication to the given Request object."""
|
||||
def __init__(self, username):
|
||||
# setup any auth-related data here
|
||||
self.username = username
|
||||
|
||||
return r
|
||||
def __call__(self, r):
|
||||
# modify and return the request
|
||||
r.headers['X-Pizza'] = self.username
|
||||
return r
|
||||
|
||||
Then, we can make a request using our Pizza Auth::
|
||||
|
||||
>>> requests.get('http://pizzabin.org/admin', auth=(pizza_auth, 'kenneth'))
|
||||
>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
|
||||
<Response [200]>
|
||||
|
||||
|
||||
Streaming Requests
|
||||
------------------
|
||||
|
||||
With ``requests.Response.iter_lines()`` you can easily iterate over streaming
|
||||
APIs such as the `Twitter Streaming API <https://dev.twitter.com/docs/streaming-api>`_.
|
||||
|
||||
To use the Twitter Streaming API to track the keyword "requests":
|
||||
|
||||
::
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
r = requests.post('https://stream.twitter.com/1/statuses/filter.json',
|
||||
data={'track': 'requests'}, auth=('username', 'password'))
|
||||
|
||||
for line in r.iter_lines():
|
||||
if line: # filter out keep-alive new lines
|
||||
print json.loads(line)
|
||||
|
||||
|
||||
Verbose Logging
|
||||
---------------
|
||||
|
||||
@@ -199,7 +254,200 @@ by your application, you can turn on verbose logging.
|
||||
|
||||
To do so, just configure Requests with a stream to write to::
|
||||
|
||||
>>> requests.settings.verbose = sys.stderr
|
||||
>>> requests.get('http://httpbin.org/headers')
|
||||
>>> my_config = {'verbose': sys.stderr}
|
||||
>>> requests.get('http://httpbin.org/headers', config=my_config)
|
||||
2011-08-17T03:04:23.380175 GET http://httpbin.org/headers
|
||||
<Response [200]>
|
||||
|
||||
|
||||
Proxies
|
||||
-------
|
||||
|
||||
If you need to use a proxy, you can configure individual requests with the
|
||||
``proxies`` argument to any request method:
|
||||
|
||||
::
|
||||
|
||||
import requests
|
||||
|
||||
proxies = {
|
||||
"http": "10.10.1.10:3128",
|
||||
"https": "10.10.1.10:1080",
|
||||
}
|
||||
|
||||
requests.get("http://example.org", proxies=proxies)
|
||||
|
||||
You can also configure proxies by environment variables ``HTTP_PROXY`` and ``HTTPS_PROXY``.
|
||||
|
||||
::
|
||||
|
||||
$ export HTTP_PROXY="10.10.1.10:3128"
|
||||
$ export HTTPS_PROXY="10.10.1.10:1080"
|
||||
$ python
|
||||
>>> import requests
|
||||
>>> requests.get("http://example.org")
|
||||
|
||||
To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syntax:
|
||||
|
||||
::
|
||||
|
||||
proxies = {
|
||||
"http": "http://user:pass@10.10.1.10:3128/",
|
||||
}
|
||||
|
||||
|
||||
HTTP Verbs
|
||||
----------
|
||||
|
||||
Requests provides access to almost the full range of HTTP verbs: GET, OPTIONS,
|
||||
HEAD, POST, PUT, PATCH and DELETE. The following provides detailed examples of
|
||||
using these various verbs in Requests, using the GitHub API.
|
||||
|
||||
We will begin with the verb most commonly used: GET. HTTP GET is an idempotent
|
||||
method that returns a resource from a given URL. As a result, it is the verb
|
||||
you ought to use when attempting to retrieve data from a web location. An
|
||||
example usage would be attempting to get information about a specific commit
|
||||
from GitHub. Suppose we wanted commit ``a050faf`` on Requests. We would get it
|
||||
like so::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
|
||||
|
||||
We should confirm that GitHub responded correctly. If it has, we want to work
|
||||
out what type of content it is. Do this like so::
|
||||
|
||||
>>> if (r.status_code == requests.codes.ok):
|
||||
... print r.headers['content-type']
|
||||
...
|
||||
application/json; charset=utf-8
|
||||
|
||||
So, GitHub returns JSON. That's great, we can use the JSON module to turn it
|
||||
into Python objects. Because GitHub returned UTF-8, we should use the
|
||||
``r.text`` method, not the ``r.content`` method. ``r.content`` returns a
|
||||
bytestring, while ``r.text`` returns a Unicode-encoded string. I have no plans
|
||||
to perform byte-manipulation on this response, so I want any Unicode code
|
||||
points encoded.::
|
||||
|
||||
>>> import json
|
||||
>>> commit_data = json.loads(r.text)
|
||||
>>> print commit_data.keys()
|
||||
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
|
||||
>>> print commit_data[u'committer']
|
||||
{u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'}
|
||||
>>> print commit_data[u'message']
|
||||
makin' history
|
||||
|
||||
So far, so simple. Well, let's investigate the GitHub API a little bit. Now,
|
||||
we could look at the documentation, but we might have a little more fun if we
|
||||
use Requests instead. We can take advantage of the Requests OPTIONS verb to
|
||||
see what kinds of HTTP methods are supported on the url we just used.::
|
||||
|
||||
>>> verbs = requests.options(r.url)
|
||||
>>> verbs.status_code
|
||||
500
|
||||
|
||||
Uh, what? That's unhelpful! Turns out GitHub, like many API providers, don't
|
||||
actually implement the OPTIONS method. This is an annoying oversight, but it's
|
||||
OK, we can just use the boring documentation. If GitHub had correctly
|
||||
implemented OPTIONS, however, they should return the allowed methods in the
|
||||
headers, e.g.::
|
||||
|
||||
>>> verbs = requests.options('http://a-good-website.com/api/cats')
|
||||
>>> print verbs.headers['allow']
|
||||
GET,HEAD,POST,OPTIONS
|
||||
|
||||
Turning to the documentation, we see that the only other method allowed for
|
||||
commits is POST, which creates a new commit. As we're using the Requests repo,
|
||||
we should probably avoid making ham-handed POSTS to it. Instead, let's play
|
||||
with the Issues feature of GitHub.
|
||||
|
||||
This documentation was added in response to Issue #482. Given that this issue
|
||||
already exists, we will use it as an example. Let's start by getting it.::
|
||||
|
||||
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> issue = json.loads(r.text)
|
||||
>>> print issue[u'title']
|
||||
Feature any http verb in docs
|
||||
>>> print issue[u'comments']
|
||||
3
|
||||
|
||||
Cool, we have three comments. Let's take a look at the last of them.::
|
||||
|
||||
>>> r = requests.get(r.url + u'/comments')
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> comments = json.loads(r.text)
|
||||
>>> print comments[0].keys()
|
||||
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
|
||||
>>> print comments[2][u'body']
|
||||
Probably in the "advanced" section
|
||||
|
||||
Well, that seems like a silly place. Let's post a comment telling the poster
|
||||
that he's silly. Who is the poster, anyway?::
|
||||
|
||||
>>> print comments[2][u'user'][u'login']
|
||||
kennethreitz
|
||||
|
||||
OK, so let's tell this Kenneth guy that we think this example should go in the
|
||||
quickstart guide instead. According to the GitHub API doc, the way to do this
|
||||
is to POST to the thread. Let's do it.::
|
||||
|
||||
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
|
||||
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
|
||||
>>> r = requests.post(url=url, data=body)
|
||||
>>> r.status_code
|
||||
404
|
||||
|
||||
Huh, that's weird. We probably need to authenticate. That'll be a pain, right?
|
||||
Wrong. Requests makes it easy to use many forms of authentication, including
|
||||
the very common Basic Auth.::
|
||||
|
||||
>>> from requests.auth import HTTPBasicAuth
|
||||
>>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
|
||||
>>> r = requests.post(url=url, data=body, auth=auth)
|
||||
>>> r.status_code
|
||||
201
|
||||
>>> content = json.loads(r.text)
|
||||
>>> print content[u'body']
|
||||
Sounds great! I'll get right on it.
|
||||
|
||||
Brilliant. Oh, wait, no! I meant to add that it would take me a while, because
|
||||
I had to go feed my cat. If only I could edit this comment! Happily, GitHub
|
||||
allows us to use another HTTP verb, PATCH, to edit this comment. Let's do
|
||||
that.::
|
||||
|
||||
>>> print content[u"id"]
|
||||
5804413
|
||||
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
|
||||
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
|
||||
>>> r = requests.patch(url=url, data=body, auth=auth)
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
Excellent. Now, just to torture this Kenneth guy, I've decided to let him
|
||||
sweat and not tell him that I'm working on this. That means I want to delete
|
||||
this comment. GitHub lets us delete comments using the incredibly aptly named
|
||||
DELETE method. Let's get rid of it.::
|
||||
|
||||
>>> r = requests.delete(url=url, auth=auth)
|
||||
>>> r.status_code
|
||||
204
|
||||
>>> r.headers['status']
|
||||
'204 No Content'
|
||||
|
||||
Excellent. All gone. The last thing I want to know is how much of my ratelimit
|
||||
I've used. Let's find out. GitHub sends that information in the headers, so
|
||||
rather than download the whole page I'll send a HEAD request to get the
|
||||
headers.::
|
||||
|
||||
>>> r = requests.head(url=url, auth=auth)
|
||||
>>> print r.headers
|
||||
// ...snip... //
|
||||
'x-ratelimit-remaining': '4995'
|
||||
'x-ratelimit-limit': '5000'
|
||||
// ...snip... //
|
||||
|
||||
Excellent. Time to write a Python program that abuses the GitHub API in all
|
||||
kinds of exciting ways, 4995 more times.
|
||||
|
||||
@@ -26,9 +26,9 @@ Cheeseshop Mirror
|
||||
-----------------
|
||||
|
||||
If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's
|
||||
personal `Cheeseshop mirror <http://pip.kreitz.co/>`_::
|
||||
personal `Cheeseshop mirror <http://pip.kennethreitz.com/>`_::
|
||||
|
||||
$ pip install -i http://pip.kreitz.co/simple requests
|
||||
$ pip install -i http://pip.kennethreitz.com/simple requests
|
||||
|
||||
|
||||
Get the Code
|
||||
@@ -43,11 +43,11 @@ You can either clone the public repository::
|
||||
|
||||
Download the `tarball <https://github.com/kennethreitz/requests/tarball/master>`_::
|
||||
|
||||
$ curl -O https://github.com/kennethreitz/requests/tarball/master
|
||||
$ curl -OL https://github.com/kennethreitz/requests/tarball/master
|
||||
|
||||
Or, download the `zipball <https://github.com/kennethreitz/requests/zipball/master>`_::
|
||||
|
||||
$ curl -O https://github.com/kennethreitz/requests/zipball/master
|
||||
$ curl -OL https://github.com/kennethreitz/requests/zipball/master
|
||||
|
||||
|
||||
Once you have a copy of the source, you can embed it in your Python package,
|
||||
|
||||
@@ -15,29 +15,221 @@ First, make sure that:
|
||||
* Requests is :ref:`up-to-date <updates>`
|
||||
|
||||
|
||||
Lets gets started with some simple use cases and examples.
|
||||
Let's get started with some simple examples.
|
||||
|
||||
|
||||
Make a GET Request
|
||||
Make a Request
|
||||
------------------
|
||||
|
||||
Making a standard request with Requests is very simple.
|
||||
Making a request with Requests is very simple.
|
||||
|
||||
Let's get GitHub's public timeline ::
|
||||
Begin by importing the Requests module::
|
||||
|
||||
>>> import requests
|
||||
|
||||
r = requests.get('https://github.com/timeline.json')
|
||||
Now, let's try to get a webpage. For this example, let's get GitHub's public
|
||||
timeline ::
|
||||
|
||||
>>> r = requests.get('https://github.com/timeline.json')
|
||||
|
||||
Now, we have a :class:`Response` object called ``r``. We can get all the
|
||||
information we need from this.
|
||||
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")
|
||||
|
||||
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.delete("http://httpbin.org/delete")
|
||||
>>> r = requests.head("http://httpbin.org/get")
|
||||
>>> r = requests.options("http://httpbin.org/get")
|
||||
|
||||
That's all well and good, but it's also only the start of what Requests can
|
||||
do.
|
||||
|
||||
|
||||
Passing Parameters In URLs
|
||||
--------------------------
|
||||
|
||||
You often want to send some sort of data in the URL's query string. If
|
||||
you were constructing the URL by hand, this data would be given as key/value
|
||||
pairs in the URL after a question mark, e.g. ``httpbin.org/get?key=val``.
|
||||
Requests allows you to provide these arguments as a dictionary, using the
|
||||
``params`` keyword argument. As an example, if you wanted to pass
|
||||
``key1=value1`` and ``key2=value2`` to ``httpbin.org/get``, you would use the
|
||||
following code::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
>>> r = requests.get("http://httpbin.org/get", params=payload)
|
||||
|
||||
You can see that the URL has been correctly encoded by printing the URL::
|
||||
|
||||
>>> print r.url
|
||||
u'http://httpbin.org/get?key2=value2&key1=value1'
|
||||
|
||||
|
||||
Response Content
|
||||
----------------
|
||||
|
||||
We can read the content of the server's response::
|
||||
We can read the content of the server's response. Consider the GitHub timeline
|
||||
again::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://github.com/timeline.json')
|
||||
>>> r.text
|
||||
'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
Requests will automatically decode content from the server. Most unicode
|
||||
charsets are seamlessly decoded.
|
||||
|
||||
When you make a request, ``r.encoding`` is set, based on the HTTP headers.
|
||||
Requests will use that encoding when you access ``r.text``. If ``r.encoding``
|
||||
is ``None``, Requests will make an extremely educated guess of the encoding
|
||||
of the response body. You can manually set ``r.encoding`` to any encoding
|
||||
you'd like, and that charset will be used.
|
||||
|
||||
|
||||
Binary Response Content
|
||||
-----------------------
|
||||
|
||||
You can also access the response body as bytes, for non-text requests::
|
||||
|
||||
>>> r.content
|
||||
'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
|
||||
|
||||
For example, to create an image from binary data returned by a request, you can
|
||||
use the following code:
|
||||
|
||||
>>> from PIL import Image
|
||||
>>> from StringIO import StringIO
|
||||
>>> i = Image.open(StringIO(r.content))
|
||||
|
||||
|
||||
JSON Response Content
|
||||
---------------------
|
||||
|
||||
There's also a builtin JSON decoder, in case you're dealing with JSON data::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://github.com/timeline.json')
|
||||
>>> r.json
|
||||
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
|
||||
|
||||
In case the JSON decoding fails, ``r.json`` simply returns ``None``.
|
||||
|
||||
|
||||
Raw Response Content
|
||||
--------------------
|
||||
|
||||
In the rare case that you'd like to get the absolute raw socket response from the server,
|
||||
you can access ``r.raw``::
|
||||
|
||||
>>> r.raw
|
||||
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
|
||||
|
||||
>>> r.raw.read(10)
|
||||
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
|
||||
|
||||
|
||||
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::
|
||||
|
||||
>>> import json
|
||||
>>> url = 'https://api.github.com/some/endpoint'
|
||||
>>> payload = {'some': 'data'}
|
||||
>>> headers = {'content-type': 'application/json'}
|
||||
|
||||
>>> r = requests.post(url, data=json.dumps(payload), headers=headers)
|
||||
|
||||
|
||||
More complicated POST requests
|
||||
------------------------------
|
||||
|
||||
Typically, you want to send some form-encoded data — much like an HTML form.
|
||||
To do this, simply pass a dictionary to the `data` argument. Your
|
||||
dictionary of data will automatically be form-encoded when the request is made::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
>>> r = requests.post("http://httpbin.org/post", data=payload)
|
||||
>>> print r.text
|
||||
{
|
||||
// ...snip... //
|
||||
"form": {
|
||||
"key2": "value2",
|
||||
"key1": "value1"
|
||||
},
|
||||
// ...snip... //
|
||||
}
|
||||
|
||||
There are many times that you want to send data that is not form-encoded. If you pass in a ``string`` instead of a ``dict``, that data will be posted directly.
|
||||
|
||||
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
|
||||
|
||||
>>> import json
|
||||
>>> url = 'https://api.github.com/some/endpoint'
|
||||
>>> payload = {'some': 'data'}
|
||||
|
||||
>>> r = requests.post(url, data=json.dumps(payload))
|
||||
|
||||
|
||||
POST a Multipart-Encoded File
|
||||
-----------------------------
|
||||
|
||||
Requests makes it simple to upload Multipart-encoded files::
|
||||
|
||||
>>> url = 'http://httpbin.org/post'
|
||||
>>> files = {'report.xls': open('report.xls', 'rb')}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.text
|
||||
{
|
||||
// ...snip... //
|
||||
"files": {
|
||||
"report.xls": "<censored...binary...data>"
|
||||
},
|
||||
// ...snip... //
|
||||
}
|
||||
|
||||
You can set the filename explicitly::
|
||||
|
||||
>>> url = 'http://httpbin.org/post'
|
||||
>>> files = {'file': ('report.xls', open('report.xls', 'rb'))}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.text
|
||||
{
|
||||
// ...snip... //
|
||||
"files": {
|
||||
"file": "<censored...binary...data>"
|
||||
},
|
||||
// ...snip... //
|
||||
}
|
||||
|
||||
If you want, you can send strings to be received as files::
|
||||
|
||||
>>> url = 'http://httpbin.org/post'
|
||||
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.text
|
||||
{
|
||||
// ...snip... //
|
||||
"files": {
|
||||
"file": "some,data,to,send\\nanother,row,to,send\\n"
|
||||
},
|
||||
// ...snip... //
|
||||
}
|
||||
|
||||
|
||||
Response Status Codes
|
||||
@@ -45,6 +237,7 @@ Response Status Codes
|
||||
|
||||
We can check the response status code::
|
||||
|
||||
>>> r = requests.get("http://httpbin.org/get')
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
@@ -54,7 +247,7 @@ reference::
|
||||
>>> r.status_code == requests.codes.ok
|
||||
True
|
||||
|
||||
If we made a bad request, we can raise it with
|
||||
If we made a bad request (non-200 response), we can raise it with
|
||||
:class:`Response.raise_for_status()`::
|
||||
|
||||
>>> _r = requests.get('http://httpbin.org/status/404')
|
||||
@@ -67,7 +260,8 @@ If we made a bad request, we can raise it with
|
||||
raise self.error
|
||||
urllib2.HTTPError: HTTP Error 404: NOT FOUND
|
||||
|
||||
But, since our ``status_code`` was ``200``, when we call it::
|
||||
But, since our ``status_code`` for ``r`` was ``200``, when we call
|
||||
``raise_for_status()`` we get::
|
||||
|
||||
>>> r.raise_for_status()
|
||||
None
|
||||
@@ -78,8 +272,7 @@ All is well.
|
||||
Response Headers
|
||||
----------------
|
||||
|
||||
We can view the server's response headers with a simple Python dictionary
|
||||
interface::
|
||||
We can view the server's response headers using a Python dictionary::
|
||||
|
||||
>>> r.headers
|
||||
{
|
||||
@@ -119,13 +312,8 @@ If a response contains some Cookies, you can get quick access to them::
|
||||
>>> url = 'http://httpbin.org/cookies/set/requests-is/awesome'
|
||||
>>> r = requests.get(url)
|
||||
|
||||
>>> print r.cookies
|
||||
{'requests-is': 'awesome'}
|
||||
|
||||
The underlying CookieJar is also available for more advanced handling::
|
||||
|
||||
>>> r.request.cookiejar
|
||||
<cookielib.CookieJar>
|
||||
>>> r.cookies['requests-is']
|
||||
'awesome'
|
||||
|
||||
To send your own cookies to the server, you can use the ``cookies``
|
||||
parameter::
|
||||
@@ -134,34 +322,131 @@ parameter::
|
||||
>>> cookies = dict(cookies_are='working')
|
||||
|
||||
>>> r = requests.get(url, cookies=cookies)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
|
||||
|
||||
Basic Authentication
|
||||
--------------------
|
||||
|
||||
Most web services require authentication. There many different types of
|
||||
authentication, but the most common is called HTTP Basic Auth.
|
||||
Many web services require authentication. There many different types of
|
||||
authentication, but the most common is HTTP Basic Auth.
|
||||
|
||||
Making requests with Basic Auth is easy, with Requests::
|
||||
Making requests with Basic Auth is extremely simple::
|
||||
|
||||
>>> from requests.auth import HTTPBasicAuth
|
||||
>>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
|
||||
<Response [200]>
|
||||
|
||||
Due to the prevalence of HTTP Basic Auth, requests provides a shorthand for
|
||||
this authentication method::
|
||||
|
||||
>>> requests.get('https://api.github.com/user', auth=('user', 'pass'))
|
||||
<Response [200]>
|
||||
|
||||
Providing the credentials as a tuple in this fashion is functionally equivalent
|
||||
to the ``HTTPBasicAuth`` example above.
|
||||
|
||||
|
||||
Digest Authentication
|
||||
---------------------
|
||||
|
||||
Another popular form of protecting web service is Digest Authentication.
|
||||
|
||||
Requests supports it!::
|
||||
Another popular form of web service protection is Digest Authentication::
|
||||
|
||||
>>> from requests.auth import HTTPDigestAuth
|
||||
>>> url = 'http://httpbin.org/digest-auth/auth/user/pass'
|
||||
>>> requests.get(url, auth=('digest', 'user', 'pass'))
|
||||
>>> requests.get(url, auth=HTTPDigestAuth('user', 'pass'))
|
||||
<Response [200]>
|
||||
|
||||
|
||||
OAuth Authentication
|
||||
--------------------
|
||||
|
||||
Miguel Araujo's `requests-oauth <http://pypi.python.org/pypi/requests-oauth>`_
|
||||
project provides a simple interface for establishing OAuth connections.
|
||||
Documentation and examples can be found on the requests-oauth
|
||||
`git repository <https://github.com/maraujop/requests-oauth>`_.
|
||||
|
||||
|
||||
Redirection and History
|
||||
-----------------------
|
||||
|
||||
Requests will automatically perform location redirection while using the GET
|
||||
and OPTIONS verbs.
|
||||
|
||||
GitHub redirects all HTTP requests to HTTPS. We can use the ``history`` method
|
||||
of the Response object to track redirection. Let's see what Github does::
|
||||
|
||||
>>> r = requests.get('http://github.com')
|
||||
>>> r.url
|
||||
'https://github.com/'
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> r.history
|
||||
[<Response [301]>]
|
||||
|
||||
The :class:`Response.history` list contains a list of the
|
||||
:class:`Request` objects that were created in order to complete the request.
|
||||
|
||||
If you're using GET or OPTIONS, you can disable redirection handling with the
|
||||
``allow_redirects`` parameter::
|
||||
|
||||
>>> r = requests.get('http://github.com', allow_redirects=False)
|
||||
>>> r.status_code
|
||||
301
|
||||
>>> r.history
|
||||
[]
|
||||
|
||||
If you're using POST, PUT, PATCH, DELETE or HEAD, you can enable
|
||||
redirection as well::
|
||||
|
||||
>>> r = requests.post('http://github.com', allow_redirects=True)
|
||||
>>> r.url
|
||||
'https://github.com/'
|
||||
>>> r.history
|
||||
[<Response [301]>]
|
||||
|
||||
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
You can tell requests to stop waiting for a response after a given number of
|
||||
seconds with the ``timeout`` parameter::
|
||||
|
||||
>>> requests.get('http://github.com', timeout=0.001)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
requests.exceptions.Timeout: Request timed out.
|
||||
|
||||
.. admonition:: Note:
|
||||
|
||||
``timeout`` only effects the connection process itself, not the
|
||||
downloading of the response body.
|
||||
|
||||
|
||||
Errors and Exceptions
|
||||
---------------------
|
||||
|
||||
In the event of a network problem (e.g. DNS failure, refused connection, etc),
|
||||
Requests will raise a :class:`ConnectionError` exception.
|
||||
|
||||
In the event of the rare invalid HTTP response, Requests will raise
|
||||
an :class:`HTTPError` exception.
|
||||
|
||||
If a request times out, a :class:`Timeout` exception is raised.
|
||||
|
||||
If a request exceeds the configured number of maximum redirections, a
|
||||
:class:`TooManyRedirects` exception is raised.
|
||||
|
||||
All exceptions that Requests explicitly raises inherit from
|
||||
:class:`requests.exceptions.RequestException`.
|
||||
|
||||
You can refer to :ref:`Configuration API Docs <configurations>` for immediate
|
||||
raising of :class:`HTTPError` exceptions via the ``danger_mode`` option or
|
||||
have Requests catch the majority of
|
||||
:class:`requests.exceptions.RequestException` exceptions with the ``safe_mode``
|
||||
option.
|
||||
|
||||
-----------------------
|
||||
|
||||
Ready for more? Check out the :ref:`advanced <advanced>` section.
|
||||
|
||||
7
reqs.txt
7
reqs.txt
@@ -1,7 +0,0 @@
|
||||
envoy==0.0.2
|
||||
httpbin==0.0.4
|
||||
gunicorn
|
||||
nose
|
||||
pyflakes
|
||||
omnijson
|
||||
rudolf
|
||||
@@ -9,25 +9,25 @@
|
||||
requests
|
||||
~~~~~~~~
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.7.6'
|
||||
__build__ = 0x000706
|
||||
__version__ = '0.13.1'
|
||||
__build__ = 0x001301
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2012 Kenneth Reitz'
|
||||
|
||||
|
||||
from . import utils
|
||||
from .models import Request, Response
|
||||
from .api import request, get, head, post, patch, put, delete
|
||||
from .sessions import session
|
||||
from .api import request, get, head, post, patch, put, delete, options
|
||||
from .sessions import session, Session
|
||||
from .status_codes import codes
|
||||
from .exceptions import (
|
||||
RequestException, AuthenticationError, Timeout, URLRequired,
|
||||
TooManyRedirects
|
||||
RequestException, Timeout, URLRequired,
|
||||
TooManyRedirects, HTTPError, ConnectionError
|
||||
)
|
||||
|
||||
@@ -6,29 +6,16 @@ requests.api
|
||||
|
||||
This module implements the Requests API.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
from .sessions import session
|
||||
from . import sessions
|
||||
from .safe_mode import catch_exceptions_if_in_safe_mode
|
||||
|
||||
__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete')
|
||||
|
||||
|
||||
def request(method, url,
|
||||
params=None,
|
||||
data=None,
|
||||
headers=None,
|
||||
cookies=None,
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=False,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
config=None):
|
||||
@catch_exceptions_if_in_safe_mode
|
||||
def request(method, url, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
@@ -38,21 +25,21 @@ def request(method, url,
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) Auth typle to enable Basic/Digest/Custom HTTP Auth.
|
||||
: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) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
:param session: (optional) A :class:`Session` object to be used for the request.
|
||||
:param config: (optional) A configuration dictionary. See ``request.defaults`` for allowed keys and their default values.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
"""
|
||||
|
||||
s = session()
|
||||
return s.request(
|
||||
method, url, params, data, headers, cookies, files, auth,
|
||||
timeout, allow_redirects, proxies, hooks, return_response,
|
||||
config
|
||||
)
|
||||
s = kwargs.pop('session') if 'session' in kwargs else sessions.session()
|
||||
return s.request(method=method, url=url, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -60,52 +47,63 @@ def get(url, **kwargs):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('GET', url, **kwargs)
|
||||
return request('get', url, **kwargs)
|
||||
|
||||
|
||||
def options(url, **kwargs):
|
||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('options', url, **kwargs)
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('HEAD', url, **kwargs)
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return request('head', url, **kwargs)
|
||||
|
||||
|
||||
def post(url, data='', **kwargs):
|
||||
def post(url, data=None, **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def put(url, data='', **kwargs):
|
||||
def put(url, data=None, **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(url, data='', **kwargs):
|
||||
def patch(url, data=None, **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
@@ -115,7 +113,7 @@ def delete(url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('delete', url, **kwargs)
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.async
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module contains an asynchronous replica of ``requests.api``, powered
|
||||
by gevent. All API methods return a ``Request`` instance (as opposed to
|
||||
``Response``). A list of requests can be sent with ``map()``.
|
||||
"""
|
||||
|
||||
try:
|
||||
import gevent
|
||||
from gevent import monkey as curious_george
|
||||
except ImportError:
|
||||
raise RuntimeError('Gevent is required for requests.async.')
|
||||
|
||||
# Monkey-patch.
|
||||
curious_george.patch_all(thread=False)
|
||||
|
||||
from . import api
|
||||
from .hooks import dispatch_hook
|
||||
|
||||
|
||||
__all__ = (
|
||||
'map',
|
||||
'get', 'head', 'post', 'put', 'patch', 'delete', 'request'
|
||||
)
|
||||
|
||||
|
||||
def patched(f):
|
||||
"""Patches a given API function to not send."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
|
||||
kwargs['return_response'] = False
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
|
||||
if pools:
|
||||
r._pools = pools
|
||||
|
||||
r.send()
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
# Patched requests.api functions.
|
||||
get = patched(api.get)
|
||||
head = patched(api.head)
|
||||
post = patched(api.post)
|
||||
put = patched(api.put)
|
||||
patch = patched(api.patch)
|
||||
delete = patched(api.delete)
|
||||
request = patched(api.request)
|
||||
|
||||
|
||||
def map(requests, prefetch=True):
|
||||
"""Concurrently converts a list of Requests to Responses.
|
||||
|
||||
:param requests: a collection of Request objects.
|
||||
:param prefetch: If False, the content will not be downloaded immediately.
|
||||
"""
|
||||
|
||||
jobs = [gevent.spawn(send, r) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
if prefetch:
|
||||
[r.response.content for r in requests]
|
||||
|
||||
return [r.response for r in requests]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
211
requests/auth.py
211
requests/auth.py
@@ -7,37 +7,136 @@ requests.auth
|
||||
This module contains the authentication handlers for Requests.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
from base64 import b64encode
|
||||
from urlparse import urlparse
|
||||
|
||||
from .utils import randombytes, parse_dict_header
|
||||
from .compat import urlparse, str
|
||||
from .utils import parse_dict_header
|
||||
|
||||
try:
|
||||
from oauthlib.oauth1.rfc5849 import (Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER)
|
||||
from oauthlib.common import extract_params
|
||||
# hush pyflakes:
|
||||
SIGNATURE_HMAC; SIGNATURE_TYPE_AUTH_HEADER
|
||||
except (ImportError, SyntaxError):
|
||||
SIGNATURE_HMAC = None
|
||||
SIGNATURE_TYPE_AUTH_HEADER = None
|
||||
|
||||
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
||||
|
||||
def _basic_auth_str(username, password):
|
||||
"""Returns a Basic Auth string."""
|
||||
|
||||
return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1')
|
||||
|
||||
|
||||
def http_basic(r, username, password):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object.
|
||||
Arguments should be considered non-positional.
|
||||
class AuthBase(object):
|
||||
"""Base class that all auth implementations derive from"""
|
||||
|
||||
"""
|
||||
username = str(username)
|
||||
password = str(password)
|
||||
|
||||
auth_s = b64encode('%s:%s' % (username, password))
|
||||
r.headers['Authorization'] = ('Basic %s' % auth_s)
|
||||
|
||||
return r
|
||||
def __call__(self, r):
|
||||
raise NotImplementedError('Auth hooks must be callable.')
|
||||
|
||||
|
||||
def http_digest(r, username, password):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object.
|
||||
Arguments should be considered non-positional.
|
||||
"""
|
||||
class OAuth1(AuthBase):
|
||||
"""Signs the request using OAuth 1 (RFC5849)"""
|
||||
def __init__(self, client_key,
|
||||
client_secret=None,
|
||||
resource_owner_key=None,
|
||||
resource_owner_secret=None,
|
||||
callback_uri=None,
|
||||
signature_method=SIGNATURE_HMAC,
|
||||
signature_type=SIGNATURE_TYPE_AUTH_HEADER,
|
||||
rsa_key=None, verifier=None):
|
||||
|
||||
def handle_401(r):
|
||||
try:
|
||||
signature_type = signature_type.upper()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
self.client = Client(client_key, client_secret, resource_owner_key,
|
||||
resource_owner_secret, callback_uri, signature_method,
|
||||
signature_type, rsa_key, verifier)
|
||||
|
||||
def __call__(self, r):
|
||||
"""Add OAuth parameters to the request.
|
||||
|
||||
Parameters may be included from the body if the content-type is
|
||||
urlencoded, if no content type is set an educated guess is made.
|
||||
"""
|
||||
contenttype = r.headers.get('Content-Type', None)
|
||||
# extract_params will not give params unless the body is a properly
|
||||
# formatted string, a dictionary or a list of 2-tuples.
|
||||
decoded_body = extract_params(r.data)
|
||||
if contenttype == None and decoded_body != None:
|
||||
# extract_params can only check the present r.data and does not know
|
||||
# of r.files, thus an extra check is performed. We know that
|
||||
# if files are present the request will not have
|
||||
# Content-type: x-www-form-urlencoded. We guess it will have
|
||||
# a mimetype of multipart/form-encoded and if this is not the case
|
||||
# we assume the correct header will be set later.
|
||||
if r.files:
|
||||
# Omit body data in the signing and since it will always
|
||||
# be empty (cant add paras to body if multipart) and we wish
|
||||
# to preserve body.
|
||||
r.headers['Content-Type'] = 'multipart/form-encoded'
|
||||
r.url, r.headers, _ = self.client.sign(
|
||||
unicode(r.url), unicode(r.method), None, r.headers)
|
||||
else:
|
||||
# Normal signing
|
||||
r.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
r.url, r.headers, r.data = self.client.sign(
|
||||
unicode(r.url), unicode(r.method), r.data, r.headers)
|
||||
|
||||
# Having the authorization header, key or value, in unicode will
|
||||
# result in UnicodeDecodeErrors when the request is concatenated
|
||||
# by httplib. This can easily be seen when attaching files.
|
||||
# Note that simply encoding the value is not enough since Python
|
||||
# saves the type of first key set. Thus we remove and re-add.
|
||||
# >>> d = {u'a':u'foo'}
|
||||
# >>> d['a'] = 'foo'
|
||||
# >>> d
|
||||
# { u'a' : 'foo' }
|
||||
u_header = unicode('Authorization')
|
||||
if u_header in r.headers:
|
||||
auth_header = r.headers[u_header].encode('utf-8')
|
||||
del r.headers[u_header]
|
||||
r.headers['Authorization'] = auth_header
|
||||
|
||||
return r
|
||||
|
||||
|
||||
class HTTPBasicAuth(AuthBase):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object."""
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def __call__(self, r):
|
||||
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
|
||||
|
||||
class HTTPProxyAuth(HTTPBasicAuth):
|
||||
"""Attaches HTTP Proxy Authenetication to a given Request object."""
|
||||
def __call__(self, r):
|
||||
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
|
||||
|
||||
class HTTPDigestAuth(AuthBase):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object."""
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def handle_401(self, r):
|
||||
"""Takes the given response and tries digest-auth, if needed."""
|
||||
|
||||
r.request.deregister_hook('response', self.handle_401)
|
||||
|
||||
s_auth = r.headers.get('www-authenticate', '')
|
||||
|
||||
if 'digest' in s_auth.lower():
|
||||
@@ -56,22 +155,32 @@ def http_digest(r, username, password):
|
||||
algorithm = algorithm.upper()
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if algorithm == 'MD5':
|
||||
H = lambda x: hashlib.md5(x).hexdigest()
|
||||
def md5_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.md5(x).hexdigest()
|
||||
hash_utf8 = md5_utf8
|
||||
elif algorithm == 'SHA':
|
||||
H = lambda x: hashlib.sha1(x).hexdigest()
|
||||
def sha_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
hash_utf8 = sha_utf8
|
||||
# XXX MD5-sess
|
||||
KD = lambda s, d: H("%s:%s" % (s, d))
|
||||
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
|
||||
|
||||
if H is None:
|
||||
if hash_utf8 is None:
|
||||
return None
|
||||
|
||||
# XXX not implemented yet
|
||||
entdig = None
|
||||
p_parsed = urlparse(r.request.url)
|
||||
path = p_parsed.path + p_parsed.query
|
||||
path = p_parsed.path
|
||||
if p_parsed.query:
|
||||
path += '?' + p_parsed.query
|
||||
|
||||
A1 = "%s:%s:%s" % (username, realm, password)
|
||||
A2 = "%s:%s" % (r.request.method, path)
|
||||
A1 = '%s:%s:%s' % (self.username, realm, self.password)
|
||||
A2 = '%s:%s' % (r.request.method, path)
|
||||
|
||||
if qop == 'auth':
|
||||
if nonce == last_nonce:
|
||||
@@ -81,21 +190,23 @@ def http_digest(r, username, password):
|
||||
last_nonce = nonce
|
||||
|
||||
ncvalue = '%08x' % nonce_count
|
||||
cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
|
||||
nonce_count, nonce, time.ctime(), randombytes(8)))
|
||||
.hexdigest()[:16]
|
||||
)
|
||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
|
||||
respdig = KD(H(A1), noncebit)
|
||||
s = str(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, hash_utf8(A2))
|
||||
respdig = KD(hash_utf8(A1), noncebit)
|
||||
elif qop is None:
|
||||
respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
|
||||
respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
|
||||
else:
|
||||
# XXX handle auth-int.
|
||||
return None
|
||||
|
||||
# XXX should the partial digests be encoded too?
|
||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||
'response="%s"' % (username, realm, nonce, path, respdig)
|
||||
'response="%s"' % (self.username, realm, nonce, path, respdig)
|
||||
if opaque:
|
||||
base += ', opaque="%s"' % opaque
|
||||
if entdig:
|
||||
@@ -104,7 +215,6 @@ def http_digest(r, username, password):
|
||||
if qop:
|
||||
base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
|
||||
|
||||
|
||||
r.request.headers['Authorization'] = 'Digest %s' % (base)
|
||||
r.request.send(anyway=True)
|
||||
_r = r.request.response
|
||||
@@ -114,33 +224,6 @@ def http_digest(r, username, password):
|
||||
|
||||
return r
|
||||
|
||||
r.hooks['response'] = handle_401
|
||||
return r
|
||||
|
||||
|
||||
def dispatch(t):
|
||||
"""Given an auth tuple, return an expanded version."""
|
||||
|
||||
if not t:
|
||||
return t
|
||||
else:
|
||||
t = list(t)
|
||||
|
||||
# Make sure they're passing in something.
|
||||
assert len(t) >= 2
|
||||
|
||||
# If only two items are passed in, assume HTTPBasic.
|
||||
if (len(t) == 2):
|
||||
t.insert(0, 'basic')
|
||||
|
||||
# Allow built-in string referenced auths.
|
||||
if isinstance(t[0], basestring):
|
||||
if t[0] in ('basic', 'forced_basic'):
|
||||
t[0] = http_basic
|
||||
elif t[0] in ('digest',):
|
||||
t[0] = http_digest
|
||||
|
||||
# Return a custom callable.
|
||||
return (t[0], tuple(t[1:]))
|
||||
|
||||
|
||||
def __call__(self, r):
|
||||
r.register_hook('response', self.handle_401)
|
||||
return r
|
||||
|
||||
105
requests/compat.py
Normal file
105
requests/compat.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
pythoncompat
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
# -------
|
||||
# Pythons
|
||||
# -------
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version_info
|
||||
|
||||
#: Python 2.x?
|
||||
is_py2 = (_ver[0] == 2)
|
||||
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
#: 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.
|
||||
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
|
||||
if is_py2:
|
||||
from urllib import quote, unquote, urlencode
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||
from urllib2 import parse_http_list
|
||||
import cookielib
|
||||
from Cookie import Morsel
|
||||
from StringIO import StringIO
|
||||
|
||||
bytes = str
|
||||
str = unicode
|
||||
basestring = basestring
|
||||
|
||||
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
|
||||
from urllib.request import parse_http_list
|
||||
from http import cookiejar as cookielib
|
||||
from http.cookies import Morsel
|
||||
from io import StringIO
|
||||
|
||||
str = str
|
||||
bytes = bytes
|
||||
basestring = (str,bytes)
|
||||
|
||||
363
requests/cookies.py
Normal file
363
requests/cookies.py
Normal file
@@ -0,0 +1,363 @@
|
||||
"""
|
||||
Compatibility code to be able to use `cookielib.CookieJar` with requests.
|
||||
|
||||
requests.utils imports from here, so be careful with imports.
|
||||
"""
|
||||
|
||||
import collections
|
||||
from .compat import cookielib, urlparse, Morsel
|
||||
|
||||
try:
|
||||
import threading
|
||||
# grr, pyflakes: this fixes "redefinition of unused 'threading'"
|
||||
threading
|
||||
except ImportError:
|
||||
import dummy_threading as threading
|
||||
|
||||
class MockRequest(object):
|
||||
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
|
||||
|
||||
The code in `cookielib.CookieJar` expects this interface in order to correctly
|
||||
manage cookie policies, i.e., determine whether a cookie can be set, given the
|
||||
domains of the request and the cookie.
|
||||
|
||||
The original request object is read-only. The client is responsible for collecting
|
||||
the new headers via `get_new_headers()` and interpreting them appropriately. You
|
||||
probably want `get_cookie_header`, defined below.
|
||||
"""
|
||||
|
||||
def __init__(self, request):
|
||||
self._r = request
|
||||
self._new_headers = {}
|
||||
|
||||
def get_type(self):
|
||||
return urlparse(self._r.full_url).scheme
|
||||
|
||||
def get_host(self):
|
||||
return urlparse(self._r.full_url).netloc
|
||||
|
||||
def get_origin_req_host(self):
|
||||
if self._r.response.history:
|
||||
r = self._r.response.history[0]
|
||||
return urlparse(r).netloc
|
||||
else:
|
||||
return self.get_host()
|
||||
|
||||
def get_full_url(self):
|
||||
return self._r.full_url
|
||||
|
||||
def is_unverifiable(self):
|
||||
# unverifiable == redirected
|
||||
return bool(self._r.response.history)
|
||||
|
||||
def has_header(self, name):
|
||||
return name in self._r.headers or name in self._new_headers
|
||||
|
||||
def get_header(self, name, default=None):
|
||||
return self._r.headers.get(name, self._new_headers.get(name, default))
|
||||
|
||||
def add_header(self, key, val):
|
||||
"""cookielib has no legitimate use for this method; add it back if you find one."""
|
||||
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
|
||||
|
||||
def add_unredirected_header(self, name, value):
|
||||
self._new_headers[name] = value
|
||||
|
||||
def get_new_headers(self):
|
||||
return self._new_headers
|
||||
|
||||
class MockResponse(object):
|
||||
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
|
||||
|
||||
...what? Basically, expose the parsed HTTP headers from the server response
|
||||
the way `cookielib` expects to see them.
|
||||
"""
|
||||
|
||||
def __init__(self, headers):
|
||||
"""Make a MockResponse for `cookielib` to read.
|
||||
|
||||
:param headers: a httplib.HTTPMessage or analogous carrying the headers
|
||||
"""
|
||||
self._headers = headers
|
||||
|
||||
def info(self):
|
||||
return self._headers
|
||||
|
||||
def getheaders(self, name):
|
||||
self._headers.getheaders(name)
|
||||
|
||||
def extract_cookies_to_jar(jar, request, response):
|
||||
"""Extract the cookies from the response into a CookieJar.
|
||||
|
||||
:param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
|
||||
:param request: our own requests.Request object
|
||||
:param response: urllib3.HTTPResponse object
|
||||
"""
|
||||
# the _original_response field is the wrapped httplib.HTTPResponse object,
|
||||
req = MockRequest(request)
|
||||
# pull out the HTTPMessage with the headers and put it in the mock:
|
||||
res = MockResponse(response._original_response.msg)
|
||||
jar.extract_cookies(res, req)
|
||||
|
||||
def get_cookie_header(jar, request):
|
||||
"""Produce an appropriate Cookie header string to be sent with `request`, or None."""
|
||||
r = MockRequest(request)
|
||||
jar.add_cookie_header(r)
|
||||
return r.get_new_headers().get('Cookie')
|
||||
|
||||
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
||||
"""Unsets a cookie by name, by default over all domains and paths.
|
||||
|
||||
Wraps CookieJar.clear(), is O(n).
|
||||
"""
|
||||
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))
|
||||
|
||||
for domain, path, name in clearables:
|
||||
cookiejar.clear(domain, path, name)
|
||||
|
||||
class CookieConflictError(RuntimeError):
|
||||
"""There are two cookies that meet the criteria specified in the cookie jar.
|
||||
Use .get and .set and include domain and path args in order to be more specific."""
|
||||
|
||||
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""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).
|
||||
|
||||
Unlike a regular CookieJar, this class is pickleable.
|
||||
"""
|
||||
|
||||
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)."""
|
||||
try:
|
||||
return self._find_no_duplicates(name, domain, path)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
"""Dict-like set() that also supports optional domain and path args in
|
||||
order to resolve naming collisions from using one cookie jar over
|
||||
multiple domains."""
|
||||
# support client code that unsets cookies by assignment of a None value:
|
||||
if value is None:
|
||||
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
|
||||
return
|
||||
|
||||
if isinstance(value, Morsel):
|
||||
c = morsel_to_cookie(value)
|
||||
else:
|
||||
c = create_cookie(name, value, **kwargs)
|
||||
self.set_cookie(c)
|
||||
return c
|
||||
|
||||
def keys(self):
|
||||
"""Dict-like keys() that returns a list of names of cookies from the jar.
|
||||
See values() and items()."""
|
||||
keys = []
|
||||
for cookie in iter(self):
|
||||
keys.append(cookie.name)
|
||||
return keys
|
||||
|
||||
def values(self):
|
||||
"""Dict-like values() that returns a list of values of cookies from the jar.
|
||||
See keys() and items()."""
|
||||
values = []
|
||||
for cookie in iter(self):
|
||||
values.append(cookie.value)
|
||||
return values
|
||||
|
||||
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."""
|
||||
items = []
|
||||
for cookie in iter(self):
|
||||
items.append((cookie.name, cookie.value))
|
||||
return items
|
||||
|
||||
def list_domains(self):
|
||||
"""Utility method to list all the domains in the jar."""
|
||||
domains = []
|
||||
for cookie in iter(self):
|
||||
if cookie.domain not in domains:
|
||||
domains.append(cookie.domain)
|
||||
return domains
|
||||
|
||||
def list_paths(self):
|
||||
"""Utility method to list all the paths in the jar."""
|
||||
paths = []
|
||||
for cookie in iter(self):
|
||||
if cookie.path not in paths:
|
||||
paths.append(cookie.path)
|
||||
return paths
|
||||
|
||||
def multiple_domains(self):
|
||||
"""Returns True if there are multiple domains in the jar.
|
||||
Returns False otherwise."""
|
||||
domains = []
|
||||
for cookie in iter(self):
|
||||
if cookie.domain is not None and cookie.domain in domains:
|
||||
return True
|
||||
domains.append(cookie.domain)
|
||||
return False # there is only one domain in jar
|
||||
|
||||
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."""
|
||||
dictionary = {}
|
||||
for cookie in iter(self):
|
||||
if (domain == None or cookie.domain == domain) and (path == None
|
||||
or cookie.path == path):
|
||||
dictionary[cookie.name] = cookie.value
|
||||
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)."""
|
||||
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."""
|
||||
self.set(name, value)
|
||||
|
||||
def __delitem__(self, name):
|
||||
"""Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
|
||||
remove_cookie_by_name(self, name)
|
||||
|
||||
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."""
|
||||
for cookie in iter(self):
|
||||
if cookie.name == name:
|
||||
if domain is None or cookie.domain == domain:
|
||||
if path is None or cookie.path == path:
|
||||
return cookie.value
|
||||
|
||||
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."""
|
||||
toReturn = None
|
||||
for cookie in iter(self):
|
||||
if cookie.name == name:
|
||||
if domain is None or cookie.domain == domain:
|
||||
if path is None or cookie.path == path:
|
||||
if toReturn != None: # if there are multiple cookies that meet passed in criteria
|
||||
raise CookieConflictError('There are multiple cookies with name, %r' % (name))
|
||||
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
|
||||
|
||||
if toReturn:
|
||||
return toReturn
|
||||
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
|
||||
|
||||
def __getstate__(self):
|
||||
"""Unlike a normal CookieJar, this class is pickleable."""
|
||||
state = self.__dict__.copy()
|
||||
# remove the unpickleable RLock object
|
||||
state.pop('_cookies_lock')
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""Unlike a normal CookieJar, this class is pickleable."""
|
||||
self.__dict__.update(state)
|
||||
if '_cookies_lock' not in self.__dict__:
|
||||
self._cookies_lock = threading.RLock()
|
||||
|
||||
def copy(self):
|
||||
"""This is not implemented. Calling this will throw an exception."""
|
||||
raise NotImplementedError
|
||||
|
||||
def create_cookie(name, value, **kwargs):
|
||||
"""Make a cookie from underspecified parameters.
|
||||
|
||||
By default, the pair of `name` and `value` will be set for the domain ''
|
||||
and sent on every request (this is sometimes called a "supercookie").
|
||||
"""
|
||||
result = dict(
|
||||
version=0,
|
||||
name=name,
|
||||
value=value,
|
||||
port=None,
|
||||
domain='',
|
||||
path='/',
|
||||
secure=False,
|
||||
expires=None,
|
||||
discard=True,
|
||||
comment=None,
|
||||
comment_url=None,
|
||||
rest={'HttpOnly': None},
|
||||
rfc2109=False,
|
||||
)
|
||||
|
||||
badargs = set(kwargs) - set(result)
|
||||
if badargs:
|
||||
err = 'create_cookie() got unexpected keyword arguments: %s'
|
||||
raise TypeError(err % list(badargs))
|
||||
|
||||
result.update(kwargs)
|
||||
result['port_specified'] = bool(result['port'])
|
||||
result['domain_specified'] = bool(result['domain'])
|
||||
result['domain_initial_dot'] = result['domain'].startswith('.')
|
||||
result['path_specified'] = bool(result['path'])
|
||||
|
||||
return cookielib.Cookie(**result)
|
||||
|
||||
def morsel_to_cookie(morsel):
|
||||
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
|
||||
c = create_cookie(
|
||||
name=morsel.key,
|
||||
value=morsel.value,
|
||||
version=morsel['version'] or 0,
|
||||
port=None,
|
||||
port_specified=False,
|
||||
domain=morsel['domain'],
|
||||
domain_specified=bool(morsel['domain']),
|
||||
domain_initial_dot=morsel['domain'].startswith('.'),
|
||||
path=morsel['path'],
|
||||
path_specified=bool(morsel['path']),
|
||||
secure=bool(morsel['secure']),
|
||||
expires=morsel['max-age'] or morsel['expires'],
|
||||
discard=False,
|
||||
comment=morsel['comment'],
|
||||
comment_url=bool(morsel['comment']),
|
||||
rest={'HttpOnly': morsel['httponly']},
|
||||
rfc2109=False,
|
||||
)
|
||||
return c
|
||||
|
||||
def cookiejar_from_dict(cookie_dict, cookiejar=None):
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
if cookiejar is None:
|
||||
cookiejar = RequestsCookieJar()
|
||||
|
||||
if cookie_dict is not None:
|
||||
for name in cookie_dict:
|
||||
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
|
||||
return cookiejar
|
||||
@@ -6,18 +6,26 @@ requests.defaults
|
||||
|
||||
This module provides the Requests configuration defaults.
|
||||
|
||||
settings parameters:
|
||||
Configurations:
|
||||
|
||||
- :base_headers: - Sets default User-Agent to `python-requests.org`
|
||||
- :accept_gzip: - Whether or not to accept gzip-compressed data
|
||||
- :proxies: - http proxies?
|
||||
- :verbose: - display verbose information?
|
||||
- :timeout: - timeout time until request terminates
|
||||
- :max_redirects: - maximum number of allowed redirects?
|
||||
- :decode_unicode: - whether or not to accept unicode?
|
||||
:base_headers: Default HTTP headers.
|
||||
:verbose: Stream to write request logging to.
|
||||
:max_redirects: Maximum number of redirects allowed within a request.s
|
||||
:keep_alive: Reuse HTTP Connections?
|
||||
:max_retries: The number of times a request should be retried in the event of a connection failure.
|
||||
:danger_mode: If true, Requests will raise errors immediately.
|
||||
:safe_mode: If true, Requests will catch all errors.
|
||||
:strict_mode: If true, Requests will do its best to follow RFCs (e.g. POST redirects).
|
||||
:pool_maxsize: The maximium size of an HTTP connection pool.
|
||||
:pool_connections: The number of active HTTP connection pools to use.
|
||||
:encode_uri: If true, URIs will automatically be percent-encoded.
|
||||
:trust_env: If true, the surrouding environment will be trusted (environ, netrc).
|
||||
:param store_cookies: If false, the received cookies as part of the HTTP response would be ignored.
|
||||
|
||||
"""
|
||||
|
||||
SCHEMAS = ['http', 'https']
|
||||
|
||||
from . import __version__
|
||||
|
||||
defaults = dict()
|
||||
@@ -29,11 +37,17 @@ defaults['base_headers'] = {
|
||||
'Accept': '*/*'
|
||||
}
|
||||
|
||||
defaults['proxies'] = {}
|
||||
defaults['verbose'] = None
|
||||
defaults['timeout'] = None
|
||||
defaults['max_redirects'] = 30
|
||||
defaults['decode_unicode'] = True
|
||||
defaults['timeout_fallback'] = True
|
||||
# defaults['keep_alive'] = True
|
||||
# defaults['max_connections'] = 10
|
||||
defaults['pool_connections'] = 10
|
||||
defaults['pool_maxsize'] = 10
|
||||
defaults['max_retries'] = 0
|
||||
defaults['danger_mode'] = False
|
||||
defaults['safe_mode'] = False
|
||||
defaults['strict_mode'] = False
|
||||
defaults['keep_alive'] = True
|
||||
defaults['encode_uri'] = True
|
||||
defaults['trust_env'] = True
|
||||
defaults['store_cookies'] = True
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,19 @@ This module contains the set of Requests' exceptions.
|
||||
|
||||
"""
|
||||
|
||||
class RequestException(Exception):
|
||||
class RequestException(RuntimeError):
|
||||
"""There was an ambiguous exception that occurred while handling your
|
||||
request."""
|
||||
|
||||
class AuthenticationError(RequestException):
|
||||
"""The authentication credentials provided were invalid."""
|
||||
class HTTPError(RequestException):
|
||||
"""An HTTP error occurred."""
|
||||
response = None
|
||||
|
||||
class ConnectionError(RequestException):
|
||||
"""A Connection error occurred."""
|
||||
|
||||
class SSLError(ConnectionError):
|
||||
"""An SSL error occurred."""
|
||||
|
||||
class Timeout(RequestException):
|
||||
"""The request timed out."""
|
||||
@@ -23,3 +30,12 @@ class URLRequired(RequestException):
|
||||
|
||||
class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
|
||||
class MissingSchema(RequestException, ValueError):
|
||||
"""The URL schema (e.g. http or https) is missing."""
|
||||
|
||||
class InvalidSchema(RequestException, ValueError):
|
||||
"""See defaults.py for valid schemas."""
|
||||
|
||||
class InvalidURL(RequestException, ValueError):
|
||||
""" The URL provided was somehow invalid. """
|
||||
|
||||
@@ -12,6 +12,9 @@ Available hooks:
|
||||
A dictionary of the arguments being sent to Request().
|
||||
|
||||
``pre_request``:
|
||||
The Request object, directly after being created.
|
||||
|
||||
``pre_send``:
|
||||
The Request object, directly before being sent.
|
||||
|
||||
``post_request``:
|
||||
@@ -22,19 +25,29 @@ Available hooks:
|
||||
|
||||
"""
|
||||
|
||||
import warnings
|
||||
import traceback
|
||||
|
||||
|
||||
HOOKS = ('args', 'pre_request', 'pre_send', 'post_request', 'response')
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
"""Dispatches a hook dictionary on a given piece of data."""
|
||||
|
||||
hooks = hooks or dict()
|
||||
|
||||
if key in hooks:
|
||||
try:
|
||||
return hooks.get(key).__call__(hook_data) or hook_data
|
||||
hooks = hooks.get(key)
|
||||
|
||||
except Exception, why:
|
||||
warnings.warn(str(why))
|
||||
if hasattr(hooks, '__call__'):
|
||||
hooks = [hooks]
|
||||
|
||||
for hook in hooks:
|
||||
try:
|
||||
_hook_data = hook(hook_data)
|
||||
if _hook_data is not None:
|
||||
hook_data = _hook_data
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
return hook_data
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.monkeys
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Urllib2 Monkey patches.
|
||||
|
||||
"""
|
||||
|
||||
import urllib2
|
||||
import re
|
||||
|
||||
class Request(urllib2.Request):
|
||||
"""Hidden wrapper around the urllib2.Request object. Allows for manual
|
||||
setting of HTTP methods.
|
||||
"""
|
||||
|
||||
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
|
||||
urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
|
||||
self.method = method
|
||||
|
||||
def get_method(self):
|
||||
if self.method:
|
||||
return self.method
|
||||
|
||||
return urllib2.Request.get_method(self)
|
||||
|
||||
|
||||
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
"""HTTP Redirect handler."""
|
||||
def _pass(self, req, fp, code, msg, headers):
|
||||
pass
|
||||
|
||||
http_error_302 = _pass
|
||||
http_error_303 = _pass
|
||||
http_error_307 = _pass
|
||||
http_error_301 = _pass
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from . import poster
|
||||
from . import urllib3
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Copyright (c) 2010 Chris AtLee
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
"""poster module
|
||||
|
||||
Support for streaming HTTP uploads, and multipart/form-data encoding
|
||||
|
||||
```poster.version``` is a 3-tuple of integers representing the version number.
|
||||
New releases of poster will always have a version number that compares greater
|
||||
than an older version of poster.
|
||||
New in version 0.6."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from . import streaminghttp
|
||||
from . import encode
|
||||
|
||||
version = (0, 8, 0) # Thanks JP!
|
||||
@@ -1,414 +0,0 @@
|
||||
"""multipart/form-data encoding module
|
||||
|
||||
This module provides functions that faciliate encoding name/value pairs
|
||||
as multipart/form-data suitable for a HTTP POST or PUT request.
|
||||
|
||||
multipart/form-data is the standard way to upload files over HTTP"""
|
||||
|
||||
__all__ = ['gen_boundary', 'encode_and_quote', 'MultipartParam',
|
||||
'encode_string', 'encode_file_header', 'get_body_size', 'get_headers',
|
||||
'multipart_encode']
|
||||
|
||||
try:
|
||||
import uuid
|
||||
def gen_boundary():
|
||||
"""Returns a random string to use as the boundary for a message"""
|
||||
return uuid.uuid4().hex
|
||||
except ImportError:
|
||||
import random, sha
|
||||
def gen_boundary():
|
||||
"""Returns a random string to use as the boundary for a message"""
|
||||
bits = random.getrandbits(160)
|
||||
return sha.new(str(bits)).hexdigest()
|
||||
|
||||
import urllib, re, os, mimetypes
|
||||
try:
|
||||
from email.header import Header
|
||||
except ImportError:
|
||||
# Python 2.4
|
||||
from email.Header import Header
|
||||
|
||||
def encode_and_quote(data):
|
||||
"""If ``data`` is unicode, return urllib.quote_plus(data.encode("utf-8"))
|
||||
otherwise return urllib.quote_plus(data)"""
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if isinstance(data, unicode):
|
||||
data = data.encode("utf-8")
|
||||
return urllib.quote_plus(data)
|
||||
|
||||
def _strify(s):
|
||||
"""If s is a unicode string, encode it to UTF-8 and return the results,
|
||||
otherwise return str(s), or None if s is None"""
|
||||
if s is None:
|
||||
return None
|
||||
if isinstance(s, unicode):
|
||||
return s.encode("utf-8")
|
||||
return str(s)
|
||||
|
||||
class MultipartParam(object):
|
||||
"""Represents a single parameter in a multipart/form-data request
|
||||
|
||||
``name`` is the name of this parameter.
|
||||
|
||||
If ``value`` is set, it must be a string or unicode object to use as the
|
||||
data for this parameter.
|
||||
|
||||
If ``filename`` is set, it is what to say that this parameter's filename
|
||||
is. Note that this does not have to be the actual filename any local file.
|
||||
|
||||
If ``filetype`` is set, it is used as the Content-Type for this parameter.
|
||||
If unset it defaults to "text/plain; charset=utf8"
|
||||
|
||||
If ``filesize`` is set, it specifies the length of the file ``fileobj``
|
||||
|
||||
If ``fileobj`` is set, it must be a file-like object that supports
|
||||
.read().
|
||||
|
||||
Both ``value`` and ``fileobj`` must not be set, doing so will
|
||||
raise a ValueError assertion.
|
||||
|
||||
If ``fileobj`` is set, and ``filesize`` is not specified, then
|
||||
the file's size will be determined first by stat'ing ``fileobj``'s
|
||||
file descriptor, and if that fails, by seeking to the end of the file,
|
||||
recording the current position as the size, and then by seeking back to the
|
||||
beginning of the file.
|
||||
|
||||
``cb`` is a callable which will be called from iter_encode with (self,
|
||||
current, total), representing the current parameter, current amount
|
||||
transferred, and the total size.
|
||||
"""
|
||||
def __init__(self, name, value=None, filename=None, filetype=None,
|
||||
filesize=None, fileobj=None, cb=None):
|
||||
self.name = Header(name).encode()
|
||||
self.value = _strify(value)
|
||||
if filename is None:
|
||||
self.filename = None
|
||||
else:
|
||||
if isinstance(filename, unicode):
|
||||
# Encode with XML entities
|
||||
self.filename = filename.encode("ascii", "xmlcharrefreplace")
|
||||
else:
|
||||
self.filename = str(filename)
|
||||
self.filename = self.filename.encode("string_escape").\
|
||||
replace('"', '\\"')
|
||||
self.filetype = _strify(filetype)
|
||||
|
||||
self.filesize = filesize
|
||||
self.fileobj = fileobj
|
||||
self.cb = cb
|
||||
|
||||
if self.value is not None and self.fileobj is not None:
|
||||
raise ValueError("Only one of value or fileobj may be specified")
|
||||
|
||||
if fileobj is not None and filesize is None:
|
||||
# Try and determine the file size
|
||||
try:
|
||||
self.filesize = os.fstat(fileobj.fileno()).st_size
|
||||
except (OSError, AttributeError):
|
||||
try:
|
||||
fileobj.seek(0, 2)
|
||||
self.filesize = fileobj.tell()
|
||||
fileobj.seek(0)
|
||||
except:
|
||||
raise ValueError("Could not determine filesize")
|
||||
|
||||
def __cmp__(self, other):
|
||||
attrs = ['name', 'value', 'filename', 'filetype', 'filesize', 'fileobj']
|
||||
myattrs = [getattr(self, a) for a in attrs]
|
||||
oattrs = [getattr(other, a) for a in attrs]
|
||||
return cmp(myattrs, oattrs)
|
||||
|
||||
def reset(self):
|
||||
if self.fileobj is not None:
|
||||
self.fileobj.seek(0)
|
||||
elif self.value is None:
|
||||
raise ValueError("Don't know how to reset this parameter")
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, paramname, filename):
|
||||
"""Returns a new MultipartParam object constructed from the local
|
||||
file at ``filename``.
|
||||
|
||||
``filesize`` is determined by os.path.getsize(``filename``)
|
||||
|
||||
``filetype`` is determined by mimetypes.guess_type(``filename``)[0]
|
||||
|
||||
``filename`` is set to os.path.basename(``filename``)
|
||||
"""
|
||||
|
||||
return cls(paramname, filename=os.path.basename(filename),
|
||||
filetype=mimetypes.guess_type(filename)[0],
|
||||
filesize=os.path.getsize(filename),
|
||||
fileobj=open(filename, "rb"))
|
||||
|
||||
@classmethod
|
||||
def from_params(cls, params):
|
||||
"""Returns a list of MultipartParam objects from a sequence of
|
||||
name, value pairs, MultipartParam instances,
|
||||
or from a mapping of names to values
|
||||
|
||||
The values may be strings or file objects, or MultipartParam objects.
|
||||
MultipartParam object names must match the given names in the
|
||||
name,value pairs or mapping, if applicable."""
|
||||
if hasattr(params, 'items'):
|
||||
params = params.items()
|
||||
|
||||
retval = []
|
||||
for item in params:
|
||||
if isinstance(item, cls):
|
||||
retval.append(item)
|
||||
continue
|
||||
name, value = item
|
||||
if isinstance(value, cls):
|
||||
assert value.name == name
|
||||
retval.append(value)
|
||||
continue
|
||||
if hasattr(value, 'read'):
|
||||
# Looks like a file object
|
||||
filename = getattr(value, 'name', None)
|
||||
if filename is not None:
|
||||
filetype = mimetypes.guess_type(filename)[0]
|
||||
else:
|
||||
filetype = None
|
||||
|
||||
retval.append(cls(name=name, filename=filename,
|
||||
filetype=filetype, fileobj=value))
|
||||
else:
|
||||
retval.append(cls(name, value))
|
||||
return retval
|
||||
|
||||
def encode_hdr(self, boundary):
|
||||
"""Returns the header of the encoding of this parameter"""
|
||||
boundary = encode_and_quote(boundary)
|
||||
|
||||
headers = ["--%s" % boundary]
|
||||
|
||||
if self.filename:
|
||||
disposition = 'form-data; name="%s"; filename="%s"' % (self.name,
|
||||
self.filename)
|
||||
else:
|
||||
disposition = 'form-data; name="%s"' % self.name
|
||||
|
||||
headers.append("Content-Disposition: %s" % disposition)
|
||||
|
||||
if self.filetype:
|
||||
filetype = self.filetype
|
||||
else:
|
||||
filetype = "text/plain; charset=utf-8"
|
||||
|
||||
headers.append("Content-Type: %s" % filetype)
|
||||
|
||||
headers.append("")
|
||||
headers.append("")
|
||||
|
||||
return "\r\n".join(headers)
|
||||
|
||||
def encode(self, boundary):
|
||||
"""Returns the string encoding of this parameter"""
|
||||
if self.value is None:
|
||||
value = self.fileobj.read()
|
||||
else:
|
||||
value = self.value
|
||||
|
||||
if re.search("^--%s$" % re.escape(boundary), value, re.M):
|
||||
raise ValueError("boundary found in encoded string")
|
||||
|
||||
return "%s%s\r\n" % (self.encode_hdr(boundary), value)
|
||||
|
||||
def iter_encode(self, boundary, blocksize=4096):
|
||||
"""Yields the encoding of this parameter
|
||||
If self.fileobj is set, then blocks of ``blocksize`` bytes are read and
|
||||
yielded."""
|
||||
total = self.get_size(boundary)
|
||||
current = 0
|
||||
if self.value is not None:
|
||||
block = self.encode(boundary)
|
||||
current += len(block)
|
||||
yield block
|
||||
if self.cb:
|
||||
self.cb(self, current, total)
|
||||
else:
|
||||
block = self.encode_hdr(boundary)
|
||||
current += len(block)
|
||||
yield block
|
||||
if self.cb:
|
||||
self.cb(self, current, total)
|
||||
last_block = ""
|
||||
encoded_boundary = "--%s" % encode_and_quote(boundary)
|
||||
boundary_exp = re.compile("^%s$" % re.escape(encoded_boundary),
|
||||
re.M)
|
||||
while True:
|
||||
block = self.fileobj.read(blocksize)
|
||||
if not block:
|
||||
current += 2
|
||||
yield "\r\n"
|
||||
if self.cb:
|
||||
self.cb(self, current, total)
|
||||
break
|
||||
last_block += block
|
||||
if boundary_exp.search(last_block):
|
||||
raise ValueError("boundary found in file data")
|
||||
last_block = last_block[-len(encoded_boundary)-2:]
|
||||
current += len(block)
|
||||
yield block
|
||||
if self.cb:
|
||||
self.cb(self, current, total)
|
||||
|
||||
def get_size(self, boundary):
|
||||
"""Returns the size in bytes that this param will be when encoded
|
||||
with the given boundary."""
|
||||
if self.filesize is not None:
|
||||
valuesize = self.filesize
|
||||
else:
|
||||
valuesize = len(self.value)
|
||||
|
||||
return len(self.encode_hdr(boundary)) + 2 + valuesize
|
||||
|
||||
def encode_string(boundary, name, value):
|
||||
"""Returns ``name`` and ``value`` encoded as a multipart/form-data
|
||||
variable. ``boundary`` is the boundary string used throughout
|
||||
a single request to separate variables."""
|
||||
|
||||
return MultipartParam(name, value).encode(boundary)
|
||||
|
||||
def encode_file_header(boundary, paramname, filesize, filename=None,
|
||||
filetype=None):
|
||||
"""Returns the leading data for a multipart/form-data field that contains
|
||||
file data.
|
||||
|
||||
``boundary`` is the boundary string used throughout a single request to
|
||||
separate variables.
|
||||
|
||||
``paramname`` is the name of the variable in this request.
|
||||
|
||||
``filesize`` is the size of the file data.
|
||||
|
||||
``filename`` if specified is the filename to give to this field. This
|
||||
field is only useful to the server for determining the original filename.
|
||||
|
||||
``filetype`` if specified is the MIME type of this file.
|
||||
|
||||
The actual file data should be sent after this header has been sent.
|
||||
"""
|
||||
|
||||
return MultipartParam(paramname, filesize=filesize, filename=filename,
|
||||
filetype=filetype).encode_hdr(boundary)
|
||||
|
||||
def get_body_size(params, boundary):
|
||||
"""Returns the number of bytes that the multipart/form-data encoding
|
||||
of ``params`` will be."""
|
||||
size = sum(p.get_size(boundary) for p in MultipartParam.from_params(params))
|
||||
return size + len(boundary) + 6
|
||||
|
||||
def get_headers(params, boundary):
|
||||
"""Returns a dictionary with Content-Type and Content-Length headers
|
||||
for the multipart/form-data encoding of ``params``."""
|
||||
headers = {}
|
||||
boundary = urllib.quote_plus(boundary)
|
||||
headers['Content-Type'] = "multipart/form-data; boundary=%s" % boundary
|
||||
headers['Content-Length'] = str(get_body_size(params, boundary))
|
||||
return headers
|
||||
|
||||
class multipart_yielder:
|
||||
def __init__(self, params, boundary, cb):
|
||||
self.params = params
|
||||
self.boundary = boundary
|
||||
self.cb = cb
|
||||
|
||||
self.i = 0
|
||||
self.p = None
|
||||
self.param_iter = None
|
||||
self.current = 0
|
||||
self.total = get_body_size(params, boundary)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
"""generator function to yield multipart/form-data representation
|
||||
of parameters"""
|
||||
if self.param_iter is not None:
|
||||
try:
|
||||
block = self.param_iter.next()
|
||||
self.current += len(block)
|
||||
if self.cb:
|
||||
self.cb(self.p, self.current, self.total)
|
||||
return block
|
||||
except StopIteration:
|
||||
self.p = None
|
||||
self.param_iter = None
|
||||
|
||||
if self.i is None:
|
||||
raise StopIteration
|
||||
elif self.i >= len(self.params):
|
||||
self.param_iter = None
|
||||
self.p = None
|
||||
self.i = None
|
||||
block = "--%s--\r\n" % self.boundary
|
||||
self.current += len(block)
|
||||
if self.cb:
|
||||
self.cb(self.p, self.current, self.total)
|
||||
return block
|
||||
|
||||
self.p = self.params[self.i]
|
||||
self.param_iter = self.p.iter_encode(self.boundary)
|
||||
self.i += 1
|
||||
return self.next()
|
||||
|
||||
def reset(self):
|
||||
self.i = 0
|
||||
self.current = 0
|
||||
for param in self.params:
|
||||
param.reset()
|
||||
|
||||
def multipart_encode(params, boundary=None, cb=None):
|
||||
"""Encode ``params`` as multipart/form-data.
|
||||
|
||||
``params`` should be a sequence of (name, value) pairs or MultipartParam
|
||||
objects, or a mapping of names to values.
|
||||
Values are either strings parameter values, or file-like objects to use as
|
||||
the parameter value. The file-like objects must support .read() and either
|
||||
.fileno() or both .seek() and .tell().
|
||||
|
||||
If ``boundary`` is set, then it as used as the MIME boundary. Otherwise
|
||||
a randomly generated boundary will be used. In either case, if the
|
||||
boundary string appears in the parameter values a ValueError will be
|
||||
raised.
|
||||
|
||||
If ``cb`` is set, it should be a callback which will get called as blocks
|
||||
of data are encoded. It will be called with (param, current, total),
|
||||
indicating the current parameter being encoded, the current amount encoded,
|
||||
and the total amount to encode.
|
||||
|
||||
Returns a tuple of `datagen`, `headers`, where `datagen` is a
|
||||
generator that will yield blocks of data that make up the encoded
|
||||
parameters, and `headers` is a dictionary with the assoicated
|
||||
Content-Type and Content-Length headers.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> datagen, headers = multipart_encode( [("key", "value1"), ("key", "value2")] )
|
||||
>>> s = "".join(datagen)
|
||||
>>> assert "value2" in s and "value1" in s
|
||||
|
||||
>>> p = MultipartParam("key", "value2")
|
||||
>>> datagen, headers = multipart_encode( [("key", "value1"), p] )
|
||||
>>> s = "".join(datagen)
|
||||
>>> assert "value2" in s and "value1" in s
|
||||
|
||||
>>> datagen, headers = multipart_encode( {"key": "value1"} )
|
||||
>>> s = "".join(datagen)
|
||||
>>> assert "value2" not in s and "value1" in s
|
||||
|
||||
"""
|
||||
if boundary is None:
|
||||
boundary = gen_boundary()
|
||||
else:
|
||||
boundary = urllib.quote_plus(boundary)
|
||||
|
||||
headers = get_headers(params, boundary)
|
||||
params = MultipartParam.from_params(params)
|
||||
|
||||
return multipart_yielder(params, boundary, cb), headers
|
||||
@@ -1,199 +0,0 @@
|
||||
"""Streaming HTTP uploads module.
|
||||
|
||||
This module extends the standard httplib and urllib2 objects so that
|
||||
iterable objects can be used in the body of HTTP requests.
|
||||
|
||||
In most cases all one should have to do is call :func:`register_openers()`
|
||||
to register the new streaming http handlers which will take priority over
|
||||
the default handlers, and then you can use iterable objects in the body
|
||||
of HTTP requests.
|
||||
|
||||
**N.B.** You must specify a Content-Length header if using an iterable object
|
||||
since there is no way to determine in advance the total size that will be
|
||||
yielded, and there is no way to reset an interator.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> from StringIO import StringIO
|
||||
>>> import urllib2, poster.streaminghttp
|
||||
|
||||
>>> opener = poster.streaminghttp.register_openers()
|
||||
|
||||
>>> s = "Test file data"
|
||||
>>> f = StringIO(s)
|
||||
|
||||
>>> req = urllib2.Request("http://localhost:5000", f,
|
||||
... {'Content-Length': str(len(s))})
|
||||
"""
|
||||
|
||||
import httplib, urllib2, socket
|
||||
from httplib import NotConnected
|
||||
|
||||
__all__ = ['StreamingHTTPConnection', 'StreamingHTTPRedirectHandler',
|
||||
'StreamingHTTPHandler', 'register_openers']
|
||||
|
||||
if hasattr(httplib, 'HTTPS'):
|
||||
__all__.extend(['StreamingHTTPSHandler', 'StreamingHTTPSConnection'])
|
||||
|
||||
class _StreamingHTTPMixin:
|
||||
"""Mixin class for HTTP and HTTPS connections that implements a streaming
|
||||
send method."""
|
||||
def send(self, value):
|
||||
"""Send ``value`` to the server.
|
||||
|
||||
``value`` can be a string object, a file-like object that supports
|
||||
a .read() method, or an iterable object that supports a .next()
|
||||
method.
|
||||
"""
|
||||
# Based on python 2.6's httplib.HTTPConnection.send()
|
||||
if self.sock is None:
|
||||
if self.auto_open:
|
||||
self.connect()
|
||||
else:
|
||||
raise NotConnected()
|
||||
|
||||
# send the data to the server. if we get a broken pipe, then close
|
||||
# the socket. we want to reconnect when somebody tries to send again.
|
||||
#
|
||||
# NOTE: we DO propagate the error, though, because we cannot simply
|
||||
# ignore the error... the caller will know if they can retry.
|
||||
if self.debuglevel > 0:
|
||||
print "send:", repr(value)
|
||||
try:
|
||||
blocksize = 8192
|
||||
if hasattr(value, 'read') :
|
||||
if hasattr(value, 'seek'):
|
||||
value.seek(0)
|
||||
if self.debuglevel > 0:
|
||||
print "sendIng a read()able"
|
||||
data = value.read(blocksize)
|
||||
while data:
|
||||
self.sock.sendall(data)
|
||||
data = value.read(blocksize)
|
||||
elif hasattr(value, 'next'):
|
||||
if hasattr(value, 'reset'):
|
||||
value.reset()
|
||||
if self.debuglevel > 0:
|
||||
print "sendIng an iterable"
|
||||
for data in value:
|
||||
self.sock.sendall(data)
|
||||
else:
|
||||
self.sock.sendall(value)
|
||||
except socket.error, v:
|
||||
if v[0] == 32: # Broken pipe
|
||||
self.close()
|
||||
raise
|
||||
|
||||
class StreamingHTTPConnection(_StreamingHTTPMixin, httplib.HTTPConnection):
|
||||
"""Subclass of `httplib.HTTPConnection` that overrides the `send()` method
|
||||
to support iterable body objects"""
|
||||
|
||||
class StreamingHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
"""Subclass of `urllib2.HTTPRedirectHandler` that overrides the
|
||||
`redirect_request` method to properly handle redirected POST requests
|
||||
|
||||
This class is required because python 2.5's HTTPRedirectHandler does
|
||||
not remove the Content-Type or Content-Length headers when requesting
|
||||
the new resource, but the body of the original request is not preserved.
|
||||
"""
|
||||
|
||||
handler_order = urllib2.HTTPRedirectHandler.handler_order - 1
|
||||
|
||||
# From python2.6 urllib2's HTTPRedirectHandler
|
||||
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||
"""Return a Request or None in response to a redirect.
|
||||
|
||||
This is called by the http_error_30x methods when a
|
||||
redirection response is received. If a redirection should
|
||||
take place, return a new Request to allow http_error_30x to
|
||||
perform the redirect. Otherwise, raise HTTPError if no-one
|
||||
else should try to handle this url. Return None if you can't
|
||||
but another Handler might.
|
||||
"""
|
||||
m = req.get_method()
|
||||
if (code in (301, 302, 303, 307) and m in ("GET", "HEAD")
|
||||
or code in (301, 302, 303) and m == "POST"):
|
||||
# Strictly (according to RFC 2616), 301 or 302 in response
|
||||
# to a POST MUST NOT cause a redirection without confirmation
|
||||
# from the user (of urllib2, in this case). In practice,
|
||||
# essentially all clients do redirect in this case, so we
|
||||
# do the same.
|
||||
# be conciliant with URIs containing a space
|
||||
newurl = newurl.replace(' ', '%20')
|
||||
newheaders = dict((k, v) for k, v in req.headers.items()
|
||||
if k.lower() not in (
|
||||
"content-length", "content-type")
|
||||
)
|
||||
return urllib2.Request(newurl,
|
||||
headers=newheaders,
|
||||
origin_req_host=req.get_origin_req_host(),
|
||||
unverifiable=True)
|
||||
else:
|
||||
raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
|
||||
|
||||
class StreamingHTTPHandler(urllib2.HTTPHandler):
|
||||
"""Subclass of `urllib2.HTTPHandler` that uses
|
||||
StreamingHTTPConnection as its http connection class."""
|
||||
|
||||
handler_order = urllib2.HTTPHandler.handler_order - 1
|
||||
|
||||
def http_open(self, req):
|
||||
"""Open a StreamingHTTPConnection for the given request"""
|
||||
return self.do_open(StreamingHTTPConnection, req)
|
||||
|
||||
def http_request(self, req):
|
||||
"""Handle a HTTP request. Make sure that Content-Length is specified
|
||||
if we're using an interable value"""
|
||||
# Make sure that if we're using an iterable object as the request
|
||||
# body, that we've also specified Content-Length
|
||||
if req.has_data():
|
||||
data = req.get_data()
|
||||
if hasattr(data, 'read') or hasattr(data, 'next'):
|
||||
if not req.has_header('Content-length'):
|
||||
raise ValueError(
|
||||
"No Content-Length specified for iterable body")
|
||||
return urllib2.HTTPHandler.do_request_(self, req)
|
||||
|
||||
if hasattr(httplib, 'HTTPS'):
|
||||
class StreamingHTTPSConnection(_StreamingHTTPMixin,
|
||||
httplib.HTTPSConnection):
|
||||
"""Subclass of `httplib.HTTSConnection` that overrides the `send()`
|
||||
method to support iterable body objects"""
|
||||
|
||||
class StreamingHTTPSHandler(urllib2.HTTPSHandler):
|
||||
"""Subclass of `urllib2.HTTPSHandler` that uses
|
||||
StreamingHTTPSConnection as its http connection class."""
|
||||
|
||||
handler_order = urllib2.HTTPSHandler.handler_order - 1
|
||||
|
||||
def https_open(self, req):
|
||||
return self.do_open(StreamingHTTPSConnection, req)
|
||||
|
||||
def https_request(self, req):
|
||||
# Make sure that if we're using an iterable object as the request
|
||||
# body, that we've also specified Content-Length
|
||||
if req.has_data():
|
||||
data = req.get_data()
|
||||
if hasattr(data, 'read') or hasattr(data, 'next'):
|
||||
if not req.has_header('Content-length'):
|
||||
raise ValueError(
|
||||
"No Content-Length specified for iterable body")
|
||||
return urllib2.HTTPSHandler.do_request_(self, req)
|
||||
|
||||
|
||||
def get_handlers():
|
||||
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
|
||||
if hasattr(httplib, "HTTPS"):
|
||||
handlers.append(StreamingHTTPSHandler)
|
||||
return handlers
|
||||
|
||||
def register_openers():
|
||||
"""Register the streaming http handlers in the global urllib2 default
|
||||
opener object.
|
||||
|
||||
Returns the created OpenerDirector object."""
|
||||
opener = urllib2.build_opener(*get_handlers())
|
||||
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
return opener
|
||||
42
requests/packages/urllib3/__init__.py
Normal file
42
requests/packages/urllib3/__init__.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# urllib3/__init__.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
urllib3 - Thread-safe connection pooling and re-using.
|
||||
"""
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = 'dev'
|
||||
|
||||
|
||||
from .connectionpool import (
|
||||
HTTPConnectionPool,
|
||||
HTTPSConnectionPool,
|
||||
connection_from_url
|
||||
)
|
||||
|
||||
from . import exceptions
|
||||
from .filepost import encode_multipart_formdata
|
||||
from .poolmanager import PoolManager, ProxyManager, proxy_from_url
|
||||
from .response import HTTPResponse
|
||||
from .util import make_headers, get_host
|
||||
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
import logging
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
# ... Clean up.
|
||||
del logging
|
||||
del NullHandler
|
||||
131
requests/packages/urllib3/_collections.py
Normal file
131
requests/packages/urllib3/_collections.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# urllib3/_collections.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from collections import deque
|
||||
|
||||
from threading import RLock
|
||||
|
||||
__all__ = ['RecentlyUsedContainer']
|
||||
|
||||
|
||||
class AccessEntry(object):
|
||||
__slots__ = ('key', 'is_valid')
|
||||
|
||||
def __init__(self, key, is_valid=True):
|
||||
self.key = key
|
||||
self.is_valid = is_valid
|
||||
|
||||
|
||||
class RecentlyUsedContainer(dict):
|
||||
"""
|
||||
Provides a dict-like that maintains up to ``maxsize`` keys while throwing
|
||||
away the least-recently-used keys beyond ``maxsize``.
|
||||
"""
|
||||
|
||||
# If len(self.access_log) exceeds self._maxsize * CLEANUP_FACTOR, then we
|
||||
# will attempt to cleanup the invalidated entries in the access_log
|
||||
# datastructure during the next 'get' operation.
|
||||
CLEANUP_FACTOR = 10
|
||||
|
||||
def __init__(self, maxsize=10):
|
||||
self._maxsize = maxsize
|
||||
|
||||
self._container = {}
|
||||
|
||||
# We use a deque to to store our keys ordered by the last access.
|
||||
self.access_log = deque()
|
||||
self.access_log_lock = RLock()
|
||||
|
||||
# We look up the access log entry by the key to invalidate it so we can
|
||||
# insert a new authorative entry at the head without having to dig and
|
||||
# find the old entry for removal immediately.
|
||||
self.access_lookup = {}
|
||||
|
||||
# Trigger a heap cleanup when we get past this size
|
||||
self.access_log_limit = maxsize * self.CLEANUP_FACTOR
|
||||
|
||||
def _invalidate_entry(self, key):
|
||||
"If exists: Invalidate old entry and return it."
|
||||
old_entry = self.access_lookup.get(key)
|
||||
if old_entry:
|
||||
old_entry.is_valid = False
|
||||
|
||||
return old_entry
|
||||
|
||||
def _push_entry(self, key):
|
||||
"Push entry onto our access log, invalidate the old entry if exists."
|
||||
self._invalidate_entry(key)
|
||||
|
||||
new_entry = AccessEntry(key)
|
||||
self.access_lookup[key] = new_entry
|
||||
|
||||
self.access_log_lock.acquire()
|
||||
self.access_log.appendleft(new_entry)
|
||||
self.access_log_lock.release()
|
||||
|
||||
def _prune_entries(self, num):
|
||||
"Pop entries from our access log until we popped ``num`` valid ones."
|
||||
while num > 0:
|
||||
self.access_log_lock.acquire()
|
||||
p = self.access_log.pop()
|
||||
self.access_log_lock.release()
|
||||
|
||||
if not p.is_valid:
|
||||
continue # Invalidated entry, skip
|
||||
|
||||
dict.pop(self, p.key, None)
|
||||
self.access_lookup.pop(p.key, None)
|
||||
num -= 1
|
||||
|
||||
def _prune_invalidated_entries(self):
|
||||
"Rebuild our access_log without the invalidated entries."
|
||||
self.access_log_lock.acquire()
|
||||
self.access_log = deque(e for e in self.access_log if e.is_valid)
|
||||
self.access_log_lock.release()
|
||||
|
||||
def _get_ordered_access_keys(self):
|
||||
"Return ordered access keys for inspection. Used for testing."
|
||||
self.access_log_lock.acquire()
|
||||
r = [e.key for e in self.access_log if e.is_valid]
|
||||
self.access_log_lock.release()
|
||||
|
||||
return r
|
||||
|
||||
def __getitem__(self, key):
|
||||
item = dict.get(self, key)
|
||||
|
||||
if not item:
|
||||
raise KeyError(key)
|
||||
|
||||
# Insert new entry with new high priority, also implicitly invalidates
|
||||
# the old entry.
|
||||
self._push_entry(key)
|
||||
|
||||
if len(self.access_log) > self.access_log_limit:
|
||||
# Heap is getting too big, try to clean up any tailing invalidated
|
||||
# entries.
|
||||
self._prune_invalidated_entries()
|
||||
|
||||
return item
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
# Add item to our container and access log
|
||||
dict.__setitem__(self, key, item)
|
||||
self._push_entry(key)
|
||||
|
||||
# Discard invalid and excess entries
|
||||
self._prune_entries(len(self) - self._maxsize)
|
||||
|
||||
def __delitem__(self, key):
|
||||
self._invalidate_entry(key)
|
||||
self.access_lookup.pop(key, None)
|
||||
dict.__delitem__(self, key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
522
requests/packages/urllib3/connectionpool.py
Normal file
522
requests/packages/urllib3/connectionpool.py
Normal file
@@ -0,0 +1,522 @@
|
||||
# urllib3/connectionpool.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from socket import error as SocketError, timeout as SocketTimeout
|
||||
|
||||
try: # Python 3
|
||||
from http.client import HTTPConnection, HTTPException
|
||||
from http.client import HTTP_PORT, HTTPS_PORT
|
||||
except ImportError:
|
||||
from httplib import HTTPConnection, HTTPException
|
||||
from httplib import HTTP_PORT, HTTPS_PORT
|
||||
|
||||
try: # Python 3
|
||||
from queue import LifoQueue, Empty, Full
|
||||
except ImportError:
|
||||
from Queue import LifoQueue, Empty, Full
|
||||
|
||||
|
||||
try: # Compiled with SSL?
|
||||
HTTPSConnection = object
|
||||
BaseSSLError = None
|
||||
ssl = None
|
||||
|
||||
try: # Python 3
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
from httplib import HTTPSConnection
|
||||
|
||||
import ssl
|
||||
BaseSSLError = ssl.SSLError
|
||||
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
from .request import RequestMethods
|
||||
from .response import HTTPResponse
|
||||
from .util import get_host, is_connection_dropped
|
||||
from .exceptions import (
|
||||
EmptyPoolError,
|
||||
HostChangedError,
|
||||
MaxRetryError,
|
||||
SSLError,
|
||||
TimeoutError,
|
||||
)
|
||||
|
||||
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||
from .packages import six
|
||||
|
||||
|
||||
xrange = six.moves.xrange
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_Default = object()
|
||||
|
||||
port_by_scheme = {
|
||||
'http': HTTP_PORT,
|
||||
'https': HTTPS_PORT,
|
||||
}
|
||||
|
||||
|
||||
## Connection objects (extension of httplib)
|
||||
|
||||
class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
"""
|
||||
Based on httplib.HTTPSConnection but wraps the socket with
|
||||
SSL certification.
|
||||
"""
|
||||
cert_reqs = None
|
||||
ca_certs = None
|
||||
|
||||
def set_cert(self, key_file=None, cert_file=None,
|
||||
cert_reqs='CERT_NONE', ca_certs=None):
|
||||
ssl_req_scheme = {
|
||||
'CERT_NONE': ssl.CERT_NONE,
|
||||
'CERT_OPTIONAL': ssl.CERT_OPTIONAL,
|
||||
'CERT_REQUIRED': ssl.CERT_REQUIRED
|
||||
}
|
||||
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.cert_reqs = ssl_req_scheme.get(cert_reqs) or ssl.CERT_NONE
|
||||
self.ca_certs = ca_certs
|
||||
|
||||
def connect(self):
|
||||
# Add certificate verification
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
|
||||
# Wrap socket using verification with the root certs in
|
||||
# trusted_root_certs
|
||||
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
|
||||
cert_reqs=self.cert_reqs,
|
||||
ca_certs=self.ca_certs)
|
||||
if self.ca_certs:
|
||||
match_hostname(self.sock.getpeercert(), self.host)
|
||||
|
||||
|
||||
## Pool objects
|
||||
|
||||
class ConnectionPool(object):
|
||||
"""
|
||||
Base class for all connection pools, such as
|
||||
:class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.
|
||||
"""
|
||||
|
||||
scheme = None
|
||||
QueueCls = LifoQueue
|
||||
|
||||
def __init__(self, host, port=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def __str__(self):
|
||||
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
||||
self.host, self.port)
|
||||
|
||||
|
||||
class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
"""
|
||||
Thread-safe connection pool for one host.
|
||||
|
||||
:param host:
|
||||
Host used for this HTTP Connection (e.g. "localhost"), passed into
|
||||
:class:`httplib.HTTPConnection`.
|
||||
|
||||
:param port:
|
||||
Port used for this HTTP Connection (None is equivalent to 80), passed
|
||||
into :class:`httplib.HTTPConnection`.
|
||||
|
||||
:param strict:
|
||||
Causes BadStatusLine to be raised if the status line can't be parsed
|
||||
as a valid HTTP/1.0 or 1.1 status line, passed into
|
||||
:class:`httplib.HTTPConnection`.
|
||||
|
||||
:param timeout:
|
||||
Socket timeout for each individual connection, can be a float. None
|
||||
disables timeout.
|
||||
|
||||
: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
|
||||
connections will be created but they will not be saved once they've
|
||||
been used.
|
||||
|
||||
:param block:
|
||||
If set to True, no more than ``maxsize`` connections will be used at
|
||||
a time. When no free connections are available, the call will block
|
||||
until a connection has been released. This is a useful side effect for
|
||||
particular multithreaded situations where one does not want to use more
|
||||
than maxsize connections per host to prevent flooding.
|
||||
|
||||
:param headers:
|
||||
Headers to include with all requests, unless other headers are given
|
||||
explicitly.
|
||||
"""
|
||||
|
||||
scheme = 'http'
|
||||
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, maxsize=1,
|
||||
block=False, headers=None):
|
||||
super(HTTPConnectionPool, self).__init__(host, port)
|
||||
|
||||
self.strict = strict
|
||||
self.timeout = timeout
|
||||
self.pool = self.QueueCls(maxsize)
|
||||
self.block = block
|
||||
self.headers = headers or {}
|
||||
|
||||
# Fill the queue up so that doing get() on it will block properly
|
||||
for _ in xrange(maxsize):
|
||||
self.pool.put(None)
|
||||
|
||||
# These are mostly for testing and debugging purposes.
|
||||
self.num_connections = 0
|
||||
self.num_requests = 0
|
||||
|
||||
def _new_conn(self):
|
||||
"""
|
||||
Return a fresh :class:`httplib.HTTPConnection`.
|
||||
"""
|
||||
self.num_connections += 1
|
||||
log.info("Starting new HTTP connection (%d): %s" %
|
||||
(self.num_connections, self.host))
|
||||
return HTTPConnection(host=self.host, port=self.port)
|
||||
|
||||
def _get_conn(self, timeout=None):
|
||||
"""
|
||||
Get a connection. Will return a pooled connection if one is available.
|
||||
|
||||
If no connections are available and :prop:`.block` is ``False``, then a
|
||||
fresh connection is returned.
|
||||
|
||||
:param timeout:
|
||||
Seconds to wait before giving up and raising
|
||||
:class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and
|
||||
:prop:`.block` is ``True``.
|
||||
"""
|
||||
conn = None
|
||||
try:
|
||||
conn = self.pool.get(block=self.block, timeout=timeout)
|
||||
|
||||
# If this is a persistent connection, check if it got disconnected
|
||||
if conn and is_connection_dropped(conn):
|
||||
log.info("Resetting dropped connection: %s" % self.host)
|
||||
conn.close()
|
||||
|
||||
except Empty:
|
||||
if self.block:
|
||||
raise EmptyPoolError(self,
|
||||
"Pool reached maximum size and no more "
|
||||
"connections are allowed.")
|
||||
pass # Oh well, we'll create a new connection then
|
||||
|
||||
return conn or self._new_conn()
|
||||
|
||||
def _put_conn(self, conn):
|
||||
"""
|
||||
Put a connection back into the pool.
|
||||
|
||||
:param conn:
|
||||
Connection object for the current host and port as returned by
|
||||
:meth:`._new_conn` or :meth:`._get_conn`.
|
||||
|
||||
If the pool is already full, the connection is discarded because we
|
||||
exceeded maxsize. If connections are discarded frequently, then maxsize
|
||||
should be increased.
|
||||
"""
|
||||
try:
|
||||
self.pool.put(conn, block=False)
|
||||
except Full:
|
||||
# This should never happen if self.block == True
|
||||
log.warning("HttpConnectionPool is full, discarding connection: %s"
|
||||
% self.host)
|
||||
|
||||
def _make_request(self, conn, method, url, timeout=_Default,
|
||||
**httplib_request_kw):
|
||||
"""
|
||||
Perform a request on a given httplib connection object taken from our
|
||||
pool.
|
||||
"""
|
||||
self.num_requests += 1
|
||||
|
||||
if timeout is _Default:
|
||||
timeout = self.timeout
|
||||
|
||||
conn.timeout = timeout # This only does anything in Py26+
|
||||
conn.request(method, url, **httplib_request_kw)
|
||||
|
||||
# Set timeout
|
||||
sock = getattr(conn, 'sock', False) # AppEngine doesn't have sock attr.
|
||||
if sock:
|
||||
sock.settimeout(timeout)
|
||||
|
||||
httplib_response = conn.getresponse()
|
||||
|
||||
# AppEngine doesn't have a version attr.
|
||||
http_version = getattr(conn, '_http_vsn_str', 'HTTP/?'),
|
||||
log.debug("\"%s %s %s\" %s %s" % (method, url, http_version,
|
||||
httplib_response.status,
|
||||
httplib_response.length))
|
||||
|
||||
return httplib_response
|
||||
|
||||
|
||||
def is_same_host(self, url):
|
||||
"""
|
||||
Check if the given ``url`` is a member of the same host as this
|
||||
connection pool.
|
||||
"""
|
||||
# TODO: Add optional support for socket.gethostbyname checking.
|
||||
scheme, host, port = get_host(url)
|
||||
|
||||
if self.port and not port:
|
||||
# Use explicit default port for comparison when none is given.
|
||||
port = port_by_scheme.get(scheme)
|
||||
|
||||
return (url.startswith('/') or
|
||||
(scheme, host, port) == (self.scheme, self.host, self.port))
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None, retries=3,
|
||||
redirect=True, assert_same_host=True, timeout=_Default,
|
||||
pool_timeout=None, release_conn=None, **response_kw):
|
||||
"""
|
||||
Get a connection from the pool and perform an HTTP request. This is the
|
||||
lowest level call for making a request, so you'll need to specify all
|
||||
the raw details.
|
||||
|
||||
.. note::
|
||||
|
||||
More commonly, it's appropriate to use a convenience method provided
|
||||
by :class:`.RequestMethods`, such as :meth:`request`.
|
||||
|
||||
.. note::
|
||||
|
||||
`release_conn` will only behave as expected if
|
||||
`preload_content=False` because we want to make
|
||||
`preload_content=False` the default behaviour someday soon without
|
||||
breaking backwards compatibility.
|
||||
|
||||
:param method:
|
||||
HTTP request method (such as GET, POST, PUT, etc.)
|
||||
|
||||
:param body:
|
||||
Data to send in the request body (useful for creating
|
||||
POST requests, see HTTPConnectionPool.post_url for
|
||||
more convenience).
|
||||
|
||||
:param headers:
|
||||
Dictionary of custom headers to send, such as User-Agent,
|
||||
If-None-Match, etc. If None, pool headers are used. If provided,
|
||||
these headers completely replace any pool-specific headers.
|
||||
|
||||
:param retries:
|
||||
Number of retries to allow before raising a MaxRetryError exception.
|
||||
|
||||
:param redirect:
|
||||
Automatically handle redirects (status codes 301, 302, 303, 307),
|
||||
each redirect counts as a retry.
|
||||
|
||||
:param assert_same_host:
|
||||
If ``True``, will make sure that the host of the pool requests is
|
||||
consistent else will raise HostChangedError. When False, you can
|
||||
use the pool on an HTTP proxy and request foreign hosts.
|
||||
|
||||
:param timeout:
|
||||
If specified, overrides the default timeout for this one request.
|
||||
|
||||
:param pool_timeout:
|
||||
If set and the pool is set to block=True, then this method will
|
||||
block for ``pool_timeout`` seconds and raise EmptyPoolError if no
|
||||
connection is available within the time period.
|
||||
|
||||
:param release_conn:
|
||||
If False, then the urlopen call will not release the connection
|
||||
back into the pool once a response is received (but will release if
|
||||
you read the entire contents of the response such as when
|
||||
`preload_content=True`). This is useful if you're not preloading
|
||||
the response's content immediately. You will need to call
|
||||
``r.release_conn()`` on the response ``r`` to return the connection
|
||||
back into the pool. If None, it takes the value of
|
||||
``response_kw.get('preload_content', True)``.
|
||||
|
||||
:param \**response_kw:
|
||||
Additional parameters are passed to
|
||||
:meth:`urllib3.response.HTTPResponse.from_httplib`
|
||||
"""
|
||||
if headers is None:
|
||||
headers = self.headers
|
||||
|
||||
if retries < 0:
|
||||
raise MaxRetryError(self, url)
|
||||
|
||||
if timeout is _Default:
|
||||
timeout = self.timeout
|
||||
|
||||
if release_conn is None:
|
||||
release_conn = response_kw.get('preload_content', True)
|
||||
|
||||
# Check host
|
||||
if assert_same_host and not self.is_same_host(url):
|
||||
host = "%s://%s" % (self.scheme, self.host)
|
||||
if self.port:
|
||||
host = "%s:%d" % (host, self.port)
|
||||
|
||||
raise HostChangedError(self, url, retries - 1)
|
||||
|
||||
conn = None
|
||||
|
||||
try:
|
||||
# Request a connection from the queue
|
||||
# (Could raise SocketError: Bad file descriptor)
|
||||
conn = self._get_conn(timeout=pool_timeout)
|
||||
|
||||
# Make the request on the httplib connection object
|
||||
httplib_response = self._make_request(conn, method, url,
|
||||
timeout=timeout,
|
||||
body=body, headers=headers)
|
||||
|
||||
# If we're going to release the connection in ``finally:``, then
|
||||
# the request doesn't need to know about the connection. Otherwise
|
||||
# it will also try to release it and we'll have a double-release
|
||||
# mess.
|
||||
response_conn = not release_conn and conn
|
||||
|
||||
# Import httplib's response into our own wrapper object
|
||||
response = HTTPResponse.from_httplib(httplib_response,
|
||||
pool=self,
|
||||
connection=response_conn,
|
||||
**response_kw)
|
||||
|
||||
# else:
|
||||
# The connection will be put back into the pool when
|
||||
# ``response.release_conn()`` is called (implicitly by
|
||||
# ``response.read()``)
|
||||
|
||||
except Empty as e:
|
||||
# Timed out by queue
|
||||
raise TimeoutError(self, "Request timed out. (pool_timeout=%s)" %
|
||||
pool_timeout)
|
||||
|
||||
except SocketTimeout as e:
|
||||
# Timed out by socket
|
||||
raise TimeoutError(self, "Request timed out. (timeout=%s)" %
|
||||
timeout)
|
||||
|
||||
except BaseSSLError as e:
|
||||
# SSL certificate error
|
||||
raise SSLError(e)
|
||||
|
||||
except CertificateError as e:
|
||||
# Name mismatch
|
||||
raise SSLError(e)
|
||||
|
||||
except (HTTPException, SocketError) as e:
|
||||
# Connection broken, discard. It will be replaced next _get_conn().
|
||||
conn = None
|
||||
# This is necessary so we can access e below
|
||||
err = e
|
||||
|
||||
finally:
|
||||
if conn and release_conn:
|
||||
# Put the connection back to be reused
|
||||
self._put_conn(conn)
|
||||
|
||||
if not conn:
|
||||
log.warn("Retrying (%d attempts remain) after connection "
|
||||
"broken by '%r': %s" % (retries, err, url))
|
||||
return self.urlopen(method, url, body, headers, retries - 1,
|
||||
redirect, assert_same_host) # Try again
|
||||
|
||||
# Handle redirect?
|
||||
redirect_location = redirect and response.get_redirect_location()
|
||||
if redirect_location:
|
||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
||||
return self.urlopen(method, redirect_location, body, headers,
|
||||
retries - 1, redirect, assert_same_host)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
"""
|
||||
Same as :class:`.HTTPConnectionPool`, but HTTPS.
|
||||
|
||||
When Python is compiled with the :mod:`ssl` module, then
|
||||
:class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates,
|
||||
instead of :class:httplib.HTTPSConnection`.
|
||||
|
||||
The ``key_file``, ``cert_file``, ``cert_reqs``, and ``ca_certs`` parameters
|
||||
are only used if :mod:`ssl` is available and are fed into
|
||||
:meth:`ssl.wrap_socket` to upgrade the connection socket into an SSL socket.
|
||||
"""
|
||||
|
||||
scheme = 'https'
|
||||
|
||||
def __init__(self, host, port=None,
|
||||
strict=False, timeout=None, maxsize=1,
|
||||
block=False, headers=None,
|
||||
key_file=None, cert_file=None,
|
||||
cert_reqs='CERT_NONE', ca_certs=None):
|
||||
|
||||
super(HTTPSConnectionPool, self).__init__(host, port,
|
||||
strict, timeout, maxsize,
|
||||
block, headers)
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.cert_reqs = cert_reqs
|
||||
self.ca_certs = ca_certs
|
||||
|
||||
def _new_conn(self):
|
||||
"""
|
||||
Return a fresh :class:`httplib.HTTPSConnection`.
|
||||
"""
|
||||
self.num_connections += 1
|
||||
log.info("Starting new HTTPS connection (%d): %s"
|
||||
% (self.num_connections, self.host))
|
||||
|
||||
if not ssl: # Platform-specific: Python compiled without +ssl
|
||||
if not HTTPSConnection or HTTPSConnection is object:
|
||||
raise SSLError("Can't connect to HTTPS URL because the SSL "
|
||||
"module is not available.")
|
||||
|
||||
return HTTPSConnection(host=self.host, port=self.port)
|
||||
|
||||
connection = VerifiedHTTPSConnection(host=self.host, port=self.port)
|
||||
connection.set_cert(key_file=self.key_file, cert_file=self.cert_file,
|
||||
cert_reqs=self.cert_reqs, ca_certs=self.ca_certs)
|
||||
return connection
|
||||
|
||||
|
||||
def connection_from_url(url, **kw):
|
||||
"""
|
||||
Given a url, return an :class:`.ConnectionPool` instance of its host.
|
||||
|
||||
This is a shortcut for not having to parse out the scheme, host, and port
|
||||
of the url before creating an :class:`.ConnectionPool` instance.
|
||||
|
||||
:param url:
|
||||
Absolute URL string that must include the scheme. Port is optional.
|
||||
|
||||
:param \**kw:
|
||||
Passes additional parameters to the constructor of the appropriate
|
||||
:class:`.ConnectionPool`. Useful for specifying things like
|
||||
timeout, maxsize, headers, etc.
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> conn = connection_from_url('http://google.com/')
|
||||
>>> r = conn.request('GET', '/')
|
||||
"""
|
||||
scheme, host, port = get_host(url)
|
||||
if scheme == 'https':
|
||||
return HTTPSConnectionPool(host, port=port, **kw)
|
||||
else:
|
||||
return HTTPConnectionPool(host, port=port, **kw)
|
||||
0
requests/packages/urllib3/contrib/__init__.py
Normal file
0
requests/packages/urllib3/contrib/__init__.py
Normal file
120
requests/packages/urllib3/contrib/ntlmpool.py
Normal file
120
requests/packages/urllib3/contrib/ntlmpool.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# urllib3/contrib/ntlmpool.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
NTLM authenticating pool, contributed by erikcederstran
|
||||
|
||||
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
|
||||
"""
|
||||
|
||||
try:
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
from httplib import HTTPSConnection
|
||||
from logging import getLogger
|
||||
from ntlm import ntlm
|
||||
|
||||
from urllib3 import HTTPSConnectionPool
|
||||
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
|
||||
class NTLMConnectionPool(HTTPSConnectionPool):
|
||||
"""
|
||||
Implements an NTLM authentication version of an urllib3 connection pool
|
||||
"""
|
||||
|
||||
scheme = 'https'
|
||||
|
||||
def __init__(self, user, pw, authurl, *args, **kwargs):
|
||||
"""
|
||||
authurl is a random URL on the server that is protected by NTLM.
|
||||
user is the Windows user, probably in the DOMAIN\username format.
|
||||
pw is the password for the user.
|
||||
"""
|
||||
super(NTLMConnectionPool, self).__init__(*args, **kwargs)
|
||||
self.authurl = authurl
|
||||
self.rawuser = user
|
||||
user_parts = user.split('\\', 1)
|
||||
self.domain = user_parts[0].upper()
|
||||
self.user = user_parts[1]
|
||||
self.pw = pw
|
||||
|
||||
def _new_conn(self):
|
||||
# Performs the NTLM handshake that secures the connection. The socket
|
||||
# must be kept open while requests are performed.
|
||||
self.num_connections += 1
|
||||
log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s' %
|
||||
(self.num_connections, self.host, self.authurl))
|
||||
|
||||
headers = {}
|
||||
headers['Connection'] = 'Keep-Alive'
|
||||
req_header = 'Authorization'
|
||||
resp_header = 'www-authenticate'
|
||||
|
||||
conn = HTTPSConnection(host=self.host, port=self.port)
|
||||
|
||||
# Send negotiation message
|
||||
headers[req_header] = (
|
||||
'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser))
|
||||
log.debug('Request headers: %s' % headers)
|
||||
conn.request('GET', self.authurl, None, headers)
|
||||
res = conn.getresponse()
|
||||
reshdr = dict(res.getheaders())
|
||||
log.debug('Response status: %s %s' % (res.status, res.reason))
|
||||
log.debug('Response headers: %s' % reshdr)
|
||||
log.debug('Response data: %s [...]' % res.read(100))
|
||||
|
||||
# Remove the reference to the socket, so that it can not be closed by
|
||||
# the response object (we want to keep the socket open)
|
||||
res.fp = None
|
||||
|
||||
# Server should respond with a challenge message
|
||||
auth_header_values = reshdr[resp_header].split(', ')
|
||||
auth_header_value = None
|
||||
for s in auth_header_values:
|
||||
if s[:5] == 'NTLM ':
|
||||
auth_header_value = s[5:]
|
||||
if auth_header_value is None:
|
||||
raise Exception('Unexpected %s response header: %s' %
|
||||
(resp_header, reshdr[resp_header]))
|
||||
|
||||
# Send authentication message
|
||||
ServerChallenge, NegotiateFlags = \
|
||||
ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value)
|
||||
auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge,
|
||||
self.user,
|
||||
self.domain,
|
||||
self.pw,
|
||||
NegotiateFlags)
|
||||
headers[req_header] = 'NTLM %s' % auth_msg
|
||||
log.debug('Request headers: %s' % headers)
|
||||
conn.request('GET', self.authurl, None, headers)
|
||||
res = conn.getresponse()
|
||||
log.debug('Response status: %s %s' % (res.status, res.reason))
|
||||
log.debug('Response headers: %s' % dict(res.getheaders()))
|
||||
log.debug('Response data: %s [...]' % res.read()[:100])
|
||||
if res.status != 200:
|
||||
if res.status == 401:
|
||||
raise Exception('Server rejected request: wrong '
|
||||
'username or password')
|
||||
raise Exception('Wrong server response: %s %s' %
|
||||
(res.status, res.reason))
|
||||
|
||||
res.fp = None
|
||||
log.debug('Connection established')
|
||||
return conn
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None, retries=3,
|
||||
redirect=True, assert_same_host=True):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
headers['Connection'] = 'Keep-Alive'
|
||||
return super(NTLMConnectionPool, self).urlopen(method, url, body,
|
||||
headers, retries,
|
||||
redirect,
|
||||
assert_same_host)
|
||||
67
requests/packages/urllib3/exceptions.py
Normal file
67
requests/packages/urllib3/exceptions.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# urllib3/exceptions.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
## Base Exceptions
|
||||
|
||||
class HTTPError(Exception):
|
||||
"Base exception used by this module."
|
||||
pass
|
||||
|
||||
|
||||
class PoolError(HTTPError):
|
||||
"Base exception for errors caused within a pool."
|
||||
def __init__(self, pool, message):
|
||||
self.pool = pool
|
||||
HTTPError.__init__(self, "%s: %s" % (pool, message))
|
||||
|
||||
|
||||
class SSLError(HTTPError):
|
||||
"Raised when SSL certificate fails in an HTTPS connection."
|
||||
pass
|
||||
|
||||
|
||||
## Leaf Exceptions
|
||||
|
||||
class MaxRetryError(PoolError):
|
||||
"Raised when the maximum number of retries is exceeded."
|
||||
|
||||
def __init__(self, pool, url):
|
||||
message = "Max retries exceeded with url: %s" % url
|
||||
PoolError.__init__(self, pool, message)
|
||||
|
||||
self.url = url
|
||||
|
||||
|
||||
class HostChangedError(PoolError):
|
||||
"Raised when an existing pool gets a request for a foreign host."
|
||||
|
||||
def __init__(self, pool, url, retries=3):
|
||||
message = "Tried to open a foreign host with url: %s" % url
|
||||
PoolError.__init__(self, pool, message)
|
||||
|
||||
self.url = url
|
||||
self.retries = retries
|
||||
|
||||
|
||||
class TimeoutError(PoolError):
|
||||
"Raised when a socket timeout occurs."
|
||||
pass
|
||||
|
||||
|
||||
class EmptyPoolError(PoolError):
|
||||
"Raised when a pool runs out of connections and no more are allowed."
|
||||
pass
|
||||
|
||||
|
||||
class LocationParseError(ValueError, HTTPError):
|
||||
"Raised when get_host or similar fails to parse the URL input."
|
||||
|
||||
def __init__(self, location):
|
||||
message = "Failed to parse: %s" % location
|
||||
super(LocationParseError, self).__init__(self, message)
|
||||
|
||||
self.location = location
|
||||
88
requests/packages/urllib3/filepost.py
Normal file
88
requests/packages/urllib3/filepost.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# urllib3/filepost.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import codecs
|
||||
import mimetypes
|
||||
|
||||
try:
|
||||
from mimetools import choose_boundary
|
||||
except ImportError:
|
||||
from .packages.mimetools_choose_boundary import choose_boundary
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .packages import six
|
||||
from .packages.six import b
|
||||
|
||||
writer = codecs.lookup('utf-8')[3]
|
||||
|
||||
|
||||
def get_content_type(filename):
|
||||
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
|
||||
|
||||
def iter_fields(fields):
|
||||
"""
|
||||
Iterate over fields.
|
||||
|
||||
Supports list of (k, v) tuples and dicts.
|
||||
"""
|
||||
if isinstance(fields, dict):
|
||||
return ((k, v) for k, v in six.iteritems(fields))
|
||||
|
||||
return ((k, v) for k, v in fields)
|
||||
|
||||
|
||||
def encode_multipart_formdata(fields, boundary=None):
|
||||
"""
|
||||
Encode a dictionary of ``fields`` using the multipart/form-data mime format.
|
||||
|
||||
:param fields:
|
||||
Dictionary of fields or list of (key, value) field tuples. The key is
|
||||
treated as the field name, and the value as the body of the form-data
|
||||
bytes. If the value is a tuple of two elements, then the first element
|
||||
is treated as the filename of the form-data section.
|
||||
|
||||
Field names and filenames must be unicode.
|
||||
|
||||
:param boundary:
|
||||
If not specified, then a random boundary will be generated using
|
||||
:func:`mimetools.choose_boundary`.
|
||||
"""
|
||||
body = BytesIO()
|
||||
if boundary is None:
|
||||
boundary = choose_boundary()
|
||||
|
||||
for fieldname, value in iter_fields(fields):
|
||||
body.write(b('--%s\r\n' % (boundary)))
|
||||
|
||||
if isinstance(value, tuple):
|
||||
filename, data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"; '
|
||||
'filename="%s"\r\n' % (fieldname, filename))
|
||||
body.write(b('Content-Type: %s\r\n\r\n' %
|
||||
(get_content_type(filename))))
|
||||
else:
|
||||
data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
|
||||
% (fieldname))
|
||||
body.write(b'Content-Type: text/plain\r\n\r\n')
|
||||
|
||||
if isinstance(data, int):
|
||||
data = str(data) # Backwards compatibility
|
||||
|
||||
if isinstance(data, six.text_type):
|
||||
writer(body).write(data)
|
||||
else:
|
||||
body.write(data)
|
||||
|
||||
body.write(b'\r\n')
|
||||
|
||||
body.write(b('--%s--\r\n' % (boundary)))
|
||||
|
||||
content_type = b('multipart/form-data; boundary=%s' % boundary)
|
||||
|
||||
return body.getvalue(), content_type
|
||||
4
requests/packages/urllib3/packages/__init__.py
Normal file
4
requests/packages/urllib3/packages/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from . import ssl_match_hostname
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""The function mimetools.choose_boundary() from Python 2.7, which seems to
|
||||
have disappeared in Python 3 (although email.generator._make_boundary() might
|
||||
work as a replacement?).
|
||||
|
||||
Tweaked to use lock from threading rather than thread.
|
||||
"""
|
||||
import os
|
||||
from threading import Lock
|
||||
_counter_lock = Lock()
|
||||
|
||||
_counter = 0
|
||||
def _get_next_counter():
|
||||
global _counter
|
||||
with _counter_lock:
|
||||
_counter += 1
|
||||
return _counter
|
||||
|
||||
_prefix = None
|
||||
|
||||
def choose_boundary():
|
||||
"""Return a string usable as a multipart boundary.
|
||||
|
||||
The string chosen is unique within a single program run, and
|
||||
incorporates the user id (if available), process id (if available),
|
||||
and current time. So it's very unlikely the returned string appears
|
||||
in message text, but there's no guarantee.
|
||||
|
||||
The boundary contains dots so you have to quote it in the header."""
|
||||
|
||||
global _prefix
|
||||
import time
|
||||
if _prefix is None:
|
||||
import socket
|
||||
try:
|
||||
hostid = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
hostid = '127.0.0.1'
|
||||
try:
|
||||
uid = repr(os.getuid())
|
||||
except AttributeError:
|
||||
uid = '1'
|
||||
try:
|
||||
pid = repr(os.getpid())
|
||||
except AttributeError:
|
||||
pid = '1'
|
||||
_prefix = hostid + '.' + uid + '.' + pid
|
||||
return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
|
||||
372
requests/packages/urllib3/packages/six.py
Normal file
372
requests/packages/urllib3/packages/six.py
Normal file
@@ -0,0 +1,372 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
#Copyright (c) 2010-2011 Benjamin Peterson
|
||||
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
#this software and associated documentation files (the "Software"), to deal in
|
||||
#the Software without restriction, including without limitation the rights to
|
||||
#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
#the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
#subject to the following conditions:
|
||||
|
||||
#The above copyright notice and this permission notice shall be included in all
|
||||
#copies or substantial portions of the Software.
|
||||
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.1.0"
|
||||
|
||||
|
||||
# True if we are running on Python 3.
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result)
|
||||
# This is a bit ugly, but it avoids running this again.
|
||||
delattr(tp, self.name)
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
|
||||
class _MovedItems(types.ModuleType):
|
||||
"""Lazy loading of moved objects"""
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
del attr
|
||||
|
||||
moves = sys.modules["six.moves"] = _MovedItems("moves")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
|
||||
_iterkeys = "keys"
|
||||
_itervalues = "values"
|
||||
_iteritems = "items"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
|
||||
_iterkeys = "iterkeys"
|
||||
_itervalues = "itervalues"
|
||||
_iteritems = "iteritems"
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
|
||||
advance_iterator = next
|
||||
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
|
||||
|
||||
def iterkeys(d):
|
||||
"""Return an iterator over the keys of a dictionary."""
|
||||
return getattr(d, _iterkeys)()
|
||||
|
||||
def itervalues(d):
|
||||
"""Return an iterator over the values of a dictionary."""
|
||||
return getattr(d, _itervalues)()
|
||||
|
||||
def iteritems(d):
|
||||
"""Return an iterator over the (key, value) pairs of a dictionary."""
|
||||
return getattr(d, _iteritems)()
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
def u(s):
|
||||
return s
|
||||
if sys.version_info[1] <= 1:
|
||||
def int2byte(i):
|
||||
return bytes((i,))
|
||||
else:
|
||||
# This is about 2x faster than the implementation above on 3.2+
|
||||
int2byte = operator.methodcaller("to_bytes", 1, "big")
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
int2byte = chr
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
if PY3:
|
||||
import builtins
|
||||
exec_ = getattr(builtins, "exec")
|
||||
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(builtins, "print")
|
||||
del builtins
|
||||
|
||||
else:
|
||||
def exec_(code, globs=None, locs=None):
|
||||
"""Execute code in a namespace."""
|
||||
if globs is None:
|
||||
frame = sys._getframe(1)
|
||||
globs = frame.f_globals
|
||||
if locs is None:
|
||||
locs = frame.f_locals
|
||||
del frame
|
||||
elif locs is None:
|
||||
locs = globs
|
||||
exec("""exec code in globs, locs""")
|
||||
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
|
||||
def with_metaclass(meta, base=object):
|
||||
"""Create a base class with a metaclass."""
|
||||
return meta("NewBase", (base,), {})
|
||||
@@ -0,0 +1,61 @@
|
||||
"""The match_hostname() function from Python 3.2, essential when using SSL."""
|
||||
|
||||
import re
|
||||
|
||||
__version__ = '3.2.2'
|
||||
|
||||
class CertificateError(ValueError):
|
||||
pass
|
||||
|
||||
def _dnsname_to_pat(dn):
|
||||
pats = []
|
||||
for frag in dn.split(r'.'):
|
||||
if frag == '*':
|
||||
# When '*' is a fragment by itself, it matches a non-empty dotless
|
||||
# fragment.
|
||||
pats.append('[^.]+')
|
||||
else:
|
||||
# Otherwise, '*' matches any dotless fragment.
|
||||
frag = re.escape(frag)
|
||||
pats.append(frag.replace(r'\*', '[^.]*'))
|
||||
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
|
||||
def match_hostname(cert, hostname):
|
||||
"""Verify that *cert* (in decoded format as returned by
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
|
||||
are mostly followed, but IP addresses are not accepted for *hostname*.
|
||||
|
||||
CertificateError is raised on failure. On success, the function
|
||||
returns nothing.
|
||||
"""
|
||||
if not cert:
|
||||
raise ValueError("empty or no certificate")
|
||||
dnsnames = []
|
||||
san = cert.get('subjectAltName', ())
|
||||
for key, value in san:
|
||||
if key == 'DNS':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if not dnsnames:
|
||||
# The subject is only checked when there is no dNSName entry
|
||||
# in subjectAltName
|
||||
for sub in cert.get('subject', ()):
|
||||
for key, value in sub:
|
||||
# XXX according to RFC 2818, the most specific Common Name
|
||||
# must be used.
|
||||
if key == 'commonName':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match either of %s"
|
||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
||||
elif len(dnsnames) == 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match %r"
|
||||
% (hostname, dnsnames[0]))
|
||||
else:
|
||||
raise CertificateError("no appropriate commonName or "
|
||||
"subjectAltName fields were found")
|
||||
138
requests/packages/urllib3/poolmanager.py
Normal file
138
requests/packages/urllib3/poolmanager.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# urllib3/poolmanager.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import logging
|
||||
|
||||
from ._collections import RecentlyUsedContainer
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
from .connectionpool import get_host, connection_from_url, port_by_scheme
|
||||
from .exceptions import HostChangedError
|
||||
from .request import RequestMethods
|
||||
|
||||
|
||||
__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url']
|
||||
|
||||
|
||||
pool_classes_by_scheme = {
|
||||
'http': HTTPConnectionPool,
|
||||
'https': HTTPSConnectionPool,
|
||||
}
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PoolManager(RequestMethods):
|
||||
"""
|
||||
Allows for arbitrary requests while transparently keeping track of
|
||||
necessary connection pools for you.
|
||||
|
||||
:param num_pools:
|
||||
Number of connection pools to cache before discarding the least recently
|
||||
used pool.
|
||||
|
||||
:param \**connection_pool_kw:
|
||||
Additional parameters are used to create fresh
|
||||
:class:`urllib3.connectionpool.ConnectionPool` instances.
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> manager = PoolManager(num_pools=2)
|
||||
>>> r = manager.urlopen("http://google.com/")
|
||||
>>> r = manager.urlopen("http://google.com/mail")
|
||||
>>> r = manager.urlopen("http://yahoo.com/")
|
||||
>>> len(manager.pools)
|
||||
2
|
||||
|
||||
"""
|
||||
|
||||
# TODO: Make sure there are no memory leaks here.
|
||||
|
||||
def __init__(self, num_pools=10, **connection_pool_kw):
|
||||
self.connection_pool_kw = connection_pool_kw
|
||||
self.pools = RecentlyUsedContainer(num_pools)
|
||||
|
||||
def connection_from_host(self, host, port=80, scheme='http'):
|
||||
"""
|
||||
Get a :class:`ConnectionPool` based on the host, port, and scheme.
|
||||
|
||||
Note that an appropriate ``port`` value is required here to normalize
|
||||
connection pools in our container most effectively.
|
||||
"""
|
||||
pool_key = (scheme, host, port)
|
||||
|
||||
# If the scheme, host, or port doesn't match existing open connections,
|
||||
# open a new ConnectionPool.
|
||||
pool = self.pools.get(pool_key)
|
||||
if pool:
|
||||
return pool
|
||||
|
||||
# Make a fresh ConnectionPool of the desired type
|
||||
pool_cls = pool_classes_by_scheme[scheme]
|
||||
pool = pool_cls(host, port, **self.connection_pool_kw)
|
||||
|
||||
self.pools[pool_key] = pool
|
||||
|
||||
return pool
|
||||
|
||||
def connection_from_url(self, url):
|
||||
"""
|
||||
Similar to :func:`urllib3.connectionpool.connection_from_url` but
|
||||
doesn't pass any additional parameters to the
|
||||
:class:`urllib3.connectionpool.ConnectionPool` constructor.
|
||||
|
||||
Additional parameters are taken from the :class:`.PoolManager`
|
||||
constructor.
|
||||
"""
|
||||
scheme, host, port = get_host(url)
|
||||
|
||||
port = port or port_by_scheme.get(scheme, 80)
|
||||
|
||||
return self.connection_from_host(host, port=port, scheme=scheme)
|
||||
|
||||
def urlopen(self, method, url, **kw):
|
||||
"""
|
||||
Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen`.
|
||||
|
||||
``url`` must be absolute, such that an appropriate
|
||||
:class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
|
||||
"""
|
||||
conn = self.connection_from_url(url)
|
||||
try:
|
||||
return conn.urlopen(method, url, **kw)
|
||||
|
||||
except HostChangedError as e:
|
||||
kw['retries'] = e.retries # Persist retries countdown
|
||||
return self.urlopen(method, e.url, **kw)
|
||||
|
||||
|
||||
class ProxyManager(RequestMethods):
|
||||
"""
|
||||
Given a ConnectionPool to a proxy, the ProxyManager's ``urlopen`` method
|
||||
will make requests to any url through the defined proxy.
|
||||
"""
|
||||
|
||||
def __init__(self, proxy_pool):
|
||||
self.proxy_pool = proxy_pool
|
||||
|
||||
def _set_proxy_headers(self, headers=None):
|
||||
headers = headers or {}
|
||||
|
||||
# Same headers are curl passes for --proxy1.0
|
||||
headers['Accept'] = '*/*'
|
||||
headers['Proxy-Connection'] = 'Keep-Alive'
|
||||
|
||||
return headers
|
||||
|
||||
def urlopen(self, method, url, **kw):
|
||||
"Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
|
||||
kw['assert_same_host'] = False
|
||||
kw['headers'] = self._set_proxy_headers(kw.get('headers'))
|
||||
return self.proxy_pool.urlopen(method, url, **kw)
|
||||
|
||||
|
||||
def proxy_from_url(url, **pool_kw):
|
||||
proxy_pool = connection_from_url(url, **pool_kw)
|
||||
return ProxyManager(proxy_pool)
|
||||
128
requests/packages/urllib3/request.py
Normal file
128
requests/packages/urllib3/request.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# urllib3/request.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
try:
|
||||
from urllib.parse import urlencode
|
||||
except ImportError:
|
||||
from urllib import urlencode
|
||||
|
||||
from .filepost import encode_multipart_formdata
|
||||
|
||||
|
||||
__all__ = ['RequestMethods']
|
||||
|
||||
|
||||
class RequestMethods(object):
|
||||
"""
|
||||
Convenience mixin for classes who implement a :meth:`urlopen` method, such
|
||||
as :class:`~urllib3.connectionpool.HTTPConnectionPool` and
|
||||
:class:`~urllib3.poolmanager.PoolManager`.
|
||||
|
||||
Provides behavior for making common types of HTTP request methods and
|
||||
decides which type of request field encoding to use.
|
||||
|
||||
Specifically,
|
||||
|
||||
:meth:`.request_encode_url` is for sending requests whose fields are encoded
|
||||
in the URL (such as GET, HEAD, DELETE).
|
||||
|
||||
:meth:`.request_encode_body` is for sending requests whose fields are
|
||||
encoded in the *body* of the request using multipart or www-orm-urlencoded
|
||||
(such as for POST, PUT, PATCH).
|
||||
|
||||
:meth:`.request` is for making any kind of request, it will look up the
|
||||
appropriate encoding format and use one of the above two methods to make
|
||||
the request.
|
||||
"""
|
||||
|
||||
_encode_url_methods = set(['DELETE', 'GET', 'HEAD', 'OPTIONS'])
|
||||
|
||||
_encode_body_methods = set(['PATCH', 'POST', 'PUT', 'TRACE'])
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None,
|
||||
encode_multipart=True, multipart_boundary=None,
|
||||
**kw): # Abstract
|
||||
raise NotImplemented("Classes extending RequestMethods must implement "
|
||||
"their own ``urlopen`` method.")
|
||||
|
||||
def request(self, method, url, fields=None, headers=None, **urlopen_kw):
|
||||
"""
|
||||
Make a request using :meth:`urlopen` with the appropriate encoding of
|
||||
``fields`` based on the ``method`` used.
|
||||
|
||||
This is a convenience method that requires the least amount of manual
|
||||
effort. It can be used in most situations, while still having the option
|
||||
to drop down to more specific methods when necessary, such as
|
||||
:meth:`request_encode_url`, :meth:`request_encode_body`,
|
||||
or even the lowest level :meth:`urlopen`.
|
||||
"""
|
||||
method = method.upper()
|
||||
|
||||
if method in self._encode_url_methods:
|
||||
return self.request_encode_url(method, url, fields=fields,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
else:
|
||||
return self.request_encode_body(method, url, fields=fields,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
|
||||
def request_encode_url(self, method, url, fields=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 fields:
|
||||
url += '?' + urlencode(fields)
|
||||
return self.urlopen(method, url, **urlopen_kw)
|
||||
|
||||
def request_encode_body(self, method, url, fields=None, headers=None,
|
||||
encode_multipart=True, multipart_boundary=None,
|
||||
**urlopen_kw):
|
||||
"""
|
||||
Make a request using :meth:`urlopen` with the ``fields`` encoded in
|
||||
the body. This is useful for request methods like POST, PUT, PATCH, etc.
|
||||
|
||||
When ``encode_multipart=True`` (default), then
|
||||
:meth:`urllib3.filepost.encode_multipart_formdata` is used to encode the
|
||||
payload with the appropriate content type. Otherwise
|
||||
:meth:`urllib.urlencode` is used with the
|
||||
'application/x-www-form-urlencoded' content type.
|
||||
|
||||
Multipart encoding must be used when posting files, and it's reasonably
|
||||
safe to use it in other times too. However, it may break request signing,
|
||||
such as with OAuth.
|
||||
|
||||
Supports an optional ``fields`` parameter of key/value strings AND
|
||||
key/filetuple. A filetuple is a (filename, data) tuple. For example: ::
|
||||
|
||||
fields = {
|
||||
'foo': 'bar',
|
||||
'fakefile': ('foofile.txt', 'contents of foofile'),
|
||||
'realfile': ('barfile.txt', open('realfile').read()),
|
||||
'nonamefile': ('contents of nonamefile field'),
|
||||
}
|
||||
|
||||
When uploading a file, providing a filename (the first parameter of the
|
||||
tuple) is optional but recommended to best mimick behavior of browsers.
|
||||
|
||||
Note that if ``headers`` are supplied, the 'Content-Type' header will be
|
||||
overwritten because it depends on the dynamic random boundary string
|
||||
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')
|
||||
|
||||
headers = headers or {}
|
||||
headers.update({'Content-Type': content_type})
|
||||
|
||||
return self.urlopen(method, url, body=body, headers=headers,
|
||||
**urlopen_kw)
|
||||
202
requests/packages/urllib3/response.py
Normal file
202
requests/packages/urllib3/response.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# urllib3/response.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import gzip
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .exceptions import HTTPError
|
||||
from .packages.six import string_types as basestring
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def decode_gzip(data):
|
||||
gzipper = gzip.GzipFile(fileobj=BytesIO(data))
|
||||
return gzipper.read()
|
||||
|
||||
|
||||
def decode_deflate(data):
|
||||
try:
|
||||
return zlib.decompress(data)
|
||||
except zlib.error:
|
||||
return zlib.decompress(data, -zlib.MAX_WBITS)
|
||||
|
||||
|
||||
class HTTPResponse(object):
|
||||
"""
|
||||
HTTP Response container.
|
||||
|
||||
Backwards-compatible to httplib's HTTPResponse but the response ``body`` is
|
||||
loaded and decoded on-demand when the ``data`` property is accessed.
|
||||
|
||||
Extra parameters for behaviour not present in httplib.HTTPResponse:
|
||||
|
||||
:param preload_content:
|
||||
If True, the response's body will be preloaded during construction.
|
||||
|
||||
:param decode_content:
|
||||
If True, attempts to decode specific content-encoding's based on headers
|
||||
(like 'gzip' and 'deflate') will be skipped and raw data will be used
|
||||
instead.
|
||||
|
||||
:param original_response:
|
||||
When this HTTPResponse wrapper is generated from an httplib.HTTPResponse
|
||||
object, it's convenient to include the original for debug purposes. It's
|
||||
otherwise unused.
|
||||
"""
|
||||
|
||||
CONTENT_DECODERS = {
|
||||
'gzip': decode_gzip,
|
||||
'deflate': decode_deflate,
|
||||
}
|
||||
|
||||
def __init__(self, body='', headers=None, status=0, version=0, reason=None,
|
||||
strict=0, preload_content=True, decode_content=True,
|
||||
original_response=None, pool=None, connection=None):
|
||||
self.headers = headers or {}
|
||||
self.status = status
|
||||
self.version = version
|
||||
self.reason = reason
|
||||
self.strict = strict
|
||||
|
||||
self._decode_content = decode_content
|
||||
self._body = body if body and isinstance(body, basestring) else None
|
||||
self._fp = None
|
||||
self._original_response = original_response
|
||||
|
||||
self._pool = pool
|
||||
self._connection = connection
|
||||
|
||||
if hasattr(body, 'read'):
|
||||
self._fp = body
|
||||
|
||||
if preload_content and not self._body:
|
||||
self._body = self.read(decode_content=decode_content)
|
||||
|
||||
def get_redirect_location(self):
|
||||
"""
|
||||
Should we redirect and where to?
|
||||
|
||||
:returns: Truthy redirect location string if we got a redirect status
|
||||
code and valid location. ``None`` if redirect status and no
|
||||
location. ``False`` if not a redirect status code.
|
||||
"""
|
||||
if self.status in [301, 302, 303, 307]:
|
||||
return self.headers.get('location')
|
||||
|
||||
return False
|
||||
|
||||
def release_conn(self):
|
||||
if not self._pool or not self._connection:
|
||||
return
|
||||
|
||||
self._pool._put_conn(self._connection)
|
||||
self._connection = None
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
# For backwords-compat with earlier urllib3 0.4 and earlier.
|
||||
if self._body:
|
||||
return self._body
|
||||
|
||||
if self._fp:
|
||||
return self.read(cache_content=True)
|
||||
|
||||
def read(self, amt=None, decode_content=None, cache_content=False):
|
||||
"""
|
||||
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
|
||||
parameters: ``decode_content`` and ``cache_content``.
|
||||
|
||||
:param amt:
|
||||
How much of the content to read. If specified, decoding and caching
|
||||
is skipped because we can't decode partial content nor does it make
|
||||
sense to cache partial content as the full response.
|
||||
|
||||
:param decode_content:
|
||||
If True, will attempt to decode the body based on the
|
||||
'content-encoding' header. (Overridden if ``amt`` is set.)
|
||||
|
||||
:param cache_content:
|
||||
If True, will save the returned data such that the same result is
|
||||
returned despite of the state of the underlying file object. This
|
||||
is useful if you want the ``.data`` property to continue working
|
||||
after having ``.read()`` the file object. (Overridden if ``amt`` is
|
||||
set.)
|
||||
"""
|
||||
content_encoding = self.headers.get('content-encoding')
|
||||
decoder = self.CONTENT_DECODERS.get(content_encoding)
|
||||
if decode_content is None:
|
||||
decode_content = self._decode_content
|
||||
|
||||
if self._fp is None:
|
||||
return
|
||||
|
||||
try:
|
||||
if amt is None:
|
||||
# cStringIO doesn't like amt=None
|
||||
data = self._fp.read()
|
||||
else:
|
||||
return self._fp.read(amt)
|
||||
|
||||
try:
|
||||
if decode_content and decoder:
|
||||
data = decoder(data)
|
||||
except IOError:
|
||||
raise HTTPError("Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding)
|
||||
|
||||
if cache_content:
|
||||
self._body = data
|
||||
|
||||
return data
|
||||
|
||||
finally:
|
||||
if self._original_response and self._original_response.isclosed():
|
||||
self.release_conn()
|
||||
|
||||
@classmethod
|
||||
def from_httplib(ResponseCls, r, **response_kw):
|
||||
"""
|
||||
Given an :class:`httplib.HTTPResponse` instance ``r``, return a
|
||||
corresponding :class:`urllib3.response.HTTPResponse` object.
|
||||
|
||||
Remaining parameters are passed to the HTTPResponse constructor, along
|
||||
with ``original_response=r``.
|
||||
"""
|
||||
|
||||
# Normalize headers between different versions of Python
|
||||
headers = {}
|
||||
for k, v in r.getheaders():
|
||||
# Python 3: Header keys are returned capitalised
|
||||
k = k.lower()
|
||||
|
||||
has_value = headers.get(k)
|
||||
if has_value: # Python 3: Repeating header keys are unmerged.
|
||||
v = ', '.join([has_value, v])
|
||||
|
||||
headers[k] = v
|
||||
|
||||
# HTTPResponse objects in Python 3 don't have a .strict attribute
|
||||
strict = getattr(r, 'strict', 0)
|
||||
return ResponseCls(body=r,
|
||||
headers=headers,
|
||||
status=r.status,
|
||||
version=r.version,
|
||||
reason=r.reason,
|
||||
strict=strict,
|
||||
original_response=r,
|
||||
**response_kw)
|
||||
|
||||
# Backwards-compatibility methods for httplib.HTTPResponse
|
||||
def getheaders(self):
|
||||
return self.headers
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
return self.headers.get(name, default)
|
||||
136
requests/packages/urllib3/util.py
Normal file
136
requests/packages/urllib3/util.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# urllib3/util.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
try:
|
||||
from select import poll, POLLIN
|
||||
except ImportError: # `poll` doesn't exist on OSX and other platforms
|
||||
poll = False
|
||||
try:
|
||||
from select import select
|
||||
except ImportError: # `select` doesn't exist on AppEngine.
|
||||
select = False
|
||||
|
||||
from .packages import six
|
||||
from .exceptions import LocationParseError
|
||||
|
||||
|
||||
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
|
||||
basic_auth=None):
|
||||
"""
|
||||
Shortcuts for generating request headers.
|
||||
|
||||
:param keep_alive:
|
||||
If ``True``, adds 'connection: keep-alive' header.
|
||||
|
||||
:param accept_encoding:
|
||||
Can be a boolean, list, or string.
|
||||
``True`` translates to 'gzip,deflate'.
|
||||
List will get joined by comma.
|
||||
String will be used as provided.
|
||||
|
||||
:param user_agent:
|
||||
String representing the user-agent you want, such as
|
||||
"python-urllib3/0.6"
|
||||
|
||||
:param basic_auth:
|
||||
Colon-separated username:password string for 'authorization: basic ...'
|
||||
auth header.
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> make_headers(keep_alive=True, user_agent="Batman/1.0")
|
||||
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
|
||||
>>> make_headers(accept_encoding=True)
|
||||
{'accept-encoding': 'gzip,deflate'}
|
||||
"""
|
||||
headers = {}
|
||||
if accept_encoding:
|
||||
if isinstance(accept_encoding, str):
|
||||
pass
|
||||
elif isinstance(accept_encoding, list):
|
||||
accept_encoding = ','.join(accept_encoding)
|
||||
else:
|
||||
accept_encoding = 'gzip,deflate'
|
||||
headers['accept-encoding'] = accept_encoding
|
||||
|
||||
if user_agent:
|
||||
headers['user-agent'] = user_agent
|
||||
|
||||
if keep_alive:
|
||||
headers['connection'] = 'keep-alive'
|
||||
|
||||
if basic_auth:
|
||||
headers['authorization'] = 'Basic ' + \
|
||||
b64encode(six.b(basic_auth)).decode('utf-8')
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
def get_host(url):
|
||||
"""
|
||||
Given a url, return its scheme, host and port (None if it's not there).
|
||||
|
||||
For example: ::
|
||||
|
||||
>>> get_host('http://google.com/mail/')
|
||||
('http', 'google.com', None)
|
||||
>>> get_host('google.com:80')
|
||||
('http', 'google.com', 80)
|
||||
"""
|
||||
|
||||
# This code is actually similar to urlparse.urlsplit, but much
|
||||
# simplified for our needs.
|
||||
port = None
|
||||
scheme = 'http'
|
||||
|
||||
if '://' in url:
|
||||
scheme, url = url.split('://', 1)
|
||||
if '/' in url:
|
||||
url, _path = url.split('/', 1)
|
||||
if '@' in url:
|
||||
_auth, url = url.split('@', 1)
|
||||
if ':' in url:
|
||||
url, port = url.split(':', 1)
|
||||
|
||||
if not port.isdigit():
|
||||
raise LocationParseError("Failed to parse: %s" % url)
|
||||
|
||||
port = int(port)
|
||||
|
||||
return scheme, url, port
|
||||
|
||||
|
||||
|
||||
def is_connection_dropped(conn):
|
||||
"""
|
||||
Returns True if the connection is dropped and should be closed.
|
||||
|
||||
:param conn:
|
||||
``HTTPConnection`` object.
|
||||
|
||||
Note: For platforms like AppEngine, this will always return ``False`` to
|
||||
let the platform handle connection recycling transparently for us.
|
||||
"""
|
||||
sock = getattr(conn, 'sock', False)
|
||||
if not sock: #Platform-specific: AppEngine
|
||||
return False
|
||||
|
||||
if not poll: # Platform-specific
|
||||
if not select: #Platform-specific: AppEngine
|
||||
return False
|
||||
|
||||
return select([sock], [], [], 0.0)[0]
|
||||
|
||||
# This version is better on platforms that support it.
|
||||
p = poll()
|
||||
p.register(sock, POLLIN)
|
||||
for (fno, ev) in p.poll(0.0):
|
||||
if fno == sock.fileno():
|
||||
# Either data is buffered (bad), or the connection is dropped.
|
||||
return True
|
||||
38
requests/safe_mode.py
Normal file
38
requests/safe_mode.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.safe_mode
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module contains a decorator that implements safe_mode.
|
||||
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
from .models import Response
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .exceptions import RequestException, ConnectionError, HTTPError
|
||||
import socket
|
||||
|
||||
def catch_exceptions_if_in_safe_mode(function):
|
||||
"""New implementation of safe_mode. We catch all exceptions at the API level
|
||||
and then return a blank Response object with the error field filled. This decorator
|
||||
wraps request() in api.py.
|
||||
"""
|
||||
|
||||
def wrapped(method, url, **kwargs):
|
||||
# if save_mode, we catch exceptions and fill error field
|
||||
if (kwargs.get('config') and kwargs.get('config').get('safe_mode')) or (kwargs.get('session')
|
||||
and kwargs.get('session').config.get('safe_mode')):
|
||||
try:
|
||||
return function(method, url, **kwargs)
|
||||
except (RequestException, ConnectionError, HTTPError, socket.timeout) as e:
|
||||
r = Response()
|
||||
r.error = e
|
||||
r.raw = HTTPResponse() # otherwise, tests fail
|
||||
r.status_code = 0 # with this status_code, content returns None
|
||||
return r
|
||||
return function(method, url, **kwargs)
|
||||
return wrapped
|
||||
@@ -9,13 +9,14 @@ requests (cookies, auth, proxies).
|
||||
|
||||
"""
|
||||
|
||||
import cookielib
|
||||
|
||||
from copy import deepcopy
|
||||
from .compat import cookielib
|
||||
from .cookies import cookiejar_from_dict, remove_cookie_by_name
|
||||
from .defaults import defaults
|
||||
from .models import Request
|
||||
from .hooks import dispatch_hook
|
||||
from .utils import add_dict_to_cookiejar, cookiejar_from_dict, header_expand
|
||||
|
||||
from .utils import header_expand
|
||||
from .packages.urllib3.poolmanager import PoolManager
|
||||
|
||||
def merge_kwargs(local_kwarg, default_kwarg):
|
||||
"""Merges kwarg dictionaries.
|
||||
@@ -26,7 +27,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
|
||||
if isinstance(local_kwarg, basestring):
|
||||
if isinstance(local_kwarg, str):
|
||||
return local_kwarg
|
||||
|
||||
if local_kwarg is None:
|
||||
@@ -41,7 +42,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
kwargs.update(local_kwarg)
|
||||
|
||||
# Remove keys that are set to None.
|
||||
for (k,v) in local_kwarg.items():
|
||||
for (k, v) in list(local_kwarg.items()):
|
||||
if v is None:
|
||||
del kwargs[k]
|
||||
|
||||
@@ -53,7 +54,7 @@ class Session(object):
|
||||
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
||||
'params', 'config']
|
||||
'params', 'config', 'verify', 'cert', 'prefetch']
|
||||
|
||||
|
||||
def __init__(self,
|
||||
@@ -65,23 +66,37 @@ class Session(object):
|
||||
hooks=None,
|
||||
params=None,
|
||||
config=None,
|
||||
keep_alive=True):
|
||||
prefetch=False,
|
||||
verify=True,
|
||||
cert=None):
|
||||
|
||||
self.headers = headers or {}
|
||||
self.cookies = cookies or {}
|
||||
self.auth = auth
|
||||
self.timeout = timeout
|
||||
self.proxies = proxies or {}
|
||||
self.hooks = hooks or {}
|
||||
self.params = params or {}
|
||||
self.config = config or {}
|
||||
self.keep_alive = keep_alive
|
||||
self.prefetch = prefetch
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
for (k, v) in defaults.items():
|
||||
self.config.setdefault(k, v)
|
||||
for (k, v) in list(defaults.items()):
|
||||
self.config.setdefault(k, deepcopy(v))
|
||||
|
||||
self.init_poolmanager()
|
||||
|
||||
# Set up a CookieJar to be used by default
|
||||
self.cookies = cookielib.FileCookieJar()
|
||||
if isinstance(cookies, cookielib.CookieJar):
|
||||
self.cookies = cookies
|
||||
else:
|
||||
self.cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
def init_poolmanager(self):
|
||||
self.poolmanager = PoolManager(
|
||||
num_pools=self.config.get('pool_connections'),
|
||||
maxsize=self.config.get('pool_maxsize')
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<requests-client at 0x%x>' % (id(self))
|
||||
@@ -100,11 +115,14 @@ class Session(object):
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=False,
|
||||
allow_redirects=True,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
config=None):
|
||||
config=None,
|
||||
prefetch=False,
|
||||
verify=None,
|
||||
cert=None):
|
||||
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
@@ -116,27 +134,34 @@ class Session(object):
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) Auth typle to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param allow_redirects: (optional) Boolean. Set to True by default.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
:param config: (optional) A configuration dictionary. See ``request.defaults`` for allowed keys and their default values.
|
||||
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
"""
|
||||
|
||||
method = str(method).upper()
|
||||
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
# Default empty dicts for dict params.
|
||||
data = {} if data is None else data
|
||||
files = {} if files is None else files
|
||||
headers = {} if headers is None else headers
|
||||
params = {} if params is None else params
|
||||
hooks = {} if hooks is None else hooks
|
||||
prefetch = self.prefetch or prefetch
|
||||
|
||||
if isinstance(cookies, dict):
|
||||
cookies = add_dict_to_cookiejar(self.cookies, cookies)
|
||||
# use session's hooks as defaults
|
||||
for key, cb in list(self.hooks.items()):
|
||||
hooks.setdefault(key, cb)
|
||||
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Expand header values
|
||||
# Expand header values.
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
for k, v in list(headers.items()) or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
args = dict(
|
||||
@@ -152,28 +177,59 @@ class Session(object):
|
||||
timeout=timeout,
|
||||
allow_redirects=allow_redirects,
|
||||
proxies=proxies,
|
||||
config=config
|
||||
config=config,
|
||||
prefetch=prefetch,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
_poolmanager=self.poolmanager
|
||||
)
|
||||
|
||||
# merge session cookies into passed-in ones
|
||||
dead_cookies = None
|
||||
# passed-in cookies must become a CookieJar:
|
||||
if not isinstance(cookies, cookielib.CookieJar):
|
||||
args['cookies'] = cookiejar_from_dict(cookies)
|
||||
# support unsetting cookies that have been passed in with None values
|
||||
# this is only meaningful when `cookies` is a dict ---
|
||||
# for a real CookieJar, the client should use session.cookies.clear()
|
||||
if cookies is not None:
|
||||
dead_cookies = [name for name in cookies if cookies[name] is None]
|
||||
# merge the session's cookies into the passed-in cookies:
|
||||
for cookie in self.cookies:
|
||||
args['cookies'].set_cookie(cookie)
|
||||
# remove the unset cookies from the jar we'll be using with the current request
|
||||
# (but not from the session's own store of cookies):
|
||||
if dead_cookies is not None:
|
||||
for name in dead_cookies:
|
||||
remove_cookie_by_name(args['cookies'], name)
|
||||
|
||||
# Merge local kwargs with session kwargs.
|
||||
for attr in self.__attrs__:
|
||||
# we already merged cookies:
|
||||
if attr == 'cookies':
|
||||
continue
|
||||
|
||||
session_val = getattr(self, attr, None)
|
||||
local_val = args.get(attr)
|
||||
|
||||
args[attr] = merge_kwargs(local_val, session_val)
|
||||
|
||||
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('args', args['hooks'], args)
|
||||
|
||||
# Create the (empty) response.
|
||||
r = Request(**args)
|
||||
|
||||
# Give the response some context.
|
||||
r.session = self
|
||||
|
||||
# Don't send if asked nicely.
|
||||
if not return_response:
|
||||
return r
|
||||
|
||||
# Send the HTTP Request.
|
||||
r.send()
|
||||
r.send(prefetch=prefetch)
|
||||
|
||||
# Return the response.
|
||||
return r.response
|
||||
|
||||
|
||||
@@ -181,69 +237,88 @@ class Session(object):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('GET', url, **kwargs)
|
||||
return self.request('get', url, **kwargs)
|
||||
|
||||
|
||||
def options(self, url, **kwargs):
|
||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('options', url, **kwargs)
|
||||
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('HEAD', url, **kwargs)
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return self.request('head', url, **kwargs)
|
||||
|
||||
|
||||
def post(self, url, data='', **kwargs):
|
||||
def post(self, url, data=None, **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('post', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def put(self, url, data='', **kwargs):
|
||||
def put(self, url, data=None, **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(self, url, data='', **kwargs):
|
||||
def patch(self, url, data=None, **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('patch', url, data='', **kwargs)
|
||||
return self.request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('delete', url, **kwargs)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
|
||||
def __setstate__(self, state):
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
self.init_poolmanager()
|
||||
|
||||
|
||||
def session(**kwargs):
|
||||
"""Returns a :class:`Session` for context-management."""
|
||||
|
||||
return Session(**kwargs)
|
||||
return Session(**kwargs)
|
||||
|
||||
@@ -79,7 +79,7 @@ _codes = {
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for (code, titles) in _codes.items():
|
||||
for (code, titles) in list(_codes.items()):
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith('\\'):
|
||||
|
||||
@@ -8,6 +8,7 @@ Data structures that power Requests.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class CaseInsensitiveDict(dict):
|
||||
"""Case-insensitive Dictionary
|
||||
|
||||
@@ -17,7 +18,7 @@ class CaseInsensitiveDict(dict):
|
||||
@property
|
||||
def lower_keys(self):
|
||||
if not hasattr(self, '_lower_keys') or not self._lower_keys:
|
||||
self._lower_keys = dict((k.lower(), k) for k in self.iterkeys())
|
||||
self._lower_keys = dict((k.lower(), k) for k in list(self.keys()))
|
||||
return self._lower_keys
|
||||
|
||||
def _clear_lower_keys(self):
|
||||
@@ -29,7 +30,7 @@ class CaseInsensitiveDict(dict):
|
||||
self._clear_lower_keys()
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
dict.__delitem__(self, self.lower_keys.get(key.lower(), key))
|
||||
self._lower_keys.clear()
|
||||
|
||||
def __contains__(self, key):
|
||||
@@ -62,4 +63,4 @@ class LookupDict(dict):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__dict__.get(key, default)
|
||||
return self.__dict__.get(key, default)
|
||||
|
||||
@@ -4,20 +4,105 @@
|
||||
requests.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module provides utlity functions that are used within Requests
|
||||
This module provides utility functions that are used within Requests
|
||||
that are also useful for external consumption.
|
||||
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import cookielib
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import zlib
|
||||
from netrc import netrc, NetrcParseError
|
||||
|
||||
from urllib2 import parse_http_list as _parse_list_header
|
||||
from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import quote, urlparse, basestring, bytes, str
|
||||
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
||||
|
||||
_hush_pyflakes = (RequestsCookieJar,)
|
||||
|
||||
CERTIFI_BUNDLE_PATH = None
|
||||
try:
|
||||
# see if requests's own CA certificate bundle is installed
|
||||
import certifi
|
||||
CERTIFI_BUNDLE_PATH = certifi.where()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
NETRC_FILES = ('.netrc', '_netrc')
|
||||
|
||||
# common paths for the OS's CA certificate bundle
|
||||
POSSIBLE_CA_BUNDLE_PATHS = [
|
||||
# Red Hat, CentOS, Fedora and friends (provided by the ca-certificates package):
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
# Ubuntu, Debian, and friends (provided by the ca-certificates package):
|
||||
'/etc/ssl/certs/ca-certificates.crt',
|
||||
# FreeBSD (provided by the ca_root_nss package):
|
||||
'/usr/local/share/certs/ca-root-nss.crt',
|
||||
]
|
||||
|
||||
def get_os_ca_bundle_path():
|
||||
"""Try to pick an available CA certificate bundle provided by the OS."""
|
||||
for path in POSSIBLE_CA_BUNDLE_PATHS:
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
# if certifi is installed, use its CA bundle;
|
||||
# otherwise, try and use the OS bundle
|
||||
DEFAULT_CA_BUNDLE_PATH = CERTIFI_BUNDLE_PATH or get_os_ca_bundle_path()
|
||||
|
||||
def dict_to_sequence(d):
|
||||
"""Returns an internal sequence dictionary update."""
|
||||
|
||||
if hasattr(d, 'items'):
|
||||
d = d.items()
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def get_netrc_auth(url):
|
||||
"""Returns the Requests tuple auth for a given url from netrc."""
|
||||
|
||||
try:
|
||||
locations = (os.path.expanduser('~/{0}'.format(f)) for f in NETRC_FILES)
|
||||
netrc_path = None
|
||||
|
||||
for loc in locations:
|
||||
if os.path.exists(loc) and not netrc_path:
|
||||
netrc_path = loc
|
||||
|
||||
# Abort early if there isn't one.
|
||||
if netrc_path is None:
|
||||
return netrc_path
|
||||
|
||||
ri = urlparse(url)
|
||||
|
||||
# Strip port numbers from netloc
|
||||
host = ri.netloc.split(':')[0]
|
||||
|
||||
try:
|
||||
_netrc = netrc(netrc_path).authenticators(host)
|
||||
if _netrc:
|
||||
# Return with login / password
|
||||
login_i = (0 if _netrc[0] else 1)
|
||||
return (_netrc[login_i], _netrc[2])
|
||||
except (NetrcParseError, IOError):
|
||||
# If there was a parsing error or a permissions issue reading the file,
|
||||
# we'll just skip netrc auth
|
||||
pass
|
||||
|
||||
# AppEngine hackiness.
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
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] != '>':
|
||||
return name
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
@@ -125,16 +210,22 @@ def header_expand(headers):
|
||||
collector = []
|
||||
|
||||
if isinstance(headers, dict):
|
||||
headers = headers.items()
|
||||
|
||||
headers = list(headers.items())
|
||||
elif isinstance(headers, basestring):
|
||||
return headers
|
||||
elif isinstance(headers, str):
|
||||
# As discussed in https://github.com/kennethreitz/requests/issues/400
|
||||
# latin-1 is the most conservative encoding used on the web. Anyone
|
||||
# who needs more can encode to a byte-string before calling
|
||||
return headers.encode("latin-1")
|
||||
elif headers is None:
|
||||
return headers
|
||||
|
||||
for i, (value, params) in enumerate(headers):
|
||||
|
||||
_params = []
|
||||
|
||||
for (p_k, p_v) in params.items():
|
||||
for (p_k, p_v) in list(params.items()):
|
||||
|
||||
_params.append('%s=%s' % (p_k, p_v))
|
||||
|
||||
@@ -145,10 +236,9 @@ def header_expand(headers):
|
||||
|
||||
collector.append('; '.join(_params))
|
||||
|
||||
if not len(headers) == i+1:
|
||||
if not len(headers) == i + 1:
|
||||
collector.append(', ')
|
||||
|
||||
|
||||
# Remove trailing separators.
|
||||
if collector[-1] in (', ', '; '):
|
||||
del collector[-1]
|
||||
@@ -156,22 +246,6 @@ def header_expand(headers):
|
||||
return ''.join(collector)
|
||||
|
||||
|
||||
|
||||
def randombytes(n):
|
||||
"""Return n random bytes."""
|
||||
# Use /dev/urandom if it is available. Fall back to random module
|
||||
# if not. It might be worthwhile to extend this function to use
|
||||
# other platform-specific mechanisms for getting random bytes.
|
||||
if os.path.exists("/dev/urandom"):
|
||||
f = open("/dev/urandom")
|
||||
s = f.read(n)
|
||||
f.close()
|
||||
return s
|
||||
else:
|
||||
L = [chr(random.randrange(0, 256)) for i in range(n)]
|
||||
return "".join(L)
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
"""Returns a key/value dictionary from a CookieJar.
|
||||
|
||||
@@ -180,33 +254,15 @@ def dict_from_cookiejar(cj):
|
||||
|
||||
cookie_dict = {}
|
||||
|
||||
for _, cookies in cj._cookies.items():
|
||||
for _, cookies in cookies.items():
|
||||
for cookie in cookies.values():
|
||||
for _, cookies in list(cj._cookies.items()):
|
||||
for _, cookies in list(cookies.items()):
|
||||
for cookie in list(cookies.values()):
|
||||
# print cookie
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
|
||||
return cookie_dict
|
||||
|
||||
|
||||
def cookiejar_from_dict(cookie_dict):
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
# return cookiejar if one was passed in
|
||||
if isinstance(cookie_dict, cookielib.CookieJar):
|
||||
return cookie_dict
|
||||
|
||||
# create cookiejar
|
||||
cj = cookielib.CookieJar()
|
||||
|
||||
cj = add_dict_to_cookiejar(cj, cookie_dict)
|
||||
|
||||
return cj
|
||||
|
||||
|
||||
def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
@@ -214,31 +270,9 @@ def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
for k, v in cookie_dict.items():
|
||||
|
||||
cookie = cookielib.Cookie(
|
||||
version=0,
|
||||
name=k,
|
||||
value=v,
|
||||
port=None,
|
||||
port_specified=False,
|
||||
domain='',
|
||||
domain_specified=False,
|
||||
domain_initial_dot=False,
|
||||
path='/',
|
||||
path_specified=True,
|
||||
secure=False,
|
||||
expires=None,
|
||||
discard=True,
|
||||
comment=None,
|
||||
comment_url=None,
|
||||
rest={'HttpOnly': None},
|
||||
rfc2109=False
|
||||
)
|
||||
|
||||
# add cookie to cookiejar
|
||||
cj2 = cookiejar_from_dict(cookie_dict)
|
||||
for cookie in cj2:
|
||||
cj.set_cookie(cookie)
|
||||
|
||||
return cj
|
||||
|
||||
|
||||
@@ -269,33 +303,19 @@ def get_encoding_from_headers(headers):
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
|
||||
|
||||
def unicode_from_html(content):
|
||||
"""Attempts to decode an HTML string into unicode.
|
||||
If unsuccessful, the original content is returned.
|
||||
"""
|
||||
|
||||
encodings = get_encodings_from_content(content)
|
||||
|
||||
for encoding in encodings:
|
||||
|
||||
try:
|
||||
return unicode(content, encoding)
|
||||
except (UnicodeError, TypeError):
|
||||
pass
|
||||
|
||||
return content
|
||||
if 'text' in content_type:
|
||||
return 'ISO-8859-1'
|
||||
|
||||
|
||||
def stream_decode_response_unicode(iterator, r):
|
||||
"""Stream decodes a iterator."""
|
||||
encoding = get_encoding_from_headers(r.headers)
|
||||
if encoding is None:
|
||||
|
||||
if r.encoding is None:
|
||||
for item in iterator:
|
||||
yield item
|
||||
return
|
||||
|
||||
decoder = codecs.getincrementaldecoder(encoding)(errors='replace')
|
||||
decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace')
|
||||
for chunk in iterator:
|
||||
rv = decoder.decode(chunk)
|
||||
if rv:
|
||||
@@ -327,37 +347,109 @@ def get_unicode_from_response(r):
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
return unicode(r.content, encoding)
|
||||
return str(r.content, encoding)
|
||||
except UnicodeError:
|
||||
tried_encodings.append(encoding)
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
return unicode(r.content, encoding, errors='replace')
|
||||
return str(r.content, encoding, errors='replace')
|
||||
except TypeError:
|
||||
return r.content
|
||||
|
||||
|
||||
def decode_gzip(content):
|
||||
"""Return gzip-decoded string.
|
||||
def stream_decompress(iterator, mode='gzip'):
|
||||
"""
|
||||
Stream decodes an iterator over compressed data
|
||||
|
||||
:param content: bytestring to gzip-decode.
|
||||
:param iterator: An iterator over compressed data
|
||||
:param mode: 'gzip' or 'deflate'
|
||||
:return: An iterator over decompressed data
|
||||
"""
|
||||
|
||||
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
|
||||
if mode not in ['gzip', 'deflate']:
|
||||
raise ValueError('stream_decompress mode must be gzip or deflate')
|
||||
|
||||
|
||||
def stream_decode_gzip(iterator):
|
||||
"""Stream decodes a gzip-encoded iterator"""
|
||||
zlib_mode = 16 + zlib.MAX_WBITS if mode == 'gzip' else -zlib.MAX_WBITS
|
||||
dec = zlib.decompressobj(zlib_mode)
|
||||
try:
|
||||
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
for chunk in iterator:
|
||||
rv = dec.decompress(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
buf = dec.decompress('')
|
||||
except zlib.error:
|
||||
# If there was an error decompressing, just return the raw chunk
|
||||
yield chunk
|
||||
# Continue to return the rest of the raw data
|
||||
for chunk in iterator:
|
||||
yield chunk
|
||||
else:
|
||||
# Make sure everything has been returned from the decompression object
|
||||
buf = dec.decompress(bytes())
|
||||
rv = buf + dec.flush()
|
||||
if rv:
|
||||
yield rv
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
|
||||
def stream_untransfer(gen, resp):
|
||||
if 'gzip' in resp.headers.get('content-encoding', ''):
|
||||
gen = stream_decompress(gen, mode='gzip')
|
||||
elif 'deflate' in resp.headers.get('content-encoding', ''):
|
||||
gen = stream_decompress(gen, mode='deflate')
|
||||
|
||||
return gen
|
||||
|
||||
|
||||
# The unreserved URI characters (RFC 3986)
|
||||
UNRESERVED_SET = frozenset(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
+ "0123456789-._~")
|
||||
|
||||
|
||||
def unquote_unreserved(uri):
|
||||
"""Un-escape any percent-escape sequences in a URI that are unreserved
|
||||
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
|
||||
"""
|
||||
try:
|
||||
parts = uri.split('%')
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2 and h.isalnum():
|
||||
c = chr(int(h, 16))
|
||||
if c in UNRESERVED_SET:
|
||||
parts[i] = c + parts[i][2:]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
return ''.join(parts)
|
||||
except ValueError:
|
||||
return uri
|
||||
|
||||
|
||||
def requote_uri(uri):
|
||||
"""Re-quote the given 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="!#$%&'()*+,/:;=?@[]~")
|
||||
|
||||
def get_environ_proxies():
|
||||
"""Return a dict of environment proxies."""
|
||||
|
||||
proxy_keys = [
|
||||
'all',
|
||||
'http',
|
||||
'https',
|
||||
'ftp',
|
||||
'socks',
|
||||
'no'
|
||||
]
|
||||
|
||||
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
|
||||
proxies = [(key, get_proxy(key + '_proxy')) for key in proxy_keys]
|
||||
return dict([(key, val) for (key, val) in proxies if val])
|
||||
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
nose
|
||||
rudolf2
|
||||
62
setup.py
62
setup.py
@@ -1,29 +1,51 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
distutils/setuptools install script. See inline comments for packaging documentation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import requests
|
||||
from requests.compat import is_py3
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
# hush pyflakes
|
||||
setup
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
||||
|
||||
|
||||
if sys.argv[-1] == "publish":
|
||||
os.system("python setup.py sdist upload")
|
||||
if sys.argv[-1] == 'publish':
|
||||
os.system('python setup.py sdist upload')
|
||||
sys.exit()
|
||||
|
||||
if sys.argv[-1] == "test":
|
||||
os.system("python test_requests.py")
|
||||
sys.exit()
|
||||
packages = [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.urllib3',
|
||||
'requests.packages.urllib3.packages',
|
||||
'requests.packages.urllib3.packages.ssl_match_hostname',
|
||||
'requests.packages.urllib3.packages.mimetools_choose_boundary',
|
||||
]
|
||||
|
||||
required = []
|
||||
# certifi is a Python package containing a CA certificate bundle for SSL verification.
|
||||
# On certain supported platforms (e.g., Red Hat / Debian / FreeBSD), Requests can
|
||||
# use the system CA bundle instead; see `requests.utils` for details.
|
||||
# If your platform is supported, set `requires` to [] instead:
|
||||
requires = ['certifi>=0.0.7']
|
||||
|
||||
if sys.version_info[:2] < (2,6):
|
||||
required.append('simplejson')
|
||||
# chardet is used to optimally guess the encodings of pages that don't declare one.
|
||||
# At this time, chardet is not a required dependency. However, it's sufficiently
|
||||
# important that pip/setuptools should install it when it's unavailable.
|
||||
if is_py3:
|
||||
chardet_package = 'chardet2'
|
||||
else:
|
||||
chardet_package = 'chardet>=1.0.0'
|
||||
requires.append('oauthlib>=0.1.0,<0.2.0')
|
||||
|
||||
requires.append(chardet_package)
|
||||
|
||||
setup(
|
||||
name='requests',
|
||||
@@ -34,23 +56,21 @@ setup(
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='http://python-requests.org',
|
||||
packages= [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.poster'
|
||||
],
|
||||
install_requires=required,
|
||||
license='ISC',
|
||||
packages=packages,
|
||||
package_data={'': ['LICENSE', 'NOTICE']},
|
||||
include_package_data=True,
|
||||
install_requires=requires,
|
||||
license=open("LICENSE").read(),
|
||||
classifiers=(
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: ISC License (ISCL)',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
# 'Programming Language :: Python :: 3.0',
|
||||
# 'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.0',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
),
|
||||
)
|
||||
|
||||
539
test_requests.py
539
test_requests.py
@@ -1,539 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import time
|
||||
import cookielib
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import envoy
|
||||
from urllib2 import HTTPError
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
|
||||
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
|
||||
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
|
||||
global _httpbin
|
||||
|
||||
if not _httpbin:
|
||||
|
||||
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
|
||||
self.httpbin = c
|
||||
_httpbin = True
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""Teardown."""
|
||||
# self.httpbin.kill()
|
||||
|
||||
def test_entry_points(self):
|
||||
import requests
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = requests.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
|
||||
assert heads['User-agent'] in r.content
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
"""Issue XX"""
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['User-agent'] in r.content)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['user-agent'] in r.content)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = requests.head(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = requests.put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PATCH(self):
|
||||
r = requests.patch(httpbin('patch'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('digest', 'user', 'pass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post = requests.post(url).raise_for_status()
|
||||
|
||||
post = requests.post(url, data={'some': 'data'})
|
||||
self.assertEqual(post.status_code, 200)
|
||||
|
||||
post2 = requests.post(url, files={'some': open('test_requests.py')})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = requests.post(url, data='[{"some": "json"}]')
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post = requests.post(url,
|
||||
files={'some': open('test_requests.py')},
|
||||
data={'some': 'data'})
|
||||
|
||||
self.assertEqual(post.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
post2 = requests.post(url,
|
||||
files={'some': open('test_requests.py')},
|
||||
headers = {'User-Agent': 'requests-tests'})
|
||||
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = requests.get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
|
||||
def test_request_ok_set(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '404'))
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
|
||||
def test_status_raising(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
def test_cookie_jar(self):
|
||||
|
||||
jar = cookielib.CookieJar()
|
||||
self.assertFalse(jar)
|
||||
|
||||
url = httpbin('cookies', 'set', 'requests_cookie', 'awesome')
|
||||
r = requests.get(url, cookies=jar)
|
||||
self.assertTrue(jar)
|
||||
|
||||
cookie_found = False
|
||||
for cookie in jar:
|
||||
if cookie.name == 'requests_cookie':
|
||||
self.assertEquals(cookie.value, 'awesome')
|
||||
cookie_found = True
|
||||
self.assertTrue(cookie_found)
|
||||
|
||||
r = requests.get(httpbin('cookies'), cookies=jar)
|
||||
self.assertTrue('awesome' in r.content)
|
||||
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/get')
|
||||
|
||||
requests.get(url, params={'foo': u'føø'})
|
||||
requests.get(url, params={u'føø': u'føø'})
|
||||
requests.get(url, params={'føø': 'føø'})
|
||||
requests.get(url, params={'foo': u'foo'})
|
||||
requests.get(service('ø'), params={'foo': u'foo'})
|
||||
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = ('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = requests.get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEquals(r.status_code, 401)
|
||||
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
self.assertEquals(rbody.get('form'), None)
|
||||
self.assertEquals(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params='fooaowpeuf')
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'),
|
||||
params='fooaowpeuf', data="foobar")
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), None)
|
||||
self.assertEquals(rbody.get('data'), 'foobar')
|
||||
|
||||
|
||||
# def test_idna(self):
|
||||
# r = requests.get(u'http://➡.ws/httpbin')
|
||||
# assert 'httpbin' in r.url
|
||||
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('get'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test=['foo','baz']),
|
||||
data=dict(test2="foobar",test3=['foo','baz']))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar',test3='foo'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('relative-redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = s.get(httpbin('user-agent'))
|
||||
assert heads['User-agent'] in r1.content
|
||||
|
||||
r2 = s.get(httpbin('user-agent'))
|
||||
assert heads['User-agent'] in r2.content
|
||||
|
||||
new_heads = {'User-agent': 'blah'}
|
||||
r3 = s.get(httpbin('user-agent'), headers=new_heads)
|
||||
assert new_heads['User-agent'] in r3.content
|
||||
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
|
||||
def test_session_persistent_params(self):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = s.get(httpbin('get'))
|
||||
assert params['a'] in r1.content
|
||||
|
||||
|
||||
params2 = {'b': 'b_test'}
|
||||
|
||||
r2 = s.get(httpbin('get'), params=params2)
|
||||
assert params['a'] in r2.content
|
||||
assert params2['b'] in r2.content
|
||||
|
||||
|
||||
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
|
||||
|
||||
r3 = s.get(httpbin('get'), params=params3)
|
||||
|
||||
assert not params['a'] in r3.content
|
||||
assert params3['b'] in r3.content
|
||||
assert params3['c'] in r3.content
|
||||
|
||||
def test_invalid_content(self):
|
||||
|
||||
r = requests.get('http://somedomainthatclearlydoesntexistg.com')
|
||||
assert r.content == None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
263
tests/test_cookies.py
Executable file
263
tests/test_cookies.py
Executable file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
# Path hack.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
import requests
|
||||
from requests.compat import cookielib
|
||||
|
||||
# More hacks
|
||||
sys.path.append('.')
|
||||
from test_requests import httpbin, TestBaseMixin
|
||||
|
||||
class CookieTests(TestBaseMixin, unittest.TestCase):
|
||||
|
||||
def test_cookies_from_response(self):
|
||||
"""Basic test that we correctly parse received cookies in the Response object."""
|
||||
r = requests.get(httpbin('cookies', 'set', 'myname', 'myvalue'))
|
||||
|
||||
# test deprecated dictionary interface
|
||||
self.assertEqual(r.cookies['myname'], 'myvalue')
|
||||
self.assertEqual(r.cookies.get('myname'), 'myvalue')
|
||||
# test CookieJar interface
|
||||
jar = r.cookies
|
||||
self.assertEqual(len(jar), 1)
|
||||
cookie_from_jar = list(jar)[0]
|
||||
self.assertCookieHas(cookie_from_jar, name='myname', value='myvalue')
|
||||
|
||||
q = requests.get(httpbin('cookies'), cookies=jar)
|
||||
self.assertEqual(json.loads(q.text)['cookies'], {'myname': 'myvalue'})
|
||||
|
||||
def test_crossdomain_cookies(self):
|
||||
"""Cookies should not be sent to domains they didn't originate from."""
|
||||
r = requests.get("http://github.com")
|
||||
c = r.cookies
|
||||
# github should send us cookies
|
||||
self.assertTrue(len(c) >= 1)
|
||||
|
||||
# github cookies should not be sent to httpbin.org:
|
||||
r2 = requests.get(httpbin('cookies'), cookies=c)
|
||||
self.assertEqual(json.loads(r2.text)['cookies'], {})
|
||||
|
||||
# let's do this again using the session object
|
||||
s = requests.session()
|
||||
s.get("http://github.com")
|
||||
self.assertTrue(len(s.cookies) >= 1)
|
||||
r = s.get(httpbin('cookies'))
|
||||
self.assertEqual(json.loads(r.text)['cookies'], {})
|
||||
# we can set a cookie and get exactly that same-domain cookie back:
|
||||
r = s.get(httpbin('cookies', 'set', 'myname', 'myvalue'))
|
||||
self.assertEqual(json.loads(r.text)['cookies'], {'myname': 'myvalue'})
|
||||
|
||||
def test_overwrite(self):
|
||||
"""Cookies should get overwritten when appropriate."""
|
||||
r = requests.get(httpbin('cookies', 'set', 'shimon', 'yochai'))
|
||||
cookies = r.cookies
|
||||
requests.get(httpbin('cookies', 'set', 'elazar', 'shimon'), cookies=cookies)
|
||||
r = requests.get(httpbin('cookies'), cookies=cookies)
|
||||
self.assertEqual(json.loads(r.text)['cookies'],
|
||||
{'shimon': 'yochai', 'elazar': 'shimon'})
|
||||
# overwrite the value of 'shimon'
|
||||
r = requests.get(httpbin('cookies', 'set', 'shimon', 'gamaliel'), cookies=cookies)
|
||||
self.assertEqual(len(cookies), 2)
|
||||
r = requests.get(httpbin('cookies'), cookies=cookies)
|
||||
self.assertEqual(json.loads(r.text)['cookies'],
|
||||
{'shimon': 'gamaliel', 'elazar': 'shimon'})
|
||||
|
||||
def test_redirects(self):
|
||||
"""Test that cookies set by a 302 page are correctly processed."""
|
||||
r = requests.get(httpbin('cookies', 'set', 'redirects', 'work'))
|
||||
self.assertEqual(r.history[0].status_code, 302)
|
||||
expected_cookies = {'redirects': 'work'}
|
||||
self.assertEqual(json.loads(r.text)['cookies'], expected_cookies)
|
||||
|
||||
r2 = requests.get(httpbin('cookies', 'set', 'very', 'well'), cookies=r.cookies)
|
||||
expected_cookies = {'redirects': 'work', 'very': 'well'}
|
||||
self.assertEqual(json.loads(r2.text)['cookies'], expected_cookies)
|
||||
self.assertTrue(r.cookies is r2.cookies)
|
||||
|
||||
def test_none_cookie(self):
|
||||
"""Regression test: don't send a Cookie header with a string value of 'None'!"""
|
||||
page = json.loads(requests.get(httpbin('headers')).text)
|
||||
self.assertTrue('Cookie' not in page['headers'])
|
||||
|
||||
def test_secure_cookies(self):
|
||||
"""Test that secure cookies can only be sent via https."""
|
||||
header = "Set-Cookie: ThisIsA=SecureCookie; Path=/; Secure; HttpOnly"
|
||||
url = 'https://httpbin.org/response-headers?%s' % (requests.utils.quote(header),)
|
||||
cookies = requests.get(url, verify=False).cookies
|
||||
self.assertEqual(len(cookies), 1)
|
||||
self.assertEqual(list(cookies)[0].secure, True)
|
||||
|
||||
secure_resp = requests.get('https://httpbin.org/cookies', cookies=cookies, verify=False)
|
||||
secure_cookies_sent = json.loads(secure_resp.text)['cookies']
|
||||
self.assertEqual(secure_cookies_sent, {'ThisIsA': 'SecureCookie'})
|
||||
|
||||
insecure_resp = requests.get('http://httpbin.org/cookies', cookies=cookies)
|
||||
insecure_cookies_sent = json.loads(insecure_resp.text)['cookies']
|
||||
self.assertEqual(insecure_cookies_sent, {})
|
||||
|
||||
def test_disabled_cookie_persistence(self):
|
||||
"""Test that cookies are not persisted when configured accordingly."""
|
||||
|
||||
config = {'store_cookies' : False}
|
||||
|
||||
# Check the case when no cookie is passed as part of the request and the one in response is ignored
|
||||
cookies = requests.get(httpbin('cookies', 'set', 'key', 'value'), config = config).cookies
|
||||
self.assertTrue(cookies.get("key") is None)
|
||||
|
||||
# Test that the cookies passed while making the request still gets used and is available in response object.
|
||||
# only the ones received from server is not saved
|
||||
cookies_2 = requests.get(httpbin('cookies', 'set', 'key', 'value'), config = config,\
|
||||
cookies = {"key_2" : "value_2"}).cookies
|
||||
self.assertEqual(len(cookies_2), 1)
|
||||
self.assertEqual(cookies_2.get("key_2"), "value_2")
|
||||
|
||||
# Use the session and make sure that the received cookie is not used in subsequent calls
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies', 'set', 'key', 'value'), config = config)
|
||||
r = s.get(httpbin('cookies'))
|
||||
self.assertEqual(json.loads(r.text)['cookies'], {})
|
||||
|
||||
def test_jar_utility_functions(self):
|
||||
"""Test utility functions such as list_domains, list_paths, multiple_domains."""
|
||||
r = requests.get("http://github.com")
|
||||
c = r.cookies
|
||||
# github should send us cookies
|
||||
self.assertTrue(len(c) >= 1)
|
||||
self.assertEqual(len(c), len(r.cookies.keys()))
|
||||
self.assertEqual(len(c), len(r.cookies.values()))
|
||||
self.assertEqual(len(c), len(r.cookies.items()))
|
||||
|
||||
# domain and path utility functions
|
||||
domain = r.cookies.list_domains()[0]
|
||||
path = r.cookies.list_paths()[0]
|
||||
self.assertEqual(dict(r.cookies), r.cookies.get_dict(domain=domain, path=path))
|
||||
self.assertEqual(len(r.cookies.list_domains()), 1)
|
||||
self.assertEqual(len(r.cookies.list_paths()), 1)
|
||||
self.assertFalse(r.cookies.multiple_domains())
|
||||
|
||||
def test_convert_jar_to_dict(self):
|
||||
"""Test that keys, values, and items are defined and that we can convert
|
||||
cookie jars to plain old Python dicts."""
|
||||
r = requests.get(httpbin('cookies', 'set', 'myname', 'myvalue'))
|
||||
|
||||
# test keys, values, and items
|
||||
self.assertEqual(r.cookies.keys(), ['myname'])
|
||||
self.assertEqual(r.cookies.values(), ['myvalue'])
|
||||
self.assertEqual(r.cookies.items(), [('myname','myvalue')])
|
||||
|
||||
# test if we can convert jar to dict
|
||||
dictOfCookies = dict(r.cookies)
|
||||
self.assertEqual(dictOfCookies, {'myname':'myvalue'})
|
||||
self.assertEqual(dictOfCookies, r.cookies.get_dict())
|
||||
|
||||
class LWPCookieJarTest(TestBaseMixin, unittest.TestCase):
|
||||
"""Check store/load of cookies to FileCookieJar's, specifically LWPCookieJar's."""
|
||||
|
||||
COOKIEJAR_CLASS = cookielib.LWPCookieJar
|
||||
|
||||
def setUp(self):
|
||||
# blank the file
|
||||
self.cookiejar_file = tempfile.NamedTemporaryFile()
|
||||
self.cookiejar_filename = self.cookiejar_file.name
|
||||
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar.save()
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
self.cookiejar_file.close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def test_cookiejar_persistence(self):
|
||||
"""Test that we can save cookies to a FileCookieJar."""
|
||||
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar.load()
|
||||
# initially should be blank
|
||||
self.assertEqual(len(cookiejar), 0)
|
||||
|
||||
response = requests.get(httpbin('cookies', 'set', 'key', 'value'), cookies=cookiejar)
|
||||
self.assertEqual(len(cookiejar), 1)
|
||||
cookie = list(cookiejar)[0]
|
||||
self.assertEqual(json.loads(response.text)['cookies'], {'key': 'value'})
|
||||
self.assertCookieHas(cookie, name='key', value='value')
|
||||
|
||||
# save and reload the cookies from the file:
|
||||
cookiejar.save(ignore_discard=True)
|
||||
cookiejar_2 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar_2.load(ignore_discard=True)
|
||||
self.assertEqual(len(cookiejar_2), 1)
|
||||
cookie_2 = list(cookiejar_2)[0]
|
||||
# this cookie should have been saved with the correct domain restriction:
|
||||
self.assertCookieHas(cookie_2, name='key', value='value',
|
||||
domain='httpbin.org', path='/')
|
||||
|
||||
# httpbin sets session cookies, so if we don't ignore the discard attribute,
|
||||
# there should be no cookie:
|
||||
cookiejar_3 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar_3.load()
|
||||
self.assertEqual(len(cookiejar_3), 0)
|
||||
|
||||
def test_crossdomain(self):
|
||||
"""Test persistence of the domains associated with the cookies."""
|
||||
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar.load()
|
||||
self.assertEqual(len(cookiejar), 0)
|
||||
|
||||
# github sets a cookie
|
||||
requests.get("http://github.com", cookies=cookiejar)
|
||||
num_github_cookies = len(cookiejar)
|
||||
self.assertTrue(num_github_cookies >= 1)
|
||||
# httpbin sets another
|
||||
requests.get(httpbin('cookies', 'set', 'key', 'value'), cookies=cookiejar)
|
||||
num_total_cookies = len(cookiejar)
|
||||
self.assertTrue(num_total_cookies >= 2)
|
||||
self.assertTrue(num_total_cookies > num_github_cookies)
|
||||
|
||||
# save and load
|
||||
cookiejar.save(ignore_discard=True)
|
||||
cookiejar_2 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar_2.load(ignore_discard=True)
|
||||
self.assertEqual(len(cookiejar_2), num_total_cookies)
|
||||
r = requests.get(httpbin('cookies'), cookies=cookiejar_2)
|
||||
self.assertEqual(json.loads(r.text)['cookies'], {'key': 'value'})
|
||||
|
||||
def test_persistent_cookies(self):
|
||||
"""Test that we correctly interpret persistent cookies."""
|
||||
# httpbin's normal cookie methods don't send persistent cookies,
|
||||
# so cook up the appropriate header and force it to send
|
||||
header = "Set-Cookie: Persistent=CookiesAreScary; expires=Sun, 04-May-2032 04:56:50 GMT; path=/"
|
||||
url = httpbin('response-headers?%s' % (requests.utils.quote(header),))
|
||||
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
|
||||
requests.get(url, cookies=cookiejar)
|
||||
self.assertEqual(len(cookiejar), 1)
|
||||
self.assertCookieHas(list(cookiejar)[0], name='Persistent', value='CookiesAreScary')
|
||||
|
||||
requests.get(httpbin('cookies', 'set', 'ThisCookieIs', 'SessionOnly'), cookies=cookiejar)
|
||||
self.assertEqual(len(cookiejar), 2)
|
||||
self.assertEqual(len([c for c in cookiejar if c.name == 'Persistent']), 1)
|
||||
self.assertEqual(len([c for c in cookiejar if c.name == 'ThisCookieIs']), 1)
|
||||
|
||||
# save and load
|
||||
cookiejar.save()
|
||||
cookiejar_2 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
|
||||
cookiejar_2.load()
|
||||
# we should only load the persistent cookie
|
||||
self.assertEqual(len(cookiejar_2), 1)
|
||||
self.assertCookieHas(list(cookiejar_2)[0], name='Persistent', value='CookiesAreScary')
|
||||
|
||||
class MozCookieJarTest(LWPCookieJarTest):
|
||||
"""Same test, but substitute MozillaCookieJar."""
|
||||
|
||||
COOKIEJAR_CLASS = cookielib.MozillaCookieJar
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
945
tests/test_requests.py
Executable file
945
tests/test_requests.py
Executable file
@@ -0,0 +1,945 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# from __future__ import unicode_literals
|
||||
|
||||
# Path hack.
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
import pickle
|
||||
import tempfile
|
||||
|
||||
import requests
|
||||
from requests.compat import str, StringIO
|
||||
# import envoy
|
||||
from requests import HTTPError
|
||||
from requests import get, post, head, put
|
||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||
|
||||
if 'HTTPBIN_URL' not in os.environ:
|
||||
os.environ['HTTPBIN_URL'] = 'http://httpbin.org/'
|
||||
|
||||
HTTPBIN_URL = os.environ.get('HTTPBIN_URL')
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
|
||||
class TestSetup(object):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
|
||||
global _httpbin
|
||||
|
||||
if (not 'HTTPBIN_URL' in os.environ) and not _httpbin:
|
||||
# c = envoy.connect('httpbin %s' % (PORT))
|
||||
# time.sleep(1)
|
||||
_httpbin = True
|
||||
|
||||
class TestBaseMixin(object):
|
||||
|
||||
def assertCookieHas(self, cookie, **kwargs):
|
||||
"""Assert that a cookie has various specified properties."""
|
||||
for attr, expected_value in kwargs.items():
|
||||
cookie_attr = getattr(cookie, attr)
|
||||
message = 'Failed comparison for %s: %s != %s' % (attr, cookie_attr, expected_value)
|
||||
self.assertEqual(cookie_attr, expected_value, message)
|
||||
|
||||
class RequestsTestSuite(TestSetup, TestBaseMixin, unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, get, 'hiwpefhipowhefopw')
|
||||
|
||||
def test_path_is_not_double_encoded(self):
|
||||
request = requests.Request("http://0.0.0.0/get/test case")
|
||||
|
||||
self.assertEqual(request.path_url, "/get/test%20case")
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = get(httpbin('get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_response_sent(self):
|
||||
r = get(httpbin('get'))
|
||||
|
||||
self.assertTrue(r.request.sent)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
|
||||
assert heads['User-agent'] in r.text
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# def test_unicode_headers(self):
|
||||
# # Simply calling requests with a unicode instance should simply work
|
||||
# # when the characters are all representable using latin-1:
|
||||
# heads = { u'User-Agent': u'Requests Test Suite' }
|
||||
# requests.get(url=httpbin('get'), headers=heads)
|
||||
|
||||
# # Characters outside latin-1 should raise an exception:
|
||||
# heads = { u'User-Agent': u'\u30cd\u30c3\u30c8\u30ef\u30fc\u30af' }
|
||||
# self.assertRaises(UnicodeEncodeError, requests.get,
|
||||
# url=httpbin('get'), headers=heads)
|
||||
|
||||
# def test_session_with_escaped_url(self):
|
||||
# # Test a URL that contains percent-escaped characters
|
||||
# # This URL should not be modified (double-escaped)
|
||||
# # Tests:
|
||||
# # - Quoted illegal characters ("%20" (' '), "%3C" ('<'), "%3E" ('>'))
|
||||
# # - Quoted reserved characters ("%25" ('%'), "%23" ('#'), "%2F" ('/'))
|
||||
# # - Quoted non-ASCII characters ("%C3%98", "%C3%A5")
|
||||
# path_fully_escaped = '%3Ca%25b%23c%2Fd%3E/%C3%98%20%C3%A5'
|
||||
# url = httpbin('get/' + path_fully_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
|
||||
|
||||
# # Test that illegal characters in a path get properly percent-escaped
|
||||
# # Tests:
|
||||
# # - Bare illegal characters (space, '<')
|
||||
# # - Bare non-ASCII characters ('\u00d8')
|
||||
# path = u'<a%25b%23c%2Fd%3E/\u00d8 %C3%A5'
|
||||
# url = httpbin('get/' + path)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
|
||||
|
||||
# # Test that reserved characters in a path do not get percent-escaped
|
||||
# # Tests:
|
||||
# # - All reserved characters (RFC 3986), except '?', '#', '[' and ']',
|
||||
# # which are not allowed in the path, and ';' which delimits
|
||||
# # parameters.
|
||||
# # All such characters must be allowed bare in path, and must not be
|
||||
# # encoded.
|
||||
# # - Special unreserved characters (RFC 3986), which should not be
|
||||
# # encoded (even though it wouldn't hurt).
|
||||
# path_reserved = '!$&\'()*+,/:=@-._~'
|
||||
# url = httpbin('get/' + path_reserved)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_reserved))
|
||||
|
||||
# # Test that percent-encoded unreserved characters in a path get
|
||||
# # normalised to their un-encoded forms.
|
||||
# path_unreserved = 'ABCDwxyz1234-._~'
|
||||
# path_unreserved_escaped = '%41%42%43%44%77%78%79%7A%31%32%33%34%2D%2E%5F%7E'
|
||||
# url = httpbin('get/' + path_unreserved_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_unreserved))
|
||||
|
||||
# # Re-run all of the same tests on the query part of the URI
|
||||
# query_fully_escaped = '%3Ca%25b%23c%2Fd%3E=%C3%98%20%C3%A5'
|
||||
# url = httpbin('get/?' + query_fully_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
|
||||
|
||||
# query = u'<a%25b%23c%2Fd%3E=\u00d8 %C3%A5'
|
||||
# url = httpbin('get/?' + query)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
|
||||
|
||||
# # The legal characters in query happens to be the same as in path
|
||||
# query_reserved = '!$&\'()*+,/:=@-._~'
|
||||
# url = httpbin('get/?' + query_reserved)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_reserved))
|
||||
|
||||
# query_unreserved = 'ABCDwxyz=1234-._~'
|
||||
# query_unreserved_escaped = '%41%42%43%44%77%78%79%7A=%31%32%33%34%2D%2E%5F%7E'
|
||||
# url = httpbin('get/?' + query_unreserved_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_unreserved))
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
"""Issue XX"""
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
self.assertTrue(heads['User-agent'] in r.text)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
self.assertTrue(heads['user-agent'] in r.text)
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = head(httpbin('get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPBasicAuth('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
auth = ('user', 'pass')
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPDigestAuth('user', 'wrongpass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post1 = post(url).raise_for_status()
|
||||
|
||||
post1 = post(url, data={'some': 'data'})
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
with open(__file__) as f:
|
||||
post2 = post(url, files={'some': f})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = post(url, data='[{"some": "json"}]')
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
with open(__file__) as f:
|
||||
url = service('post')
|
||||
post1 = post(url,
|
||||
files={'some': f},
|
||||
data={'some': 'data'})
|
||||
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
with open(__file__) as f:
|
||||
|
||||
post2 = post(url,
|
||||
files={'some': f},
|
||||
headers={'User-Agent': 'requests-tests'})
|
||||
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_STRINGS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
post1 = post(url, files={'fname.txt': 'fdata'})
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
post2 = post(url, files={'fname.txt': 'fdata', 'fname2.txt':'more fdata'})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = post(url, files={'fname.txt': 'fdata', 'fname2.txt':open(__file__,'rb')})
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
post4 = post(url, files={'fname.txt': 'fdata'})
|
||||
self.assertEqual(post4.status_code, 200)
|
||||
|
||||
post5 = post(url, files={'file': ('file.txt', 'more fdata')})
|
||||
self.assertEqual(post5.status_code, 200)
|
||||
|
||||
post6 = post(url, files={'fname.txt': '\xe9'})
|
||||
self.assertEqual(post6.status_code, 200)
|
||||
|
||||
post7 = post(url, files={'fname.txt': 'fdata to verify'})
|
||||
rbody = json.loads(post7.text)
|
||||
self.assertTrue(rbody.get('files', None))
|
||||
self.assertTrue(rbody['files'].get('fname.txt'), None)
|
||||
self.assertEqual(rbody['files']['fname.txt'], 'fdata to verify')
|
||||
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
def test_request_ok_set(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('status', '404'))
|
||||
# print r.status_code
|
||||
# r.raise_for_status()
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
def test_status_raising(self):
|
||||
r = get(httpbin('status', '404'))
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
def test_default_status_raising(self):
|
||||
config = {'danger_mode': True}
|
||||
args = [httpbin('status', '404')]
|
||||
kwargs = dict(config=config)
|
||||
self.assertRaises(HTTPError, get, *args, **kwargs)
|
||||
|
||||
r = get(httpbin('status', '200'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
def test_response_has_unicode_url(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('get')
|
||||
|
||||
response = get(url)
|
||||
|
||||
assert isinstance(response.url, str)
|
||||
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/get')
|
||||
|
||||
get(url, params={'foo': 'føø'})
|
||||
get(url, params={'føø': 'føø'})
|
||||
get(url, params={'føø': 'føø'})
|
||||
get(url, params={'foo': 'foo'})
|
||||
get(service('ø'), params={'foo': 'foo'})
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = HTTPBasicAuth('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
|
||||
self.assertEqual(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
def test_file_post_data(self):
|
||||
|
||||
filecontent = b"fooaowpeufbarasjhf"
|
||||
testfile = tempfile.NamedTemporaryFile()
|
||||
testfile.write(filecontent)
|
||||
testfile.flush()
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data=open(testfile.name, "rb"),
|
||||
headers={"content-type": "application/octet-stream"})
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), filecontent.decode('ascii'))
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_nonurlencoded_postdata(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data="foobar")
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), 'foobar')
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('get'), params=dict(test=['foo', 'baz']))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), params=dict(test=['foo', 'baz']))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(
|
||||
service('post'),
|
||||
params=dict(test=['foo', 'baz']),
|
||||
data=dict(test2="foobar", test3=['foo', 'baz']))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
# print(r.text)
|
||||
# print('-----------------------')
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), dict(test2='foobar', test3=['foo', 'baz']))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(r.history), 0)
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(r.history), 0)
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('redirect', '3'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.history), 3)
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('relative-redirect', '3'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.history), 3)
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = requests.session()
|
||||
r = get(httpbin('get'), session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = get(httpbin('user-agent'), session=s)
|
||||
assert heads['User-agent'] in r1.text
|
||||
|
||||
r2 = get(httpbin('user-agent'), session=s)
|
||||
assert heads['User-agent'] in r2.text
|
||||
|
||||
new_heads = {'User-agent': 'blah'}
|
||||
r3 = get(httpbin('user-agent'), headers=new_heads, session=s)
|
||||
assert new_heads['User-agent'] in r3.text
|
||||
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
def test_single_hook(self):
|
||||
|
||||
def add_foo_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Foo': 'foo'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
for service in SERVICES:
|
||||
url = service('headers')
|
||||
response = get(url=url, hooks={'args': add_foo_header})
|
||||
|
||||
assert 'foo' in response.text
|
||||
|
||||
def test_multiple_hooks(self):
|
||||
|
||||
def add_foo_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Foo': 'foo'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
def add_bar_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Bar': 'bar'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
for service in SERVICES:
|
||||
url = service('headers')
|
||||
|
||||
response = get(url=url,
|
||||
hooks={
|
||||
'args': [add_foo_header, add_bar_header]
|
||||
}
|
||||
)
|
||||
|
||||
assert 'foo' in response.text
|
||||
assert 'bar' in response.text
|
||||
|
||||
def test_session_persistent_cookies(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
# Internally dispatched cookies are sent.
|
||||
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
|
||||
r = get(httpbin('cookies'), cookies=_c, session=s)
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
|
||||
# Those cookies persist transparently.
|
||||
c = json.loads(r.text).get('cookies')
|
||||
self.assertEqual(c, _c)
|
||||
|
||||
# Double check.
|
||||
r = get(httpbin('cookies'), cookies={}, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
self.assertEqual(c, _c)
|
||||
|
||||
# Remove a cookie by setting it's value to None.
|
||||
r = get(httpbin('cookies'), cookies={'bessie': None}, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
del _c['bessie']
|
||||
self.assertEqual(c, _c)
|
||||
|
||||
# Test session-level cookies.
|
||||
s = requests.session(cookies=_c)
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
self.assertEqual(c, _c)
|
||||
|
||||
# Have the server set a cookie.
|
||||
r = get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
# And server-set cookie persistience.
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
def test_session_persistent_params(self):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = get(httpbin('get'), session=s)
|
||||
assert params['a'] in r1.text
|
||||
|
||||
params2 = {'b': 'b_test'}
|
||||
|
||||
r2 = get(httpbin('get'), params=params2, session=s)
|
||||
assert params['a'] in r2.text
|
||||
assert params2['b'] in r2.text
|
||||
|
||||
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
|
||||
|
||||
r3 = get(httpbin('get'), params=params3, session=s)
|
||||
|
||||
assert not params['a'] in r3.text
|
||||
assert params3['b'] in r3.text
|
||||
assert params3['c'] in r3.text
|
||||
|
||||
def test_session_cookies_with_return_response_false(self):
|
||||
s = requests.session()
|
||||
# return_response=False as it does requests.async.get
|
||||
rq = get(httpbin('cookies', 'set', 'k', 'v'), return_response=False,
|
||||
allow_redirects=True, session=s)
|
||||
rq.send(prefetch=True)
|
||||
c = rq.response.json.get('cookies')
|
||||
assert 'k' in c
|
||||
assert 'k' in s.cookies
|
||||
|
||||
def test_session_pickling(self):
|
||||
|
||||
s = requests.session(
|
||||
headers={'header': 'value'},
|
||||
cookies={'a-cookie': 'cookie-value'},
|
||||
auth=('username', 'password'))
|
||||
|
||||
ds = pickle.loads(pickle.dumps(s))
|
||||
|
||||
self.assertEqual(s.headers, ds.headers)
|
||||
self.assertEqual(s.auth, ds.auth)
|
||||
|
||||
# Cookie doesn't have a good __eq__, so verify manually:
|
||||
self.assertEqual(len(ds.cookies), 1)
|
||||
for cookie in ds.cookies:
|
||||
self.assertCookieHas(cookie, name='a-cookie', value='cookie-value')
|
||||
|
||||
def test_unpickled_session_requests(self):
|
||||
s = requests.session()
|
||||
r = get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert 'k' in c
|
||||
|
||||
ds = pickle.loads(pickle.dumps(s))
|
||||
r = get(httpbin('cookies'), session=ds)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert 'k' in c
|
||||
|
||||
ds1 = pickle.loads(pickle.dumps(requests.session()))
|
||||
ds2 = pickle.loads(pickle.dumps(requests.session(prefetch=True)))
|
||||
assert not ds1.prefetch
|
||||
assert ds2.prefetch
|
||||
|
||||
def test_invalid_content(self):
|
||||
# WARNING: if you're using a terrible DNS provider (comcast),
|
||||
# this will fail.
|
||||
try:
|
||||
hah = 'http://somedomainthatclearlydoesntexistg.com'
|
||||
r = get(hah, allow_redirects=False)
|
||||
except requests.ConnectionError:
|
||||
pass # \o/
|
||||
else:
|
||||
assert False
|
||||
|
||||
config = {'safe_mode': True}
|
||||
r = get(hah, allow_redirects=False, config=config)
|
||||
assert r.content == None
|
||||
|
||||
def test_cached_response(self):
|
||||
|
||||
r1 = get(httpbin('get'), prefetch=False)
|
||||
assert not r1._content
|
||||
assert r1.content
|
||||
assert r1.text
|
||||
|
||||
r2 = get(httpbin('get'), prefetch=True)
|
||||
assert r2._content
|
||||
assert r2.content
|
||||
assert r2.text
|
||||
|
||||
def test_iter_lines(self):
|
||||
|
||||
lines = (0, 2, 10, 100)
|
||||
|
||||
for i in lines:
|
||||
r = get(httpbin('stream', str(i)), prefetch=False)
|
||||
lines = list(r.iter_lines())
|
||||
len_lines = len(lines)
|
||||
|
||||
self.assertEqual(i, len_lines)
|
||||
|
||||
# Tests that trailing whitespaces within lines do not get stripped.
|
||||
# Tests that a trailing non-terminated line does not get stripped.
|
||||
quote = (
|
||||
'''Agamemnon \n'''
|
||||
'''\tWhy will he not upon our fair request\r\n'''
|
||||
'''\tUntent his person and share the air with us?'''
|
||||
)
|
||||
|
||||
# Make a request and monkey-patch its contents
|
||||
r = get(httpbin('get'))
|
||||
r.raw = StringIO(quote)
|
||||
|
||||
lines = list(r.iter_lines())
|
||||
len_lines = len(lines)
|
||||
self.assertEqual(len_lines, 3)
|
||||
|
||||
joined = lines[0] + '\n' + lines[1] + '\r\n' + lines[2]
|
||||
self.assertEqual(joined, quote)
|
||||
|
||||
def test_safe_mode(self):
|
||||
|
||||
safe = requests.session(config=dict(safe_mode=True))
|
||||
|
||||
# Safe mode creates empty responses for failed requests.
|
||||
# Iterating on these responses should produce empty sequences
|
||||
r = get('http://_/', session=safe)
|
||||
self.assertEqual(list(r.iter_lines()), [])
|
||||
assert isinstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
r = get('http://_/', session=safe)
|
||||
self.assertEqual(list(r.iter_content()), [])
|
||||
assert isinstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
# When not in safe mode, should raise Timeout exception
|
||||
self.assertRaises(
|
||||
requests.exceptions.Timeout,
|
||||
get,
|
||||
httpbin('stream', '1000'), timeout=0.0001)
|
||||
|
||||
# In safe mode, should return a blank response
|
||||
r = get(httpbin('stream', '1000'), timeout=0.0001,
|
||||
config=dict(safe_mode=True))
|
||||
assert r.content is None
|
||||
assert isinstance(r.error, requests.exceptions.Timeout)
|
||||
|
||||
def test_upload_binary_data(self):
|
||||
|
||||
requests.get(httpbin('post'), auth=('a', 'b'), data='\xff')
|
||||
|
||||
def test_useful_exception_for_invalid_port(self):
|
||||
# If we pass a legitimate URL with an invalid port, we should fail.
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
get,
|
||||
'http://google.com:banana')
|
||||
|
||||
def test_useful_exception_for_invalid_scheme(self):
|
||||
|
||||
# If we pass a legitimate URL with a scheme not supported
|
||||
# by requests, we should fail.
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
get,
|
||||
'ftp://ftp.kernel.org/pub/')
|
||||
|
||||
def test_can_have_none_in_header_values(self):
|
||||
try:
|
||||
# Don't choke on headers with none in the value.
|
||||
requests.get(httpbin('headers'), headers={'Foo': None})
|
||||
except TypeError:
|
||||
self.fail()
|
||||
|
||||
def test_danger_mode_redirects(self):
|
||||
s = requests.session()
|
||||
s.config['danger_mode'] = True
|
||||
s.get(httpbin('redirect', '4'))
|
||||
|
||||
|
||||
def test_empty_response(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
r.text
|
||||
|
||||
def test_max_redirects(self):
|
||||
"""Test the max_redirects config variable, normally and under safe_mode."""
|
||||
def unsafe_callable():
|
||||
requests.get(httpbin('redirect', '3'), config=dict(max_redirects=2))
|
||||
self.assertRaises(requests.exceptions.TooManyRedirects, unsafe_callable)
|
||||
|
||||
# add safe mode
|
||||
response = requests.get(httpbin('redirect', '3'), config=dict(safe_mode=True, max_redirects=2))
|
||||
self.assertTrue(response.content is None)
|
||||
self.assertTrue(isinstance(response.error, requests.exceptions.TooManyRedirects))
|
||||
|
||||
def test_connection_keepalive_and_close(self):
|
||||
"""Test that we send 'Connection: close' when keep_alive is disabled."""
|
||||
# keep-alive should be on by default
|
||||
r1 = requests.get(httpbin('get'))
|
||||
# XXX due to proxying issues, test the header sent back by httpbin, rather than
|
||||
# the header reported in its message body. See kennethreitz/httpbin#46
|
||||
self.assertEqual(r1.headers['Connection'].lower(), 'keep-alive')
|
||||
|
||||
# but when we disable it, we should send a 'Connection: close'
|
||||
# and get the same back:
|
||||
r2 = requests.get(httpbin('get'), config=dict(keep_alive=False))
|
||||
self.assertEqual(r2.headers['Connection'].lower(), 'close')
|
||||
|
||||
def test_head_content(self):
|
||||
"""Test that empty bodies are properly supported."""
|
||||
|
||||
r = requests.head(httpbin('headers'))
|
||||
r.content
|
||||
r.text
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
131
tests/test_requests_ext.py
Normal file
131
tests/test_requests_ext.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
from requests.compat import is_py2, is_py3
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def test_addition(self):
|
||||
assert (1 + 1) == 2
|
||||
|
||||
|
||||
def test_ssl_hostname_ok(self):
|
||||
requests.get('https://github.com', verify=True)
|
||||
|
||||
|
||||
def test_ssl_hostname_not_ok(self):
|
||||
requests.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com')
|
||||
|
||||
|
||||
def test_ssl_hostname_session_not_ok(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, s.get, 'https://kennethreitz.com')
|
||||
|
||||
s.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
|
||||
def test_binary_post(self):
|
||||
'''We need to be careful how we build the utf-8 string since
|
||||
unicode literals are a syntax error in python3
|
||||
'''
|
||||
|
||||
if is_py2:
|
||||
# Blasphemy!
|
||||
utf8_string = eval("u'Smörgås'.encode('utf-8')")
|
||||
elif is_py3:
|
||||
utf8_string = 'Smörgås'.encode('utf-8')
|
||||
else:
|
||||
raise EnvironmentError('Flesh out this test for your environment.')
|
||||
requests.post('http://www.google.com/', data=utf8_string)
|
||||
|
||||
|
||||
|
||||
def test_unicode_error(self):
|
||||
url = 'http://blip.fm/~1abvfu'
|
||||
requests.get(url)
|
||||
|
||||
|
||||
def test_chunked_head_redirect(self):
|
||||
url = "http://t.co/NFrx0zLG"
|
||||
r = requests.head(url, allow_redirects=True)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_unicode_redirect(self):
|
||||
'''This url redirects to a location that has a nonstandard
|
||||
character in it, that breaks requests in python2.7
|
||||
|
||||
After some research, the cause was identified as an unintended
|
||||
sideeffect of overriding of str with unicode.
|
||||
|
||||
In the case that the redirected url is actually a malformed
|
||||
"bytes" object, i.e. a string with character c where
|
||||
ord(c) > 127,
|
||||
then unicode(url) breaks.
|
||||
'''
|
||||
r = requests.get('http://www.marketwire.com/mw/release.' +
|
||||
'do?id=1628202&sourceType=3')
|
||||
assert r.ok
|
||||
|
||||
def test_unicode_url_outright(self):
|
||||
'''This url visits in my browser'''
|
||||
r = requests.get('http://www.marketwire.com/press-release/' +
|
||||
'jp-morgan-behauptet-sich-der-spitze-euro' +
|
||||
'p%C3%A4ischer-anleihe-analysten-laut-umf' +
|
||||
'rageergebnissen-1628202.htm')
|
||||
assert r.ok
|
||||
|
||||
def test_redirect_encoding(self):
|
||||
'''This url redirects to
|
||||
http://www.dealipedia.com/deal_view_investment.php?r=20012'''
|
||||
|
||||
r = requests.get('http://feedproxy.google.com/~r/Dealipedia' +
|
||||
'News/~3/BQtUJRJeZlo/deal_view_investment.' +
|
||||
'php')
|
||||
assert r.ok
|
||||
|
||||
def test_cookies_on_redirects(self):
|
||||
"""Test interaction between cookie handling and redirection."""
|
||||
# get a cookie for tinyurl.com ONLY
|
||||
s = requests.session()
|
||||
s.get(url='http://tinyurl.com/preview.php?disable=1')
|
||||
# we should have set a cookie for tinyurl: preview=0
|
||||
self.assertIn('preview', s.cookies)
|
||||
self.assertEqual(s.cookies['preview'], '0')
|
||||
self.assertEqual(list(s.cookies)[0].name, 'preview')
|
||||
self.assertEqual(list(s.cookies)[0].domain, 'tinyurl.com')
|
||||
|
||||
# get cookies on another domain
|
||||
r2 = s.get(url='http://httpbin.org/cookies')
|
||||
# the cookie is not there
|
||||
self.assertNotIn('preview', json.loads(r2.text)['cookies'])
|
||||
|
||||
# this redirects to another domain, httpbin.org
|
||||
# cookies of the first domain should NOT be sent to the next one
|
||||
r3 = s.get(url='http://tinyurl.com/7zp3jnr')
|
||||
assert r3.url == 'http://httpbin.org/cookies'
|
||||
self.assertNotIn('preview', json.loads(r2.text)['cookies'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
31
tests/test_requests_https.py
Executable file
31
tests/test_requests_https.py
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys, os
|
||||
import json
|
||||
import unittest
|
||||
|
||||
# Path hack.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
import requests
|
||||
|
||||
class HTTPSTest(unittest.TestCase):
|
||||
"""Smoke test for https functionality."""
|
||||
|
||||
smoke_url = "https://github.com"
|
||||
|
||||
def perform_smoke_test(self, verify=False):
|
||||
result = requests.get(self.smoke_url, verify=verify)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_smoke(self):
|
||||
"""Smoke test without verification."""
|
||||
self.perform_smoke_test(verify=False)
|
||||
|
||||
def test_smoke_verified(self):
|
||||
"""Smoke test with SSL verification."""
|
||||
self.perform_smoke_test(verify=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user