Compare commits
991 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
373a34a144 | ||
|
|
9324c2a9b4 | ||
|
|
fb2931801e | ||
|
|
8ac6b266c9 | ||
|
|
efe5d13ed6 | ||
|
|
a9963e3841 | ||
|
|
f1d104e004 | ||
|
|
996f99b267 | ||
|
|
62d2ea8034 | ||
|
|
ed15c70cd0 | ||
|
|
cf6b20429a | ||
|
|
324336e7f6 | ||
|
|
d240b1fe15 | ||
|
|
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 | ||
|
|
8c90c610cd | ||
|
|
1a85e110b8 | ||
|
|
12fa108ede | ||
|
|
9c3ca6e740 | ||
|
|
12ada03548 | ||
|
|
daa4fbf4e2 | ||
|
|
6fefffd03e | ||
|
|
7ce305fa89 | ||
|
|
04cdd0f2e7 | ||
|
|
33ee732082 | ||
|
|
d0b96f8e3b | ||
|
|
cf36d8c753 | ||
|
|
bd562a7ab3 | ||
|
|
72c8163aee | ||
|
|
2bae57e0a6 | ||
|
|
f7968b6797 | ||
|
|
4cae63a297 | ||
|
|
628325ae90 | ||
|
|
b3e2410f44 | ||
|
|
a02189a8e9 | ||
|
|
d343fda7f0 | ||
|
|
f9bcf7c611 | ||
|
|
23b0535a0d | ||
|
|
32c7cbf1ad | ||
|
|
8fe6f8c618 | ||
|
|
80c55f5767 | ||
|
|
3efd5dbab4 | ||
|
|
eaf1264879 | ||
|
|
f5592ceb48 | ||
|
|
e32840f631 | ||
|
|
4596c02339 | ||
|
|
4fcf14e124 | ||
|
|
797301a63b | ||
|
|
3615cbc9df | ||
|
|
b8c55da48d | ||
|
|
5b54b3e53f | ||
|
|
e43b438729 | ||
|
|
3326030862 | ||
|
|
8836c4b12a | ||
|
|
3a4f6ba8b3 | ||
|
|
6ac1e02691 | ||
|
|
6140fac6aa | ||
|
|
a957bcfd7b | ||
|
|
73ed950e70 | ||
|
|
6ef5094be6 | ||
|
|
077758f713 | ||
|
|
8b9f689ecc | ||
|
|
428fc9c0bd | ||
|
|
d04f31b302 | ||
|
|
4673d3dfa7 | ||
|
|
a548b6249d | ||
|
|
a9822b0bb6 | ||
|
|
3d3f05884f | ||
|
|
50c39fd95a | ||
|
|
28a9534383 | ||
|
|
c108c5300f | ||
|
|
c60618ae01 | ||
|
|
0008b035e2 | ||
|
|
be69529dca | ||
|
|
dac050bf40 | ||
|
|
e7a7615e0d | ||
|
|
7c271a466f | ||
|
|
95336a0542 | ||
|
|
e257151ffc | ||
|
|
84a2b35b71 | ||
|
|
6b9cb059f6 | ||
|
|
5b5b3def00 | ||
|
|
84e89429f0 | ||
|
|
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 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,10 +1,10 @@
|
||||
.idea
|
||||
MANIFEST
|
||||
coverage.xml
|
||||
nosetests.xml
|
||||
pylint.txt
|
||||
*.pyc
|
||||
docs/_build
|
||||
toy.py
|
||||
.gitignore
|
||||
junit-report.xml
|
||||
junit-report.xml
|
||||
requests.egg-info/
|
||||
docs/_build
|
||||
*.pyc
|
||||
*.swp
|
||||
|
||||
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 simpleci
|
||||
install:
|
||||
- pip install nose
|
||||
- pip install . --use-mirrors
|
||||
@@ -7,6 +7,12 @@ Development Lead
|
||||
- Kenneth Reitz <me@kennethreitz.com>
|
||||
|
||||
|
||||
Urllib3
|
||||
```````
|
||||
|
||||
- Andrey Petrov <andrey.petrov@shazow.net>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
@@ -46,4 +52,48 @@ Patches and Suggestions
|
||||
- Josselin Jacquard
|
||||
- Travis N. Vaught
|
||||
- Fredrik Möllerstrand
|
||||
- Daniel Hengeveld
|
||||
- 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
|
||||
237
HISTORY.rst
237
HISTORY.rst
@@ -1,6 +1,243 @@
|
||||
.. :changelog:
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
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)
|
||||
++++++++++++++++++
|
||||
|
||||
* Digest authentication bugfix (attach query data to path)
|
||||
|
||||
0.7.5 (2011-11-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Response.content = None if there was an invalid repsonse.
|
||||
* Redirection auth handling.
|
||||
|
||||
0.7.4 (2011-10-26)
|
||||
++++++++++++++++++
|
||||
|
||||
* Session Hooks fix.
|
||||
|
||||
0.7.3 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
* Digest Auth fix.
|
||||
|
||||
|
||||
0.7.2 (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
|
||||
|
||||
31
Makefile
31
Makefile
@@ -1,11 +1,26 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
init:
|
||||
pip install -r reqs.txt
|
||||
python setup.py develop
|
||||
pip install -r requirements.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py
|
||||
nosetests ./tests/*
|
||||
|
||||
lazy:
|
||||
nosetests --with-color tests/test_requests.py
|
||||
|
||||
simple:
|
||||
nosetests tests/test_requests.py
|
||||
|
||||
server:
|
||||
gunicorn httpbin:app --bind=0.0.0.0:7077 &
|
||||
|
||||
ci: init
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
nosetests tests/test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
simpleci:
|
||||
nosetests tests/test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
stats:
|
||||
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
|
||||
@@ -13,4 +28,14 @@ stats:
|
||||
site:
|
||||
cd docs; make dirhtml
|
||||
|
||||
pyc:
|
||||
find . -name "*.pyc" -exec rm '{}' ';'
|
||||
|
||||
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.
|
||||
|
||||
74
README.rst
74
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,64 +22,31 @@ 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
|
||||
+ Simple HTTP Header Request Attachment
|
||||
+ Simple Data/Params Request Attachment
|
||||
+ Simple Multipart File Uploads
|
||||
+ CookieJar Support
|
||||
+ Redirection History
|
||||
+ Proxy Support
|
||||
+ Redirection Recursion Urllib Fix
|
||||
+ Auto Decompression of GZipped Content
|
||||
+ Unicode URL Support
|
||||
|
||||
- Simple Authentication
|
||||
+ Simple URL + HTTP Auth Registry
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
Installation
|
||||
@@ -97,8 +67,10 @@ But, you really shouldn't do that.
|
||||
Contribute
|
||||
----------
|
||||
|
||||
If you'd like to contribute, simply fork `the repository`_, commit your changes to the **develop** branch (or branch off of it), and send a pull request. Make sure you add yourself to AUTHORS_.
|
||||
|
||||
#. 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`_ 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_.
|
||||
|
||||
.. _`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:
|
||||
|
||||
|
||||
28
docs/_themes/kr/layout.html
vendored
28
docs/_themes/kr/layout.html
vendored
@@ -2,19 +2,35 @@
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
<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 %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
</div>
|
||||
<a href="https://github.com/kennethreitz/requests">
|
||||
<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" />
|
||||
<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 || [];
|
||||
@@ -44,4 +60,6 @@
|
||||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
103
docs/_themes/kr/static/flasky.css_t
vendored
103
docs/_themes/kr/static/flasky.css_t
vendored
@@ -387,12 +387,17 @@ a:hover tt {
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
@media screen and (max-width: 870px) {
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
@@ -415,6 +420,10 @@ a:hover tt {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -423,8 +432,100 @@ a:hover tt {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.github {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
70
docs/_themes/kr/static/small_flask.css
vendored
70
docs/_themes/kr/static/small_flask.css
vendored
@@ -1,70 +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;
|
||||
}
|
||||
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,12 +1,14 @@
|
||||
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.
|
||||
- `urbanairship-python <https://github.com/benjaminws/urbanairship-python>`_, a fork of the Urban Airship API wrapper.
|
||||
- `WhitespaceBot <https://github.com/Gunio/WhitespaceBot/>`_, a project that automatically forks repos, strips trailing whitespace, and sends a pull request.
|
||||
- `python-rexster <https://github.com/CulturePlex/python-rexster>`_, Rexter client that provides a simple interface for graph databases.
|
||||
- `daikon <https://github.com/neogenix/daikon>`_, a CLI for ElasticSearch.
|
||||
|
||||
Articles & Talks
|
||||
================
|
||||
@@ -16,6 +18,7 @@ Articles & Talks
|
||||
- `Issac Kelly's 'Consuming Web APIs' talk <http://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
|
||||
- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
|
||||
- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
|
||||
- `French blog post introducing Requests <http://www.nicosphere.net/requests-urllib2-de-python-simplifie-2432/>`_
|
||||
|
||||
|
||||
Integrations
|
||||
@@ -47,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,24 @@ 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`_ 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_.
|
||||
|
||||
Development dependencies
|
||||
------------------------
|
||||
|
||||
You'll need to install ``gunicorn`` and ``httpbin`` in order to run requests' test suite::
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
|
||||
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,11 +10,9 @@ 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.
|
||||
|
||||
@@ -25,31 +23,33 @@ 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,
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`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>`_,
|
||||
a U.S. Federal Institution,
|
||||
NIH,
|
||||
`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 +58,23 @@ 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
|
||||
|
||||
|
||||
User Guide
|
||||
@@ -85,7 +100,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 +126,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,20 +37,80 @@ 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
|
||||
Requests has first-class support for concurrent requests, powered by gevent.
|
||||
This allows you to send a bunch of HTTP requests at the same time.
|
||||
|
||||
First, let's import the async module. Heads up — if you don't have
|
||||
`gevent <gevent>`_ this will fail::
|
||||
`gevent <http://pypi.python.org/pypi/gevent>`_ this will fail::
|
||||
|
||||
from requests import async
|
||||
|
||||
@@ -74,9 +134,16 @@ 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)
|
||||
>>> responses = async.map(rs)
|
||||
>>> responses
|
||||
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
|
||||
|
||||
.. admonition:: Throttling
|
||||
|
||||
The ``map`` function also takes a ``size`` parameter, that specifies the number of connections to make at a time::
|
||||
|
||||
async.map(rs, size=5)
|
||||
|
||||
|
||||
Event Hooks
|
||||
-----------
|
||||
@@ -121,14 +188,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 +207,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 +229,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 +288,34 @@ 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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -30,15 +30,198 @@ Let's get GitHub's public timeline ::
|
||||
Now, we have a :class:`Response` object called ``r``. We can get all the
|
||||
information we need from this.
|
||||
|
||||
Typically, you want to send some sort of data in the urls query string.
|
||||
To do this, simply pass a dictionary to the `params` argument. Your
|
||||
dictionary of data will automatically be encoded when the request is made::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
>>> r = requests.get("http://httpbin.org/get", params=payload)
|
||||
>>> print r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"args": {
|
||||
"key2": "value2",
|
||||
"key1": "value1"
|
||||
},
|
||||
"url": "http://httpbin.org/get",
|
||||
"headers": {
|
||||
"Connections": "keep-alive",
|
||||
"Content-Length": "",
|
||||
"Accept-Encoding": "identity, deflate, compress, gzip",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "python-requests/0.11.0",
|
||||
"Host": httpbin.org",
|
||||
"Content-Type": ""
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
Response Content
|
||||
----------------
|
||||
|
||||
We can read the content of the server's response::
|
||||
|
||||
>>> r.content
|
||||
>>> 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
|
||||
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))
|
||||
|
||||
|
||||
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'
|
||||
|
||||
|
||||
|
||||
Make a POST Request
|
||||
-------------------
|
||||
|
||||
POST requests are equally simple::
|
||||
|
||||
r = requests.post("http://httpbin.org/post")
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {},
|
||||
"form": {
|
||||
"key2": "value2",
|
||||
"key1": "value1"
|
||||
},
|
||||
"url": "http://httpbin.org/post",
|
||||
"args": {},
|
||||
"headers": {
|
||||
"Content-Length": "23",
|
||||
"Accept-Encoding": "identity, deflate, compress, gzip",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "python-requests/0.8.0",
|
||||
"Host": "127.0.0.1:7077",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
"data": ""
|
||||
}
|
||||
|
||||
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::
|
||||
|
||||
url = 'https://api.github.com/some/endpoint'
|
||||
payload = {'some': 'data'}
|
||||
|
||||
r = requests.post(url, data=json.dumps(payload))
|
||||
|
||||
|
||||
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::
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {
|
||||
"report.xls": "<censored...binary...data>"
|
||||
},
|
||||
"form": {},
|
||||
"url": "http://httpbin.org/post",
|
||||
"args": {},
|
||||
"headers": {
|
||||
"Content-Length": "3196",
|
||||
"Accept-Encoding": "identity, deflate, compress, gzip",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "python-requests/0.8.0",
|
||||
"Host": "httpbin.org:80",
|
||||
"Content-Type": "multipart/form-data; boundary=127.0.0.1.502.21746.1321131593.786.1"
|
||||
},
|
||||
"data": ""
|
||||
}
|
||||
|
||||
Setting filename explicitly::
|
||||
|
||||
>>> url = 'http://httpbin.org/post'
|
||||
>>> files = {'file': ('report.xls', open('report.xls', 'rb'))}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {
|
||||
"file": "<censored...binary...data>"
|
||||
},
|
||||
"form": {},
|
||||
"url": "http://httpbin.org/post",
|
||||
"args": {},
|
||||
"headers": {
|
||||
"Content-Length": "3196",
|
||||
"Accept-Encoding": "identity, deflate, compress, gzip",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "python-requests/0.8.0",
|
||||
"Host": "httpbin.org:80",
|
||||
"Content-Type": "multipart/form-data; boundary=127.0.0.1.502.21746.1321131593.786.1"
|
||||
},
|
||||
"data": ""
|
||||
}
|
||||
|
||||
|
||||
Response Status Codes
|
||||
---------------------
|
||||
@@ -54,7 +237,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')
|
||||
@@ -119,13 +302,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,7 +312,7 @@ parameter::
|
||||
>>> cookies = dict(cookies_are='working')
|
||||
|
||||
>>> r = requests.get(url, cookies=cookies)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
|
||||
|
||||
@@ -142,26 +320,113 @@ Basic Authentication
|
||||
--------------------
|
||||
|
||||
Most web services require authentication. There many different types of
|
||||
authentication, but the most common is called HTTP Basic Auth.
|
||||
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
|
||||
-----------------------
|
||||
|
||||
Ready for more? Check out the advanced_ section.
|
||||
Requests will automatically perform location redirection while using idempotent methods.
|
||||
|
||||
GitHub redirects all HTTP requests to HTTPS. Let's see what happens::
|
||||
|
||||
>>> 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, HEAD, 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, *&c*, you can also explicitly 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.2'
|
||||
__build__ = 0x000702
|
||||
__version__ = '0.12.0'
|
||||
__build__ = 0x001200
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2012 Kenneth Reitz'
|
||||
|
||||
|
||||
from . import utils
|
||||
from .models import HTTPError, Request, Response
|
||||
from .api import request, get, head, post, patch, put, delete
|
||||
from .sessions import session
|
||||
from .models import Request, Response
|
||||
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,36 +6,38 @@ 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
|
||||
|
||||
__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete')
|
||||
def request(method, url, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('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 session: (optional) A :class:`Session` object to be used for the request.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
: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.
|
||||
"""
|
||||
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -43,52 +45,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)
|
||||
@@ -98,7 +111,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)
|
||||
|
||||
@@ -12,19 +12,19 @@ by gevent. All API methods return a ``Request`` instance (as opposed to
|
||||
try:
|
||||
import gevent
|
||||
from gevent import monkey as curious_george
|
||||
from gevent.pool import Pool
|
||||
except ImportError:
|
||||
raise RuntimeError('Gevent is required for requests.async.')
|
||||
|
||||
# Monkey-patch.
|
||||
curious_george.patch_all(thread=False)
|
||||
curious_george.patch_all(thread=False, select=False)
|
||||
|
||||
from . import api
|
||||
from .hooks import dispatch_hook
|
||||
|
||||
|
||||
__all__ = (
|
||||
'map',
|
||||
'get', 'head', 'post', 'put', 'patch', 'delete', 'request'
|
||||
'map', 'imap',
|
||||
'get', 'options', 'head', 'post', 'put', 'patch', 'delete', 'request'
|
||||
)
|
||||
|
||||
|
||||
@@ -32,24 +32,34 @@ def patched(f):
|
||||
"""Patches a given API function to not send."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
return f(*args, return_response=False, **kwargs)
|
||||
|
||||
kwargs['return_response'] = False
|
||||
kwargs['prefetch'] = True
|
||||
|
||||
config = kwargs.get('config', {})
|
||||
config.update(safe_mode=True)
|
||||
|
||||
kwargs['config'] = config
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
def send(r, pool=None, prefetch=False):
|
||||
"""Sends the request object using the specified pool. If a pool isn't
|
||||
specified this method blocks. Pools are useful because you can specify size
|
||||
and can hence limit concurrency."""
|
||||
|
||||
if pools:
|
||||
r._pools = pools
|
||||
if pool != None:
|
||||
return pool.spawn(r.send, prefetch=prefetch)
|
||||
|
||||
r.send()
|
||||
|
||||
return r.response
|
||||
return gevent.spawn(r.send, prefetch=prefetch)
|
||||
|
||||
|
||||
# Patched requests.api functions.
|
||||
get = patched(api.get)
|
||||
options = patched(api.options)
|
||||
head = patched(api.head)
|
||||
post = patched(api.post)
|
||||
put = patched(api.put)
|
||||
@@ -58,21 +68,39 @@ delete = patched(api.delete)
|
||||
request = patched(api.request)
|
||||
|
||||
|
||||
def map(requests, prefetch=True):
|
||||
def map(requests, prefetch=True, size=None):
|
||||
"""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.
|
||||
:param size: Specifies the number of requests to make at a time. If None, no throttling occurs.
|
||||
"""
|
||||
|
||||
jobs = [gevent.spawn(send, r) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
requests = list(requests)
|
||||
|
||||
if prefetch:
|
||||
[r.response.content for r in requests]
|
||||
pool = Pool(size) if size else None
|
||||
jobs = [send(r, pool, prefetch=prefetch) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
return [r.response for r in requests]
|
||||
|
||||
|
||||
def imap(requests, prefetch=True, size=2):
|
||||
"""Concurrently converts a generator object of Requests to
|
||||
a generator of Responses.
|
||||
|
||||
:param requests: a generator of Request objects.
|
||||
:param prefetch: If False, the content will not be downloaded immediately.
|
||||
:param size: Specifies the number of requests to make at a time. default is 2
|
||||
"""
|
||||
|
||||
pool = Pool(size)
|
||||
|
||||
def send(r):
|
||||
r.send(prefetch)
|
||||
return r.response
|
||||
|
||||
for r in pool.imap_unordered(send, requests):
|
||||
yield r
|
||||
|
||||
pool.join()
|
||||
174
requests/auth.py
174
requests/auth.py
@@ -11,33 +11,92 @@ import time
|
||||
import hashlib
|
||||
|
||||
from base64 import b64encode
|
||||
from urlparse import urlparse
|
||||
|
||||
from .compat import urlparse, str, is_py2
|
||||
from .utils import randombytes, parse_dict_header
|
||||
|
||||
try:
|
||||
from oauthlib.oauth1.rfc5849 import (Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER)
|
||||
from oauthlib.common import extract_params
|
||||
except (ImportError, SyntaxError):
|
||||
SIGNATURE_HMAC = None
|
||||
SIGNATURE_TYPE_AUTH_HEADER = None
|
||||
|
||||
def http_basic(r, username, password):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object.
|
||||
Arguments should be considered non-positional.
|
||||
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
||||
|
||||
"""
|
||||
username = str(username)
|
||||
password = str(password)
|
||||
def _basic_auth_str(username, password):
|
||||
"""Returns a Basic Auth string."""
|
||||
|
||||
auth_s = b64encode('%s:%s' % (username, password))
|
||||
r.headers['Authorization'] = ('Basic %s' % auth_s)
|
||||
|
||||
return r
|
||||
return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1')
|
||||
|
||||
|
||||
def http_digest(r, username, password):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object.
|
||||
Arguments should be considered non-positional.
|
||||
"""
|
||||
class AuthBase(object):
|
||||
"""Base class that all auth implementations derive from"""
|
||||
|
||||
def handle_401(r):
|
||||
def __call__(self, r):
|
||||
raise NotImplementedError('Auth hooks must be callable.')
|
||||
|
||||
|
||||
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):
|
||||
|
||||
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):
|
||||
contenttype = r.headers.get('Content-Type', None)
|
||||
decoded_body = extract_params(r.data)
|
||||
if contenttype == None and decoded_body != None:
|
||||
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)
|
||||
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,21 +115,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
|
||||
path = urlparse(r.request.url).path
|
||||
p_parsed = urlparse(r.request.url)
|
||||
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:
|
||||
@@ -80,21 +150,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 += randombytes(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:
|
||||
@@ -103,49 +175,15 @@ 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
|
||||
_r.history.append(r)
|
||||
print _r.status_code
|
||||
|
||||
# r.request.response
|
||||
|
||||
print locals()
|
||||
|
||||
print _r.headers
|
||||
return _r
|
||||
|
||||
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)
|
||||
|
||||
264
requests/cookies.py
Normal file
264
requests/cookies.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
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,
|
||||
# and in safe mode, it may be None if the request didn't actually complete.
|
||||
# in that case, just skip the cookie extraction.
|
||||
if response._original_response is not None:
|
||||
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 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, domain=None, path=None, default=None):
|
||||
try:
|
||||
return self._find(name, domain, path)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
# 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 __getitem__(self, name):
|
||||
return self._find(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
self.set(name, value)
|
||||
|
||||
def __delitem__(self, name):
|
||||
remove_cookie_by_name(self, name)
|
||||
|
||||
def _find(self, name, domain=None, path=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:
|
||||
return cookie.value
|
||||
|
||||
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
# remove the unpickleable RLock object
|
||||
state.pop('_cookies_lock')
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
if '_cookies_lock' not in self.__dict__:
|
||||
self._cookies_lock = threading.RLock()
|
||||
|
||||
def copy(self):
|
||||
"""We're probably better off forbidding this."""
|
||||
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,25 @@ 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).
|
||||
|
||||
"""
|
||||
|
||||
SCHEMAS = ['http', 'https']
|
||||
|
||||
from . import __version__
|
||||
|
||||
defaults = dict()
|
||||
@@ -26,13 +33,19 @@ defaults = dict()
|
||||
defaults['base_headers'] = {
|
||||
'User-Agent': 'python-requests/%s' % __version__,
|
||||
'Accept-Encoding': ', '.join(('identity', 'deflate', 'compress', 'gzip')),
|
||||
'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
|
||||
|
||||
|
||||
|
||||
@@ -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,7 +25,10 @@ Available hooks:
|
||||
|
||||
"""
|
||||
|
||||
import warnings
|
||||
import traceback
|
||||
|
||||
|
||||
HOOKS = ('args', 'pre_request', 'pre_send', 'post_request', 'response')
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
@@ -31,10 +37,15 @@ def dispatch_hook(key, hooks, hook_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) or 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
|
||||
|
||||
3
requests/packages/oreos/__init__.py
Normal file
3
requests/packages/oreos/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .core import dict_from_string
|
||||
24
requests/packages/oreos/core.py
Normal file
24
requests/packages/oreos/core.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
oreos.core
|
||||
~~~~~~~~~~
|
||||
|
||||
The creamy white center.
|
||||
"""
|
||||
|
||||
from .monkeys import SimpleCookie
|
||||
|
||||
|
||||
def dict_from_string(s):
|
||||
"""Returns a MultiDict with Cookies."""
|
||||
|
||||
cookies = dict()
|
||||
|
||||
c = SimpleCookie()
|
||||
c.load(s)
|
||||
|
||||
for k,v in c.items():
|
||||
cookies.update({k: v.value})
|
||||
|
||||
return cookies
|
||||
773
requests/packages/oreos/monkeys.py
Normal file
773
requests/packages/oreos/monkeys.py
Normal file
@@ -0,0 +1,773 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
oreos.monkeys
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Monkeypatches.
|
||||
"""
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
|
||||
####
|
||||
# Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software
|
||||
# and its documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appear in all
|
||||
# copies and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of
|
||||
# Timothy O'Malley not be used in advertising or publicity
|
||||
# pertaining to distribution of the software without specific, written
|
||||
# prior permission.
|
||||
#
|
||||
# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
||||
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
|
||||
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
||||
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
####
|
||||
#
|
||||
# Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp
|
||||
# by Timothy O'Malley <timo@alum.mit.edu>
|
||||
#
|
||||
# Cookie.py is a Python module for the handling of HTTP
|
||||
# cookies as a Python dictionary. See RFC 2109 for more
|
||||
# information on cookies.
|
||||
#
|
||||
# The original idea to treat Cookies as a dictionary came from
|
||||
# Dave Mitchell (davem@magnet.com) in 1995, when he released the
|
||||
# first version of nscookie.py.
|
||||
#
|
||||
####
|
||||
|
||||
r"""
|
||||
Here's a sample session to show how to use this module.
|
||||
At the moment, this is the only documentation.
|
||||
|
||||
The Basics
|
||||
----------
|
||||
|
||||
Importing is easy..
|
||||
|
||||
>>> import Cookie
|
||||
|
||||
Most of the time you start by creating a cookie. Cookies come in
|
||||
three flavors, each with slightly different encoding semantics, but
|
||||
more on that later.
|
||||
|
||||
>>> C = Cookie.SimpleCookie()
|
||||
>>> C = Cookie.SerialCookie()
|
||||
>>> C = Cookie.SmartCookie()
|
||||
|
||||
[Note: Long-time users of Cookie.py will remember using
|
||||
Cookie.Cookie() to create an Cookie object. Although deprecated, it
|
||||
is still supported by the code. See the Backward Compatibility notes
|
||||
for more information.]
|
||||
|
||||
Once you've created your Cookie, you can add values just as if it were
|
||||
a dictionary.
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C["fig"] = "newton"
|
||||
>>> C["sugar"] = "wafer"
|
||||
>>> C.output()
|
||||
'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer'
|
||||
|
||||
Notice that the printable representation of a Cookie is the
|
||||
appropriate format for a Set-Cookie: header. This is the
|
||||
default behavior. You can change the header and printed
|
||||
attributes by using the .output() function
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C["rocky"] = "road"
|
||||
>>> C["rocky"]["path"] = "/cookie"
|
||||
>>> print C.output(header="Cookie:")
|
||||
Cookie: rocky=road; Path=/cookie
|
||||
>>> print C.output(attrs=[], header="Cookie:")
|
||||
Cookie: rocky=road
|
||||
|
||||
The load() method of a Cookie extracts cookies from a string. In a
|
||||
CGI script, you would use this method to extract the cookies from the
|
||||
HTTP_COOKIE environment variable.
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C.load("chips=ahoy; vienna=finger")
|
||||
>>> C.output()
|
||||
'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger'
|
||||
|
||||
The load() method is darn-tootin smart about identifying cookies
|
||||
within a string. Escaped quotation marks, nested semicolons, and other
|
||||
such trickeries do not confuse it.
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
|
||||
>>> print C
|
||||
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
|
||||
|
||||
Each element of the Cookie also supports all of the RFC 2109
|
||||
Cookie attributes. Here's an example which sets the Path
|
||||
attribute.
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C["oreo"] = "doublestuff"
|
||||
>>> C["oreo"]["path"] = "/"
|
||||
>>> print C
|
||||
Set-Cookie: oreo=doublestuff; Path=/
|
||||
|
||||
Each dictionary element has a 'value' attribute, which gives you
|
||||
back the value associated with the key.
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C["twix"] = "none for you"
|
||||
>>> C["twix"].value
|
||||
'none for you'
|
||||
|
||||
|
||||
A Bit More Advanced
|
||||
-------------------
|
||||
|
||||
As mentioned before, there are three different flavors of Cookie
|
||||
objects, each with different encoding/decoding semantics. This
|
||||
section briefly discusses the differences.
|
||||
|
||||
SimpleCookie
|
||||
|
||||
The SimpleCookie expects that all values should be standard strings.
|
||||
Just to be sure, SimpleCookie invokes the str() builtin to convert
|
||||
the value to a string, when the values are set dictionary-style.
|
||||
|
||||
>>> C = Cookie.SimpleCookie()
|
||||
>>> C["number"] = 7
|
||||
>>> C["string"] = "seven"
|
||||
>>> C["number"].value
|
||||
'7'
|
||||
>>> C["string"].value
|
||||
'seven'
|
||||
>>> C.output()
|
||||
'Set-Cookie: number=7\r\nSet-Cookie: string=seven'
|
||||
|
||||
|
||||
SerialCookie
|
||||
|
||||
The SerialCookie expects that all values should be serialized using
|
||||
cPickle (or pickle, if cPickle isn't available). As a result of
|
||||
serializing, SerialCookie can save almost any Python object to a
|
||||
value, and recover the exact same object when the cookie has been
|
||||
returned. (SerialCookie can yield some strange-looking cookie
|
||||
values, however.)
|
||||
|
||||
>>> C = Cookie.SerialCookie()
|
||||
>>> C["number"] = 7
|
||||
>>> C["string"] = "seven"
|
||||
>>> C["number"].value
|
||||
7
|
||||
>>> C["string"].value
|
||||
'seven'
|
||||
>>> C.output()
|
||||
'Set-Cookie: number="I7\\012."\r\nSet-Cookie: string="S\'seven\'\\012p1\\012."'
|
||||
|
||||
Be warned, however, if SerialCookie cannot de-serialize a value (because
|
||||
it isn't a valid pickle'd object), IT WILL RAISE AN EXCEPTION.
|
||||
|
||||
|
||||
SmartCookie
|
||||
|
||||
The SmartCookie combines aspects of each of the other two flavors.
|
||||
When setting a value in a dictionary-fashion, the SmartCookie will
|
||||
serialize (ala cPickle) the value *if and only if* it isn't a
|
||||
Python string. String objects are *not* serialized. Similarly,
|
||||
when the load() method parses out values, it attempts to de-serialize
|
||||
the value. If it fails, then it fallsback to treating the value
|
||||
as a string.
|
||||
|
||||
>>> C = Cookie.SmartCookie()
|
||||
>>> C["number"] = 7
|
||||
>>> C["string"] = "seven"
|
||||
>>> C["number"].value
|
||||
7
|
||||
>>> C["string"].value
|
||||
'seven'
|
||||
>>> C.output()
|
||||
'Set-Cookie: number="I7\\012."\r\nSet-Cookie: string=seven'
|
||||
|
||||
|
||||
Backwards Compatibility
|
||||
-----------------------
|
||||
|
||||
In order to keep compatibilty with earlier versions of Cookie.py,
|
||||
it is still possible to use Cookie.Cookie() to create a Cookie. In
|
||||
fact, this simply returns a SmartCookie.
|
||||
|
||||
>>> C = Cookie.Cookie()
|
||||
>>> print C.__class__.__name__
|
||||
SmartCookie
|
||||
|
||||
|
||||
Finis.
|
||||
""" #"
|
||||
# ^
|
||||
# |----helps out font-lock
|
||||
|
||||
#
|
||||
# Import our required modules
|
||||
#
|
||||
import string
|
||||
|
||||
try:
|
||||
from cPickle import dumps, loads
|
||||
except ImportError:
|
||||
from pickle import dumps, loads
|
||||
|
||||
import re, warnings
|
||||
|
||||
__all__ = ["CookieError","BaseCookie","SimpleCookie","SerialCookie",
|
||||
"SmartCookie","Cookie"]
|
||||
|
||||
_nulljoin = ''.join
|
||||
_semispacejoin = '; '.join
|
||||
_spacejoin = ' '.join
|
||||
|
||||
#
|
||||
# Define an exception visible to External modules
|
||||
#
|
||||
class CookieError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# These quoting routines conform to the RFC2109 specification, which in
|
||||
# turn references the character definitions from RFC2068. They provide
|
||||
# a two-way quoting algorithm. Any non-text character is translated
|
||||
# into a 4 character sequence: a forward-slash followed by the
|
||||
# three-digit octal equivalent of the character. Any '\' or '"' is
|
||||
# quoted with a preceeding '\' slash.
|
||||
#
|
||||
# These are taken from RFC2068 and RFC2109.
|
||||
# _RFC2965Forbidden is the list of forbidden chars we accept anyway
|
||||
# _LegalChars is the list of chars which don't require "'s
|
||||
# _Translator hash-table for fast quoting
|
||||
#
|
||||
_RFC2965Forbidden = "[]:{}="
|
||||
_LegalChars = ( string.ascii_letters + string.digits +
|
||||
"!#$%&'*+-.^_`|~_@" + _RFC2965Forbidden )
|
||||
_Translator = {
|
||||
'\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
|
||||
'\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
|
||||
'\006' : '\\006', '\007' : '\\007', '\010' : '\\010',
|
||||
'\011' : '\\011', '\012' : '\\012', '\013' : '\\013',
|
||||
'\014' : '\\014', '\015' : '\\015', '\016' : '\\016',
|
||||
'\017' : '\\017', '\020' : '\\020', '\021' : '\\021',
|
||||
'\022' : '\\022', '\023' : '\\023', '\024' : '\\024',
|
||||
'\025' : '\\025', '\026' : '\\026', '\027' : '\\027',
|
||||
'\030' : '\\030', '\031' : '\\031', '\032' : '\\032',
|
||||
'\033' : '\\033', '\034' : '\\034', '\035' : '\\035',
|
||||
'\036' : '\\036', '\037' : '\\037',
|
||||
|
||||
# Because of the way browsers really handle cookies (as opposed
|
||||
# to what the RFC says) we also encode , and ;
|
||||
|
||||
',' : '\\054', ';' : '\\073',
|
||||
|
||||
'"' : '\\"', '\\' : '\\\\',
|
||||
|
||||
'\177' : '\\177', '\200' : '\\200', '\201' : '\\201',
|
||||
'\202' : '\\202', '\203' : '\\203', '\204' : '\\204',
|
||||
'\205' : '\\205', '\206' : '\\206', '\207' : '\\207',
|
||||
'\210' : '\\210', '\211' : '\\211', '\212' : '\\212',
|
||||
'\213' : '\\213', '\214' : '\\214', '\215' : '\\215',
|
||||
'\216' : '\\216', '\217' : '\\217', '\220' : '\\220',
|
||||
'\221' : '\\221', '\222' : '\\222', '\223' : '\\223',
|
||||
'\224' : '\\224', '\225' : '\\225', '\226' : '\\226',
|
||||
'\227' : '\\227', '\230' : '\\230', '\231' : '\\231',
|
||||
'\232' : '\\232', '\233' : '\\233', '\234' : '\\234',
|
||||
'\235' : '\\235', '\236' : '\\236', '\237' : '\\237',
|
||||
'\240' : '\\240', '\241' : '\\241', '\242' : '\\242',
|
||||
'\243' : '\\243', '\244' : '\\244', '\245' : '\\245',
|
||||
'\246' : '\\246', '\247' : '\\247', '\250' : '\\250',
|
||||
'\251' : '\\251', '\252' : '\\252', '\253' : '\\253',
|
||||
'\254' : '\\254', '\255' : '\\255', '\256' : '\\256',
|
||||
'\257' : '\\257', '\260' : '\\260', '\261' : '\\261',
|
||||
'\262' : '\\262', '\263' : '\\263', '\264' : '\\264',
|
||||
'\265' : '\\265', '\266' : '\\266', '\267' : '\\267',
|
||||
'\270' : '\\270', '\271' : '\\271', '\272' : '\\272',
|
||||
'\273' : '\\273', '\274' : '\\274', '\275' : '\\275',
|
||||
'\276' : '\\276', '\277' : '\\277', '\300' : '\\300',
|
||||
'\301' : '\\301', '\302' : '\\302', '\303' : '\\303',
|
||||
'\304' : '\\304', '\305' : '\\305', '\306' : '\\306',
|
||||
'\307' : '\\307', '\310' : '\\310', '\311' : '\\311',
|
||||
'\312' : '\\312', '\313' : '\\313', '\314' : '\\314',
|
||||
'\315' : '\\315', '\316' : '\\316', '\317' : '\\317',
|
||||
'\320' : '\\320', '\321' : '\\321', '\322' : '\\322',
|
||||
'\323' : '\\323', '\324' : '\\324', '\325' : '\\325',
|
||||
'\326' : '\\326', '\327' : '\\327', '\330' : '\\330',
|
||||
'\331' : '\\331', '\332' : '\\332', '\333' : '\\333',
|
||||
'\334' : '\\334', '\335' : '\\335', '\336' : '\\336',
|
||||
'\337' : '\\337', '\340' : '\\340', '\341' : '\\341',
|
||||
'\342' : '\\342', '\343' : '\\343', '\344' : '\\344',
|
||||
'\345' : '\\345', '\346' : '\\346', '\347' : '\\347',
|
||||
'\350' : '\\350', '\351' : '\\351', '\352' : '\\352',
|
||||
'\353' : '\\353', '\354' : '\\354', '\355' : '\\355',
|
||||
'\356' : '\\356', '\357' : '\\357', '\360' : '\\360',
|
||||
'\361' : '\\361', '\362' : '\\362', '\363' : '\\363',
|
||||
'\364' : '\\364', '\365' : '\\365', '\366' : '\\366',
|
||||
'\367' : '\\367', '\370' : '\\370', '\371' : '\\371',
|
||||
'\372' : '\\372', '\373' : '\\373', '\374' : '\\374',
|
||||
'\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
|
||||
}
|
||||
|
||||
_idmap = ''.join(chr(x) for x in range(256))
|
||||
|
||||
def _quote(str, LegalChars=_LegalChars,
|
||||
idmap=_idmap, translate=string.translate):
|
||||
#
|
||||
# If the string does not need to be double-quoted,
|
||||
# then just return the string. Otherwise, surround
|
||||
# the string in doublequotes and precede quote (with a \)
|
||||
# special characters.
|
||||
#
|
||||
if "" == translate(str, idmap, LegalChars):
|
||||
return str
|
||||
else:
|
||||
return '"' + _nulljoin( map(_Translator.get, str, str) ) + '"'
|
||||
# end _quote
|
||||
|
||||
|
||||
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
|
||||
_QuotePatt = re.compile(r"[\\].")
|
||||
|
||||
def _unquote(str):
|
||||
# If there aren't any doublequotes,
|
||||
# then there can't be any special characters. See RFC 2109.
|
||||
if len(str) < 2:
|
||||
return str
|
||||
if str[0] != '"' or str[-1] != '"':
|
||||
return str
|
||||
|
||||
# We have to assume that we must decode this string.
|
||||
# Down to work.
|
||||
|
||||
# Remove the "s
|
||||
str = str[1:-1]
|
||||
|
||||
# Check for special sequences. Examples:
|
||||
# \012 --> \n
|
||||
# \" --> "
|
||||
#
|
||||
i = 0
|
||||
n = len(str)
|
||||
res = []
|
||||
while 0 <= i < n:
|
||||
Omatch = _OctalPatt.search(str, i)
|
||||
Qmatch = _QuotePatt.search(str, i)
|
||||
if not Omatch and not Qmatch: # Neither matched
|
||||
res.append(str[i:])
|
||||
break
|
||||
# else:
|
||||
j = k = -1
|
||||
if Omatch: j = Omatch.start(0)
|
||||
if Qmatch: k = Qmatch.start(0)
|
||||
if Qmatch and ( not Omatch or k < j ): # QuotePatt matched
|
||||
res.append(str[i:k])
|
||||
res.append(str[k+1])
|
||||
i = k+2
|
||||
else: # OctalPatt matched
|
||||
res.append(str[i:j])
|
||||
res.append( chr( int(str[j+1:j+4], 8) ) )
|
||||
i = j+4
|
||||
return _nulljoin(res)
|
||||
# end _unquote
|
||||
|
||||
# The _getdate() routine is used to set the expiration time in
|
||||
# the cookie's HTTP header. By default, _getdate() returns the
|
||||
# current time in the appropriate "expires" format for a
|
||||
# Set-Cookie header. The one optional argument is an offset from
|
||||
# now, in seconds. For example, an offset of -3600 means "one hour ago".
|
||||
# The offset may be a floating point number.
|
||||
#
|
||||
|
||||
_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
|
||||
_monthname = [None,
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
|
||||
def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
|
||||
from time import gmtime, time
|
||||
now = time()
|
||||
year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future)
|
||||
return "%s, %02d-%3s-%4d %02d:%02d:%02d GMT" % \
|
||||
(weekdayname[wd], day, monthname[month], year, hh, mm, ss)
|
||||
|
||||
|
||||
#
|
||||
# A class to hold ONE key,value pair.
|
||||
# In a cookie, each such pair may have several attributes.
|
||||
# so this class is used to keep the attributes associated
|
||||
# with the appropriate key,value pair.
|
||||
# This class also includes a coded_value attribute, which
|
||||
# is used to hold the network representation of the
|
||||
# value. This is most useful when Python objects are
|
||||
# pickled for network transit.
|
||||
#
|
||||
|
||||
class Morsel(dict):
|
||||
# RFC 2109 lists these attributes as reserved:
|
||||
# path comment domain
|
||||
# max-age secure version
|
||||
#
|
||||
# For historical reasons, these attributes are also reserved:
|
||||
# expires
|
||||
#
|
||||
# This is an extension from Microsoft:
|
||||
# httponly
|
||||
#
|
||||
# This dictionary provides a mapping from the lowercase
|
||||
# variant on the left to the appropriate traditional
|
||||
# formatting on the right.
|
||||
_reserved = { "expires" : "expires",
|
||||
"path" : "Path",
|
||||
"comment" : "Comment",
|
||||
"domain" : "Domain",
|
||||
"max-age" : "Max-Age",
|
||||
"secure" : "secure",
|
||||
"httponly" : "httponly",
|
||||
"version" : "Version",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
# Set defaults
|
||||
self.key = self.value = self.coded_value = None
|
||||
|
||||
# Set default attributes
|
||||
for K in self._reserved:
|
||||
dict.__setitem__(self, K, "")
|
||||
# end __init__
|
||||
|
||||
def __setitem__(self, K, V):
|
||||
K = K.lower()
|
||||
if not K in self._reserved:
|
||||
raise CookieError("Invalid Attribute %s" % K)
|
||||
dict.__setitem__(self, K, V)
|
||||
# end __setitem__
|
||||
|
||||
def isReservedKey(self, K):
|
||||
return K.lower() in self._reserved
|
||||
# end isReservedKey
|
||||
|
||||
def set(self, key, val, coded_val,
|
||||
LegalChars=_LegalChars,
|
||||
idmap=_idmap, translate=string.translate):
|
||||
# First we verify that the key isn't a reserved word
|
||||
# Second we make sure it only contains legal characters
|
||||
if key.lower() in self._reserved:
|
||||
raise CookieError("Attempt to set a reserved key: %s" % key)
|
||||
if "" != translate(key, idmap, LegalChars):
|
||||
raise CookieError("Illegal key value: %s" % key)
|
||||
|
||||
# It's a good key, so save it.
|
||||
self.key = key
|
||||
self.value = val
|
||||
self.coded_value = coded_val
|
||||
# end set
|
||||
|
||||
def output(self, attrs=None, header = "Set-Cookie:"):
|
||||
return "%s %s" % ( header, self.OutputString(attrs) )
|
||||
|
||||
__str__ = output
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s=%s>' % (self.__class__.__name__,
|
||||
self.key, repr(self.value) )
|
||||
|
||||
def js_output(self, attrs=None):
|
||||
# Print javascript
|
||||
return """
|
||||
<script type="text/javascript">
|
||||
<!-- begin hiding
|
||||
document.cookie = \"%s\";
|
||||
// end hiding -->
|
||||
</script>
|
||||
""" % ( self.OutputString(attrs).replace('"',r'\"'), )
|
||||
# end js_output()
|
||||
|
||||
def OutputString(self, attrs=None):
|
||||
# Build up our result
|
||||
#
|
||||
result = []
|
||||
RA = result.append
|
||||
|
||||
# First, the key=value pair
|
||||
RA("%s=%s" % (self.key, self.coded_value))
|
||||
|
||||
# Now add any defined attributes
|
||||
if attrs is None:
|
||||
attrs = self._reserved
|
||||
items = self.items()
|
||||
items.sort()
|
||||
for K,V in items:
|
||||
if V == "": continue
|
||||
if K not in attrs: continue
|
||||
if K == "expires" and type(V) == type(1):
|
||||
RA("%s=%s" % (self._reserved[K], _getdate(V)))
|
||||
elif K == "max-age" and type(V) == type(1):
|
||||
RA("%s=%d" % (self._reserved[K], V))
|
||||
elif K == "secure":
|
||||
RA(str(self._reserved[K]))
|
||||
elif K == "httponly":
|
||||
RA(str(self._reserved[K]))
|
||||
else:
|
||||
RA("%s=%s" % (self._reserved[K], V))
|
||||
|
||||
# Return the result
|
||||
return _semispacejoin(result)
|
||||
# end OutputString
|
||||
# end Morsel class
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Pattern for finding cookie
|
||||
#
|
||||
# This used to be strict parsing based on the RFC2109 and RFC2068
|
||||
# specifications. I have since discovered that MSIE 3.0x doesn't
|
||||
# follow the character rules outlined in those specs. As a
|
||||
# result, the parsing rules here are less strict.
|
||||
#
|
||||
|
||||
_LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]\_]"
|
||||
|
||||
_CookiePattern = re.compile(
|
||||
r"(?x)" # This is a Verbose pattern
|
||||
r"(?P<key>" # Start of group 'key'
|
||||
""+ _LegalCharsPatt +"+?" # Any word of at least one letter, nongreedy
|
||||
r")" # End of group 'key'
|
||||
r"\s*=\s*" # Equal Sign
|
||||
r"(?P<val>" # Start of group 'val'
|
||||
r'"(?:[^\\"]|\\.)*"' # Any doublequoted string
|
||||
r"|" # or
|
||||
r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr
|
||||
r"|" # or
|
||||
""+ _LegalCharsPatt +"*" # Any word or empty string
|
||||
r")" # End of group 'val'
|
||||
r"\s*;?" # Probably ending in a semi-colon
|
||||
)
|
||||
|
||||
|
||||
# At long last, here is the cookie class.
|
||||
# Using this class is almost just like using a dictionary.
|
||||
# See this module's docstring for example usage.
|
||||
#
|
||||
class BaseCookie(dict):
|
||||
# A container class for a set of Morsels
|
||||
#
|
||||
|
||||
def value_decode(self, val):
|
||||
"""real_value, coded_value = value_decode(STRING)
|
||||
Called prior to setting a cookie's value from the network
|
||||
representation. The VALUE is the value read from HTTP
|
||||
header.
|
||||
Override this function to modify the behavior of cookies.
|
||||
"""
|
||||
return val, val
|
||||
# end value_encode
|
||||
|
||||
def value_encode(self, val):
|
||||
"""real_value, coded_value = value_encode(VALUE)
|
||||
Called prior to setting a cookie's value from the dictionary
|
||||
representation. The VALUE is the value being assigned.
|
||||
Override this function to modify the behavior of cookies.
|
||||
"""
|
||||
strval = str(val)
|
||||
return strval, strval
|
||||
# end value_encode
|
||||
|
||||
def __init__(self, input=None):
|
||||
if input: self.load(input)
|
||||
# end __init__
|
||||
|
||||
def __set(self, key, real_value, coded_value):
|
||||
"""Private method for setting a cookie's value"""
|
||||
M = self.get(key, Morsel())
|
||||
M.set(key, real_value, coded_value)
|
||||
dict.__setitem__(self, key, M)
|
||||
# end __set
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Dictionary style assignment."""
|
||||
rval, cval = self.value_encode(value)
|
||||
self.__set(key, rval, cval)
|
||||
# end __setitem__
|
||||
|
||||
def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
|
||||
"""Return a string suitable for HTTP."""
|
||||
result = []
|
||||
items = self.items()
|
||||
items.sort()
|
||||
for K,V in items:
|
||||
result.append( V.output(attrs, header) )
|
||||
return sep.join(result)
|
||||
# end output
|
||||
|
||||
__str__ = output
|
||||
|
||||
def __repr__(self):
|
||||
L = []
|
||||
items = self.items()
|
||||
items.sort()
|
||||
for K,V in items:
|
||||
L.append( '%s=%s' % (K,repr(V.value) ) )
|
||||
return '<%s: %s>' % (self.__class__.__name__, _spacejoin(L))
|
||||
|
||||
def js_output(self, attrs=None):
|
||||
"""Return a string suitable for JavaScript."""
|
||||
result = []
|
||||
items = self.items()
|
||||
items.sort()
|
||||
for K,V in items:
|
||||
result.append( V.js_output(attrs) )
|
||||
return _nulljoin(result)
|
||||
# end js_output
|
||||
|
||||
def load(self, rawdata):
|
||||
"""Load cookies from a string (presumably HTTP_COOKIE) or
|
||||
from a dictionary. Loading cookies from a dictionary 'd'
|
||||
is equivalent to calling:
|
||||
map(Cookie.__setitem__, d.keys(), d.values())
|
||||
"""
|
||||
if type(rawdata) == type(""):
|
||||
self.__ParseString(rawdata)
|
||||
else:
|
||||
# self.update() wouldn't call our custom __setitem__
|
||||
for k, v in rawdata.items():
|
||||
self[k] = v
|
||||
return
|
||||
# end load()
|
||||
|
||||
def __ParseString(self, str, patt=_CookiePattern):
|
||||
i = 0 # Our starting point
|
||||
n = len(str) # Length of string
|
||||
M = None # current morsel
|
||||
|
||||
while 0 <= i < n:
|
||||
# Start looking for a cookie
|
||||
match = patt.search(str, i)
|
||||
if not match: break # No more cookies
|
||||
|
||||
K,V = match.group("key"), match.group("val")
|
||||
i = match.end(0)
|
||||
|
||||
# Parse the key, value in case it's metainfo
|
||||
if K[0] == "$":
|
||||
# We ignore attributes which pertain to the cookie
|
||||
# mechanism as a whole. See RFC 2109.
|
||||
# (Does anyone care?)
|
||||
if M:
|
||||
M[ K[1:] ] = V
|
||||
elif K.lower() in Morsel._reserved:
|
||||
if M:
|
||||
M[ K ] = _unquote(V)
|
||||
else:
|
||||
rval, cval = self.value_decode(V)
|
||||
self.__set(K, rval, cval)
|
||||
M = self[K]
|
||||
# end __ParseString
|
||||
# end BaseCookie class
|
||||
|
||||
class SimpleCookie(BaseCookie):
|
||||
"""SimpleCookie
|
||||
SimpleCookie supports strings as cookie values. When setting
|
||||
the value using the dictionary assignment notation, SimpleCookie
|
||||
calls the builtin str() to convert the value to a string. Values
|
||||
received from HTTP are kept as strings.
|
||||
"""
|
||||
def value_decode(self, val):
|
||||
return _unquote( val ), val
|
||||
def value_encode(self, val):
|
||||
strval = str(val)
|
||||
return strval, _quote( strval )
|
||||
# end SimpleCookie
|
||||
|
||||
class SerialCookie(BaseCookie):
|
||||
"""SerialCookie
|
||||
SerialCookie supports arbitrary objects as cookie values. All
|
||||
values are serialized (using cPickle) before being sent to the
|
||||
client. All incoming values are assumed to be valid Pickle
|
||||
representations. IF AN INCOMING VALUE IS NOT IN A VALID PICKLE
|
||||
FORMAT, THEN AN EXCEPTION WILL BE RAISED.
|
||||
|
||||
Note: Large cookie values add overhead because they must be
|
||||
retransmitted on every HTTP transaction.
|
||||
|
||||
Note: HTTP has a 2k limit on the size of a cookie. This class
|
||||
does not check for this limit, so be careful!!!
|
||||
"""
|
||||
def __init__(self, input=None):
|
||||
warnings.warn("SerialCookie class is insecure; do not use it",
|
||||
DeprecationWarning)
|
||||
BaseCookie.__init__(self, input)
|
||||
# end __init__
|
||||
def value_decode(self, val):
|
||||
# This could raise an exception!
|
||||
return loads( _unquote(val) ), val
|
||||
def value_encode(self, val):
|
||||
return val, _quote( dumps(val) )
|
||||
# end SerialCookie
|
||||
|
||||
class SmartCookie(BaseCookie):
|
||||
"""SmartCookie
|
||||
SmartCookie supports arbitrary objects as cookie values. If the
|
||||
object is a string, then it is quoted. If the object is not a
|
||||
string, however, then SmartCookie will use cPickle to serialize
|
||||
the object into a string representation.
|
||||
|
||||
Note: Large cookie values add overhead because they must be
|
||||
retransmitted on every HTTP transaction.
|
||||
|
||||
Note: HTTP has a 2k limit on the size of a cookie. This class
|
||||
does not check for this limit, so be careful!!!
|
||||
"""
|
||||
def __init__(self, input=None):
|
||||
warnings.warn("Cookie/SmartCookie class is insecure; do not use it",
|
||||
DeprecationWarning)
|
||||
BaseCookie.__init__(self, input)
|
||||
# end __init__
|
||||
def value_decode(self, val):
|
||||
strval = _unquote(val)
|
||||
try:
|
||||
return loads(strval), val
|
||||
except:
|
||||
return strval, val
|
||||
def value_encode(self, val):
|
||||
if type(val) == type(""):
|
||||
return val, _quote(val)
|
||||
else:
|
||||
return val, _quote( dumps(val) )
|
||||
# end SmartCookie
|
||||
|
||||
|
||||
###########################################################
|
||||
# Backwards Compatibility: Don't break any existing code!
|
||||
|
||||
# We provide Cookie() as an alias for SmartCookie()
|
||||
Cookie = SmartCookie
|
||||
|
||||
#
|
||||
###########################################################
|
||||
|
||||
def _test():
|
||||
import doctest, Cookie
|
||||
return doctest.testmod(Cookie)
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test()
|
||||
|
||||
|
||||
#Local Variables:
|
||||
#tab-width: 4
|
||||
#end:
|
||||
399
requests/packages/oreos/structures.py
Normal file
399
requests/packages/oreos/structures.py
Normal file
@@ -0,0 +1,399 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
oreos.structures
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The plastic blue packaging.
|
||||
|
||||
This is mostly directly stolen from mitsuhiko/werkzeug.
|
||||
"""
|
||||
|
||||
__all__ = ('MultiDict',)
|
||||
|
||||
class _Missing(object):
|
||||
|
||||
def __repr__(self):
|
||||
return 'no value'
|
||||
|
||||
def __reduce__(self):
|
||||
return '_missing'
|
||||
|
||||
_missing = _Missing()
|
||||
|
||||
|
||||
|
||||
def iter_multi_items(mapping):
|
||||
"""Iterates over the items of a mapping yielding keys and values
|
||||
without dropping any from more complex structures.
|
||||
"""
|
||||
if isinstance(mapping, MultiDict):
|
||||
for item in mapping.iteritems(multi=True):
|
||||
yield item
|
||||
elif isinstance(mapping, dict):
|
||||
for key, value in mapping.iteritems():
|
||||
if isinstance(value, (tuple, list)):
|
||||
for value in value:
|
||||
yield key, value
|
||||
else:
|
||||
yield key, value
|
||||
else:
|
||||
for item in mapping:
|
||||
yield item
|
||||
|
||||
|
||||
|
||||
class TypeConversionDict(dict):
|
||||
"""Works like a regular dict but the :meth:`get` method can perform
|
||||
type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
|
||||
are subclasses of this class and provide the same feature.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
|
||||
def get(self, key, default=None, type=None):
|
||||
"""Return the default value if the requested data doesn't exist.
|
||||
If `type` is provided and is a callable it should convert the value,
|
||||
return it or raise a :exc:`ValueError` if that is not possible. In
|
||||
this case the function will return the default as if the value was not
|
||||
found:
|
||||
|
||||
>>> d = TypeConversionDict(foo='42', bar='blub')
|
||||
>>> d.get('foo', type=int)
|
||||
42
|
||||
>>> d.get('bar', -1, type=int)
|
||||
-1
|
||||
|
||||
:param key: The key to be looked up.
|
||||
:param default: The default value to be returned if the key can't
|
||||
be looked up. If not further specified `None` is
|
||||
returned.
|
||||
:param type: A callable that is used to cast the value in the
|
||||
:class:`MultiDict`. If a :exc:`ValueError` is raised
|
||||
by this callable the default value is returned.
|
||||
"""
|
||||
try:
|
||||
rv = self[key]
|
||||
if type is not None:
|
||||
rv = type(rv)
|
||||
except (KeyError, ValueError):
|
||||
rv = default
|
||||
return rv
|
||||
|
||||
|
||||
class MultiDict(TypeConversionDict):
|
||||
"""A :class:`MultiDict` is a dictionary subclass customized to deal with
|
||||
multiple values for the same key which is for example used by the parsing
|
||||
functions in the wrappers. This is necessary because some HTML form
|
||||
elements pass multiple values for the same key.
|
||||
|
||||
:class:`MultiDict` implements all standard dictionary methods.
|
||||
Internally, it saves all values for a key as a list, but the standard dict
|
||||
access methods will only return the first value for a key. If you want to
|
||||
gain access to the other values, too, you have to use the `list` methods as
|
||||
explained below.
|
||||
|
||||
Basic Usage:
|
||||
|
||||
>>> d = MultiDict([('a', 'b'), ('a', 'c')])
|
||||
>>> d
|
||||
MultiDict([('a', 'b'), ('a', 'c')])
|
||||
>>> d['a']
|
||||
'b'
|
||||
>>> d.getlist('a')
|
||||
['b', 'c']
|
||||
>>> 'a' in d
|
||||
True
|
||||
|
||||
It behaves like a normal dict thus all dict functions will only return the
|
||||
first value when multiple values for one key are found.
|
||||
|
||||
From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
|
||||
subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
|
||||
render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
|
||||
exceptions.
|
||||
|
||||
A :class:`MultiDict` can be constructed from an iterable of
|
||||
``(key, value)`` tuples, a dict, a :class:`MultiDict` or from Werkzeug 0.2
|
||||
onwards some keyword parameters.
|
||||
|
||||
:param mapping: the initial value for the :class:`MultiDict`. Either a
|
||||
regular dict, an iterable of ``(key, value)`` tuples
|
||||
or `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, mapping=None):
|
||||
if isinstance(mapping, MultiDict):
|
||||
dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
|
||||
elif isinstance(mapping, dict):
|
||||
tmp = {}
|
||||
for key, value in mapping.iteritems():
|
||||
if isinstance(value, (tuple, list)):
|
||||
value = list(value)
|
||||
else:
|
||||
value = [value]
|
||||
tmp[key] = value
|
||||
dict.__init__(self, tmp)
|
||||
else:
|
||||
tmp = {}
|
||||
for key, value in mapping or ():
|
||||
tmp.setdefault(key, []).append(value)
|
||||
dict.__init__(self, tmp)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict(self.lists())
|
||||
|
||||
def __setstate__(self, value):
|
||||
dict.clear(self)
|
||||
dict.update(self, value)
|
||||
|
||||
def __iter__(self):
|
||||
return self.iterkeys()
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return the first data value for this key;
|
||||
raises KeyError if not found.
|
||||
|
||||
:param key: The key to be looked up.
|
||||
:raise KeyError: if the key does not exist.
|
||||
"""
|
||||
if key in self:
|
||||
return dict.__getitem__(self, key)[0]
|
||||
raise KeyError(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Like :meth:`add` but removes an existing key first.
|
||||
|
||||
:param key: the key for the value.
|
||||
:param value: the value to set.
|
||||
"""
|
||||
dict.__setitem__(self, key, [value])
|
||||
|
||||
def add(self, key, value):
|
||||
"""Adds a new value for the key.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
:param key: the key for the value.
|
||||
:param value: the value to add.
|
||||
"""
|
||||
dict.setdefault(self, key, []).append(value)
|
||||
|
||||
def getlist(self, key, type=None):
|
||||
"""Return the list of items for a given key. If that key is not in the
|
||||
`MultiDict`, the return value will be an empty list. Just as `get`
|
||||
`getlist` accepts a `type` parameter. All items will be converted
|
||||
with the callable defined there.
|
||||
|
||||
:param key: The key to be looked up.
|
||||
:param type: A callable that is used to cast the value in the
|
||||
:class:`MultiDict`. If a :exc:`ValueError` is raised
|
||||
by this callable the value will be removed from the list.
|
||||
:return: a :class:`list` of all the values for the key.
|
||||
"""
|
||||
try:
|
||||
rv = dict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
return []
|
||||
if type is None:
|
||||
return list(rv)
|
||||
result = []
|
||||
for item in rv:
|
||||
try:
|
||||
result.append(type(item))
|
||||
except ValueError:
|
||||
pass
|
||||
return result
|
||||
|
||||
def setlist(self, key, new_list):
|
||||
"""Remove the old values for a key and add new ones. Note that the list
|
||||
you pass the values in will be shallow-copied before it is inserted in
|
||||
the dictionary.
|
||||
|
||||
>>> d = MultiDict()
|
||||
>>> d.setlist('foo', ['1', '2'])
|
||||
>>> d['foo']
|
||||
'1'
|
||||
>>> d.getlist('foo')
|
||||
['1', '2']
|
||||
|
||||
:param key: The key for which the values are set.
|
||||
:param new_list: An iterable with the new values for the key. Old values
|
||||
are removed first.
|
||||
"""
|
||||
dict.__setitem__(self, key, list(new_list))
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
"""Returns the value for the key if it is in the dict, otherwise it
|
||||
returns `default` and sets that value for `key`.
|
||||
|
||||
:param key: The key to be looked up.
|
||||
:param default: The default value to be returned if the key is not
|
||||
in the dict. If not further specified it's `None`.
|
||||
"""
|
||||
if key not in self:
|
||||
self[key] = default
|
||||
else:
|
||||
default = self[key]
|
||||
return default
|
||||
|
||||
def setlistdefault(self, key, default_list=None):
|
||||
"""Like `setdefault` but sets multiple values. The list returned
|
||||
is not a copy, but the list that is actually used internally. This
|
||||
means that you can put new values into the dict by appending items
|
||||
to the list:
|
||||
|
||||
>>> d = MultiDict({"foo": 1})
|
||||
>>> d.setlistdefault("foo").extend([2, 3])
|
||||
>>> d.getlist("foo")
|
||||
[1, 2, 3]
|
||||
|
||||
:param key: The key to be looked up.
|
||||
:param default: An iterable of default values. It is either copied
|
||||
(in case it was a list) or converted into a list
|
||||
before returned.
|
||||
:return: a :class:`list`
|
||||
"""
|
||||
if key not in self:
|
||||
default_list = list(default_list or ())
|
||||
dict.__setitem__(self, key, default_list)
|
||||
else:
|
||||
default_list = dict.__getitem__(self, key)
|
||||
return default_list
|
||||
|
||||
def items(self, multi=False):
|
||||
"""Return a list of ``(key, value)`` pairs.
|
||||
|
||||
:param multi: If set to `True` the list returned will have a
|
||||
pair for each value of each key. Otherwise it
|
||||
will only contain pairs for the first value of
|
||||
each key.
|
||||
|
||||
:return: a :class:`list`
|
||||
"""
|
||||
return list(self.iteritems(multi))
|
||||
|
||||
def lists(self):
|
||||
"""Return a list of ``(key, values)`` pairs, where values is the list of
|
||||
all values associated with the key.
|
||||
|
||||
:return: a :class:`list`
|
||||
"""
|
||||
return list(self.iterlists())
|
||||
|
||||
def values(self):
|
||||
"""Returns a list of the first value on every key's value list.
|
||||
|
||||
:return: a :class:`list`.
|
||||
"""
|
||||
return [self[key] for key in self.iterkeys()]
|
||||
|
||||
def listvalues(self):
|
||||
"""Return a list of all values associated with a key. Zipping
|
||||
:meth:`keys` and this is the same as calling :meth:`lists`:
|
||||
|
||||
>>> d = MultiDict({"foo": [1, 2, 3]})
|
||||
>>> zip(d.keys(), d.listvalues()) == d.lists()
|
||||
True
|
||||
|
||||
:return: a :class:`list`
|
||||
"""
|
||||
return list(self.iterlistvalues())
|
||||
|
||||
def iteritems(self, multi=False):
|
||||
"""Like :meth:`items` but returns an iterator."""
|
||||
for key, values in dict.iteritems(self):
|
||||
if multi:
|
||||
for value in values:
|
||||
yield key, value
|
||||
else:
|
||||
yield key, values[0]
|
||||
|
||||
def iterlists(self):
|
||||
"""Like :meth:`items` but returns an iterator."""
|
||||
for key, values in dict.iteritems(self):
|
||||
yield key, list(values)
|
||||
|
||||
def itervalues(self):
|
||||
"""Like :meth:`values` but returns an iterator."""
|
||||
for values in dict.itervalues(self):
|
||||
yield values[0]
|
||||
|
||||
def iterlistvalues(self):
|
||||
"""Like :meth:`listvalues` but returns an iterator."""
|
||||
return dict.itervalues(self)
|
||||
|
||||
def copy(self):
|
||||
"""Return a shallow copy of this object."""
|
||||
return self.__class__(self)
|
||||
|
||||
def to_dict(self, flat=True):
|
||||
"""Return the contents as regular dict. If `flat` is `True` the
|
||||
returned dict will only have the first item present, if `flat` is
|
||||
`False` all values will be returned as lists.
|
||||
|
||||
:param flat: If set to `False` the dict returned will have lists
|
||||
with all the values in it. Otherwise it will only
|
||||
contain the first value for each key.
|
||||
:return: a :class:`dict`
|
||||
"""
|
||||
if flat:
|
||||
return dict(self.iteritems())
|
||||
return dict(self.lists())
|
||||
|
||||
def update(self, other_dict):
|
||||
"""update() extends rather than replaces existing key lists."""
|
||||
for key, value in iter_multi_items(other_dict):
|
||||
MultiDict.add(self, key, value)
|
||||
|
||||
def pop(self, key, default=_missing):
|
||||
"""Pop the first item for a list on the dict. Afterwards the
|
||||
key is removed from the dict, so additional values are discarded:
|
||||
|
||||
>>> d = MultiDict({"foo": [1, 2, 3]})
|
||||
>>> d.pop("foo")
|
||||
1
|
||||
>>> "foo" in d
|
||||
False
|
||||
|
||||
:param key: the key to pop.
|
||||
:param default: if provided the value to return if the key was
|
||||
not in the dictionary.
|
||||
"""
|
||||
try:
|
||||
return dict.pop(self, key)[0]
|
||||
except KeyError as e:
|
||||
if default is not _missing:
|
||||
return default
|
||||
raise KeyError(str(e))
|
||||
|
||||
def popitem(self):
|
||||
"""Pop an item from the dict."""
|
||||
try:
|
||||
item = dict.popitem(self)
|
||||
return (item[0], item[1][0])
|
||||
except KeyError as e:
|
||||
raise KeyError(str(e))
|
||||
|
||||
def poplist(self, key):
|
||||
"""Pop the list for a key from the dict. If the key is not in the dict
|
||||
an empty list is returned.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
If the key does no longer exist a list is returned instead of
|
||||
raising an error.
|
||||
"""
|
||||
return dict.pop(self, key, [])
|
||||
|
||||
def popitemlist(self):
|
||||
"""Pop a ``(key, list)`` tuple from the dict."""
|
||||
try:
|
||||
return dict.popitem(self)
|
||||
except KeyError as e:
|
||||
raise KeyError(str(e))
|
||||
|
||||
def __copy__(self):
|
||||
return self.copy()
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items(multi=True))
|
||||
@@ -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
|
||||
@@ -2,20 +2,20 @@
|
||||
|
||||
"""
|
||||
requests.session
|
||||
~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a Session object to manage and persist settings across
|
||||
requests (cookies, auth, proxies).
|
||||
|
||||
"""
|
||||
|
||||
import cookielib
|
||||
|
||||
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 +26,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 +41,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]
|
||||
|
||||
@@ -51,7 +51,9 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
class Session(object):
|
||||
"""A Requests session."""
|
||||
|
||||
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params', 'config']
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
||||
'params', 'config', 'verify', 'cert', 'prefetch']
|
||||
|
||||
|
||||
def __init__(self,
|
||||
@@ -62,22 +64,38 @@ class Session(object):
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
params=None,
|
||||
config=None):
|
||||
config=None,
|
||||
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.prefetch = prefetch
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
for (k, v) in defaults.items():
|
||||
for (k, v) in list(defaults.items()):
|
||||
self.config.setdefault(k, 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))
|
||||
@@ -96,11 +114,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.
|
||||
@@ -112,27 +133,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 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(
|
||||
@@ -148,28 +176,63 @@ 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', hooks, args)
|
||||
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)
|
||||
|
||||
# Send any cookies back up the to the session.
|
||||
for cookie in r.response.cookies:
|
||||
self.cookies.set_cookie(cookie)
|
||||
|
||||
# Return the response.
|
||||
return r.response
|
||||
|
||||
|
||||
@@ -177,69 +240,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):
|
||||
@@ -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,107 @@
|
||||
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, is_py2, urlparse
|
||||
from .compat import 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 +212,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 +238,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,20 +248,13 @@ 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:
|
||||
if is_py2:
|
||||
L = [chr(random.randrange(0, 256)) for i in range(n)]
|
||||
return "".join(L)
|
||||
else:
|
||||
L = [chr(random.randrange(0, 256)).encode('utf-8') for i in range(n)]
|
||||
return b"".join(L)
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
@@ -180,33 +265,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 +281,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 +314,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 +358,91 @@ 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.
|
||||
"""
|
||||
parts = uri.split('%')
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2:
|
||||
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)
|
||||
|
||||
|
||||
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="!#$%&'()*+,/:;=?@[]~")
|
||||
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
nose
|
||||
rudolf2
|
||||
git+git://github.com/idangazit/oauthlib.git
|
||||
68
setup.py
68
setup.py
@@ -1,29 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
"""
|
||||
distutils/setuptools install script. See inline comments for packaging documentation.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import requests
|
||||
from requests.compat import is_py3
|
||||
|
||||
try:
|
||||
from setuptools import 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)
|
||||
|
||||
# The async API in requests.async requires the gevent package.
|
||||
# This is also not a required dependency.
|
||||
extras_require = {
|
||||
'async': ['gevent'],
|
||||
}
|
||||
|
||||
setup(
|
||||
name='requests',
|
||||
@@ -34,23 +59,22 @@ 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,
|
||||
extras_require=extras_require,
|
||||
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',
|
||||
),
|
||||
)
|
||||
|
||||
533
test_requests.py
533
test_requests.py
@@ -1,533 +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
|
||||
|
||||
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(requests.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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
166
tests/test_cookies.py
Executable file
166
tests/test_cookies.py
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/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')
|
||||
# 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.assertGreaterEqual(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.assertGreaterEqual(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.assertIs(r.cookies, 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.assertNotIn('Cookie', page['headers'])
|
||||
|
||||
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.assertGreaterEqual(num_github_cookies, 1)
|
||||
# httpbin sets another
|
||||
requests.get(httpbin('cookies', 'set', 'key', 'value'), cookies=cookiejar)
|
||||
num_total_cookies = len(cookiejar)
|
||||
self.assertGreaterEqual(num_total_cookies, 2)
|
||||
self.assertGreater(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'})
|
||||
|
||||
class MozCookieJarTest(LWPCookieJarTest):
|
||||
"""Same test, but substitute MozillaCookieJar."""
|
||||
|
||||
COOKIEJAR_CLASS = cookielib.MozillaCookieJar
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
851
tests/test_requests.py
Executable file
851
tests/test_requests.py
Executable file
@@ -0,0 +1,851 @@
|
||||
#!/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 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():
|
||||
message = 'Failed comparison for %s' % (attr,)
|
||||
self.assertEqual(getattr(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_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_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_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
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
67
tests/test_requests_async.py
Executable file
67
tests/test_requests_async.py
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import select
|
||||
has_poll = hasattr(select, "poll")
|
||||
|
||||
from requests import async
|
||||
|
||||
sys.path.append('.')
|
||||
from test_requests import httpbin, RequestsTestSuite, SERVICES
|
||||
|
||||
|
||||
class RequestsTestSuiteUsingAsyncApi(RequestsTestSuite):
|
||||
"""Requests async test cases."""
|
||||
|
||||
def patched(f):
|
||||
"""Automatically send request after creation."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
|
||||
request = f(*args, **kwargs)
|
||||
return async.map([request])[0]
|
||||
|
||||
return wrapped
|
||||
|
||||
# Patched requests.api functions.
|
||||
global request
|
||||
request = patched(async.request)
|
||||
|
||||
global delete, get, head, options, patch, post, put
|
||||
delete = patched(async.delete)
|
||||
get = patched(async.get)
|
||||
head = patched(async.head)
|
||||
options = patched(async.options)
|
||||
patch = patched(async.patch)
|
||||
post = patched(async.post)
|
||||
put = patched(async.put)
|
||||
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
async.request
|
||||
|
||||
async.delete
|
||||
async.get
|
||||
async.head
|
||||
async.options
|
||||
async.patch
|
||||
async.post
|
||||
async.put
|
||||
|
||||
async.map
|
||||
async.send
|
||||
|
||||
def test_select_poll(self):
|
||||
"""Test to make sure we don't overwrite the poll"""
|
||||
self.assertEqual(hasattr(select, "poll"), has_poll)
|
||||
|
||||
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