Compare commits
558 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f463e897a | ||
|
|
58eacfa1ad | ||
|
|
c93419f512 | ||
|
|
53ba629a23 | ||
|
|
62925fb668 | ||
|
|
94da24e387 | ||
|
|
2d283c9f2d | ||
|
|
54ad646067 | ||
|
|
12760f2295 | ||
|
|
c64c0ab121 | ||
|
|
2f39e0e2aa | ||
|
|
23ced527f7 | ||
|
|
2affd6385e | ||
|
|
58376489af | ||
|
|
a2dbf54456 | ||
|
|
03d844f926 | ||
|
|
9ffc5e6a95 | ||
|
|
22e31b4b73 | ||
|
|
c0e9f411fc | ||
|
|
61421843bc | ||
|
|
ce6762ff63 | ||
|
|
d8f2463b57 | ||
|
|
f0c592b09e | ||
|
|
a4cd1a85d6 | ||
|
|
3440fca2e8 | ||
|
|
10b6d77945 | ||
|
|
917a15f154 | ||
|
|
0a52d9405e | ||
|
|
cde6fdd00f | ||
|
|
a829ef9b46 | ||
|
|
056da86f6d | ||
|
|
c1eb7027fc | ||
|
|
2cb523b267 | ||
|
|
6d0315f78f | ||
|
|
b891877ee6 | ||
|
|
9d0d11a369 | ||
|
|
6c73d864ef | ||
|
|
b9b5be7c4c | ||
|
|
d991fb032a | ||
|
|
9f42a50742 | ||
|
|
88011f4032 | ||
|
|
1aa7658256 | ||
|
|
e77344345a | ||
|
|
a0ffeaa56e | ||
|
|
efbae1855b | ||
|
|
f040b2aa5d | ||
|
|
09c52e7282 | ||
|
|
4b6649e37b | ||
|
|
aeeb0af3a2 | ||
|
|
148c1b5c13 | ||
|
|
377ac9501a | ||
|
|
78e5262dbd | ||
|
|
0f40290039 | ||
|
|
4ffc3e4d1f | ||
|
|
f44b86e039 | ||
|
|
3bc01eed6f | ||
|
|
3dc61e5d68 | ||
|
|
98680b64f4 | ||
|
|
d8268fb7b4 | ||
|
|
2e5a674601 | ||
|
|
61617e74ed | ||
|
|
b676bf3214 | ||
|
|
9dd823c289 | ||
|
|
d3b786399e | ||
|
|
a5e5a51fb4 | ||
|
|
09d97c1126 | ||
|
|
6a3ca7372f | ||
|
|
c951f0543c | ||
|
|
134699faf9 | ||
|
|
d06dabab59 | ||
|
|
a05aba1033 | ||
|
|
5a26241287 | ||
|
|
abfb38bd57 | ||
|
|
bd1f1404a0 | ||
|
|
f865d61713 | ||
|
|
1a6637c285 | ||
|
|
22701d149a | ||
|
|
d5fd3d3331 | ||
|
|
d5a8816926 | ||
|
|
6b13ecdc85 | ||
|
|
7eb9a9298e | ||
|
|
3e6e68baa1 | ||
|
|
0ab7a52f7c | ||
|
|
852de116dd | ||
|
|
0eccb547a2 | ||
|
|
d4385f6fc8 | ||
|
|
ee90f0af60 | ||
|
|
9edba838b3 | ||
|
|
b5c136152f | ||
|
|
6ef3710970 | ||
|
|
f75ee9fb6e | ||
|
|
8d9d9fb064 | ||
|
|
7de6b1e162 | ||
|
|
ec2859c0de | ||
|
|
f5775594cc | ||
|
|
37786613e0 | ||
|
|
ea2b639d31 | ||
|
|
79f3e69f5e | ||
|
|
840540b6b1 | ||
|
|
9473f15909 | ||
|
|
286ddb672d | ||
|
|
972089826e | ||
|
|
c25a72ea24 | ||
|
|
73635df1b2 | ||
|
|
77bd9c4a9d | ||
|
|
f401287afb | ||
|
|
3becc47366 | ||
|
|
7d3d074e66 | ||
|
|
f40a1d6a8a | ||
|
|
82c9aa912e | ||
|
|
56b5f02551 | ||
|
|
c4f16c351c | ||
|
|
012cc33d44 | ||
|
|
415fb53cb4 | ||
|
|
b65b8c5ed3 | ||
|
|
c0cb698191 | ||
|
|
d2176cf052 | ||
|
|
f71bff1226 | ||
|
|
47f5a0db64 | ||
|
|
9e771aa79c | ||
|
|
29db7b7216 | ||
|
|
66de6b528d | ||
|
|
4f64938ff0 | ||
|
|
e5597dfcce | ||
|
|
787c1e8c6e | ||
|
|
ec6584f2d1 | ||
|
|
62f0df4434 | ||
|
|
5cdcf58b3b | ||
|
|
b84547d786 | ||
|
|
d83919a6f7 | ||
|
|
2ef782b8e6 | ||
|
|
b738c97ec3 | ||
|
|
022c370bcd | ||
|
|
587385bfe0 | ||
|
|
ca033b83fe | ||
|
|
949a29c34f | ||
|
|
be884341f4 | ||
|
|
c88fd8cc9d | ||
|
|
8385fb397b | ||
|
|
e0df4b5f18 | ||
|
|
39ad5e7388 | ||
|
|
0df505bd7c | ||
|
|
6f6a920a68 | ||
|
|
32293655ff | ||
|
|
07ad75ee04 | ||
|
|
e4b41320e6 | ||
|
|
9f119ee420 | ||
|
|
e0df2168b7 | ||
|
|
d7e8073198 | ||
|
|
65a0fd88a9 | ||
|
|
4b7cf389e4 | ||
|
|
86d466c745 | ||
|
|
de9d84489b | ||
|
|
18a736fbe6 | ||
|
|
7bdf37bc27 | ||
|
|
41517e2111 | ||
|
|
555472bf1e | ||
|
|
7da16c584a | ||
|
|
a8cf5b8502 | ||
|
|
cdab4fabf4 | ||
|
|
d9c49ad30d | ||
|
|
188e7609b3 | ||
|
|
2b62980e26 | ||
|
|
805abee9b4 | ||
|
|
a1bb7fb0d2 | ||
|
|
9083735963 | ||
|
|
dcf20c4cfe | ||
|
|
d57a5efab3 | ||
|
|
3781c96125 | ||
|
|
1faa76a86f | ||
|
|
ecf57cac5c | ||
|
|
25db83fe85 | ||
|
|
ba0db9db86 | ||
|
|
a6415cf895 | ||
|
|
df935f5b03 | ||
|
|
43a64d9515 | ||
|
|
70a6d035d9 | ||
|
|
716b627c1e | ||
|
|
798a1ffdec | ||
|
|
8028103b71 | ||
|
|
8430e6ccbb | ||
|
|
d4c461042b | ||
|
|
60b1a0ba13 | ||
|
|
cfd84d74ee | ||
|
|
eec9bdd434 | ||
|
|
f74d5e96dd | ||
|
|
e63e68ef48 | ||
|
|
73425fbffb | ||
|
|
f53aed1611 | ||
|
|
2ed976ea71 | ||
|
|
767f758aac | ||
|
|
961790f95c | ||
|
|
f705c41c1d | ||
|
|
93be6916f9 | ||
|
|
340931ba8e | ||
|
|
2c5ea69e5d | ||
|
|
d59510481a | ||
|
|
f3036adc4f | ||
|
|
489a412f96 | ||
|
|
72e155e529 | ||
|
|
e715d7184b | ||
|
|
3bb13f8fbb | ||
|
|
01993d21dc | ||
|
|
5e94f38001 | ||
|
|
3004ad5398 | ||
|
|
e7c9bbb96f | ||
|
|
00ab8fbfea | ||
|
|
9930c9c737 | ||
|
|
2ee814d924 | ||
|
|
8b55a253b1 | ||
|
|
786fe94ac4 | ||
|
|
fede8e5af3 | ||
|
|
2a34335dc3 | ||
|
|
3b0d8b8e39 | ||
|
|
715a57dec8 | ||
|
|
2aabb71dc8 | ||
|
|
003c795afe | ||
|
|
1621015e00 | ||
|
|
98114245c6 | ||
|
|
8eb4243f12 | ||
|
|
569b59f495 | ||
|
|
8f42369aa9 | ||
|
|
f4e7809e7c | ||
|
|
2afe3b2e99 | ||
|
|
9ce7f0bf5b | ||
|
|
7430a49f45 | ||
|
|
538dbaecbe | ||
|
|
5943afa25d | ||
|
|
6e76ab7188 | ||
|
|
2b6ebd2521 | ||
|
|
eacb91afb1 | ||
|
|
4c8af1fff4 | ||
|
|
c03e14242b | ||
|
|
9cb3d6444d | ||
|
|
053613688b | ||
|
|
2eb682671d | ||
|
|
e7786ec14f | ||
|
|
88fda94218 | ||
|
|
b599886beb | ||
|
|
7aedc6bb22 | ||
|
|
efefc6b778 | ||
|
|
50e592209f | ||
|
|
18857a0eed | ||
|
|
8781b56a0e | ||
|
|
e585e496c9 | ||
|
|
f93275c47f | ||
|
|
4f83ef8328 | ||
|
|
0f1bb96f01 | ||
|
|
e6e9b550c0 | ||
|
|
f8f1db8458 | ||
|
|
7e825acd9b | ||
|
|
f7596c75dc | ||
|
|
505d2487e3 | ||
|
|
ab36f3cc6f | ||
|
|
49ca72c802 | ||
|
|
d2ba5798ec | ||
|
|
c3bef6b0d9 | ||
|
|
98dea03710 | ||
|
|
0c609b59ab | ||
|
|
a7fef58756 | ||
|
|
3ff8d43801 | ||
|
|
9763a671da | ||
|
|
1866f7596e | ||
|
|
8f3f4e2744 | ||
|
|
4438bfcc44 | ||
|
|
65b6ef6036 | ||
|
|
1ca423ec80 | ||
|
|
770c3d7548 | ||
|
|
736e8cd735 | ||
|
|
6046fcffe8 | ||
|
|
d13bf9f113 | ||
|
|
ae5419db17 | ||
|
|
9c2f9f61ef | ||
|
|
636476e704 | ||
|
|
aafca7acdc | ||
|
|
a527ecfabd | ||
|
|
967cfbe663 | ||
|
|
9df450193f | ||
|
|
082029ff2e | ||
|
|
c0baec1431 | ||
|
|
561366cab0 | ||
|
|
f0950a9e6c | ||
|
|
c5d0a0931e | ||
|
|
36dcce1a06 | ||
|
|
ed0242ae3a | ||
|
|
eda4b55f7a | ||
|
|
6e3cfe5dd5 | ||
|
|
fb49481ddf | ||
|
|
17ecb6891c | ||
|
|
4c3432b759 | ||
|
|
59f916ca4a | ||
|
|
6d6252aa9f | ||
|
|
2426eeb371 | ||
|
|
e5f1053b30 | ||
|
|
c73f653352 | ||
|
|
5b937b384b | ||
|
|
8ebeb3dc3f | ||
|
|
dfa59c2d97 | ||
|
|
511cc4c034 | ||
|
|
fa9234da05 | ||
|
|
afcc883d7f | ||
|
|
1e465fd255 | ||
|
|
49a3664222 | ||
|
|
848f2c297e | ||
|
|
5c47ce1136 | ||
|
|
88f13598f3 | ||
|
|
d22ac00098 | ||
|
|
dccfc5ba3c | ||
|
|
e7247ce3f6 | ||
|
|
168c3e6913 | ||
|
|
82d36d8259 | ||
|
|
b9e5cce2d2 | ||
|
|
1abd13700b | ||
|
|
54ed5ed469 | ||
|
|
cdb700737b | ||
|
|
ca0aea640d | ||
|
|
20a8a9b681 | ||
|
|
9c18febf45 | ||
|
|
848aca21be | ||
|
|
f37b968475 | ||
|
|
f0660e33a2 | ||
|
|
333fa87489 | ||
|
|
037b38badb | ||
|
|
39acf1dbd2 | ||
|
|
59b69d1fb8 | ||
|
|
d06908d655 | ||
|
|
5b5ffc9714 | ||
|
|
75703d57e6 | ||
|
|
bd6b981d12 | ||
|
|
6a2eea8b21 | ||
|
|
325ea7b7e2 | ||
|
|
4ffae38627 | ||
|
|
d7908a9fde | ||
|
|
0cd23d8d6e | ||
|
|
aca91e06f2 | ||
|
|
88177ec33f | ||
|
|
1d5c4f3f0f | ||
|
|
478d49027f | ||
|
|
46a770e03f | ||
|
|
ba25184ed5 | ||
|
|
7f1ad5c127 | ||
|
|
ab19b79375 | ||
|
|
c63472e328 | ||
|
|
6963b8490c | ||
|
|
9d16c72767 | ||
|
|
186a589783 | ||
|
|
12ef43e407 | ||
|
|
92f7478174 | ||
|
|
19d38d502f | ||
|
|
52d328ec3c | ||
|
|
a554828931 | ||
|
|
80a861cb84 | ||
|
|
0d07d1afb3 | ||
|
|
030b9763b5 | ||
|
|
03a3ca5004 | ||
|
|
1325409560 | ||
|
|
13aeb9cb06 | ||
|
|
7d217bf9bd | ||
|
|
cb2116dcce | ||
|
|
db90d85a8d | ||
|
|
af791d48c6 | ||
|
|
120a2f385a | ||
|
|
5bb2be9a23 | ||
|
|
e958511df0 | ||
|
|
1642996798 | ||
|
|
c41932e184 | ||
|
|
25ea8cdb38 | ||
|
|
e706d18cf8 | ||
|
|
e752455b6f | ||
|
|
3e72f234d0 | ||
|
|
25dc07e8ed | ||
|
|
13de69299f | ||
|
|
90e109c241 | ||
|
|
d25ba77b2e | ||
|
|
c0d4b23cea | ||
|
|
99eead01a3 | ||
|
|
d60845303b | ||
|
|
b14584f36e | ||
|
|
23d8522285 | ||
|
|
c4f9340fb4 | ||
|
|
d372a5b10b | ||
|
|
7aad3fd08c | ||
|
|
76c4b68e4a | ||
|
|
07441c8951 | ||
|
|
98ff29f793 | ||
|
|
08cab48431 | ||
|
|
7e7c275504 | ||
|
|
73abf84ace | ||
|
|
7eba5ffe48 | ||
|
|
1d2ee524d8 | ||
|
|
38f2581d6f | ||
|
|
b80f8aa475 | ||
|
|
057f32924a | ||
|
|
6a0845c984 | ||
|
|
18b29ea005 | ||
|
|
796d3225dd | ||
|
|
be62645dd5 | ||
|
|
603fd42fe6 | ||
|
|
1fc567449c | ||
|
|
c01bc5be5b | ||
|
|
577dba2bf7 | ||
|
|
a788cb7271 | ||
|
|
178ff62b93 | ||
|
|
aa99525537 | ||
|
|
7e0b9cf409 | ||
|
|
017c027629 | ||
|
|
c2480f65e6 | ||
|
|
3f86e22a07 | ||
|
|
b53975327f | ||
|
|
d0390d4f27 | ||
|
|
f3393fb24c | ||
|
|
4c21106222 | ||
|
|
87d9d9643c | ||
|
|
df5dcb8a7d | ||
|
|
69ba64380b | ||
|
|
d0285fac42 | ||
|
|
f73bda06e9 | ||
|
|
4dfd6f3fc1 | ||
|
|
cdec20af65 | ||
|
|
09a7251245 | ||
|
|
56f4b7ca68 | ||
|
|
4c8d1b9a7d | ||
|
|
6da7e22a4a | ||
|
|
5d9fcc711b | ||
|
|
2e31696156 | ||
|
|
0fb13e0b6c | ||
|
|
1da1490bcd | ||
|
|
9cdc8325ae | ||
|
|
12d66cfc41 | ||
|
|
9c8660dbb6 | ||
|
|
e7bc9bf1b2 | ||
|
|
e2ad0d0fe8 | ||
|
|
3299771e34 | ||
|
|
f2be5fae27 | ||
|
|
90cd37fda0 | ||
|
|
b0657cf163 | ||
|
|
03f6908fe9 | ||
|
|
c68f928254 | ||
|
|
ff954e16f1 | ||
|
|
d437c338d0 | ||
|
|
9fc7059140 | ||
|
|
544d08d0f6 | ||
|
|
ae55676a50 | ||
|
|
b07c1ebd85 | ||
|
|
113b67c069 | ||
|
|
12246afab8 | ||
|
|
85da715f40 | ||
|
|
b554b0c1cb | ||
|
|
9ff8cbe5fb | ||
|
|
79727abb32 | ||
|
|
ba485913b6 | ||
|
|
a95bfd3032 | ||
|
|
207a2744aa | ||
|
|
95084c9ca8 | ||
|
|
2f7923bc05 | ||
|
|
2ac3913733 | ||
|
|
231e343a3b | ||
|
|
c8ad4f0b73 | ||
|
|
25545b393b | ||
|
|
dfa89e299a | ||
|
|
318300848f | ||
|
|
d4aaef1e9a | ||
|
|
40a060cf57 | ||
|
|
ac5e6807fa | ||
|
|
250896b7f3 | ||
|
|
9d1d927420 | ||
|
|
440aeca5d4 | ||
|
|
b6af86225d | ||
|
|
c50f8f7cc8 | ||
|
|
8d8865aadb | ||
|
|
f7c10ca74d | ||
|
|
4cac9f07c4 | ||
|
|
f453892960 | ||
|
|
44b1e7ebea | ||
|
|
4ce6440795 | ||
|
|
22623bd8c2 | ||
|
|
5861254d8f | ||
|
|
ab2e7df8a9 | ||
|
|
beec48c8f8 | ||
|
|
b2427223c1 | ||
|
|
ef52044c5f | ||
|
|
ccea28db07 | ||
|
|
e21ef547f7 | ||
|
|
0af8ff9405 | ||
|
|
6e0ca583fe | ||
|
|
2bcc4a774a | ||
|
|
34268b16c3 | ||
|
|
1cfe59299b | ||
|
|
e771aa9386 | ||
|
|
cc7bee19f0 | ||
|
|
088f908d58 | ||
|
|
c4ad8afba9 | ||
|
|
0c09d16b29 | ||
|
|
30573b2d8b | ||
|
|
1a87f15e6f | ||
|
|
472a5b4748 | ||
|
|
b94510fc42 | ||
|
|
6e53290acd | ||
|
|
ca85b4ec6c | ||
|
|
a6360ca134 | ||
|
|
47aa968f3c | ||
|
|
3bf46609c8 | ||
|
|
cd0cbada65 | ||
|
|
ed559bb8b1 | ||
|
|
27e814ad76 | ||
|
|
03893d9b7f | ||
|
|
297aa04bea | ||
|
|
c678a7b402 | ||
|
|
b7242a1ad1 | ||
|
|
cf0a5089f5 | ||
|
|
de32f1774f | ||
|
|
e1c4fe21d4 | ||
|
|
89c8cbbe0c | ||
|
|
16ba63ccde | ||
|
|
347a52aa5c | ||
|
|
c920ee57b8 | ||
|
|
985a906c6c | ||
|
|
f038f68ad8 | ||
|
|
d3e6597f73 | ||
|
|
f8d729a17b | ||
|
|
b66427eab9 | ||
|
|
35d4a47cb1 | ||
|
|
a721d590b4 | ||
|
|
628e393b9a | ||
|
|
a950322b07 | ||
|
|
34917618d8 | ||
|
|
1a7c91f658 | ||
|
|
41687c8fa8 | ||
|
|
91f6e69cc4 | ||
|
|
6ec1a9ca52 | ||
|
|
9d33629b6d | ||
|
|
ef8563ab36 | ||
|
|
a392a87389 | ||
|
|
4a5b5bc86e | ||
|
|
68edcd12b1 | ||
|
|
fbc366c1a5 | ||
|
|
20b10aed1b | ||
|
|
b4505284f2 | ||
|
|
47fe258b5a | ||
|
|
21616a815b | ||
|
|
50934e056e | ||
|
|
ba1df2cfca | ||
|
|
7dc13e5cab | ||
|
|
7687746c7c | ||
|
|
5264c71d86 | ||
|
|
5d87e1aeba | ||
|
|
c03e893416 | ||
|
|
fef09a6691 | ||
|
|
1d35d439cc | ||
|
|
054e1ab3ee | ||
|
|
641f4611b2 | ||
|
|
594716abea | ||
|
|
d675abbd3f | ||
|
|
702f4039bc | ||
|
|
b479a62ec2 | ||
|
|
81c18c70f5 | ||
|
|
bbea679ab2 | ||
|
|
f1d51aa7eb |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,12 +5,15 @@ nosetests.xml
|
||||
junit-report.xml
|
||||
pylint.txt
|
||||
toy.py
|
||||
tox.ini
|
||||
violations.pyflakes.txt
|
||||
cover/
|
||||
build/
|
||||
docs/_build
|
||||
requests.egg-info/
|
||||
*.pyc
|
||||
*.swp
|
||||
*.egg
|
||||
env/
|
||||
|
||||
.workon
|
||||
|
||||
@@ -2,10 +2,10 @@ language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.2
|
||||
- 3.3
|
||||
env: HTTPBIN_URL=http://httpbin.org/
|
||||
script: make test
|
||||
script: invoke test
|
||||
install:
|
||||
- make test-deps
|
||||
- pip install -r requirements.txt
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
18
AUTHORS.rst
18
AUTHORS.rst
@@ -118,3 +118,21 @@ Patches and Suggestions
|
||||
- Martijn Pieters
|
||||
- Jonatan Heyman
|
||||
- David Bonner <dbonner@gmail.com> @rascalking
|
||||
- Vinod Chandru
|
||||
- Johnny Goodnow <j.goodnow29@gmail.com>
|
||||
- Denis Ryzhkov <denisr@denisr.com>
|
||||
- Wilfred Hughes <me@wilfred.me.uk> @dontYetKnow
|
||||
- Dmitry Medvinsky <me@dmedvinsky.name>
|
||||
- Bryce Boe <bbzbryce@gmail.com> @bboe
|
||||
- Colin Dunklau <colin.dunklau@gmail.com> @cdunklau
|
||||
- Bob Carroll <bob.carroll@alum.rit.edu> @rcarz
|
||||
- Hugo Osvaldo Barrera <hugo@osvaldobarrera.com.ar> @hobarrera
|
||||
- Łukasz Langa <lukasz@langa.pl> @llanga
|
||||
- Dave Shawley <daveshawley@gmail.com>
|
||||
- James Clarke (jam)
|
||||
- Kevin Burke <kev@inburke.com>
|
||||
- Flavio Curella
|
||||
- David Pursehouse <david.pursehouse@gmail.com> @dpursehouse
|
||||
- Jon Parise
|
||||
- Alexander Karpinsky @homm86
|
||||
- Marc Schlaich @schlamar
|
||||
113
HISTORY.rst
113
HISTORY.rst
@@ -3,6 +3,113 @@
|
||||
History
|
||||
-------
|
||||
|
||||
2.0.0 (2013-09-24)
|
||||
++++++++++++++++++
|
||||
|
||||
**API Changes:**
|
||||
|
||||
- Keys in the Headers dictionary are now native strings on all Python versions,
|
||||
i.e. bytestrings on Python 2, unicode on Python 3.
|
||||
- Proxy URLs now *must* have an explicit scheme. A ``MissingSchema`` exception
|
||||
will be raised if they don't.
|
||||
- Timeouts now apply to read time if ``Stream=False``.
|
||||
- ``RequestException`` is now a subclass of ``IOError``, not ``RuntimeError``.
|
||||
- Added new method to ``PreparedRequest`` objects: ``PreparedRequest.copy()``.
|
||||
- Added new method to ``Session`` objects: ``Session.update_request()``. This
|
||||
method updates a ``Request`` object with the data (e.g. cookies) stored on
|
||||
the ``Session``.
|
||||
- Added new method to ``Session`` objects: ``Session.prepare_request()``. This
|
||||
method updates and prepares a ``Request`` object, and returns the
|
||||
corresponding ``PreparedRequest`` object.
|
||||
- Added new method to ``HTTPAdapter`` objects: ``HTTPAdapter.proxy_headers()``.
|
||||
This should not be called directly, but improves the subclass interface.
|
||||
- ``httplib.IncompleteRead`` exceptions caused by incorrect chunked encoding
|
||||
will now raise a Requests ``ChunkedEncodingError`` instead.
|
||||
- Invalid percent-escape sequences now cause a Requests ``InvalidURL``
|
||||
exception to be raised.
|
||||
- HTTP 208 no longer uses reason phrase ``"im_used"``. Correctly uses
|
||||
``"already_reported"``.
|
||||
- HTTP 226 reason added (``"im_used"``).
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
- Vastly improved proxy support, including the CONNECT verb. Special thanks to
|
||||
the many contributors who worked towards this improvement.
|
||||
- Cookies are now properly managed when 401 authentication responses are
|
||||
received.
|
||||
- Chunked encoding fixes.
|
||||
- Support for mixed case schemes.
|
||||
- Better handling of streaming downloads.
|
||||
- Retrieve environment proxies from more locations.
|
||||
- Minor cookies fixes.
|
||||
- Imroved redirect behaviour.
|
||||
- Improved streaming behaviour, particularly for compressed data.
|
||||
- Miscellaneous small Python 3 text encoding bugs.
|
||||
- ``.netrc`` no longer overrides explicit auth.
|
||||
- Cookies set by hooks are now correctly persisted on Sessions.
|
||||
- Fix problem with cookies that specify port numbers in their host field.
|
||||
- ``BytesIO`` can be used to perform streaming uploads.
|
||||
- More generous parsing of the ``no_proxy`` environment variable.
|
||||
- Non-string objects can be passed in data values alongside files.
|
||||
|
||||
1.2.3 (2013-05-25)
|
||||
++++++++++++++++++
|
||||
|
||||
- Simple packaging fix
|
||||
|
||||
|
||||
1.2.2 (2013-05-23)
|
||||
++++++++++++++++++
|
||||
|
||||
- Simple packaging fix
|
||||
|
||||
|
||||
1.2.1 (2013-05-20)
|
||||
++++++++++++++++++
|
||||
|
||||
- Python 3.3.2 compatibility
|
||||
- Always percent-encode location headers
|
||||
- Fix connection adapter matching to be most-specific first
|
||||
- new argument to the default connection adapter for passing a block argument
|
||||
- prevent a KeyError when there's no link headers
|
||||
|
||||
1.2.0 (2013-03-31)
|
||||
++++++++++++++++++
|
||||
|
||||
- Fixed cookies on sessions and on requests
|
||||
- Significantly change how hooks are dispatched - hooks now receive all the
|
||||
arguments specified by the user when making a request so hooks can make a
|
||||
secondary request with the same parameters. This is especially necessary for
|
||||
authentication handler authors
|
||||
- certifi support was removed
|
||||
- Fixed bug where using OAuth 1 with body ``signature_type`` sent no data
|
||||
- Major proxy work thanks to @Lukasa including parsing of proxy authentication
|
||||
from the proxy url
|
||||
- Fix DigestAuth handling too many 401s
|
||||
- Update vendored urllib3 to include SSL bug fixes
|
||||
- Allow keyword arguments to be passed to ``json.loads()`` via the
|
||||
``Response.json()`` method
|
||||
- Don't send ``Content-Length`` header by default on ``GET`` or ``HEAD``
|
||||
requests
|
||||
- Add ``elapsed`` attribute to ``Response`` objects to time how long a request
|
||||
took.
|
||||
- Fix ``RequestsCookieJar``
|
||||
- Sessions and Adapters are now picklable, i.e., can be used with the
|
||||
multiprocessing library
|
||||
- Update charade to version 1.0.3
|
||||
|
||||
The change in how hooks are dispatched will likely cause a great deal of
|
||||
issues.
|
||||
|
||||
1.1.0 (2013-01-10)
|
||||
++++++++++++++++++
|
||||
|
||||
- CHUNKED REQUESTS
|
||||
- Support for iterable response bodies
|
||||
- Assume servers persist redirect params
|
||||
- Allow explicit content types to be specified for file data
|
||||
- Make merge_kwargs case-insensitive when looking up keys
|
||||
|
||||
1.0.3 (2012-12-18)
|
||||
++++++++++++++++++
|
||||
|
||||
@@ -31,7 +138,7 @@ History
|
||||
- /s/prefetch/stream
|
||||
- Removal of all configuration
|
||||
- Standard library logging
|
||||
- Make Reponse.json() callable, not property.
|
||||
- Make Response.json() callable, not property.
|
||||
- Usage of new charade project, which provides python 2 and 3 simultaneous chardet.
|
||||
- Removal of all hooks except 'response'
|
||||
- Removal of all authentication helpers (OAuth, Kerberos)
|
||||
@@ -566,10 +673,10 @@ This is not a backwards compatible change.
|
||||
++++++++++++++++++
|
||||
|
||||
* New HTTPHandling Methods
|
||||
- Reponse.__nonzero__ (false if bad HTTP Status)
|
||||
- Response.__nonzero__ (false if bad HTTP Status)
|
||||
- Response.ok (True if expected HTTP Status)
|
||||
- Response.error (Logged HTTPError if bad HTTP Status)
|
||||
- Reponse.raise_for_status() (Raises stored HTTPError)
|
||||
- Response.raise_for_status() (Raises stored HTTPError)
|
||||
|
||||
|
||||
0.2.2 (2011-02-14)
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2012 Kenneth Reitz
|
||||
Copyright 2013 Kenneth Reitz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -10,4 +10,4 @@ Copyright 2012 Kenneth Reitz
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
limitations under the License.
|
||||
|
||||
30
Makefile
30
Makefile
@@ -1,30 +0,0 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
test:
|
||||
py.test
|
||||
|
||||
test-deps:
|
||||
pip install -r requirements.txt
|
||||
|
||||
six:
|
||||
python test_requests.py
|
||||
python3 test_requests.py
|
||||
|
||||
deps: urllib3 certs charade
|
||||
|
||||
urllib3:
|
||||
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
|
||||
|
||||
charade:
|
||||
rm -fr requests/packages/charade
|
||||
git clone https://github.com/sigmavirus24/charade.git
|
||||
cd charade && git checkout master && cd ..
|
||||
mv charade/charade requests/packages/
|
||||
rm -fr charade
|
||||
|
||||
certs:
|
||||
cd requests && curl -O https://raw.github.com/kennethreitz/certifi/master/certifi/cacert.pem
|
||||
2
NOTICE
2
NOTICE
@@ -6,7 +6,7 @@ Urllib3 License
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt),
|
||||
Modifications copyright 2022 Kenneth Reitz.
|
||||
Modifications copyright 2012 Kenneth Reitz.
|
||||
|
||||
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
|
||||
|
||||
19
README.rst
19
README.rst
@@ -1,10 +1,15 @@
|
||||
Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
.. image:: https://badge.fury.io/py/requests.png
|
||||
:target: http://badge.fury.io/py/requests
|
||||
|
||||
.. image:: https://travis-ci.org/kennethreitz/requests.png?branch=master
|
||||
:target: https://travis-ci.org/kennethreitz/requests
|
||||
|
||||
.. image:: https://pypip.in/d/requests/badge.png
|
||||
:target: https://crate.io/packages/requests/
|
||||
|
||||
Requests is an Apache2 Licensed HTTP library, written in Python, for human
|
||||
beings.
|
||||
|
||||
@@ -49,12 +54,13 @@ Features
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
- Thread-safety
|
||||
- HTTP(S) proxy support
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To install requests, simply:
|
||||
To install Requests, simply:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -69,14 +75,21 @@ Or, if you absolutely must:
|
||||
But, you really shouldn't do that.
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation is available at http://docs.python-requests.org/.
|
||||
|
||||
|
||||
Contribute
|
||||
----------
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet.
|
||||
#. Fork `the repository`_ on Github to start making your changes to the **master** branch (or branch off of it).
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a `Contributor Friendly`_ tag for issues that should be ideal for people who are not very familiar with the codebase yet.
|
||||
#. If you feel uncomfortable or uncertain about an issue or your changes, feel free to email @sigmavirus24 and he will happily help you via email, Skype, remote pairing or whatever you are comfortable with.
|
||||
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/requests
|
||||
.. _AUTHORS: https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst
|
||||
.. _Contributor Friendly: https://github.com/kennethreitz/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open
|
||||
|
||||
23
docs/_templates/sidebarintro.html
vendored
23
docs/_templates/sidebarintro.html
vendored
@@ -1,6 +1,6 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Logo"/>
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" title="Rezzy the Requests Sea Turtle"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
<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.
|
||||
human beings.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -34,18 +33,24 @@
|
||||
<a href="http://gum.co/RRZc" class="gumroad-button">Requests Pro</a><script type="text/javascript" src="https://gumroad.com/js/gumroad-button.js"></script>
|
||||
</p>
|
||||
|
||||
<h3>Feedback</h3>
|
||||
<p>
|
||||
Feedback is greatly appreciated. If you have any questions, comments,
|
||||
random praise, or anonymous threats, <a href="mailto:me@kennethreitz.com">
|
||||
shoot me an email</a>.
|
||||
</p>
|
||||
|
||||
<h3>Translations</h3>
|
||||
<ul>
|
||||
<li><a href="http://docs.python-requests.org/">English</a></li>
|
||||
<li><a href="http://fr.python-requests.org/">French</a></li>
|
||||
<li><a href="http://de.python-requests.org/">German</a></li>
|
||||
<li><a href="http://jp.python-requests.org/">Japanese</a></li>
|
||||
<li><a href="http://cn.python-requests.org/">Chinese</a></li>
|
||||
<li><a href="http://pt.python-requests.org/">Portuguese</a></li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<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://stackoverflow.com/questions/tagged/python-requests" title="Use it for support regarding python-request usage">Requests @ StackOverflow</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
|
||||
3
docs/_templates/sidebarlogo.html
vendored
3
docs/_templates/sidebarlogo.html
vendored
@@ -1,6 +1,6 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Logo"/>
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" title="Rezzy the Requests Sea Turtle"/>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
@@ -32,3 +32,4 @@
|
||||
<p>
|
||||
<a href="http://gum.co/RRZc" class="gumroad-button">Requests Pro</a><script type="text/javascript" src="https://gumroad.com/js/gumroad-button.js"></script>
|
||||
</p>
|
||||
|
||||
|
||||
119
docs/api.rst
119
docs/api.rst
@@ -13,7 +13,7 @@ important right here and provide links to the canonical documentation.
|
||||
Main Interface
|
||||
--------------
|
||||
|
||||
All of Request's functionality can be accessed by these 7 methods.
|
||||
All of Requests' functionality can be accessed by these 7 methods.
|
||||
They all return an instance of the :class:`Response <Response>` object.
|
||||
|
||||
.. autofunction:: request
|
||||
@@ -41,17 +41,18 @@ Request Sessions
|
||||
.. autoclass:: Session
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: requests.adapters.HTTPAdapter
|
||||
:inherited-members:
|
||||
|
||||
|
||||
Exceptions
|
||||
~~~~~~~~~~
|
||||
|
||||
.. module:: requests
|
||||
|
||||
.. autoexception:: RequestException
|
||||
.. autoexception:: ConnectionError
|
||||
.. autoexception:: HTTPError
|
||||
.. autoexception:: URLRequired
|
||||
.. autoexception:: TooManyRedirects
|
||||
.. autoexception:: requests.exceptions.RequestException
|
||||
.. autoexception:: requests.exceptions.ConnectionError
|
||||
.. autoexception:: requests.exceptions.HTTPError
|
||||
.. autoexception:: requests.exceptions.URLRequired
|
||||
.. autoexception:: requests.exceptions.TooManyRedirects
|
||||
|
||||
|
||||
Status Code Lookup
|
||||
@@ -73,18 +74,17 @@ Status Code Lookup
|
||||
Cookies
|
||||
~~~~~~~
|
||||
|
||||
.. autofunction:: dict_from_cookiejar
|
||||
.. autofunction:: cookiejar_from_dict
|
||||
.. autofunction:: add_dict_to_cookiejar
|
||||
.. autofunction:: requests.utils.dict_from_cookiejar
|
||||
.. autofunction:: requests.utils.cookiejar_from_dict
|
||||
.. autofunction:: requests.utils.add_dict_to_cookiejar
|
||||
|
||||
|
||||
Encodings
|
||||
~~~~~~~~~
|
||||
|
||||
.. autofunction:: get_encodings_from_content
|
||||
.. autofunction:: get_encoding_from_headers
|
||||
.. autofunction:: get_unicode_from_response
|
||||
.. autofunction:: decode_gzip
|
||||
.. autofunction:: requests.utils.get_encodings_from_content
|
||||
.. autofunction:: requests.utils.get_encoding_from_headers
|
||||
.. autofunction:: requests.utils.get_unicode_from_response
|
||||
|
||||
|
||||
Classes
|
||||
@@ -104,3 +104,92 @@ Classes
|
||||
.. autoclass:: requests.Session
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: requests.adapters.HTTPAdapter
|
||||
:inherited-members:
|
||||
|
||||
|
||||
Migrating to 1.x
|
||||
----------------
|
||||
|
||||
This section details the main differences between 0.x and 1.x and is meant
|
||||
to ease the pain of upgrading.
|
||||
|
||||
|
||||
API Changes
|
||||
~~~~~~~~~~~
|
||||
|
||||
* ``Response.json`` is now a callable and not a property of a response.
|
||||
|
||||
::
|
||||
|
||||
import requests
|
||||
r = requests.get('https://github.com/timeline.json')
|
||||
r.json() # This *call* raises an exception if JSON decoding fails
|
||||
|
||||
* The ``Session`` API has changed. Sessions objects no longer take parameters.
|
||||
``Session`` is also now capitalized, but it can still be
|
||||
instantiated with a lowercase ``session`` for backwards compatibility.
|
||||
|
||||
::
|
||||
|
||||
s = requests.Session() # formerly, session took parameters
|
||||
s.auth = auth
|
||||
s.headers.update(headers)
|
||||
r = s.get('http://httpbin.org/headers')
|
||||
|
||||
* All request hooks have been removed except 'response'.
|
||||
|
||||
* Authentication helpers have been broken out into separate modules. See
|
||||
requests-oauthlib_ and requests-kerberos_.
|
||||
|
||||
.. _requests-oauthlib: https://github.com/requests/requests-oauthlib
|
||||
.. _requests-kerberos: https://github.com/requests/requests-kerberos
|
||||
|
||||
* The parameter for streaming requests was changed from ``prefetch`` to
|
||||
``stream`` and the logic was inverted. In addition, ``stream`` is now
|
||||
required for raw response reading.
|
||||
|
||||
::
|
||||
|
||||
# in 0.x, passing prefetch=False would accomplish the same thing
|
||||
r = requests.get('https://github.com/timeline.json', stream=True)
|
||||
for chunk in r.iter_content(8192):
|
||||
...
|
||||
|
||||
* The ``config`` parameter to the requests method has been removed. Some of
|
||||
these options are now configured on a ``Session`` such as keep-alive and
|
||||
maximum number of redirects. The verbosity option should be handled by
|
||||
configuring logging.
|
||||
|
||||
::
|
||||
|
||||
import requests
|
||||
import logging
|
||||
|
||||
# these two lines enable debugging at httplib level (requests->urllib3->httplib)
|
||||
# you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA.
|
||||
# the only thing missing will be the response.body which is not logged.
|
||||
import httplib
|
||||
httplib.HTTPConnection.debuglevel = 1
|
||||
|
||||
logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
requests_log = logging.getLogger("requests.packages.urllib3")
|
||||
requests_log.setLevel(logging.DEBUG)
|
||||
requests_log.propagate = True
|
||||
|
||||
requests.get('http://httpbin.org/headers')
|
||||
|
||||
|
||||
|
||||
Licensing
|
||||
~~~~~~~~~
|
||||
|
||||
One key difference that has nothing to do with the API is a change in the
|
||||
license from the ISC_ license to the `Apache 2.0`_ license. The Apache 2.0
|
||||
license ensures that contributions to Requests are also covered by the Apache
|
||||
2.0 license.
|
||||
|
||||
.. _ISC: http://opensource.org/licenses/ISC
|
||||
.. _Apache 2.0: http://opensource.org/licenses/Apache-2.0
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Support
|
||||
=======
|
||||
|
||||
If you have a questions or issues about Requests, there are several options:
|
||||
If you have questions or issues about Requests, there are several options:
|
||||
|
||||
Send a Tweet
|
||||
------------
|
||||
|
||||
@@ -43,7 +43,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'2012. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
copyright = u'2013. 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
|
||||
@@ -133,7 +133,6 @@ html_static_path = ['_static']
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
|
||||
@@ -38,4 +38,4 @@ Linux Distro Packages
|
||||
|
||||
Distributions have been made for many Linux repositories, including: Ubuntu, Debian, RHEL, and Arch.
|
||||
|
||||
These distributions are often aggressive forks. They are discouraged and not supported.
|
||||
These distributions are sometimes divergent forks, or are otherwise not kept up-to-date with the latest code and bugfixes. PyPI (and its mirrors) and GitHub are the official distribution sources; alternatives are not supported by the Requests project.
|
||||
|
||||
@@ -3,22 +3,28 @@ 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.
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a bug.
|
||||
There is a Contributor Friendly tag for issues that should be ideal for people who are not very
|
||||
familiar with the codebase yet.
|
||||
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ on Github andstart making your
|
||||
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ on GitHub and start making your
|
||||
changes to a new branch.
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Write a test which shows that the bug was fixed.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :)
|
||||
Make sure to add yourself to `AUTHORS <https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst>`_.
|
||||
|
||||
Feature Freeze
|
||||
--------------
|
||||
|
||||
As of v1.0.0, Requests has now entered a feature freeze. Requests for new
|
||||
features and Pull Requests implementing those features will not be accepted.
|
||||
|
||||
Development Dependencies
|
||||
------------------------
|
||||
|
||||
You'll need to install py.test in order to run the Requests' test suite::
|
||||
|
||||
$ make test-deps
|
||||
$ make test
|
||||
$ pip install -r requirements.txt
|
||||
$ invoke test
|
||||
py.test
|
||||
platform darwin -- Python 2.7.3 -- pytest-2.3.4
|
||||
collected 25 items
|
||||
@@ -40,10 +46,15 @@ Requests currently supports the following versions of Python:
|
||||
|
||||
Support for Python 3.1 and 3.2 may be dropped at any time.
|
||||
|
||||
Google App Engine will never be officially supported. Pull requests for compatiblity will be accepted, as long as they don't complicate the codebase.
|
||||
Google App Engine will never be officially supported. Pull Requests for compatibility will be accepted, as long as they don't complicate the codebase.
|
||||
|
||||
|
||||
Are you crazy?
|
||||
--------------
|
||||
|
||||
- SPDY support would be awesome. No C extensions.
|
||||
|
||||
Downstream Repackaging
|
||||
----------------------
|
||||
|
||||
If you are repackaging Requests, please note that you must also redistribute the ``cacerts.pem`` file in order to get correct SSL functionality.
|
||||
|
||||
@@ -38,7 +38,7 @@ Requests takes all of the work out of Python HTTP/1.1 — making your integrati
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
Amazon, Google, Twilio, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 1,500,000 times from PyPI.
|
||||
Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 5,000,000 times from PyPI.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
|
||||
@@ -13,7 +13,7 @@ The Session object allows you to persist certain parameters across
|
||||
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.
|
||||
A Session object has all the methods of the main Requests API.
|
||||
|
||||
Let's persist some cookies across requests::
|
||||
|
||||
@@ -27,7 +27,7 @@ Let's persist some cookies across requests::
|
||||
|
||||
|
||||
Sessions can also be used to provide default data to the request methods. This
|
||||
is done by providing data to the properties on a session object::
|
||||
is done by providing data to the properties on a Session object::
|
||||
|
||||
s = requests.Session()
|
||||
s.auth = ('user', 'pass')
|
||||
@@ -49,9 +49,9 @@ Request and Response Objects
|
||||
----------------------------
|
||||
|
||||
Whenever a call is made to requests.*() you are doing two major things. First,
|
||||
you are constructing a ``Request`` object which will be sent of to a server
|
||||
you are constructing a ``Request`` object which will be sent off to a server
|
||||
to request or query some resource. Second, a ``Response`` object is generated
|
||||
once ``requests`` gets a response back from the server. The response object
|
||||
once ``requests`` gets a response back from the server. The Response object
|
||||
contains all of the information returned by the server and also contains the
|
||||
``Request`` object you created originally. Here is a simple request to get some
|
||||
very important information from Wikipedia's servers::
|
||||
@@ -75,8 +75,42 @@ request, and then the request's headers::
|
||||
|
||||
>>> r.request.headers
|
||||
{'Accept-Encoding': 'identity, deflate, compress, gzip',
|
||||
'Accept': '*/*', 'User-Agent': 'python-requests/0.13.1'}
|
||||
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}
|
||||
|
||||
Prepared Requests
|
||||
-----------------
|
||||
|
||||
Whenever you receive a :class:`Response <requests.models.Response>` object
|
||||
from an API call or a Session call, the ``request`` attribute is actually the
|
||||
``PreparedRequest`` that was used. In some cases you may wish to do some extra
|
||||
work to the body or headers (or anything else really) before sending a
|
||||
request. The simple recipe for this is the following::
|
||||
|
||||
from requests import Request, Session
|
||||
|
||||
s = Session()
|
||||
prepped = Request('GET', # or any other method, 'POST', 'PUT', etc.
|
||||
url,
|
||||
data=data
|
||||
headers=headers
|
||||
# ...
|
||||
).prepare()
|
||||
# do something with prepped.body
|
||||
# do something with prepped.headers
|
||||
resp = s.send(prepped,
|
||||
stream=stream,
|
||||
verify=verify,
|
||||
proxies=proxies,
|
||||
cert=cert,
|
||||
timeout=timeout,
|
||||
# etc.
|
||||
)
|
||||
print(resp.status_code)
|
||||
|
||||
Since you are not doing anything special with the ``Request`` object, you
|
||||
prepare it immediately and modified the ``PreparedRequest`` object. You then
|
||||
send that with the other parameters you would have sent to ``requests.*`` or
|
||||
``Sesssion.*``.
|
||||
|
||||
SSL Cert Verification
|
||||
---------------------
|
||||
@@ -86,14 +120,14 @@ Requests can verify SSL certificates for HTTPS requests, just like a web browser
|
||||
>>> 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::
|
||||
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.
|
||||
|
||||
Requests can also ignore verifying the SSL certficate if you set ``verify`` to False.
|
||||
Requests can also ignore verifying the SSL certificate if you set ``verify`` to False.
|
||||
|
||||
::
|
||||
|
||||
@@ -102,7 +136,7 @@ Requests can also ignore verifying the SSL certficate if you set ``verify`` to F
|
||||
|
||||
By default, ``verify`` is set to True. Option ``verify`` only applies to host certs.
|
||||
|
||||
You can also specify the local cert file either as a path or key value pair::
|
||||
You can also specify a local cert to use as client side certificate, as a single file (containing the private key and the certificate) or as a tuple of both file's path::
|
||||
|
||||
>>> requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))
|
||||
<Response [200]>
|
||||
@@ -127,7 +161,7 @@ At this point only the response headers have been downloaded and the connection
|
||||
content = r.content
|
||||
...
|
||||
|
||||
You can further control the workflow by use of the :class:`Response.iter_content` and :class:`Response.iter_lines` methods, or reading from the underlying urllib3 :class:`urllib3.HTTPResponse` at :class:`Response.raw`.
|
||||
You can further control the workflow by use of the :class:`Response.iter_content` and :class:`Response.iter_lines` methods. Alternatively, you can read the undecoded body from the underlying urllib3 :class:`urllib3.HTTPResponse` at :class:`Response.raw`.
|
||||
|
||||
|
||||
Keep-Alive
|
||||
@@ -138,6 +172,28 @@ Excellent news — thanks to urllib3, keep-alive is 100% automatic within a ses
|
||||
Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``stream`` to ``False`` or read the ``content`` property of the ``Response`` object.
|
||||
|
||||
|
||||
Streaming Uploads
|
||||
-----------------
|
||||
|
||||
Requests supports streaming uploads, which allow you to send large streams or files without reading them into memory. To stream and upload, simply provide a file-like object for your body::
|
||||
|
||||
with open('massive-body') as f:
|
||||
requests.post('http://some.url/streamed', data=f)
|
||||
|
||||
|
||||
Chunk-Encoded Requests
|
||||
----------------------
|
||||
|
||||
Requests also supports Chunked transfer encoding for outgoing and incoming requests. To send a chunk-encoded request, simply provide a generator (or any iterator without a length) for your body::
|
||||
|
||||
|
||||
def gen():
|
||||
yield 'hi'
|
||||
yield 'there'
|
||||
|
||||
requests.post('http://some.url/chunked', data=gen())
|
||||
|
||||
|
||||
Event Hooks
|
||||
-----------
|
||||
|
||||
@@ -161,7 +217,7 @@ argument.
|
||||
|
||||
::
|
||||
|
||||
def print_url(r):
|
||||
def print_url(r, *args, **kwargs):
|
||||
print(r.url)
|
||||
|
||||
If an error occurs while executing your callback, a warning is given.
|
||||
@@ -212,6 +268,8 @@ Then, we can make a request using our Pizza Auth::
|
||||
>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
|
||||
<Response [200]>
|
||||
|
||||
.. _streaming-requests
|
||||
|
||||
Streaming Requests
|
||||
------------------
|
||||
|
||||
@@ -220,14 +278,15 @@ APIs such as the `Twitter Streaming API <https://dev.twitter.com/docs/streaming-
|
||||
|
||||
To use the Twitter Streaming API to track the keyword "requests"::
|
||||
|
||||
import requests
|
||||
import json
|
||||
import requests
|
||||
|
||||
r = requests.post('https://stream.twitter.com/1/statuses/filter.json',
|
||||
data={'track': 'requests'}, auth=('username', 'password'), stream=True)
|
||||
r = requests.get('http://httpbin.org/stream/20', stream=True)
|
||||
|
||||
for line in r.iter_lines():
|
||||
if line: # filter out keep-alive new lines
|
||||
|
||||
# filter out keep-alive new lines
|
||||
if line:
|
||||
print json.loads(line)
|
||||
|
||||
|
||||
@@ -240,8 +299,8 @@ If you need to use a proxy, you can configure individual requests with the
|
||||
import requests
|
||||
|
||||
proxies = {
|
||||
"http": "10.10.1.10:3128",
|
||||
"https": "10.10.1.10:1080",
|
||||
"http": "http://10.10.1.10:3128",
|
||||
"https": "http://10.10.1.10:1080",
|
||||
}
|
||||
|
||||
requests.get("http://example.org", proxies=proxies)
|
||||
@@ -250,8 +309,8 @@ You can also configure proxies by environment variables ``HTTP_PROXY`` and ``HTT
|
||||
|
||||
::
|
||||
|
||||
$ export HTTP_PROXY="10.10.1.10:3128"
|
||||
$ export HTTPS_PROXY="10.10.1.10:1080"
|
||||
$ export HTTP_PROXY="http://10.10.1.10:3128"
|
||||
$ export HTTPS_PROXY="http://10.10.1.10:1080"
|
||||
$ python
|
||||
>>> import requests
|
||||
>>> requests.get("http://example.org")
|
||||
@@ -313,17 +372,12 @@ out what type of content it is. Do this like so::
|
||||
...
|
||||
application/json; charset=utf-8
|
||||
|
||||
So, GitHub returns JSON. That's great, we can use the JSON module to turn it
|
||||
into Python objects. Because GitHub returned UTF-8, we should use the
|
||||
``r.text`` method, not the ``r.content`` method. ``r.content`` returns a
|
||||
bytestring, while ``r.text`` returns a Unicode-encoded string. I have no plans
|
||||
to perform byte-manipulation on this response, so I want any Unicode code
|
||||
points encoded.
|
||||
So, GitHub returns JSON. That's great, we can use the ``r.json`` method to
|
||||
parse it into Python objects.
|
||||
|
||||
::
|
||||
|
||||
>>> import json
|
||||
>>> commit_data = json.loads(r.text)
|
||||
>>> commit_data = r.json()
|
||||
>>> print commit_data.keys()
|
||||
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
|
||||
>>> print commit_data[u'committer']
|
||||
@@ -380,7 +434,7 @@ Cool, we have three comments. Let's take a look at the last of them.
|
||||
>>> r = requests.get(r.url + u'/comments')
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> comments = json.loads(r.text)
|
||||
>>> comments = r.json()
|
||||
>>> print comments[0].keys()
|
||||
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
|
||||
>>> print comments[2][u'body']
|
||||
@@ -417,7 +471,7 @@ the very common Basic Auth.
|
||||
>>> r = requests.post(url=url, data=body, auth=auth)
|
||||
>>> r.status_code
|
||||
201
|
||||
>>> content = json.loads(r.text)
|
||||
>>> content = r.json()
|
||||
>>> print content[u'body']
|
||||
Sounds great! I'll get right on it.
|
||||
|
||||
@@ -480,9 +534,61 @@ GitHub uses these for `pagination <http://developer.github.com/v3/#pagination>`_
|
||||
|
||||
Requests will automatically parse these link headers and make them easily consumable::
|
||||
|
||||
>>> r.links['next']
|
||||
'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10'
|
||||
>>> r.links["next"]
|
||||
{'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'}
|
||||
|
||||
>>> r.links['last']
|
||||
'https://api.github.com/users/kennethreitz/repos?page=6&per_page=10'
|
||||
>>> r.links["last"]
|
||||
{'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
|
||||
|
||||
Transport Adapters
|
||||
------------------
|
||||
|
||||
As of v1.0.0, Requests has moved to a modular internal design. Part of the
|
||||
reason this was done was to implement Transport Adapters, originally
|
||||
`described here`_. Transport Adapters provide a mechanism to define interaction
|
||||
methods for an HTTP service. In particular, they allow you to apply per-service
|
||||
configuration.
|
||||
|
||||
Requests ships with a single Transport Adapter, the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. This adapter provides the
|
||||
default Requests interaction with HTTP and HTTPS using the powerful `urllib3`_
|
||||
library. Whenever a Requests :class:`Session <Session>` is initialized, one of
|
||||
these is attached to the :class:`Session <Session>` object for HTTP, and one
|
||||
for HTTPS.
|
||||
|
||||
Requests enables users to create and use their own Transport Adapters that
|
||||
provide specific functionality. Once created, a Transport Adapter can be
|
||||
mounted to a Session object, along with an indication of which web services
|
||||
it should apply to.
|
||||
|
||||
::
|
||||
|
||||
>>> s = requests.Session()
|
||||
>>> s.mount('http://www.github.com', MyAdapter())
|
||||
|
||||
The mount call registers a specific instance of a Transport Adapter to a
|
||||
prefix. Once mounted, any HTTP request made using that session whose URL starts
|
||||
with the given prefix will use the given Transport Adapter.
|
||||
|
||||
Implementing a Transport Adapter is beyond the scope of this documentation, but
|
||||
a good start would be to subclass the ``requests.adapters.BaseAdapter`` class.
|
||||
|
||||
.. _`described here`: http://kennethreitz.org/exposures/the-future-of-python-http
|
||||
.. _`urllib3`: https://github.com/shazow/urllib3
|
||||
|
||||
Blocking Or Non-Blocking?
|
||||
-------------------------
|
||||
|
||||
With the default Transport Adapter in place, Requests does not provide any kind
|
||||
of non-blocking IO. The ``Response.content`` property will block until the
|
||||
entire response has been downloaded. If you require more granularity, the
|
||||
streaming features of the library (see :ref:`streaming-requests`) allow you to
|
||||
retrieve smaller quantities of the response at a time. However, these calls
|
||||
will still block.
|
||||
|
||||
If you are concerned about the use of blocking IO, there are lots of projects
|
||||
out there that combine Requests with one of Python's asynchronicity frameworks.
|
||||
Two excellent examples are `grequests`_ and `requests-futures`_.
|
||||
|
||||
.. _`grequests`: https://github.com/kennethreitz/grequests
|
||||
.. _`requests-futures`: https://github.com/ross/requests-futures
|
||||
|
||||
@@ -32,6 +32,17 @@ Providing the credentials in a tuple like this is exactly the same as the
|
||||
``HTTPBasicAuth`` example above.
|
||||
|
||||
|
||||
netrc Authentication
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If no authentication method is given with the ``auth`` argument, Requests will
|
||||
attempt to get the authentication credentials for the URL's hostname from the
|
||||
user's netrc file.
|
||||
|
||||
If credentials for the hostname are found, the request is sent with HTTP Basic
|
||||
Auth.
|
||||
|
||||
|
||||
Digest Authentication
|
||||
---------------------
|
||||
|
||||
@@ -44,6 +55,27 @@ and Requests supports this out of the box as well::
|
||||
<Response [200]>
|
||||
|
||||
|
||||
OAuth 1 Authentication
|
||||
----------------------
|
||||
|
||||
A common form of authentication for several web APIs is OAuth. The ``requests-oauthlib``
|
||||
library allows Requests users to easily make OAuth authenticated requests::
|
||||
|
||||
>>> import requests
|
||||
>>> from requests_oauthlib import OAuth1
|
||||
|
||||
>>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
|
||||
>>> auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET',
|
||||
'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')
|
||||
|
||||
>>> requests.get(url, auth=auth)
|
||||
<Response [200]>
|
||||
|
||||
For more information on how to OAuth flow works, please see the official `OAuth`_ website.
|
||||
For examples and documentation on requests-oauthlib, please see the `requests_oauthlib`_
|
||||
repository on GitHub
|
||||
|
||||
|
||||
Other Authentication
|
||||
--------------------
|
||||
|
||||
@@ -53,12 +85,11 @@ authentication handlers for more complicated or less commonly-used forms of
|
||||
authentication. Some of the best have been brought together under the
|
||||
`Requests organization`_, including:
|
||||
|
||||
- OAuth_
|
||||
- Kerberos_
|
||||
- NTLM_
|
||||
|
||||
If you want to use any of these forms of authentication, go straight to their
|
||||
Github page and follow the instructions.
|
||||
GitHub page and follow the instructions.
|
||||
|
||||
|
||||
New Forms of Authentication
|
||||
@@ -69,15 +100,28 @@ want, you can implement it yourself. Requests makes it easy to add your own
|
||||
forms of authentication.
|
||||
|
||||
To do so, subclass :class:`requests.auth.AuthBase` and implement the
|
||||
``__call__()`` method. When an authentication handler is attached to a request,
|
||||
``__call__()`` method::
|
||||
|
||||
>>> import requests
|
||||
>>> class MyAuth(requests.auth.AuthBase):
|
||||
... def __call__(self, r):
|
||||
... # Implement my authentication
|
||||
... return r
|
||||
...
|
||||
>>> url = 'http://httpbin.org/get'
|
||||
>>> requests.get(url, auth=MyAuth())
|
||||
<Response [200]>
|
||||
|
||||
When an authentication handler is attached to a request,
|
||||
it is called during request setup. The ``__call__`` method must therefore do
|
||||
whatever is required to make the authentication work. Some forms of
|
||||
authentication will additionally add hooks to provide further functionality.
|
||||
|
||||
Examples can be found under the `Requests organization`_ and in the
|
||||
Further examples can be found under the `Requests organization`_ and in the
|
||||
``auth.py`` file.
|
||||
|
||||
.. _OAuth: https://github.com/requests/requests-oauthlib
|
||||
.. _OAuth: http://oauth.net/
|
||||
.. _requests_oauthlib: https://github.com/requests/requests-oauthlib
|
||||
.. _Kerberos: https://github.com/requests/requests-kerberos
|
||||
.. _NTLM: https://github.com/requests/requests-ntlm
|
||||
.. _Requests organization: https://github.com/requests
|
||||
|
||||
@@ -10,7 +10,7 @@ The first step to using any software package is getting it properly installed.
|
||||
Distribute & Pip
|
||||
----------------
|
||||
|
||||
Installing requests is simple with `pip <http://www.pip-installer.org/>`_::
|
||||
Installing Requests is simple with `pip <http://www.pip-installer.org/>`_::
|
||||
|
||||
$ pip install requests
|
||||
|
||||
@@ -22,11 +22,11 @@ But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/other
|
||||
|
||||
|
||||
|
||||
Cheeseshop Mirror
|
||||
-----------------
|
||||
Cheeseshop (PyPI) Mirror
|
||||
------------------------
|
||||
|
||||
If the Cheeseshop is down, you can also install Requests from one of the
|
||||
mirrors. `Crate.io <http://crate.io>`_ is one of them::
|
||||
If the Cheeseshop (a.k.a. PyPI) is down, you can also install Requests from one
|
||||
of the mirrors. `Crate.io <http://crate.io>`_ is one of them::
|
||||
|
||||
$ pip install -i http://simple.crate.io/ requests
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ All contributions to Requests should keep these important rules in mind.
|
||||
.. _`apache2`:
|
||||
|
||||
Apache2 License
|
||||
-----------
|
||||
---------------
|
||||
|
||||
A large number of open source projects you find today are `GPL Licensed`_.
|
||||
While the GPL has its time and place, it should most certainly not be your
|
||||
|
||||
@@ -69,7 +69,10 @@ following code::
|
||||
You can see that the URL has been correctly encoded by printing the URL::
|
||||
|
||||
>>> print r.url
|
||||
u'http://httpbin.org/get?key2=value2&key1=value1'
|
||||
http://httpbin.org/get?key2=value2&key1=value1
|
||||
|
||||
Note that any dictionary key whose value is ``None`` will not be added to the
|
||||
URL's query string.
|
||||
|
||||
|
||||
Response Content
|
||||
@@ -81,7 +84,7 @@ again::
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://github.com/timeline.json')
|
||||
>>> r.text
|
||||
'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
Requests will automatically decode content from the server. Most unicode
|
||||
charsets are seamlessly decoded.
|
||||
@@ -114,7 +117,7 @@ You can also access the response body as bytes, for non-text requests::
|
||||
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:
|
||||
use the following code::
|
||||
|
||||
>>> from PIL import Image
|
||||
>>> from StringIO import StringIO
|
||||
@@ -131,7 +134,9 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::
|
||||
>>> r.json()
|
||||
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
|
||||
|
||||
In case the JSON decoding fails, ``r.json`` raises an exception.
|
||||
In case the JSON decoding fails, ``r.json`` raises an exception. For example, if
|
||||
the response gets a 401 (Unauthorized), attempting ``r.json`` raises ``ValueError:
|
||||
No JSON object could be decoded``
|
||||
|
||||
|
||||
Raw Response Content
|
||||
@@ -141,7 +146,7 @@ In the rare case that you'd like to get the raw socket response from the
|
||||
server, you can access ``r.raw``. If you want to do this, make sure you set
|
||||
``stream=True`` in your initial request. Once you do, you can do this::
|
||||
|
||||
>>> r = requests.get('https:/github.com/timeline.json', stream=True)
|
||||
>>> r = requests.get('https://github.com/timeline.json', stream=True)
|
||||
>>> r.raw
|
||||
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
|
||||
>>> r.raw.read(10)
|
||||
@@ -168,7 +173,7 @@ More complicated POST requests
|
||||
------------------------------
|
||||
|
||||
Typically, you want to send some form-encoded data — much like an HTML form.
|
||||
To do this, simply pass a dictionary to the `data` argument. Your
|
||||
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'}
|
||||
@@ -258,7 +263,7 @@ reference::
|
||||
>>> r.status_code == requests.codes.ok
|
||||
True
|
||||
|
||||
If we made a bad request (non-200 response), we can raise it with
|
||||
If we made a bad request (a 4XX client error or 5XX server error response), we can raise it with
|
||||
:class:`Response.raise_for_status()`::
|
||||
|
||||
>>> bad_r = requests.get('http://httpbin.org/status/404')
|
||||
@@ -287,14 +292,13 @@ We can view the server's response headers using a Python dictionary::
|
||||
|
||||
>>> r.headers
|
||||
{
|
||||
'status': '200 OK',
|
||||
'content-encoding': 'gzip',
|
||||
'transfer-encoding': 'chunked',
|
||||
'connection': 'close',
|
||||
'server': 'nginx/1.0.4',
|
||||
'x-runtime': '148ms',
|
||||
'etag': '"e1ca502697e5c9317743dc078f67693f"',
|
||||
'content-type': 'application/json; charset=utf-8'
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
|
||||
The dictionary is special, though: it's made just for HTTP headers. According to
|
||||
@@ -304,15 +308,10 @@ Headers are case-insensitive.
|
||||
So, we can access the headers using any capitalization we want::
|
||||
|
||||
>>> r.headers['Content-Type']
|
||||
'application/json; charset=utf-8'
|
||||
'application/json'
|
||||
|
||||
>>> r.headers.get('content-type')
|
||||
'application/json; charset=utf-8'
|
||||
|
||||
If a header doesn't exist in the Response, its value defaults to ``None``::
|
||||
|
||||
>>> r.headers['X-Random']
|
||||
None
|
||||
'application/json'
|
||||
|
||||
|
||||
Cookies
|
||||
@@ -320,11 +319,11 @@ Cookies
|
||||
|
||||
If a response contains some Cookies, you can get quick access to them::
|
||||
|
||||
>>> url = 'http://httpbin.org/cookies/set/requests-is/awesome'
|
||||
>>> url = 'http://example.com/some/cookie/setting/url'
|
||||
>>> r = requests.get(url)
|
||||
|
||||
>>> r.cookies['requests-is']
|
||||
'awesome'
|
||||
>>> r.cookies['example_cookie_name']
|
||||
'example_cookie_value'
|
||||
|
||||
To send your own cookies to the server, you can use the ``cookies``
|
||||
parameter::
|
||||
@@ -344,7 +343,7 @@ Requests will automatically perform location redirection while using the GET
|
||||
and OPTIONS verbs.
|
||||
|
||||
GitHub redirects all HTTP requests to HTTPS. We can use the ``history`` method
|
||||
of the Response object to track redirection. Let's see what Github does::
|
||||
of the Response object to track redirection. Let's see what GitHub does::
|
||||
|
||||
>>> r = requests.get('http://github.com')
|
||||
>>> r.url
|
||||
@@ -354,8 +353,9 @@ of the Response object to track redirection. Let's see what Github does::
|
||||
>>> 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. The list is sorted from the oldest to the most recent request.
|
||||
The :class:`Response.history` list contains the :class:`Request` objects that
|
||||
were created in order to complete the request. The list is sorted from the
|
||||
oldest to the most recent request.
|
||||
|
||||
If you're using GET or OPTIONS, you can disable redirection handling with the
|
||||
``allow_redirects`` parameter::
|
||||
@@ -379,43 +379,41 @@ redirection as well::
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
You can tell requests to stop waiting for a response after a given number of
|
||||
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.
|
||||
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
|
||||
|
||||
.. admonition:: Note:
|
||||
|
||||
``timeout`` only effects the connection process itself, not the
|
||||
downloading of the response body.
|
||||
.. admonition:: Note
|
||||
|
||||
``timeout`` is not a time limit on the entire response download;
|
||||
rather, an exception is raised if the server has not issued a
|
||||
response for ``timeout`` seconds (more precisely, if no bytes have been
|
||||
received on the underlying socket for ``timeout`` seconds).
|
||||
|
||||
|
||||
Errors and Exceptions
|
||||
---------------------
|
||||
|
||||
In the event of a network problem (e.g. DNS failure, refused connection, etc),
|
||||
Requests will raise a :class:`ConnectionError` exception.
|
||||
Requests will raise a :class:`~requests.exceptions.ConnectionError` exception.
|
||||
|
||||
In the event of the rare invalid HTTP response, Requests will raise
|
||||
an :class:`HTTPError` exception.
|
||||
In the event of the rare invalid HTTP response, Requests will raise an
|
||||
:class:`~requests.exceptions.HTTPError` exception.
|
||||
|
||||
If a request times out, a :class:`Timeout` exception is raised.
|
||||
If a request times out, a :class:`~requests.exceptions.Timeout` exception is
|
||||
raised.
|
||||
|
||||
If a request exceeds the configured number of maximum redirections, a
|
||||
:class:`TooManyRedirects` exception is raised.
|
||||
:class:`~requests.exceptions.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.
|
||||
|
||||
@@ -36,18 +36,24 @@ usage:
|
||||
The other HTTP methods are supported - see `requests.api`. Full documentation
|
||||
is at <http://python-requests.org>.
|
||||
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:copyright: (c) 2013 by Kenneth Reitz.
|
||||
:license: Apache 2.0, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '1.0.4'
|
||||
__build__ = 0x01004
|
||||
__version__ = '2.0.0'
|
||||
__build__ = 0x020000
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2012 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2013 Kenneth Reitz'
|
||||
|
||||
# Attempt to enable urllib3's SNI support, if possible
|
||||
try:
|
||||
from .packages.urllib3.contrib import pyopenssl
|
||||
pyopenssl.inject_into_urllib3()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from . import utils
|
||||
from .models import Request, Response, PreparedRequest
|
||||
|
||||
@@ -12,9 +12,11 @@ import socket
|
||||
|
||||
from .models import Response
|
||||
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
|
||||
from .hooks import dispatch_hook
|
||||
from .compat import urlparse, basestring
|
||||
from .utils import DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .packages.urllib3.util import Timeout as TimeoutSauce
|
||||
from .compat import urlparse, basestring, urldefrag, unquote
|
||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||
except_on_missing_scheme, get_auth_from_url)
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .packages.urllib3.exceptions import MaxRetryError
|
||||
from .packages.urllib3.exceptions import TimeoutError
|
||||
@@ -22,7 +24,9 @@ from .packages.urllib3.exceptions import SSLError as _SSLError
|
||||
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import ConnectionError, Timeout, SSLError
|
||||
from .auth import _basic_auth_str
|
||||
|
||||
DEFAULT_POOLBLOCK = False
|
||||
DEFAULT_POOLSIZE = 10
|
||||
DEFAULT_RETRIES = 0
|
||||
|
||||
@@ -41,20 +45,82 @@ class BaseAdapter(object):
|
||||
|
||||
|
||||
class HTTPAdapter(BaseAdapter):
|
||||
"""Built-In HTTP Adapter for Urllib3."""
|
||||
def __init__(self, pool_connections=DEFAULT_POOLSIZE, pool_maxsize=DEFAULT_POOLSIZE):
|
||||
self.max_retries = DEFAULT_RETRIES
|
||||
"""The built-in HTTP Adapter for urllib3.
|
||||
|
||||
Provides a general-case interface for Requests sessions to contact HTTP and
|
||||
HTTPS urls by implementing the Transport Adapter interface. This class will
|
||||
usually be created by the :class:`Session <Session>` class under the
|
||||
covers.
|
||||
|
||||
:param pool_connections: The number of urllib3 connection pools to cache.
|
||||
:param pool_maxsize: The maximum number of connections to save in the pool.
|
||||
:param max_retries: The maximum number of retries each connection should attempt.
|
||||
:param pool_block: Whether the connection pool should block for connections.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> import requests
|
||||
>>> s = requests.Session()
|
||||
>>> a = requests.adapters.HTTPAdapter()
|
||||
>>> s.mount('http://', a)
|
||||
"""
|
||||
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
|
||||
'_pool_block']
|
||||
|
||||
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
|
||||
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
|
||||
pool_block=DEFAULT_POOLBLOCK):
|
||||
self.max_retries = max_retries
|
||||
self.config = {}
|
||||
self.proxy_manager = {}
|
||||
|
||||
super(HTTPAdapter, self).__init__()
|
||||
|
||||
self.init_poolmanager(pool_connections, pool_maxsize)
|
||||
self._pool_connections = pool_connections
|
||||
self._pool_maxsize = pool_maxsize
|
||||
self._pool_block = pool_block
|
||||
|
||||
def init_poolmanager(self, connections, maxsize):
|
||||
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize)
|
||||
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict((attr, getattr(self, attr, None)) for attr in
|
||||
self.__attrs__)
|
||||
|
||||
def __setstate__(self, state):
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
self.init_poolmanager(self._pool_connections, self._pool_maxsize,
|
||||
block=self._pool_block)
|
||||
|
||||
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK):
|
||||
"""Initializes a urllib3 PoolManager. This method should not be called
|
||||
from user code, and is only exposed for use when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param connections: The number of urllib3 connection pools to cache.
|
||||
:param maxsize: The maximum number of connections to save in the pool.
|
||||
:param block: Block when no free connections are available.
|
||||
"""
|
||||
# save these values for pickling
|
||||
self._pool_connections = connections
|
||||
self._pool_maxsize = maxsize
|
||||
self._pool_block = block
|
||||
|
||||
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
|
||||
block=block)
|
||||
|
||||
def cert_verify(self, conn, url, verify, cert):
|
||||
if url.startswith('https') and verify:
|
||||
"""Verify a SSL certificate. This method should not be called from user
|
||||
code, and is only exposed for use when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param conn: The urllib3 connection object associated with the cert.
|
||||
:param url: The requested URL.
|
||||
:param verify: Whether we should actually verify the certificate.
|
||||
:param cert: The SSL certificate to verify.
|
||||
"""
|
||||
if url.lower().startswith('https') and verify:
|
||||
|
||||
cert_loc = None
|
||||
|
||||
@@ -82,6 +148,14 @@ class HTTPAdapter(BaseAdapter):
|
||||
conn.cert_file = cert
|
||||
|
||||
def build_response(self, req, resp):
|
||||
"""Builds a :class:`Response <requests.Response>` object from a urllib3
|
||||
response. This should not be called from user code, and is only exposed
|
||||
for use when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`
|
||||
|
||||
:param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
|
||||
:param resp: The urllib3 response object.
|
||||
"""
|
||||
response = Response()
|
||||
|
||||
# Fallback to None if there's no status_code, for whatever reason.
|
||||
@@ -93,6 +167,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
# Set encoding.
|
||||
response.encoding = get_encoding_from_headers(response.headers)
|
||||
response.raw = resp
|
||||
response.reason = response.raw.reason
|
||||
|
||||
if isinstance(req.url, bytes):
|
||||
response.url = req.url.decode('utf-8')
|
||||
@@ -106,51 +181,171 @@ class HTTPAdapter(BaseAdapter):
|
||||
response.request = req
|
||||
response.connection = self
|
||||
|
||||
# Run the Response hook.
|
||||
response = dispatch_hook('response', req.hooks, response)
|
||||
return response
|
||||
|
||||
def get_connection(self, url, proxies=None):
|
||||
"""Returns a connection for the given URL."""
|
||||
"""Returns a urllib3 connection for the given URL. This should not be
|
||||
called from user code, and is only exposed for use when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param url: The URL to connect to.
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
"""
|
||||
proxies = proxies or {}
|
||||
proxy = proxies.get(urlparse(url).scheme)
|
||||
proxy = proxies.get(urlparse(url.lower()).scheme)
|
||||
|
||||
if proxy:
|
||||
conn = proxy_from_url(proxy)
|
||||
except_on_missing_scheme(proxy)
|
||||
proxy_headers = self.proxy_headers(proxy)
|
||||
|
||||
if not proxy in self.proxy_manager:
|
||||
self.proxy_manager[proxy] = proxy_from_url(
|
||||
proxy,
|
||||
proxy_headers=proxy_headers)
|
||||
|
||||
conn = self.proxy_manager[proxy].connection_from_url(url)
|
||||
else:
|
||||
conn = self.poolmanager.connection_from_url(url)
|
||||
conn = self.poolmanager.connection_from_url(url.lower())
|
||||
|
||||
return conn
|
||||
|
||||
def close(self):
|
||||
"""Dispose of any internal state.
|
||||
"""Disposes of any internal state.
|
||||
|
||||
Currently, this just closes the PoolManager, which closes pooled
|
||||
connections.
|
||||
"""
|
||||
self.poolmanager.clear()
|
||||
|
||||
def request_url(self, request, proxies):
|
||||
"""Obtain the url to use when making the final request.
|
||||
|
||||
If the message is being sent through a proxy, the full URL has to be
|
||||
used. Otherwise, we should only use the path portion of the URL.
|
||||
|
||||
This should not be called from user code, and is only exposed for use
|
||||
when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
:param proxies: A dictionary of schemes to proxy URLs.
|
||||
"""
|
||||
proxies = proxies or {}
|
||||
proxy = proxies.get(urlparse(request.url).scheme)
|
||||
|
||||
if proxy:
|
||||
url, _ = urldefrag(request.url)
|
||||
else:
|
||||
url = request.path_url
|
||||
|
||||
return url
|
||||
|
||||
def add_headers(self, request, **kwargs):
|
||||
"""Add any headers needed by the connection. As of v2.0 this does
|
||||
nothing by default, but is left for overriding by users that subclass
|
||||
the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
This should not be called from user code, and is only exposed for use
|
||||
when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.
|
||||
:param kwargs: The keyword arguments from the call to send().
|
||||
"""
|
||||
pass
|
||||
|
||||
def proxy_headers(self, proxy):
|
||||
"""Returns a dictionary of the headers to add to any request sent
|
||||
through a proxy. This works with urllib3 magic to ensure that they are
|
||||
correctly sent to the proxy, rather than in a tunnelled request if
|
||||
CONNECT is being used.
|
||||
|
||||
This should not be called from user code, and is only exposed for use
|
||||
when subclassing the
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param proxies: The url of the proxy being used for this request.
|
||||
:param kwargs: Optional additional keyword arguments.
|
||||
"""
|
||||
headers = {}
|
||||
username, password = get_auth_from_url(proxy)
|
||||
|
||||
if username and password:
|
||||
# Proxy auth usernames and passwords will be urlencoded, we need
|
||||
# to decode them.
|
||||
username = unquote(username)
|
||||
password = unquote(password)
|
||||
headers['Proxy-Authorization'] = _basic_auth_str(username,
|
||||
password)
|
||||
|
||||
return headers
|
||||
|
||||
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
|
||||
"""Sends PreparedRequest object. Returns Response object."""
|
||||
"""Sends PreparedRequest object. Returns Response object.
|
||||
|
||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||
:param stream: (optional) Whether to stream the request content.
|
||||
:param timeout: (optional) The timeout on the request.
|
||||
:param verify: (optional) Whether to verify SSL certificates.
|
||||
:param vert: (optional) Any user-provided SSL certificate to be trusted.
|
||||
:param proxies: (optional) The proxies dictionary to apply to the request.
|
||||
"""
|
||||
|
||||
conn = self.get_connection(request.url, proxies)
|
||||
|
||||
self.cert_verify(conn, request.url, verify, cert)
|
||||
url = self.request_url(request, proxies)
|
||||
self.add_headers(request)
|
||||
|
||||
chunked = not (request.body is None or 'Content-Length' in request.headers)
|
||||
|
||||
if stream:
|
||||
timeout = TimeoutSauce(connect=timeout)
|
||||
else:
|
||||
timeout = TimeoutSauce(connect=timeout, read=timeout)
|
||||
|
||||
try:
|
||||
if not chunked:
|
||||
resp = conn.urlopen(
|
||||
method=request.method,
|
||||
url=url,
|
||||
body=request.body,
|
||||
headers=request.headers,
|
||||
redirect=False,
|
||||
assert_same_host=False,
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=self.max_retries,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Send the request.
|
||||
resp = conn.urlopen(
|
||||
method=request.method,
|
||||
url=request.path_url,
|
||||
body=request.body,
|
||||
headers=request.headers,
|
||||
redirect=False,
|
||||
assert_same_host=False,
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=self.max_retries,
|
||||
timeout=timeout,
|
||||
)
|
||||
else:
|
||||
if hasattr(conn, 'proxy_pool'):
|
||||
conn = conn.proxy_pool
|
||||
|
||||
low_conn = conn._get_conn(timeout=timeout)
|
||||
low_conn.putrequest(request.method, url, skip_accept_encoding=True)
|
||||
|
||||
for header, value in request.headers.items():
|
||||
low_conn.putheader(header, value)
|
||||
|
||||
low_conn.endheaders()
|
||||
|
||||
for i in request.body:
|
||||
low_conn.send(hex(len(i))[2:].encode('utf-8'))
|
||||
low_conn.send(b'\r\n')
|
||||
low_conn.send(i)
|
||||
low_conn.send(b'\r\n')
|
||||
low_conn.send(b'0\r\n\r\n')
|
||||
|
||||
r = low_conn.getresponse()
|
||||
resp = HTTPResponse.from_httplib(r,
|
||||
pool=conn,
|
||||
connection=low_conn,
|
||||
preload_content=False,
|
||||
decode_content=False
|
||||
)
|
||||
|
||||
except socket.error as sockerr:
|
||||
raise ConnectionError(sockerr)
|
||||
@@ -164,7 +359,7 @@ class HTTPAdapter(BaseAdapter):
|
||||
elif isinstance(e, TimeoutError):
|
||||
raise Timeout(e)
|
||||
else:
|
||||
raise Timeout('Request timed out.')
|
||||
raise
|
||||
|
||||
r = self.build_response(request, resp)
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ def head(url, **kwargs):
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return request('head', url, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ This module contains the authentication handlers for Requests.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import hashlib
|
||||
import logging
|
||||
@@ -17,7 +18,6 @@ from base64 import b64encode
|
||||
from .compat import urlparse, str
|
||||
from .utils import parse_dict_header
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
||||
@@ -36,6 +36,7 @@ class AuthBase(object):
|
||||
def __call__(self, r):
|
||||
raise NotImplementedError('Auth hooks must be callable.')
|
||||
|
||||
|
||||
class HTTPBasicAuth(AuthBase):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object."""
|
||||
def __init__(self, username, password):
|
||||
@@ -48,7 +49,7 @@ class HTTPBasicAuth(AuthBase):
|
||||
|
||||
|
||||
class HTTPProxyAuth(HTTPBasicAuth):
|
||||
"""Attaches HTTP Proxy Authenetication to a given Request object."""
|
||||
"""Attaches HTTP Proxy Authentication to a given Request object."""
|
||||
def __call__(self, r):
|
||||
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
@@ -68,18 +69,21 @@ class HTTPDigestAuth(AuthBase):
|
||||
realm = self.chal['realm']
|
||||
nonce = self.chal['nonce']
|
||||
qop = self.chal.get('qop')
|
||||
algorithm = self.chal.get('algorithm', 'MD5')
|
||||
opaque = self.chal.get('opaque', None)
|
||||
algorithm = self.chal.get('algorithm')
|
||||
opaque = self.chal.get('opaque')
|
||||
|
||||
algorithm = algorithm.upper()
|
||||
if algorithm is None:
|
||||
_algorithm = 'MD5'
|
||||
else:
|
||||
_algorithm = algorithm.upper()
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if algorithm == 'MD5':
|
||||
if _algorithm == 'MD5':
|
||||
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':
|
||||
elif _algorithm == 'SHA':
|
||||
def sha_utf8(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
@@ -101,7 +105,9 @@ class HTTPDigestAuth(AuthBase):
|
||||
A1 = '%s:%s:%s' % (self.username, realm, self.password)
|
||||
A2 = '%s:%s' % (method, path)
|
||||
|
||||
if qop == 'auth':
|
||||
if qop is None:
|
||||
respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
|
||||
elif qop == 'auth' or 'auth' in qop.split(','):
|
||||
if nonce == self.last_nonce:
|
||||
self.nonce_count += 1
|
||||
else:
|
||||
@@ -116,8 +122,6 @@ class HTTPDigestAuth(AuthBase):
|
||||
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(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
|
||||
else:
|
||||
# XXX handle auth-int.
|
||||
return None
|
||||
@@ -126,38 +130,46 @@ class HTTPDigestAuth(AuthBase):
|
||||
|
||||
# XXX should the partial digests be encoded too?
|
||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||
'response="%s"' % (self.username, realm, nonce, path, respdig)
|
||||
'response="%s"' % (self.username, realm, nonce, path, respdig)
|
||||
if opaque:
|
||||
base += ', opaque="%s"' % opaque
|
||||
if algorithm:
|
||||
base += ', algorithm="%s"' % algorithm
|
||||
if entdig:
|
||||
base += ', digest="%s"' % entdig
|
||||
base += ', algorithm="%s"' % algorithm
|
||||
if qop:
|
||||
base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
|
||||
|
||||
return 'Digest %s' % (base)
|
||||
|
||||
def handle_401(self, r):
|
||||
def handle_401(self, r, **kwargs):
|
||||
"""Takes the given response and tries digest-auth, if needed."""
|
||||
|
||||
num_401_calls = r.request.hooks['response'].count(self.handle_401)
|
||||
num_401_calls = getattr(self, 'num_401_calls', 1)
|
||||
s_auth = r.headers.get('www-authenticate', '')
|
||||
|
||||
if 'digest' in s_auth.lower() and num_401_calls < 2:
|
||||
|
||||
self.chal = parse_dict_header(s_auth.replace('Digest ', ''))
|
||||
setattr(self, 'num_401_calls', num_401_calls + 1)
|
||||
pat = re.compile(r'digest ', flags=re.IGNORECASE)
|
||||
self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
||||
|
||||
# Consume content and release the original connection
|
||||
# to allow our new request to reuse the same one.
|
||||
r.content
|
||||
r.raw.release_conn()
|
||||
prep = r.request.copy()
|
||||
prep.prepare_cookies(r.cookies)
|
||||
|
||||
r.request.headers['Authorization'] = self.build_digest_header(r.request.method, r.request.url)
|
||||
_r = r.connection.send(r.request)
|
||||
prep.headers['Authorization'] = self.build_digest_header(
|
||||
prep.method, prep.url)
|
||||
_r = r.connection.send(prep, **kwargs)
|
||||
_r.history.append(r)
|
||||
_r.request = prep
|
||||
|
||||
return _r
|
||||
|
||||
setattr(self, 'num_401_calls', 1)
|
||||
return r
|
||||
|
||||
def __call__(self, r):
|
||||
|
||||
@@ -1603,54 +1603,6 @@ vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3
|
||||
oKfN5XozNmr6mis=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
TURKTRUST Certificate Services Provider Root 1
|
||||
==============================================
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
|
||||
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP
|
||||
MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0
|
||||
acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx
|
||||
MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg
|
||||
U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB
|
||||
TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC
|
||||
aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX
|
||||
yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i
|
||||
Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ
|
||||
8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4
|
||||
W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME
|
||||
BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46
|
||||
sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE
|
||||
q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
|
||||
B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY
|
||||
nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
TURKTRUST Certificate Services Provider Root 2
|
||||
==============================================
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF
|
||||
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
|
||||
MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
|
||||
QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN
|
||||
MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr
|
||||
dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G
|
||||
A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls
|
||||
acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe
|
||||
LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI
|
||||
x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g
|
||||
QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr
|
||||
5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB
|
||||
AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G
|
||||
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt
|
||||
Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
|
||||
Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+
|
||||
hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P
|
||||
9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5
|
||||
UrbnBEI=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
SwissSign Gold CA - G2
|
||||
======================
|
||||
-----BEGIN CERTIFICATE-----
|
||||
|
||||
@@ -2,26 +2,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
ceritfi.py
|
||||
~~~~~~~~~~
|
||||
certs.py
|
||||
~~~~~~~~
|
||||
|
||||
This module returns the installation location of cacert.pem.
|
||||
This module returns the preferred default CA certificate bundle.
|
||||
|
||||
If you are packaging Requests, e.g., for a Linux distribution or a managed
|
||||
environment, you can change the definition of where() to return a separately
|
||||
packaged CA bundle.
|
||||
"""
|
||||
|
||||
import os
|
||||
try:
|
||||
import certifi
|
||||
except ImportError:
|
||||
certifi = None
|
||||
import os.path
|
||||
|
||||
|
||||
def where():
|
||||
|
||||
if certifi:
|
||||
return certifi.where()
|
||||
else:
|
||||
f = os.path.split(__file__)[0]
|
||||
return os.path.join(f, 'cacert.pem')
|
||||
"""Return the preferred certificate bundle."""
|
||||
# vendored bundle inside Requests
|
||||
return os.path.join(os.path.dirname(__file__), 'cacert.pem')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(where())
|
||||
|
||||
@@ -83,13 +83,14 @@ except ImportError:
|
||||
# ---------
|
||||
|
||||
if is_py2:
|
||||
from urllib import quote, unquote, quote_plus, unquote_plus, urlencode
|
||||
from urllib import quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, proxy_bypass
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
|
||||
from urllib2 import parse_http_list
|
||||
import cookielib
|
||||
from Cookie import Morsel
|
||||
from StringIO import StringIO
|
||||
from .packages.urllib3.packages.ordered_dict import OrderedDict
|
||||
from httplib import IncompleteRead
|
||||
|
||||
builtin_str = str
|
||||
bytes = str
|
||||
@@ -98,14 +99,14 @@ if is_py2:
|
||||
numeric_types = (int, long, float)
|
||||
|
||||
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
|
||||
from urllib.request import parse_http_list
|
||||
from urllib.request import parse_http_list, getproxies, proxy_bypass
|
||||
from http import cookiejar as cookielib
|
||||
from http.cookies import Morsel
|
||||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
from http.client import IncompleteRead
|
||||
|
||||
builtin_str = str
|
||||
str = str
|
||||
|
||||
@@ -6,6 +6,7 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests.
|
||||
requests.utils imports from here, so be careful with imports.
|
||||
"""
|
||||
|
||||
import time
|
||||
import collections
|
||||
from .compat import cookielib, urlparse, Morsel
|
||||
|
||||
@@ -69,6 +70,14 @@ class MockRequest(object):
|
||||
def unverifiable(self):
|
||||
return self.is_unverifiable()
|
||||
|
||||
@property
|
||||
def origin_req_host(self):
|
||||
return self.get_origin_req_host()
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self.get_host()
|
||||
|
||||
|
||||
class MockResponse(object):
|
||||
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
|
||||
@@ -98,6 +107,9 @@ def extract_cookies_to_jar(jar, request, response):
|
||||
:param request: our own requests.Request object
|
||||
:param response: urllib3.HTTPResponse object
|
||||
"""
|
||||
if not (hasattr(response, '_original_response') and
|
||||
response._original_response):
|
||||
return
|
||||
# the _original_response field is the wrapped httplib.HTTPResponse object,
|
||||
req = MockRequest(request)
|
||||
# pull out the HTTPMessage with the headers and put it in the mock:
|
||||
@@ -240,18 +252,33 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
"""Dict-like __getitem__() for compatibility with client code. Throws exception
|
||||
if there are more than one cookie with name. In that case, use the more
|
||||
explicit get() method instead. Caution: operation is O(n), not O(1)."""
|
||||
|
||||
return self._find_no_duplicates(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
"""Dict-like __setitem__ for compatibility with client code. Throws exception
|
||||
if there is already a cookie of that name in the jar. In that case, use the more
|
||||
explicit set() method instead."""
|
||||
|
||||
self.set(name, value)
|
||||
|
||||
def __delitem__(self, name):
|
||||
"""Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
|
||||
remove_cookie_by_name(self, name)
|
||||
|
||||
def set_cookie(self, cookie, *args, **kwargs):
|
||||
if cookie.value.startswith('"') and cookie.value.endswith('"'):
|
||||
cookie.value = cookie.value.replace('\\"', '')
|
||||
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
|
||||
|
||||
def update(self, other):
|
||||
"""Updates this jar with cookies from another CookieJar or dict-like"""
|
||||
if isinstance(other, cookielib.CookieJar):
|
||||
for cookie in other:
|
||||
self.set_cookie(cookie)
|
||||
else:
|
||||
super(RequestsCookieJar, self).update(other)
|
||||
|
||||
def _find(self, name, domain=None, path=None):
|
||||
"""Requests uses this method internally to get cookie values. Takes as args name
|
||||
and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
|
||||
@@ -297,8 +324,10 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||
self._cookies_lock = threading.RLock()
|
||||
|
||||
def copy(self):
|
||||
"""This is not implemented. Calling this will throw an exception."""
|
||||
raise NotImplementedError
|
||||
"""Return a copy of this RequestsCookieJar."""
|
||||
new_cj = RequestsCookieJar()
|
||||
new_cj.update(self)
|
||||
return new_cj
|
||||
|
||||
|
||||
def create_cookie(name, value, **kwargs):
|
||||
@@ -338,19 +367,23 @@ def create_cookie(name, value, **kwargs):
|
||||
|
||||
def morsel_to_cookie(morsel):
|
||||
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
|
||||
expires = None
|
||||
if morsel["max-age"]:
|
||||
expires = time.time() + morsel["max-age"]
|
||||
elif morsel['expires']:
|
||||
expires = morsel['expires']
|
||||
if type(expires) == type(""):
|
||||
time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
|
||||
expires = time.mktime(time.strptime(expires, time_template))
|
||||
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'],
|
||||
expires=expires,
|
||||
discard=False,
|
||||
comment=morsel['comment'],
|
||||
comment_url=bool(morsel['comment']),
|
||||
|
||||
@@ -9,14 +9,18 @@ This module contains the set of Requests' exceptions.
|
||||
"""
|
||||
|
||||
|
||||
class RequestException(RuntimeError):
|
||||
class RequestException(IOError):
|
||||
"""There was an ambiguous exception that occurred while handling your
|
||||
request."""
|
||||
|
||||
|
||||
class HTTPError(RequestException):
|
||||
"""An HTTP error occurred."""
|
||||
response = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" Initializes HTTPError with optional `response` object. """
|
||||
self.response = kwargs.pop('response', None)
|
||||
super(HTTPError, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class ConnectionError(RequestException):
|
||||
@@ -49,3 +53,7 @@ class InvalidSchema(RequestException, ValueError):
|
||||
|
||||
class InvalidURL(RequestException, ValueError):
|
||||
""" The URL provided was somehow invalid. """
|
||||
|
||||
|
||||
class ChunkedEncodingError(RequestException):
|
||||
"""The server declared chunked encoding but sent an invalid chunk."""
|
||||
|
||||
@@ -16,6 +16,7 @@ Available hooks:
|
||||
|
||||
HOOKS = ['response']
|
||||
|
||||
|
||||
def default_hooks():
|
||||
hooks = {}
|
||||
for event in HOOKS:
|
||||
@@ -24,7 +25,8 @@ def default_hooks():
|
||||
|
||||
# TODO: response is the only one
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data, **kwargs):
|
||||
"""Dispatches a hook dictionary on a given piece of data."""
|
||||
|
||||
hooks = hooks or dict()
|
||||
@@ -36,7 +38,7 @@ def dispatch_hook(key, hooks, hook_data):
|
||||
hooks = [hooks]
|
||||
|
||||
for hook in hooks:
|
||||
_hook_data = hook(hook_data)
|
||||
_hook_data = hook(hook_data, **kwargs)
|
||||
if _hook_data is not None:
|
||||
hook_data = _hook_data
|
||||
|
||||
|
||||
@@ -9,27 +9,29 @@ This module contains the primary objects that power Requests.
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from io import BytesIO
|
||||
from io import BytesIO, UnsupportedOperation
|
||||
from .hooks import default_hooks
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .status_codes import codes
|
||||
|
||||
from .auth import HTTPBasicAuth
|
||||
from .cookies import cookiejar_from_dict, get_cookie_header
|
||||
from .packages.urllib3.filepost import encode_multipart_formdata
|
||||
from .exceptions import HTTPError, RequestException, MissingSchema, InvalidURL
|
||||
from .packages.urllib3.util import parse_url
|
||||
from .exceptions import (
|
||||
HTTPError, RequestException, MissingSchema, InvalidURL,
|
||||
ChunkedEncodingError)
|
||||
from .utils import (
|
||||
stream_untransfer, guess_filename, requote_uri,
|
||||
guess_filename, get_auth_from_url, requote_uri,
|
||||
stream_decode_response_unicode, to_key_val_list, parse_header_links,
|
||||
iter_slices, guess_json_utf)
|
||||
iter_slices, guess_json_utf, super_len, to_native_string)
|
||||
from .compat import (
|
||||
cookielib, urlparse, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
|
||||
is_py2, chardet, json, builtin_str, basestring)
|
||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
|
||||
is_py2, chardet, json, builtin_str, basestring, IncompleteRead)
|
||||
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
CONTENT_CHUNK_SIZE = 10 * 1024
|
||||
ITER_CHUNK_SIZE = 10 * 1024
|
||||
ITER_CHUNK_SIZE = 512
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -61,7 +63,7 @@ class RequestEncodingMixin(object):
|
||||
"""Encode parameters in a piece of data.
|
||||
|
||||
Will successfully encode parameters when passed as a dict or a list of
|
||||
2-tuples. Order is retained if data is a list of 2-tuples but abritrary
|
||||
2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
|
||||
if parameters are supplied as a dict.
|
||||
"""
|
||||
|
||||
@@ -92,24 +94,36 @@ class RequestEncodingMixin(object):
|
||||
if parameters are supplied as a dict.
|
||||
|
||||
"""
|
||||
if (not files) or isinstance(data, str):
|
||||
return None
|
||||
if (not files):
|
||||
raise ValueError("Files must be provided.")
|
||||
elif isinstance(data, basestring):
|
||||
raise ValueError("Data must not be a string.")
|
||||
|
||||
new_fields = []
|
||||
fields = to_key_val_list(data or {})
|
||||
files = to_key_val_list(files or {})
|
||||
|
||||
for field, val in fields:
|
||||
if isinstance(val, list):
|
||||
for v in val:
|
||||
new_fields.append((field, builtin_str(v)))
|
||||
else:
|
||||
new_fields.append((field, builtin_str(val)))
|
||||
if isinstance(val, basestring) or not hasattr(val, '__iter__'):
|
||||
val = [val]
|
||||
for v in val:
|
||||
if v is not None:
|
||||
# Don't call str() on bytestrings: in Py3 it all goes wrong.
|
||||
if not isinstance(v, bytes):
|
||||
v = str(v)
|
||||
|
||||
new_fields.append(
|
||||
(field.decode('utf-8') if isinstance(field, bytes) else field,
|
||||
v.encode('utf-8') if isinstance(v, str) else v))
|
||||
|
||||
for (k, v) in files:
|
||||
# support for explicit filename
|
||||
ft = None
|
||||
if isinstance(v, (tuple, list)):
|
||||
fn, fp = v
|
||||
if len(v) == 2:
|
||||
fn, fp = v
|
||||
else:
|
||||
fn, fp, ft = v
|
||||
else:
|
||||
fn = guess_filename(v) or k
|
||||
fp = v
|
||||
@@ -117,7 +131,12 @@ class RequestEncodingMixin(object):
|
||||
fp = StringIO(fp)
|
||||
if isinstance(fp, bytes):
|
||||
fp = BytesIO(fp)
|
||||
new_fields.append((k, (fn, fp.read())))
|
||||
|
||||
if ft:
|
||||
new_v = (fn, fp.read(), ft)
|
||||
else:
|
||||
new_v = (fn, fp.read())
|
||||
new_fields.append((k, new_v))
|
||||
|
||||
body, content_type = encode_multipart_formdata(new_fields)
|
||||
|
||||
@@ -128,6 +147,9 @@ class RequestHooksMixin(object):
|
||||
def register_hook(self, event, hook):
|
||||
"""Properly register a hook."""
|
||||
|
||||
if event not in self.hooks:
|
||||
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
|
||||
|
||||
if isinstance(hook, collections.Callable):
|
||||
self.hooks[event].append(hook)
|
||||
elif hasattr(hook, '__iter__'):
|
||||
@@ -173,13 +195,12 @@ class Request(RequestHooksMixin):
|
||||
url=None,
|
||||
headers=None,
|
||||
files=None,
|
||||
data=dict(),
|
||||
params=dict(),
|
||||
data=None,
|
||||
params=None,
|
||||
auth=None,
|
||||
cookies=None,
|
||||
hooks=None):
|
||||
|
||||
|
||||
# Default empty dicts for dict params.
|
||||
data = [] if data is None else data
|
||||
files = [] if files is None else files
|
||||
@@ -199,7 +220,6 @@ class Request(RequestHooksMixin):
|
||||
self.params = params
|
||||
self.auth = auth
|
||||
self.cookies = cookies
|
||||
self.hooks = hooks
|
||||
|
||||
def __repr__(self):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
@@ -207,16 +227,17 @@ class Request(RequestHooksMixin):
|
||||
def prepare(self):
|
||||
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
|
||||
p = PreparedRequest()
|
||||
|
||||
p.prepare_method(self.method)
|
||||
p.prepare_url(self.url, self.params)
|
||||
p.prepare_headers(self.headers)
|
||||
p.prepare_cookies(self.cookies)
|
||||
p.prepare_body(self.data, self.files)
|
||||
# Note that prepare_auth must be last to enable authentication schemes
|
||||
# such as OAuth to work on a fully prepared request.
|
||||
p.prepare_auth(self.auth)
|
||||
|
||||
p.prepare(
|
||||
method=self.method,
|
||||
url=self.url,
|
||||
headers=self.headers,
|
||||
files=self.files,
|
||||
data=self.data,
|
||||
params=self.params,
|
||||
auth=self.auth,
|
||||
cookies=self.cookies,
|
||||
hooks=self.hooks,
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
@@ -251,9 +272,34 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
#: dictionary of callback hooks, for internal usage.
|
||||
self.hooks = default_hooks()
|
||||
|
||||
def prepare(self, method=None, url=None, headers=None, files=None,
|
||||
data=None, params=None, auth=None, cookies=None, hooks=None):
|
||||
"""Prepares the the entire request with the given parameters."""
|
||||
|
||||
self.prepare_method(method)
|
||||
self.prepare_url(url, params)
|
||||
self.prepare_headers(headers)
|
||||
self.prepare_cookies(cookies)
|
||||
self.prepare_body(data, files)
|
||||
self.prepare_auth(auth, url)
|
||||
# Note that prepare_auth must be last to enable authentication schemes
|
||||
# such as OAuth to work on a fully prepared request.
|
||||
|
||||
# This MUST go after prepare_auth. Authenticators could add a hook
|
||||
self.prepare_hooks(hooks)
|
||||
|
||||
def __repr__(self):
|
||||
return '<PreparedRequest [%s]>' % (self.method)
|
||||
|
||||
def copy(self):
|
||||
p = PreparedRequest()
|
||||
p.method = self.method
|
||||
p.url = self.url
|
||||
p.headers = self.headers
|
||||
p.body = self.body
|
||||
p.hooks = self.hooks
|
||||
return p
|
||||
|
||||
def prepare_method(self, method):
|
||||
"""Prepares the given HTTP method."""
|
||||
self.method = method
|
||||
@@ -272,16 +318,28 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
pass
|
||||
|
||||
# Support for unicode domain names and paths.
|
||||
scheme, netloc, path, _params, query, fragment = urlparse(url)
|
||||
scheme, auth, host, port, path, query, fragment = parse_url(url)
|
||||
|
||||
if not scheme:
|
||||
raise MissingSchema("Invalid URL %r: No schema supplied" % url)
|
||||
|
||||
if not host:
|
||||
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
||||
|
||||
# Only want to apply IDNA to the hostname
|
||||
try:
|
||||
netloc = netloc.encode('idna').decode('utf-8')
|
||||
host = host.encode('idna').decode('utf-8')
|
||||
except UnicodeError:
|
||||
raise InvalidURL('URL has an invalid label.')
|
||||
|
||||
# Carefully reconstruct the network location
|
||||
netloc = auth or ''
|
||||
if netloc:
|
||||
netloc += '@'
|
||||
netloc += host
|
||||
if port:
|
||||
netloc += ':' + str(port)
|
||||
|
||||
# Bare domains aren't valid URLs.
|
||||
if not path:
|
||||
path = '/'
|
||||
@@ -293,8 +351,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
netloc = netloc.encode('utf-8')
|
||||
if isinstance(path, str):
|
||||
path = path.encode('utf-8')
|
||||
if isinstance(_params, str):
|
||||
_params = _params.encode('utf-8')
|
||||
if isinstance(query, str):
|
||||
query = query.encode('utf-8')
|
||||
if isinstance(fragment, str):
|
||||
@@ -307,56 +363,90 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
query = enc_params
|
||||
|
||||
url = requote_uri(urlunparse([scheme, netloc, path, _params, query, fragment]))
|
||||
url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
|
||||
self.url = url
|
||||
|
||||
def prepare_headers(self, headers):
|
||||
"""Prepares the given HTTP headers."""
|
||||
|
||||
if headers:
|
||||
self.headers = CaseInsensitiveDict(headers)
|
||||
self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items())
|
||||
else:
|
||||
self.headers = CaseInsensitiveDict()
|
||||
|
||||
def prepare_body(self, data, files):
|
||||
"""Prepares the given HTTP body data."""
|
||||
|
||||
# If a generator is provided, error out.
|
||||
if isinstance(data, type(_ for _ in [])):
|
||||
raise NotImplementedError('Generator bodies are not supported yet.')
|
||||
# Check if file, fo, generator, iterator.
|
||||
# If not, run through normal process.
|
||||
|
||||
# Nottin' on you.
|
||||
body = None
|
||||
content_type = None
|
||||
length = None
|
||||
|
||||
# Multi-part file uploads.
|
||||
if files:
|
||||
(body, content_type) = self._encode_files(files, data)
|
||||
is_stream = all([
|
||||
hasattr(data, '__iter__'),
|
||||
not isinstance(data, basestring),
|
||||
not isinstance(data, list),
|
||||
not isinstance(data, dict)
|
||||
])
|
||||
|
||||
try:
|
||||
length = super_len(data)
|
||||
except (TypeError, AttributeError, UnsupportedOperation):
|
||||
length = None
|
||||
|
||||
if is_stream:
|
||||
body = data
|
||||
|
||||
if files:
|
||||
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
|
||||
|
||||
if length is not None:
|
||||
self.headers['Content-Length'] = str(length)
|
||||
else:
|
||||
self.headers['Transfer-Encoding'] = 'chunked'
|
||||
else:
|
||||
if data:
|
||||
# Multi-part file uploads.
|
||||
if files:
|
||||
(body, content_type) = self._encode_files(files, data)
|
||||
else:
|
||||
if data:
|
||||
body = self._encode_params(data)
|
||||
if isinstance(data, str) or isinstance(data, builtin_str) or hasattr(data, 'read'):
|
||||
content_type = None
|
||||
else:
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
|
||||
body = self._encode_params(data)
|
||||
if isinstance(data, str) or isinstance(data, builtin_str) or hasattr(data, 'read'):
|
||||
content_type = None
|
||||
else:
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
self.prepare_content_length(body)
|
||||
|
||||
self.headers['Content-Length'] = '0'
|
||||
# Add content-type if it wasn't explicitly provided.
|
||||
if (content_type) and (not 'content-type' in self.headers):
|
||||
self.headers['Content-Type'] = content_type
|
||||
|
||||
self.body = body
|
||||
|
||||
def prepare_content_length(self, body):
|
||||
if hasattr(body, 'seek') and hasattr(body, 'tell'):
|
||||
body.seek(0, 2)
|
||||
self.headers['Content-Length'] = str(body.tell())
|
||||
body.seek(0, 0)
|
||||
elif body is not None:
|
||||
self.headers['Content-Length'] = str(len(body))
|
||||
l = super_len(body)
|
||||
if l:
|
||||
self.headers['Content-Length'] = str(l)
|
||||
elif self.method not in ('GET', 'HEAD'):
|
||||
self.headers['Content-Length'] = '0'
|
||||
|
||||
# Add content-type if it wasn't explicitly provided.
|
||||
if (content_type) and (not 'content-type' in self.headers):
|
||||
self.headers['Content-Type'] = content_type
|
||||
|
||||
self.body = body
|
||||
|
||||
def prepare_auth(self, auth):
|
||||
def prepare_auth(self, auth, url=''):
|
||||
"""Prepares the given HTTP auth data."""
|
||||
|
||||
# If no Auth is explicitly provided, extract it from the URL first.
|
||||
if auth is None:
|
||||
url_auth = get_auth_from_url(self.url)
|
||||
auth = url_auth if any(url_auth) else None
|
||||
|
||||
if auth:
|
||||
if isinstance(auth, tuple) and len(auth) == 2:
|
||||
# special-case basic HTTP auth
|
||||
@@ -368,6 +458,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
# Update self to reflect the auth changes.
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
# Recompute Content-Length
|
||||
self.prepare_content_length(self.body)
|
||||
|
||||
def prepare_cookies(self, cookies):
|
||||
"""Prepares the given HTTP cookie data."""
|
||||
|
||||
@@ -381,6 +474,11 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
if cookie_header is not None:
|
||||
self.headers['Cookie'] = cookie_header
|
||||
|
||||
def prepare_hooks(self, hooks):
|
||||
"""Prepares the given hooks."""
|
||||
for event in hooks:
|
||||
self.register_hook(event, hooks[event])
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""The :class:`Response <Response>` object, which contains a
|
||||
@@ -422,6 +520,10 @@ class Response(object):
|
||||
#: A CookieJar of Cookies the server sent back.
|
||||
self.cookies = cookiejar_from_dict({})
|
||||
|
||||
#: The amount of time elapsed between sending the request
|
||||
#: and the arrival of the response (as a timedelta)
|
||||
self.elapsed = datetime.timedelta(0)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
@@ -433,6 +535,10 @@ class Response(object):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
return self.ok
|
||||
|
||||
def __iter__(self):
|
||||
"""Allows you to use a response as an iterator."""
|
||||
return self.iter_content(128)
|
||||
|
||||
@property
|
||||
def ok(self):
|
||||
try:
|
||||
@@ -448,24 +554,36 @@ class Response(object):
|
||||
return chardet.detect(self.content)['encoding']
|
||||
|
||||
def iter_content(self, chunk_size=1, decode_unicode=False):
|
||||
"""Iterates over the response data. This avoids reading the content
|
||||
at once into memory for large responses. The chunk size is the number
|
||||
of bytes it should read into memory. This is not necessarily the
|
||||
length of each item returned as decoding can take place.
|
||||
"""Iterates over the response data. When stream=True is set on the
|
||||
request, this avoids reading the content at once into memory for
|
||||
large responses. The chunk size is the number of bytes it should
|
||||
read into memory. This is not necessarily the length of each item
|
||||
returned as decoding can take place.
|
||||
"""
|
||||
if self._content_consumed:
|
||||
# simulate reading small chunks of the content
|
||||
return iter_slices(self._content, chunk_size)
|
||||
|
||||
def generate():
|
||||
while 1:
|
||||
chunk = self.raw.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
try:
|
||||
# Special case for urllib3.
|
||||
try:
|
||||
for chunk in self.raw.stream(chunk_size,
|
||||
decode_content=True):
|
||||
yield chunk
|
||||
except IncompleteRead as e:
|
||||
raise ChunkedEncodingError(e)
|
||||
except AttributeError:
|
||||
# Standard file-like object.
|
||||
while 1:
|
||||
chunk = self.raw.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
|
||||
self._content_consumed = True
|
||||
|
||||
gen = stream_untransfer(generate(), self)
|
||||
gen = generate()
|
||||
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
@@ -473,16 +591,15 @@ class Response(object):
|
||||
return gen
|
||||
|
||||
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None):
|
||||
"""Iterates over the response data, one line at a time. This
|
||||
avoids reading the content at once into memory for large
|
||||
responses.
|
||||
"""Iterates over the response data, one line at a time. When
|
||||
stream=True is set on the request, this avoids reading the
|
||||
content at once into memory for large responses.
|
||||
"""
|
||||
|
||||
pending = None
|
||||
|
||||
for chunk in self.iter_content(
|
||||
chunk_size=chunk_size,
|
||||
decode_unicode=decode_unicode):
|
||||
for chunk in self.iter_content(chunk_size=chunk_size,
|
||||
decode_unicode=decode_unicode):
|
||||
|
||||
if pending is not None:
|
||||
chunk = pending + chunk
|
||||
@@ -510,7 +627,7 @@ class Response(object):
|
||||
raise RuntimeError(
|
||||
'The content for this response was already consumed')
|
||||
|
||||
if self.status_code is 0:
|
||||
if self.status_code == 0:
|
||||
self._content = None
|
||||
else:
|
||||
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
|
||||
@@ -556,8 +673,11 @@ class Response(object):
|
||||
|
||||
return content
|
||||
|
||||
def json(self):
|
||||
"""Returns the json-encoded content of a response, if any."""
|
||||
def json(self, **kwargs):
|
||||
"""Returns the json-encoded content of a response, if any.
|
||||
|
||||
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
|
||||
"""
|
||||
|
||||
if not self.encoding and len(self.content) > 3:
|
||||
# No encoding set. JSON RFC 4627 section 3 states we should expect
|
||||
@@ -566,14 +686,14 @@ class Response(object):
|
||||
# a best guess).
|
||||
encoding = guess_json_utf(self.content)
|
||||
if encoding is not None:
|
||||
return json.loads(self.content.decode(encoding))
|
||||
return json.loads(self.text or self.content)
|
||||
return json.loads(self.content.decode(encoding), **kwargs)
|
||||
return json.loads(self.text or self.content, **kwargs)
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
"""Returns the parsed header links of the response, if any."""
|
||||
|
||||
header = self.headers['link']
|
||||
header = self.headers.get('link')
|
||||
|
||||
# l = MultiDict()
|
||||
l = {}
|
||||
@@ -588,7 +708,7 @@ class Response(object):
|
||||
return l
|
||||
|
||||
def raise_for_status(self):
|
||||
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""
|
||||
"""Raises stored :class:`HTTPError`, if one occurred."""
|
||||
|
||||
http_error_msg = ''
|
||||
|
||||
@@ -599,9 +719,12 @@ class Response(object):
|
||||
http_error_msg = '%s Server Error: %s' % (self.status_code, self.reason)
|
||||
|
||||
if http_error_msg:
|
||||
http_error = HTTPError(http_error_msg)
|
||||
http_error.response = self
|
||||
raise http_error
|
||||
raise HTTPError(http_error_msg, response=self)
|
||||
|
||||
def close(self):
|
||||
"""Closes the underlying file descriptor and releases the connection
|
||||
back to the pool.
|
||||
|
||||
*Note: Should not normally need to be called explicitly.*
|
||||
"""
|
||||
return self.raw.release_conn()
|
||||
|
||||
@@ -15,10 +15,15 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
__version__ = "1.0.1"
|
||||
__version__ = "1.0.3"
|
||||
from sys import version_info
|
||||
|
||||
|
||||
def detect(aBuf):
|
||||
if ((version_info < (3, 0) and isinstance(aBuf, unicode)) or
|
||||
(version_info >= (3, 0) and not isinstance(aBuf, bytes))):
|
||||
raise ValueError('Expected a bytes object, not a unicode object')
|
||||
|
||||
from . import universaldetector
|
||||
u = universaldetector.UniversalDetector()
|
||||
u.reset()
|
||||
|
||||
@@ -40,6 +40,7 @@ from .compat import wrap_ord
|
||||
ENOUGH_DATA_THRESHOLD = 1024
|
||||
SURE_YES = 0.99
|
||||
SURE_NO = 0.01
|
||||
MINIMUM_DATA_THRESHOLD = 3
|
||||
|
||||
|
||||
class CharDistributionAnalysis:
|
||||
@@ -82,7 +83,7 @@ class CharDistributionAnalysis:
|
||||
"""return confidence based on existing data"""
|
||||
# if we didn't receive any character in our consideration range,
|
||||
# return negative answer
|
||||
if self._mTotalChars <= 0:
|
||||
if self._mTotalChars <= 0 or self._mFreqChars <= MINIMUM_DATA_THRESHOLD:
|
||||
return SURE_NO
|
||||
|
||||
if self._mTotalChars != self._mFreqChars:
|
||||
|
||||
@@ -18,9 +18,17 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
base_str = (str, unicode)
|
||||
else:
|
||||
base_str = (bytes, str)
|
||||
|
||||
|
||||
def wrap_ord(a):
|
||||
if isinstance(a, str):
|
||||
if sys.version_info < (3, 0) and isinstance(a, base_str):
|
||||
return ord(a)
|
||||
elif isinstance(a, int):
|
||||
else:
|
||||
return a
|
||||
|
||||
44
requests/packages/charade/cp949prober.py
Normal file
44
requests/packages/charade/cp949prober.py
Normal file
@@ -0,0 +1,44 @@
|
||||
######################## BEGIN LICENSE BLOCK ########################
|
||||
# The Original Code is mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Netscape Communications Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 1998
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Mark Pilgrim - port to Python
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
from .mbcharsetprober import MultiByteCharSetProber
|
||||
from .codingstatemachine import CodingStateMachine
|
||||
from .chardistribution import EUCKRDistributionAnalysis
|
||||
from .mbcssm import CP949SMModel
|
||||
|
||||
|
||||
class CP949Prober(MultiByteCharSetProber):
|
||||
def __init__(self):
|
||||
MultiByteCharSetProber.__init__(self)
|
||||
self._mCodingSM = CodingStateMachine(CP949SMModel)
|
||||
# NOTE: CP949 is a superset of EUC-KR, so the distribution should be
|
||||
# not different.
|
||||
self._mDistributionAnalyzer = EUCKRDistributionAnalysis()
|
||||
self.reset()
|
||||
|
||||
def get_charset_name(self):
|
||||
return "CP949"
|
||||
@@ -25,8 +25,6 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
from . import constants
|
||||
|
||||
# KOI8-R language model
|
||||
# Character Mapping Table:
|
||||
KOI8R_CharToOrderMap = (
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
from . import constants
|
||||
|
||||
# 255: Control characters that usually does not exist in any text
|
||||
# 254: Carriage/Return
|
||||
# 253: symbol (punctuation) that does not belong to word
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
from . import constants
|
||||
|
||||
# 255: Control characters that usually does not exist in any text
|
||||
# 254: Carriage/Return
|
||||
# 253: symbol (punctuation) that does not belong to word
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
# 02110-1301 USA
|
||||
######################### END LICENSE BLOCK #########################
|
||||
|
||||
from . import constants
|
||||
|
||||
# 255: Control characters that usually does not exist in any text
|
||||
# 254: Carriage/Return
|
||||
# 253: symbol (punctuation) that does not belong to word
|
||||
|
||||
@@ -33,6 +33,7 @@ from .sjisprober import SJISProber
|
||||
from .eucjpprober import EUCJPProber
|
||||
from .gb2312prober import GB2312Prober
|
||||
from .euckrprober import EUCKRProber
|
||||
from .cp949prober import CP949Prober
|
||||
from .big5prober import Big5Prober
|
||||
from .euctwprober import EUCTWProber
|
||||
|
||||
@@ -46,6 +47,7 @@ class MBCSGroupProber(CharSetGroupProber):
|
||||
EUCJPProber(),
|
||||
GB2312Prober(),
|
||||
EUCKRProber(),
|
||||
CP949Prober(),
|
||||
Big5Prober(),
|
||||
EUCTWProber()
|
||||
]
|
||||
|
||||
@@ -78,6 +78,46 @@ Big5SMModel = {'classTable': BIG5_cls,
|
||||
'charLenTable': Big5CharLenTable,
|
||||
'name': 'Big5'}
|
||||
|
||||
# CP949
|
||||
|
||||
CP949_cls = (
|
||||
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f
|
||||
1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f
|
||||
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f
|
||||
1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f
|
||||
1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f
|
||||
4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f
|
||||
1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f
|
||||
5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f
|
||||
0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f
|
||||
6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f
|
||||
6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af
|
||||
7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf
|
||||
7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf
|
||||
2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df
|
||||
2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef
|
||||
2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff
|
||||
)
|
||||
|
||||
CP949_st = (
|
||||
#cls= 0 1 2 3 4 5 6 7 8 9 # previous state =
|
||||
eError,eStart, 3,eError,eStart,eStart, 4, 5,eError, 6, # eStart
|
||||
eError,eError,eError,eError,eError,eError,eError,eError,eError,eError, # eError
|
||||
eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe,eItsMe, # eItsMe
|
||||
eError,eError,eStart,eStart,eError,eError,eError,eStart,eStart,eStart, # 3
|
||||
eError,eError,eStart,eStart,eStart,eStart,eStart,eStart,eStart,eStart, # 4
|
||||
eError,eStart,eStart,eStart,eStart,eStart,eStart,eStart,eStart,eStart, # 5
|
||||
eError,eStart,eStart,eStart,eStart,eError,eError,eStart,eStart,eStart, # 6
|
||||
)
|
||||
|
||||
CP949CharLenTable = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2)
|
||||
|
||||
CP949SMModel = {'classTable': CP949_cls,
|
||||
'classFactor': 10,
|
||||
'stateTable': CP949_st,
|
||||
'charLenTable': CP949CharLenTable,
|
||||
'name': 'CP949'}
|
||||
|
||||
# EUC-JP
|
||||
|
||||
EUCJP_cls = (
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
from . import constants
|
||||
import sys
|
||||
import codecs
|
||||
from .latin1prober import Latin1Prober # windows-1252
|
||||
from .mbcsgroupprober import MBCSGroupProber # multi-byte character sets
|
||||
from .sbcsgroupprober import SBCSGroupProber # single-byte character sets
|
||||
@@ -70,31 +71,31 @@ class UniversalDetector:
|
||||
|
||||
if not self._mGotData:
|
||||
# If the data starts with BOM, we know it is UTF
|
||||
if aBuf[:3] == '\xEF\xBB\xBF':
|
||||
if aBuf[:3] == codecs.BOM:
|
||||
# EF BB BF UTF-8 with BOM
|
||||
self.result = {'encoding': "UTF-8", 'confidence': 1.0}
|
||||
elif aBuf[:4] == '\xFF\xFE\x00\x00':
|
||||
elif aBuf[:4] == codecs.BOM_UTF32_LE:
|
||||
# FF FE 00 00 UTF-32, little-endian BOM
|
||||
self.result = {'encoding': "UTF-32LE", 'confidence': 1.0}
|
||||
elif aBuf[:4] == '\x00\x00\xFE\xFF':
|
||||
elif aBuf[:4] == codecs.BOM_UTF32_BE:
|
||||
# 00 00 FE FF UTF-32, big-endian BOM
|
||||
self.result = {'encoding': "UTF-32BE", 'confidence': 1.0}
|
||||
elif aBuf[:4] == '\xFE\xFF\x00\x00':
|
||||
elif aBuf[:4] == b'\xFE\xFF\x00\x00':
|
||||
# FE FF 00 00 UCS-4, unusual octet order BOM (3412)
|
||||
self.result = {
|
||||
'encoding': "X-ISO-10646-UCS-4-3412",
|
||||
'confidence': 1.0
|
||||
}
|
||||
elif aBuf[:4] == '\x00\x00\xFF\xFE':
|
||||
elif aBuf[:4] == b'\x00\x00\xFF\xFE':
|
||||
# 00 00 FF FE UCS-4, unusual octet order BOM (2143)
|
||||
self.result = {
|
||||
'encoding': "X-ISO-10646-UCS-4-2143",
|
||||
'confidence': 1.0
|
||||
}
|
||||
elif aBuf[:2] == '\xFF\xFE':
|
||||
elif aBuf[:2] == codecs.BOM_LE:
|
||||
# FF FE UTF-16, little endian BOM
|
||||
self.result = {'encoding': "UTF-16LE", 'confidence': 1.0}
|
||||
elif aBuf[:2] == '\xFE\xFF':
|
||||
elif aBuf[:2] == codecs.BOM_BE:
|
||||
# FE FF UTF-16, big endian BOM
|
||||
self.result = {'encoding': "UTF-16BE", 'confidence': 1.0}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/__init__.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
@@ -23,7 +23,7 @@ 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
|
||||
from .util import make_headers, get_host, Timeout
|
||||
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# urllib3/_collections.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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 MutableMapping
|
||||
from threading import Lock
|
||||
from threading import RLock
|
||||
|
||||
try: # Python 2.7+
|
||||
from collections import OrderedDict
|
||||
@@ -40,18 +40,18 @@ class RecentlyUsedContainer(MutableMapping):
|
||||
self.dispose_func = dispose_func
|
||||
|
||||
self._container = self.ContainerCls()
|
||||
self._lock = Lock()
|
||||
self.lock = RLock()
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Re-insert the item, moving it to the end of the eviction line.
|
||||
with self._lock:
|
||||
with self.lock:
|
||||
item = self._container.pop(key)
|
||||
self._container[key] = item
|
||||
return item
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
evicted_value = _Null
|
||||
with self._lock:
|
||||
with self.lock:
|
||||
# Possibly evict the existing value of 'key'
|
||||
evicted_value = self._container.get(key, _Null)
|
||||
self._container[key] = value
|
||||
@@ -65,21 +65,21 @@ class RecentlyUsedContainer(MutableMapping):
|
||||
self.dispose_func(evicted_value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
with self._lock:
|
||||
with self.lock:
|
||||
value = self._container.pop(key)
|
||||
|
||||
if self.dispose_func:
|
||||
self.dispose_func(value)
|
||||
|
||||
def __len__(self):
|
||||
with self._lock:
|
||||
with self.lock:
|
||||
return len(self._container)
|
||||
|
||||
def __iter__(self):
|
||||
raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.')
|
||||
|
||||
def clear(self):
|
||||
with self._lock:
|
||||
with self.lock:
|
||||
# Copy pointers to all values, then wipe the mapping
|
||||
# under Python 2, this copies the list of values twice :-|
|
||||
values = list(self._container.values())
|
||||
@@ -90,5 +90,5 @@ class RecentlyUsedContainer(MutableMapping):
|
||||
self.dispose_func(value)
|
||||
|
||||
def keys(self):
|
||||
with self._lock:
|
||||
with self.lock:
|
||||
return self._container.keys()
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# urllib3/connectionpool.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
import errno
|
||||
import logging
|
||||
|
||||
from socket import error as SocketError, timeout as SocketTimeout
|
||||
import socket
|
||||
|
||||
try: # Python 3
|
||||
from http.client import HTTPConnection, HTTPException
|
||||
@@ -21,11 +21,15 @@ try: # Python 3
|
||||
from queue import LifoQueue, Empty, Full
|
||||
except ImportError:
|
||||
from Queue import LifoQueue, Empty, Full
|
||||
import Queue as _ # Platform-specific: Windows
|
||||
|
||||
|
||||
try: # Compiled with SSL?
|
||||
HTTPSConnection = object
|
||||
BaseSSLError = None
|
||||
|
||||
class BaseSSLError(BaseException):
|
||||
pass
|
||||
|
||||
ssl = None
|
||||
|
||||
try: # Python 3
|
||||
@@ -40,21 +44,29 @@ except (ImportError, AttributeError): # Platform-specific: No SSL.
|
||||
pass
|
||||
|
||||
|
||||
from .request import RequestMethods
|
||||
from .response import HTTPResponse
|
||||
from .util import get_host, is_connection_dropped, ssl_wrap_socket
|
||||
from .exceptions import (
|
||||
ClosedPoolError,
|
||||
ConnectTimeoutError,
|
||||
EmptyPoolError,
|
||||
HostChangedError,
|
||||
MaxRetryError,
|
||||
SSLError,
|
||||
TimeoutError,
|
||||
ReadTimeoutError,
|
||||
ProxyError,
|
||||
)
|
||||
|
||||
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||
from .packages.ssl_match_hostname import CertificateError, match_hostname
|
||||
from .packages import six
|
||||
|
||||
from .request import RequestMethods
|
||||
from .response import HTTPResponse
|
||||
from .util import (
|
||||
assert_fingerprint,
|
||||
get_host,
|
||||
is_connection_dropped,
|
||||
resolve_cert_reqs,
|
||||
resolve_ssl_version,
|
||||
ssl_wrap_socket,
|
||||
Timeout,
|
||||
)
|
||||
|
||||
xrange = six.moves.xrange
|
||||
|
||||
@@ -80,32 +92,51 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
ssl_version = 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
|
||||
}
|
||||
cert_reqs=None, ca_certs=None,
|
||||
assert_hostname=None, assert_fingerprint=None):
|
||||
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.cert_reqs = ssl_req_scheme.get(cert_reqs) or ssl.CERT_NONE
|
||||
self.cert_reqs = cert_reqs
|
||||
self.ca_certs = ca_certs
|
||||
self.assert_hostname = assert_hostname
|
||||
self.assert_fingerprint = assert_fingerprint
|
||||
|
||||
def connect(self):
|
||||
# Add certificate verification
|
||||
sock = socket.create_connection((self.host, self.port), self.timeout)
|
||||
try:
|
||||
sock = socket.create_connection(
|
||||
address=(self.host, self.port),
|
||||
timeout=self.timeout)
|
||||
except SocketTimeout:
|
||||
raise ConnectTimeoutError(
|
||||
self, "Connection to %s timed out. (connect timeout=%s)" %
|
||||
(self.host, self.timeout))
|
||||
|
||||
resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
|
||||
resolved_ssl_version = resolve_ssl_version(self.ssl_version)
|
||||
|
||||
if self._tunnel_host:
|
||||
self.sock = sock
|
||||
# Calls self._set_hostport(), so self.host is
|
||||
# self._tunnel_host below.
|
||||
self._tunnel()
|
||||
|
||||
# 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,
|
||||
cert_reqs=resolved_cert_reqs,
|
||||
ca_certs=self.ca_certs,
|
||||
server_hostname=self.host,
|
||||
ssl_version=self.ssl_version)
|
||||
ssl_version=resolved_ssl_version)
|
||||
|
||||
if self.ca_certs:
|
||||
match_hostname(self.sock.getpeercert(), self.host)
|
||||
if resolved_cert_reqs != ssl.CERT_NONE:
|
||||
if self.assert_fingerprint:
|
||||
assert_fingerprint(self.sock.getpeercert(binary_form=True),
|
||||
self.assert_fingerprint)
|
||||
elif self.assert_hostname is not False:
|
||||
match_hostname(self.sock.getpeercert(),
|
||||
self.assert_hostname or self.host)
|
||||
|
||||
|
||||
## Pool objects
|
||||
@@ -120,6 +151,9 @@ class ConnectionPool(object):
|
||||
QueueCls = LifoQueue
|
||||
|
||||
def __init__(self, host, port=None):
|
||||
# httplib doesn't like it when we include brackets in ipv6 addresses
|
||||
host = host.strip('[]')
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
@@ -127,6 +161,8 @@ class ConnectionPool(object):
|
||||
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
||||
self.host, self.port)
|
||||
|
||||
# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
|
||||
_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK])
|
||||
|
||||
class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
"""
|
||||
@@ -145,9 +181,15 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
as a valid HTTP/1.0 or 1.1 status line, passed into
|
||||
:class:`httplib.HTTPConnection`.
|
||||
|
||||
.. note::
|
||||
Only works in Python 2. This parameter is ignored in Python 3.
|
||||
|
||||
:param timeout:
|
||||
Socket timeout for each individual connection, can be a float. None
|
||||
disables timeout.
|
||||
Socket timeout in seconds for each individual connection. This can
|
||||
be a float or integer, which sets the timeout for the HTTP request,
|
||||
or an instance of :class:`urllib3.util.Timeout` which gives you more
|
||||
fine-grained control over request timeouts. After the constructor has
|
||||
been parsed, this is always a `urllib3.util.Timeout` object.
|
||||
|
||||
:param maxsize:
|
||||
Number of connections to save that can be reused. More than 1 is useful
|
||||
@@ -165,20 +207,39 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
:param headers:
|
||||
Headers to include with all requests, unless other headers are given
|
||||
explicitly.
|
||||
|
||||
:param _proxy:
|
||||
Parsed proxy URL, should not be used directly, instead, see
|
||||
:class:`urllib3.connectionpool.ProxyManager`"
|
||||
|
||||
:param _proxy_headers:
|
||||
A dictionary with proxy headers, should not be used directly,
|
||||
instead, see :class:`urllib3.connectionpool.ProxyManager`"
|
||||
"""
|
||||
|
||||
scheme = 'http'
|
||||
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, maxsize=1,
|
||||
block=False, headers=None):
|
||||
def __init__(self, host, port=None, strict=False,
|
||||
timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False,
|
||||
headers=None, _proxy=None, _proxy_headers=None):
|
||||
ConnectionPool.__init__(self, host, port)
|
||||
RequestMethods.__init__(self, headers)
|
||||
|
||||
self.strict = strict
|
||||
|
||||
# This is for backwards compatibility and can be removed once a timeout
|
||||
# can only be set to a Timeout object
|
||||
if not isinstance(timeout, Timeout):
|
||||
timeout = Timeout.from_float(timeout)
|
||||
|
||||
self.timeout = timeout
|
||||
|
||||
self.pool = self.QueueCls(maxsize)
|
||||
self.block = block
|
||||
|
||||
self.proxy = _proxy
|
||||
self.proxy_headers = _proxy_headers or {}
|
||||
|
||||
# Fill the queue up so that doing get() on it will block properly
|
||||
for _ in xrange(maxsize):
|
||||
self.pool.put(None)
|
||||
@@ -194,9 +255,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
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,
|
||||
strict=self.strict)
|
||||
extra_params = {}
|
||||
if not six.PY3: # Python 2
|
||||
extra_params['strict'] = self.strict
|
||||
|
||||
return HTTPConnection(host=self.host, port=self.port,
|
||||
timeout=self.timeout.connect_timeout,
|
||||
**extra_params)
|
||||
|
||||
|
||||
def _get_conn(self, timeout=None):
|
||||
"""
|
||||
@@ -257,31 +323,89 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
% self.host)
|
||||
|
||||
# Connection never got put back into the pool, close it.
|
||||
conn.close()
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def _get_timeout(self, timeout):
|
||||
""" Helper that always returns a :class:`urllib3.util.Timeout` """
|
||||
if timeout is _Default:
|
||||
return self.timeout.clone()
|
||||
|
||||
if isinstance(timeout, Timeout):
|
||||
return timeout.clone()
|
||||
else:
|
||||
# User passed us an int/float. This is for backwards compatibility,
|
||||
# can be removed later
|
||||
return Timeout.from_float(timeout)
|
||||
|
||||
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.
|
||||
|
||||
:param conn:
|
||||
a connection from one of our connection pools
|
||||
|
||||
:param timeout:
|
||||
Socket timeout in seconds for the request. This can be a
|
||||
float or integer, which will set the same timeout value for
|
||||
the socket connect and the socket read, or an instance of
|
||||
:class:`urllib3.util.Timeout`, which gives you more fine-grained
|
||||
control over your timeouts.
|
||||
"""
|
||||
self.num_requests += 1
|
||||
|
||||
if timeout is _Default:
|
||||
timeout = self.timeout
|
||||
timeout_obj = self._get_timeout(timeout)
|
||||
|
||||
conn.timeout = timeout # This only does anything in Py26+
|
||||
conn.request(method, url, **httplib_request_kw)
|
||||
try:
|
||||
timeout_obj.start_connect()
|
||||
conn.timeout = timeout_obj.connect_timeout
|
||||
# conn.request() calls httplib.*.request, not the method in
|
||||
# request.py. It also calls makefile (recv) on the socket
|
||||
conn.request(method, url, **httplib_request_kw)
|
||||
except SocketTimeout:
|
||||
raise ConnectTimeoutError(
|
||||
self, "Connection to %s timed out. (connect timeout=%s)" %
|
||||
(self.host, timeout_obj.connect_timeout))
|
||||
|
||||
# Set timeout
|
||||
sock = getattr(conn, 'sock', False) # AppEngine doesn't have sock attr.
|
||||
if sock:
|
||||
sock.settimeout(timeout)
|
||||
# Reset the timeout for the recv() on the socket
|
||||
read_timeout = timeout_obj.read_timeout
|
||||
log.debug("Setting read timeout to %s" % read_timeout)
|
||||
# App Engine doesn't have a sock attr
|
||||
if hasattr(conn, 'sock') and \
|
||||
read_timeout is not None and \
|
||||
read_timeout is not Timeout.DEFAULT_TIMEOUT:
|
||||
# In Python 3 socket.py will catch EAGAIN and return None when you
|
||||
# try and read into the file pointer created by http.client, which
|
||||
# instead raises a BadStatusLine exception. Instead of catching
|
||||
# the exception and assuming all BadStatusLine exceptions are read
|
||||
# timeouts, check for a zero timeout before making the request.
|
||||
if read_timeout == 0:
|
||||
raise ReadTimeoutError(
|
||||
self, url,
|
||||
"Read timed out. (read timeout=%s)" % read_timeout)
|
||||
conn.sock.settimeout(read_timeout)
|
||||
|
||||
# Receive the response from the server
|
||||
try:
|
||||
try: # Python 2.7+, use buffering of HTTP responses
|
||||
httplib_response = conn.getresponse(buffering=True)
|
||||
except TypeError: # Python 2.6 and older
|
||||
httplib_response = conn.getresponse()
|
||||
except SocketTimeout:
|
||||
raise ReadTimeoutError(
|
||||
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
|
||||
|
||||
except SocketError as e: # Platform-specific: Python 2
|
||||
# See the above comment about EAGAIN in Python 3. In Python 2 we
|
||||
# have to specifically catch it and throw the timeout error
|
||||
if e.errno in _blocking_errnos:
|
||||
raise ReadTimeoutError(
|
||||
self, url,
|
||||
"Read timed out. (read timeout=%s)" % read_timeout)
|
||||
raise
|
||||
|
||||
try: # Python 2.7+, use buffering of HTTP responses
|
||||
httplib_response = conn.getresponse(buffering=True)
|
||||
except TypeError: # Python 2.6 and older
|
||||
httplib_response = conn.getresponse()
|
||||
|
||||
# AppEngine doesn't have a version attr.
|
||||
http_version = getattr(conn, '_http_vsn_str', 'HTTP/?')
|
||||
@@ -361,7 +485,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
:param redirect:
|
||||
If True, automatically handle redirects (status codes 301, 302,
|
||||
303, 307). Each redirect counts as a retry.
|
||||
303, 307, 308). Each redirect counts as a retry.
|
||||
|
||||
:param assert_same_host:
|
||||
If ``True``, will make sure that the host of the pool requests is
|
||||
@@ -369,7 +493,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
use the pool on an HTTP proxy and request foreign hosts.
|
||||
|
||||
:param timeout:
|
||||
If specified, overrides the default timeout for this one request.
|
||||
If specified, overrides the default timeout for this one
|
||||
request. It may be a float (in seconds) or an instance of
|
||||
:class:`urllib3.util.Timeout`.
|
||||
|
||||
:param pool_timeout:
|
||||
If set and the pool is set to block=True, then this method will
|
||||
@@ -396,18 +522,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
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
|
||||
@@ -438,18 +557,20 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
# ``response.release_conn()`` is called (implicitly by
|
||||
# ``response.read()``)
|
||||
|
||||
except Empty as e:
|
||||
except Empty:
|
||||
# Timed out by queue
|
||||
raise TimeoutError(self, "Request timed out. (pool_timeout=%s)" %
|
||||
pool_timeout)
|
||||
raise ReadTimeoutError(
|
||||
self, url, "Read timed out, no pool connections are available.")
|
||||
|
||||
except SocketTimeout as e:
|
||||
except SocketTimeout:
|
||||
# Timed out by socket
|
||||
raise TimeoutError(self, "Request timed out. (timeout=%s)" %
|
||||
timeout)
|
||||
raise ReadTimeoutError(self, url, "Read timed out.")
|
||||
|
||||
except BaseSSLError as e:
|
||||
# SSL certificate error
|
||||
if 'timed out' in str(e) or \
|
||||
'did not complete (read)' in str(e): # Platform-specific: Python 2.6
|
||||
raise ReadTimeoutError(self, url, "Read timed out.")
|
||||
raise SSLError(e)
|
||||
|
||||
except CertificateError as e:
|
||||
@@ -457,6 +578,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
raise SSLError(e)
|
||||
|
||||
except (HTTPException, SocketError) as e:
|
||||
if isinstance(e, SocketError) and self.proxy is not None:
|
||||
raise ProxyError('Cannot connect to proxy. '
|
||||
'Socket error: %s.' % e)
|
||||
|
||||
# Connection broken, discard. It will be replaced next _get_conn().
|
||||
conn = None
|
||||
# This is necessary so we can access e below
|
||||
@@ -503,9 +628,14 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
:class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates,
|
||||
instead of :class:`httplib.HTTPSConnection`.
|
||||
|
||||
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, and ``ssl_version``
|
||||
are only used if :mod:`ssl` is available and are fed into
|
||||
:meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket into an SSL socket.
|
||||
:class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``,
|
||||
``assert_hostname`` and ``host`` in this order to verify connections.
|
||||
If ``assert_hostname`` is False, no verification is done.
|
||||
|
||||
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs`` and
|
||||
``ssl_version`` are only used if :mod:`ssl` is available and are fed into
|
||||
:meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket
|
||||
into an SSL socket.
|
||||
"""
|
||||
|
||||
scheme = 'https'
|
||||
@@ -513,17 +643,48 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
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, ssl_version=None):
|
||||
_proxy=None, _proxy_headers=None,
|
||||
key_file=None, cert_file=None, cert_reqs=None,
|
||||
ca_certs=None, ssl_version=None,
|
||||
assert_hostname=None, assert_fingerprint=None):
|
||||
|
||||
HTTPConnectionPool.__init__(self, host, port,
|
||||
strict, timeout, maxsize,
|
||||
block, headers)
|
||||
HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize,
|
||||
block, headers, _proxy, _proxy_headers)
|
||||
self.key_file = key_file
|
||||
self.cert_file = cert_file
|
||||
self.cert_reqs = cert_reqs
|
||||
self.ca_certs = ca_certs
|
||||
self.ssl_version = ssl_version
|
||||
self.assert_hostname = assert_hostname
|
||||
self.assert_fingerprint = assert_fingerprint
|
||||
|
||||
def _prepare_conn(self, connection):
|
||||
"""
|
||||
Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket`
|
||||
and establish the tunnel if proxy is used.
|
||||
"""
|
||||
|
||||
if isinstance(connection, VerifiedHTTPSConnection):
|
||||
connection.set_cert(key_file=self.key_file,
|
||||
cert_file=self.cert_file,
|
||||
cert_reqs=self.cert_reqs,
|
||||
ca_certs=self.ca_certs,
|
||||
assert_hostname=self.assert_hostname,
|
||||
assert_fingerprint=self.assert_fingerprint)
|
||||
connection.ssl_version = self.ssl_version
|
||||
|
||||
if self.proxy is not None:
|
||||
# Python 2.7+
|
||||
try:
|
||||
set_tunnel = connection.set_tunnel
|
||||
except AttributeError: # Platform-specific: Python 2.6
|
||||
set_tunnel = connection._set_tunnel
|
||||
set_tunnel(self.host, self.port, self.proxy_headers)
|
||||
# Establish tunnel connection early, because otherwise httplib
|
||||
# would improperly set Host: header to proxy's IP:port.
|
||||
connection.connect()
|
||||
|
||||
return connection
|
||||
|
||||
def _new_conn(self):
|
||||
"""
|
||||
@@ -533,27 +694,28 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
log.info("Starting new HTTPS connection (%d): %s"
|
||||
% (self.num_connections, self.host))
|
||||
|
||||
if not ssl: # Platform-specific: Python compiled without +ssl
|
||||
actual_host = self.host
|
||||
actual_port = self.port
|
||||
if self.proxy is not None:
|
||||
actual_host = self.proxy.host
|
||||
actual_port = self.proxy.port
|
||||
|
||||
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,
|
||||
strict=self.strict)
|
||||
|
||||
connection = VerifiedHTTPSConnection(host=self.host,
|
||||
port=self.port,
|
||||
strict=self.strict)
|
||||
connection.set_cert(key_file=self.key_file, cert_file=self.cert_file,
|
||||
cert_reqs=self.cert_reqs, ca_certs=self.ca_certs)
|
||||
|
||||
if self.ssl_version is None:
|
||||
connection.ssl_version = ssl.PROTOCOL_SSLv23
|
||||
connection_class = HTTPSConnection
|
||||
else:
|
||||
connection.ssl_version = self.ssl_version
|
||||
connection_class = VerifiedHTTPSConnection
|
||||
|
||||
return connection
|
||||
extra_params = {}
|
||||
if not six.PY3: # Python 2
|
||||
extra_params['strict'] = self.strict
|
||||
connection = connection_class(host=actual_host, port=actual_port,
|
||||
timeout=self.timeout.connect_timeout,
|
||||
**extra_params)
|
||||
|
||||
return self._prepare_conn(connection)
|
||||
|
||||
|
||||
def connection_from_url(url, **kw):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/contrib/ntlmpool.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
@@ -33,7 +33,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
|
||||
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.
|
||||
user is the Windows user, probably in the DOMAIN\\username format.
|
||||
pw is the password for the user.
|
||||
"""
|
||||
super(NTLMConnectionPool, self).__init__(*args, **kwargs)
|
||||
|
||||
344
requests/packages/urllib3/contrib/pyopenssl.py
Normal file
344
requests/packages/urllib3/contrib/pyopenssl.py
Normal file
@@ -0,0 +1,344 @@
|
||||
'''SSL with SNI-support for Python 2.
|
||||
|
||||
This needs the following packages installed:
|
||||
|
||||
* pyOpenSSL (tested with 0.13)
|
||||
* ndg-httpsclient (tested with 0.3.2)
|
||||
* pyasn1 (tested with 0.1.6)
|
||||
|
||||
To activate it call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3`.
|
||||
This can be done in a ``sitecustomize`` module, or at any other time before
|
||||
your application begins using ``urllib3``, like this::
|
||||
|
||||
try:
|
||||
import urllib3.contrib.pyopenssl
|
||||
urllib3.contrib.pyopenssl.inject_into_urllib3()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
Now you can use :mod:`urllib3` as you normally would, and it will support SNI
|
||||
when the required modules are installed.
|
||||
'''
|
||||
|
||||
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
|
||||
from ndg.httpsclient.subj_alt_name import SubjectAltName
|
||||
import OpenSSL.SSL
|
||||
from pyasn1.codec.der import decoder as der_decoder
|
||||
from socket import _fileobject
|
||||
import ssl
|
||||
from cStringIO import StringIO
|
||||
|
||||
from .. import connectionpool
|
||||
from .. import util
|
||||
|
||||
__all__ = ['inject_into_urllib3', 'extract_from_urllib3']
|
||||
|
||||
# SNI only *really* works if we can read the subjectAltName of certificates.
|
||||
HAS_SNI = SUBJ_ALT_NAME_SUPPORT
|
||||
|
||||
# Map from urllib3 to PyOpenSSL compatible parameter-values.
|
||||
_openssl_versions = {
|
||||
ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
|
||||
ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD,
|
||||
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
|
||||
}
|
||||
_openssl_verify = {
|
||||
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
|
||||
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
|
||||
ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
|
||||
+ OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
}
|
||||
|
||||
|
||||
orig_util_HAS_SNI = util.HAS_SNI
|
||||
orig_connectionpool_ssl_wrap_socket = connectionpool.ssl_wrap_socket
|
||||
|
||||
|
||||
def inject_into_urllib3():
|
||||
'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.'
|
||||
|
||||
connectionpool.ssl_wrap_socket = ssl_wrap_socket
|
||||
util.HAS_SNI = HAS_SNI
|
||||
|
||||
|
||||
def extract_from_urllib3():
|
||||
'Undo monkey-patching by :func:`inject_into_urllib3`.'
|
||||
|
||||
connectionpool.ssl_wrap_socket = orig_connectionpool_ssl_wrap_socket
|
||||
util.HAS_SNI = orig_util_HAS_SNI
|
||||
|
||||
|
||||
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||
def get_subj_alt_name(peer_cert):
|
||||
# Search through extensions
|
||||
dns_name = []
|
||||
if not SUBJ_ALT_NAME_SUPPORT:
|
||||
return dns_name
|
||||
|
||||
general_names = SubjectAltName()
|
||||
for i in range(peer_cert.get_extension_count()):
|
||||
ext = peer_cert.get_extension(i)
|
||||
ext_name = ext.get_short_name()
|
||||
if ext_name != 'subjectAltName':
|
||||
continue
|
||||
|
||||
# PyOpenSSL returns extension data in ASN.1 encoded form
|
||||
ext_dat = ext.get_data()
|
||||
decoded_dat = der_decoder.decode(ext_dat,
|
||||
asn1Spec=general_names)
|
||||
|
||||
for name in decoded_dat:
|
||||
if not isinstance(name, SubjectAltName):
|
||||
continue
|
||||
for entry in range(len(name)):
|
||||
component = name.getComponentByPosition(entry)
|
||||
if component.getName() != 'dNSName':
|
||||
continue
|
||||
dns_name.append(str(component.getComponent()))
|
||||
|
||||
return dns_name
|
||||
|
||||
|
||||
class fileobject(_fileobject):
|
||||
|
||||
def read(self, size=-1):
|
||||
# Use max, disallow tiny reads in a loop as they are very inefficient.
|
||||
# We never leave read() with any leftover data from a new recv() call
|
||||
# in our internal buffer.
|
||||
rbufsize = max(self._rbufsize, self.default_bufsize)
|
||||
# Our use of StringIO rather than lists of string objects returned by
|
||||
# recv() minimizes memory usage and fragmentation that occurs when
|
||||
# rbufsize is large compared to the typical return value of recv().
|
||||
buf = self._rbuf
|
||||
buf.seek(0, 2) # seek end
|
||||
if size < 0:
|
||||
# Read until EOF
|
||||
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
|
||||
while True:
|
||||
try:
|
||||
data = self._sock.recv(rbufsize)
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
continue
|
||||
if not data:
|
||||
break
|
||||
buf.write(data)
|
||||
return buf.getvalue()
|
||||
else:
|
||||
# Read until size bytes or EOF seen, whichever comes first
|
||||
buf_len = buf.tell()
|
||||
if buf_len >= size:
|
||||
# Already have size bytes in our buffer? Extract and return.
|
||||
buf.seek(0)
|
||||
rv = buf.read(size)
|
||||
self._rbuf = StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return rv
|
||||
|
||||
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
|
||||
while True:
|
||||
left = size - buf_len
|
||||
# recv() will malloc the amount of memory given as its
|
||||
# parameter even though it often returns much less data
|
||||
# than that. The returned data string is short lived
|
||||
# as we copy it into a StringIO and free it. This avoids
|
||||
# fragmentation issues on many platforms.
|
||||
try:
|
||||
data = self._sock.recv(left)
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
continue
|
||||
if not data:
|
||||
break
|
||||
n = len(data)
|
||||
if n == size and not buf_len:
|
||||
# Shortcut. Avoid buffer data copies when:
|
||||
# - We have no data in our buffer.
|
||||
# AND
|
||||
# - Our call to recv returned exactly the
|
||||
# number of bytes we were asked to read.
|
||||
return data
|
||||
if n == left:
|
||||
buf.write(data)
|
||||
del data # explicit free
|
||||
break
|
||||
assert n <= left, "recv(%d) returned %d bytes" % (left, n)
|
||||
buf.write(data)
|
||||
buf_len += n
|
||||
del data # explicit free
|
||||
#assert buf_len == buf.tell()
|
||||
return buf.getvalue()
|
||||
|
||||
def readline(self, size=-1):
|
||||
buf = self._rbuf
|
||||
buf.seek(0, 2) # seek end
|
||||
if buf.tell() > 0:
|
||||
# check if we already have it in our buffer
|
||||
buf.seek(0)
|
||||
bline = buf.readline(size)
|
||||
if bline.endswith('\n') or len(bline) == size:
|
||||
self._rbuf = StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return bline
|
||||
del bline
|
||||
if size < 0:
|
||||
# Read until \n or EOF, whichever comes first
|
||||
if self._rbufsize <= 1:
|
||||
# Speed up unbuffered case
|
||||
buf.seek(0)
|
||||
buffers = [buf.read()]
|
||||
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
|
||||
data = None
|
||||
recv = self._sock.recv
|
||||
while True:
|
||||
try:
|
||||
while data != "\n":
|
||||
data = recv(1)
|
||||
if not data:
|
||||
break
|
||||
buffers.append(data)
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
continue
|
||||
break
|
||||
return "".join(buffers)
|
||||
|
||||
buf.seek(0, 2) # seek end
|
||||
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
|
||||
while True:
|
||||
try:
|
||||
data = self._sock.recv(self._rbufsize)
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
continue
|
||||
if not data:
|
||||
break
|
||||
nl = data.find('\n')
|
||||
if nl >= 0:
|
||||
nl += 1
|
||||
buf.write(data[:nl])
|
||||
self._rbuf.write(data[nl:])
|
||||
del data
|
||||
break
|
||||
buf.write(data)
|
||||
return buf.getvalue()
|
||||
else:
|
||||
# Read until size bytes or \n or EOF seen, whichever comes first
|
||||
buf.seek(0, 2) # seek end
|
||||
buf_len = buf.tell()
|
||||
if buf_len >= size:
|
||||
buf.seek(0)
|
||||
rv = buf.read(size)
|
||||
self._rbuf = StringIO()
|
||||
self._rbuf.write(buf.read())
|
||||
return rv
|
||||
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
|
||||
while True:
|
||||
try:
|
||||
data = self._sock.recv(self._rbufsize)
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
continue
|
||||
if not data:
|
||||
break
|
||||
left = size - buf_len
|
||||
# did we just receive a newline?
|
||||
nl = data.find('\n', 0, left)
|
||||
if nl >= 0:
|
||||
nl += 1
|
||||
# save the excess data to _rbuf
|
||||
self._rbuf.write(data[nl:])
|
||||
if buf_len:
|
||||
buf.write(data[:nl])
|
||||
break
|
||||
else:
|
||||
# Shortcut. Avoid data copy through buf when returning
|
||||
# a substring of our first recv().
|
||||
return data[:nl]
|
||||
n = len(data)
|
||||
if n == size and not buf_len:
|
||||
# Shortcut. Avoid data copy through buf when
|
||||
# returning exactly all of our first recv().
|
||||
return data
|
||||
if n >= left:
|
||||
buf.write(data[:left])
|
||||
self._rbuf.write(data[left:])
|
||||
break
|
||||
buf.write(data)
|
||||
buf_len += n
|
||||
#assert buf_len == buf.tell()
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
class WrappedSocket(object):
|
||||
'''API-compatibility wrapper for Python OpenSSL's Connection-class.'''
|
||||
|
||||
def __init__(self, connection, socket):
|
||||
self.connection = connection
|
||||
self.socket = socket
|
||||
|
||||
def fileno(self):
|
||||
return self.socket.fileno()
|
||||
|
||||
def makefile(self, mode, bufsize=-1):
|
||||
return fileobject(self.connection, mode, bufsize)
|
||||
|
||||
def settimeout(self, timeout):
|
||||
return self.socket.settimeout(timeout)
|
||||
|
||||
def sendall(self, data):
|
||||
return self.connection.sendall(data)
|
||||
|
||||
def close(self):
|
||||
return self.connection.shutdown()
|
||||
|
||||
def getpeercert(self, binary_form=False):
|
||||
x509 = self.connection.get_peer_certificate()
|
||||
|
||||
if not x509:
|
||||
return x509
|
||||
|
||||
if binary_form:
|
||||
return OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_ASN1,
|
||||
x509)
|
||||
|
||||
return {
|
||||
'subject': (
|
||||
(('commonName', x509.get_subject().CN),),
|
||||
),
|
||||
'subjectAltName': [
|
||||
('DNS', value)
|
||||
for value in get_subj_alt_name(x509)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def _verify_callback(cnx, x509, err_no, err_depth, return_code):
|
||||
return err_no == 0
|
||||
|
||||
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=None):
|
||||
ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
|
||||
if certfile:
|
||||
ctx.use_certificate_file(certfile)
|
||||
if keyfile:
|
||||
ctx.use_privatekey_file(keyfile)
|
||||
if cert_reqs != ssl.CERT_NONE:
|
||||
ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
|
||||
if ca_certs:
|
||||
try:
|
||||
ctx.load_verify_locations(ca_certs, None)
|
||||
except OpenSSL.SSL.Error as e:
|
||||
raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
|
||||
|
||||
cnx = OpenSSL.SSL.Connection(ctx, sock)
|
||||
cnx.set_tlsext_host_name(server_hostname)
|
||||
cnx.set_connect_state()
|
||||
while True:
|
||||
try:
|
||||
cnx.do_handshake()
|
||||
except OpenSSL.SSL.WantReadError:
|
||||
continue
|
||||
except OpenSSL.SSL.Error as e:
|
||||
raise ssl.SSLError('bad handshake', e)
|
||||
break
|
||||
|
||||
return WrappedSocket(cnx, sock)
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/exceptions.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
@@ -20,7 +20,18 @@ class PoolError(HTTPError):
|
||||
|
||||
def __reduce__(self):
|
||||
# For pickling purposes.
|
||||
return self.__class__, (None, self.url)
|
||||
return self.__class__, (None, None)
|
||||
|
||||
|
||||
class RequestError(PoolError):
|
||||
"Base exception for PoolErrors that have associated URLs."
|
||||
def __init__(self, pool, url, message):
|
||||
self.url = url
|
||||
PoolError.__init__(self, pool, message)
|
||||
|
||||
def __reduce__(self):
|
||||
# For pickling purposes.
|
||||
return self.__class__, (None, self.url, None)
|
||||
|
||||
|
||||
class SSLError(HTTPError):
|
||||
@@ -28,6 +39,11 @@ class SSLError(HTTPError):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyError(HTTPError):
|
||||
"Raised when the connection to a proxy fails."
|
||||
pass
|
||||
|
||||
|
||||
class DecodeError(HTTPError):
|
||||
"Raised when automatic decoding based on Content-Type fails."
|
||||
pass
|
||||
@@ -35,7 +51,7 @@ class DecodeError(HTTPError):
|
||||
|
||||
## Leaf Exceptions
|
||||
|
||||
class MaxRetryError(PoolError):
|
||||
class MaxRetryError(RequestError):
|
||||
"Raised when the maximum number of retries is exceeded."
|
||||
|
||||
def __init__(self, pool, url, reason=None):
|
||||
@@ -47,23 +63,41 @@ class MaxRetryError(PoolError):
|
||||
else:
|
||||
message += " (Caused by redirect)"
|
||||
|
||||
PoolError.__init__(self, pool, message)
|
||||
self.url = url
|
||||
RequestError.__init__(self, pool, url, message)
|
||||
|
||||
|
||||
class HostChangedError(PoolError):
|
||||
class HostChangedError(RequestError):
|
||||
"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
|
||||
RequestError.__init__(self, pool, url, message)
|
||||
self.retries = retries
|
||||
|
||||
|
||||
class TimeoutError(PoolError):
|
||||
"Raised when a socket timeout occurs."
|
||||
class TimeoutStateError(HTTPError):
|
||||
""" Raised when passing an invalid state to a timeout """
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutError(HTTPError):
|
||||
""" Raised when a socket timeout error occurs.
|
||||
|
||||
Catching this error will catch both :exc:`ReadTimeoutErrors
|
||||
<ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ReadTimeoutError(TimeoutError, RequestError):
|
||||
"Raised when a socket timeout occurs while receiving data from a server"
|
||||
pass
|
||||
|
||||
|
||||
# This timeout error does not have a URL attached and needs to inherit from the
|
||||
# base HTTPError
|
||||
class ConnectTimeoutError(TimeoutError):
|
||||
"Raised when a socket timeout occurs while connecting to a server"
|
||||
pass
|
||||
|
||||
|
||||
|
||||
177
requests/packages/urllib3/fields.py
Normal file
177
requests/packages/urllib3/fields.py
Normal file
@@ -0,0 +1,177 @@
|
||||
# urllib3/fields.py
|
||||
# Copyright 2008-2013 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 email.utils
|
||||
import mimetypes
|
||||
|
||||
from .packages import six
|
||||
|
||||
|
||||
def guess_content_type(filename, default='application/octet-stream'):
|
||||
"""
|
||||
Guess the "Content-Type" of a file.
|
||||
|
||||
:param filename:
|
||||
The filename to guess the "Content-Type" of using :mod:`mimetimes`.
|
||||
:param default:
|
||||
If no "Content-Type" can be guessed, default to `default`.
|
||||
"""
|
||||
if filename:
|
||||
return mimetypes.guess_type(filename)[0] or default
|
||||
return default
|
||||
|
||||
|
||||
def format_header_param(name, value):
|
||||
"""
|
||||
Helper function to format and quote a single header parameter.
|
||||
|
||||
Particularly useful for header parameters which might contain
|
||||
non-ASCII values, like file names. This follows RFC 2231, as
|
||||
suggested by RFC 2388 Section 4.4.
|
||||
|
||||
:param name:
|
||||
The name of the parameter, a string expected to be ASCII only.
|
||||
:param value:
|
||||
The value of the parameter, provided as a unicode string.
|
||||
"""
|
||||
if not any(ch in value for ch in '"\\\r\n'):
|
||||
result = '%s="%s"' % (name, value)
|
||||
try:
|
||||
result.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
else:
|
||||
return result
|
||||
if not six.PY3: # Python 2:
|
||||
value = value.encode('utf-8')
|
||||
value = email.utils.encode_rfc2231(value, 'utf-8')
|
||||
value = '%s*=%s' % (name, value)
|
||||
return value
|
||||
|
||||
|
||||
class RequestField(object):
|
||||
"""
|
||||
A data container for request body parameters.
|
||||
|
||||
:param name:
|
||||
The name of this request field.
|
||||
:param data:
|
||||
The data/value body.
|
||||
:param filename:
|
||||
An optional filename of the request field.
|
||||
:param headers:
|
||||
An optional dict-like object of headers to initially use for the field.
|
||||
"""
|
||||
def __init__(self, name, data, filename=None, headers=None):
|
||||
self._name = name
|
||||
self._filename = filename
|
||||
self.data = data
|
||||
self.headers = {}
|
||||
if headers:
|
||||
self.headers = dict(headers)
|
||||
|
||||
@classmethod
|
||||
def from_tuples(cls, fieldname, value):
|
||||
"""
|
||||
A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters.
|
||||
|
||||
Supports constructing :class:`~urllib3.fields.RequestField` from parameter
|
||||
of key/value strings AND key/filetuple. A filetuple is a (filename, data, MIME type)
|
||||
tuple where the MIME type is optional. For example: ::
|
||||
|
||||
'foo': 'bar',
|
||||
'fakefile': ('foofile.txt', 'contents of foofile'),
|
||||
'realfile': ('barfile.txt', open('realfile').read()),
|
||||
'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'),
|
||||
'nonamefile': 'contents of nonamefile field',
|
||||
|
||||
Field names and filenames must be unicode.
|
||||
"""
|
||||
if isinstance(value, tuple):
|
||||
if len(value) == 3:
|
||||
filename, data, content_type = value
|
||||
else:
|
||||
filename, data = value
|
||||
content_type = guess_content_type(filename)
|
||||
else:
|
||||
filename = None
|
||||
content_type = None
|
||||
data = value
|
||||
|
||||
request_param = cls(fieldname, data, filename=filename)
|
||||
request_param.make_multipart(content_type=content_type)
|
||||
|
||||
return request_param
|
||||
|
||||
def _render_part(self, name, value):
|
||||
"""
|
||||
Overridable helper function to format a single header parameter.
|
||||
|
||||
:param name:
|
||||
The name of the parameter, a string expected to be ASCII only.
|
||||
:param value:
|
||||
The value of the parameter, provided as a unicode string.
|
||||
"""
|
||||
return format_header_param(name, value)
|
||||
|
||||
def _render_parts(self, header_parts):
|
||||
"""
|
||||
Helper function to format and quote a single header.
|
||||
|
||||
Useful for single headers that are composed of multiple items. E.g.,
|
||||
'Content-Disposition' fields.
|
||||
|
||||
:param header_parts:
|
||||
A sequence of (k, v) typles or a :class:`dict` of (k, v) to format as
|
||||
`k1="v1"; k2="v2"; ...`.
|
||||
"""
|
||||
parts = []
|
||||
iterable = header_parts
|
||||
if isinstance(header_parts, dict):
|
||||
iterable = header_parts.items()
|
||||
|
||||
for name, value in iterable:
|
||||
if value:
|
||||
parts.append(self._render_part(name, value))
|
||||
|
||||
return '; '.join(parts)
|
||||
|
||||
def render_headers(self):
|
||||
"""
|
||||
Renders the headers for this request field.
|
||||
"""
|
||||
lines = []
|
||||
|
||||
sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location']
|
||||
for sort_key in sort_keys:
|
||||
if self.headers.get(sort_key, False):
|
||||
lines.append('%s: %s' % (sort_key, self.headers[sort_key]))
|
||||
|
||||
for header_name, header_value in self.headers.items():
|
||||
if header_name not in sort_keys:
|
||||
if header_value:
|
||||
lines.append('%s: %s' % (header_name, header_value))
|
||||
|
||||
lines.append('\r\n')
|
||||
return '\r\n'.join(lines)
|
||||
|
||||
def make_multipart(self, content_disposition=None, content_type=None, content_location=None):
|
||||
"""
|
||||
Makes this request field into a multipart request field.
|
||||
|
||||
This method overrides "Content-Disposition", "Content-Type" and
|
||||
"Content-Location" headers to the request parameter.
|
||||
|
||||
:param content_type:
|
||||
The 'Content-Type' of the request body.
|
||||
:param content_location:
|
||||
The 'Content-Location' of the request body.
|
||||
|
||||
"""
|
||||
self.headers['Content-Disposition'] = content_disposition or 'form-data'
|
||||
self.headers['Content-Disposition'] += '; '.join(['', self._render_parts((('name', self._name), ('filename', self._filename)))])
|
||||
self.headers['Content-Type'] = content_type
|
||||
self.headers['Content-Location'] = content_location
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/filepost.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
@@ -12,6 +12,7 @@ from io import BytesIO
|
||||
|
||||
from .packages import six
|
||||
from .packages.six import b
|
||||
from .fields import RequestField
|
||||
|
||||
writer = codecs.lookup('utf-8')[3]
|
||||
|
||||
@@ -23,15 +24,38 @@ def choose_boundary():
|
||||
return uuid4().hex
|
||||
|
||||
|
||||
def get_content_type(filename):
|
||||
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
def iter_field_objects(fields):
|
||||
"""
|
||||
Iterate over fields.
|
||||
|
||||
Supports list of (k, v) tuples and dicts, and lists of
|
||||
:class:`~urllib3.fields.RequestField`.
|
||||
|
||||
"""
|
||||
if isinstance(fields, dict):
|
||||
i = six.iteritems(fields)
|
||||
else:
|
||||
i = iter(fields)
|
||||
|
||||
for field in i:
|
||||
if isinstance(field, RequestField):
|
||||
yield field
|
||||
else:
|
||||
yield RequestField.from_tuples(*field)
|
||||
|
||||
|
||||
def iter_fields(fields):
|
||||
"""
|
||||
Iterate over fields.
|
||||
|
||||
.. deprecated ::
|
||||
|
||||
The addition of `~urllib3.fields.RequestField` makes this function
|
||||
obsolete. Instead, use :func:`iter_field_objects`, which returns
|
||||
`~urllib3.fields.RequestField` objects, instead.
|
||||
|
||||
Supports list of (k, v) tuples and dicts.
|
||||
|
||||
"""
|
||||
if isinstance(fields, dict):
|
||||
return ((k, v) for k, v in six.iteritems(fields))
|
||||
@@ -44,15 +68,7 @@ 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) or (key, value, MIME type)
|
||||
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 and a suitable MIME type is guessed based on the
|
||||
filename. If the value is a tuple of three elements, then the third
|
||||
element is treated as an explicit MIME type of the form-data section.
|
||||
|
||||
Field names and filenames must be unicode.
|
||||
Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`).
|
||||
|
||||
:param boundary:
|
||||
If not specified, then a random boundary will be generated using
|
||||
@@ -62,24 +78,11 @@ def encode_multipart_formdata(fields, boundary=None):
|
||||
if boundary is None:
|
||||
boundary = choose_boundary()
|
||||
|
||||
for fieldname, value in iter_fields(fields):
|
||||
for field in iter_field_objects(fields):
|
||||
body.write(b('--%s\r\n' % (boundary)))
|
||||
|
||||
if isinstance(value, tuple):
|
||||
if len(value) == 3:
|
||||
filename, data, content_type = value
|
||||
else:
|
||||
filename, data = value
|
||||
content_type = get_content_type(filename)
|
||||
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' %
|
||||
(content_type,)))
|
||||
else:
|
||||
data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
|
||||
% (fieldname))
|
||||
body.write(b'\r\n')
|
||||
writer(body).write(field.render_headers())
|
||||
data = field.data
|
||||
|
||||
if isinstance(data, int):
|
||||
data = str(data) # Backwards compatibility
|
||||
@@ -93,6 +96,6 @@ def encode_multipart_formdata(fields, boundary=None):
|
||||
|
||||
body.write(b('--%s--\r\n' % (boundary)))
|
||||
|
||||
content_type = b('multipart/form-data; boundary=%s' % boundary)
|
||||
content_type = str('multipart/form-data; boundary=%s' % boundary)
|
||||
|
||||
return body.getvalue(), content_type
|
||||
|
||||
@@ -7,23 +7,60 @@ __version__ = '3.2.2'
|
||||
class CertificateError(ValueError):
|
||||
pass
|
||||
|
||||
def _dnsname_to_pat(dn):
|
||||
def _dnsname_match(dn, hostname, max_wildcards=1):
|
||||
"""Matching according to RFC 6125, section 6.4.3
|
||||
|
||||
http://tools.ietf.org/html/rfc6125#section-6.4.3
|
||||
"""
|
||||
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)
|
||||
if not dn:
|
||||
return False
|
||||
|
||||
parts = dn.split(r'.')
|
||||
leftmost = parts[0]
|
||||
|
||||
wildcards = leftmost.count('*')
|
||||
if wildcards > max_wildcards:
|
||||
# Issue #17980: avoid denials of service by refusing more
|
||||
# than one wildcard per fragment. A survery of established
|
||||
# policy among SSL implementations showed it to be a
|
||||
# reasonable choice.
|
||||
raise CertificateError(
|
||||
"too many wildcards in certificate DNS name: " + repr(dn))
|
||||
|
||||
# speed up common case w/o wildcards
|
||||
if not wildcards:
|
||||
return dn.lower() == hostname.lower()
|
||||
|
||||
# RFC 6125, section 6.4.3, subitem 1.
|
||||
# The client SHOULD NOT attempt to match a presented identifier in which
|
||||
# the wildcard character comprises a label other than the left-most label.
|
||||
if leftmost == '*':
|
||||
# When '*' is a fragment by itself, it matches a non-empty dotless
|
||||
# fragment.
|
||||
pats.append('[^.]+')
|
||||
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
|
||||
# RFC 6125, section 6.4.3, subitem 3.
|
||||
# The client SHOULD NOT attempt to match a presented identifier
|
||||
# where the wildcard character is embedded within an A-label or
|
||||
# U-label of an internationalized domain name.
|
||||
pats.append(re.escape(leftmost))
|
||||
else:
|
||||
# Otherwise, '*' matches any dotless string, e.g. www*
|
||||
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
|
||||
|
||||
# add the remaining fragments, ignore any wildcards
|
||||
for frag in parts[1:]:
|
||||
pats.append(re.escape(frag))
|
||||
|
||||
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
return pat.match(hostname)
|
||||
|
||||
|
||||
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*.
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
||||
rules are followed, but IP addresses are not accepted for *hostname*.
|
||||
|
||||
CertificateError is raised on failure. On success, the function
|
||||
returns nothing.
|
||||
@@ -34,7 +71,7 @@ def match_hostname(cert, hostname):
|
||||
san = cert.get('subjectAltName', ())
|
||||
for key, value in san:
|
||||
if key == 'DNS':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
if _dnsname_match(value, hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if not dnsnames:
|
||||
@@ -45,7 +82,7 @@ def match_hostname(cert, hostname):
|
||||
# XXX according to RFC 2818, the most specific Common Name
|
||||
# must be used.
|
||||
if key == 'commonName':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
if _dnsname_match(value, hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
# urllib3/poolmanager.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
|
||||
try: # Python 3
|
||||
from urllib.parse import urljoin
|
||||
except ImportError:
|
||||
from urlparse import urljoin
|
||||
|
||||
from ._collections import RecentlyUsedContainer
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
from .connectionpool import connection_from_url, port_by_scheme
|
||||
from .connectionpool import port_by_scheme
|
||||
from .request import RequestMethods
|
||||
from .util import parse_url
|
||||
|
||||
@@ -23,6 +28,9 @@ pool_classes_by_scheme = {
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs',
|
||||
'ssl_version')
|
||||
|
||||
|
||||
class PoolManager(RequestMethods):
|
||||
"""
|
||||
@@ -52,12 +60,31 @@ class PoolManager(RequestMethods):
|
||||
|
||||
"""
|
||||
|
||||
proxy = None
|
||||
|
||||
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
|
||||
RequestMethods.__init__(self, headers)
|
||||
self.connection_pool_kw = connection_pool_kw
|
||||
self.pools = RecentlyUsedContainer(num_pools,
|
||||
dispose_func=lambda p: p.close())
|
||||
|
||||
def _new_pool(self, scheme, host, port):
|
||||
"""
|
||||
Create a new :class:`ConnectionPool` based on host, port and scheme.
|
||||
|
||||
This method is used to actually create the connection pools handed out
|
||||
by :meth:`connection_from_url` and companion methods. It is intended
|
||||
to be overridden for customization.
|
||||
"""
|
||||
pool_cls = pool_classes_by_scheme[scheme]
|
||||
kwargs = self.connection_pool_kw
|
||||
if scheme == 'http':
|
||||
kwargs = self.connection_pool_kw.copy()
|
||||
for kw in SSL_KEYWORDS:
|
||||
kwargs.pop(kw, None)
|
||||
|
||||
return pool_cls(host, port, **kwargs)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Empty our store of pools and direct them all to close.
|
||||
@@ -74,22 +101,23 @@ class PoolManager(RequestMethods):
|
||||
If ``port`` isn't given, it will be derived from the ``scheme`` using
|
||||
``urllib3.connectionpool.port_by_scheme``.
|
||||
"""
|
||||
|
||||
scheme = scheme or 'http'
|
||||
|
||||
port = port or port_by_scheme.get(scheme, 80)
|
||||
|
||||
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
|
||||
with self.pools.lock:
|
||||
# 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 = self._new_pool(scheme, host, port)
|
||||
self.pools[pool_key] = pool
|
||||
return pool
|
||||
|
||||
def connection_from_url(self, url):
|
||||
@@ -121,43 +149,111 @@ class PoolManager(RequestMethods):
|
||||
if 'headers' not in kw:
|
||||
kw['headers'] = self.headers
|
||||
|
||||
response = conn.urlopen(method, u.request_uri, **kw)
|
||||
if self.proxy is not None and u.scheme == "http":
|
||||
response = conn.urlopen(method, url, **kw)
|
||||
else:
|
||||
response = conn.urlopen(method, u.request_uri, **kw)
|
||||
|
||||
redirect_location = redirect and response.get_redirect_location()
|
||||
if not redirect_location:
|
||||
return response
|
||||
|
||||
# Support relative URLs for redirecting.
|
||||
redirect_location = urljoin(url, redirect_location)
|
||||
|
||||
# RFC 2616, Section 10.3.4
|
||||
if response.status == 303:
|
||||
method = 'GET'
|
||||
|
||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
||||
kw['retries'] = kw.get('retries', 3) - 1 # Persist retries countdown
|
||||
kw['redirect'] = redirect
|
||||
return self.urlopen(method, redirect_location, **kw)
|
||||
|
||||
|
||||
class ProxyManager(RequestMethods):
|
||||
class ProxyManager(PoolManager):
|
||||
"""
|
||||
Given a ConnectionPool to a proxy, the ProxyManager's ``urlopen`` method
|
||||
will make requests to any url through the defined proxy.
|
||||
Behaves just like :class:`PoolManager`, but sends all requests through
|
||||
the defined proxy, using the CONNECT method for HTTPS URLs.
|
||||
|
||||
:param poxy_url:
|
||||
The URL of the proxy to be used.
|
||||
|
||||
:param proxy_headers:
|
||||
A dictionary contaning headers that will be sent to the proxy. In case
|
||||
of HTTP they are being sent with each request, while in the
|
||||
HTTPS/CONNECT case they are sent only once. Could be used for proxy
|
||||
authentication.
|
||||
|
||||
Example:
|
||||
>>> proxy = urllib3.ProxyManager('http://localhost:3128/')
|
||||
>>> r1 = proxy.request('GET', 'http://google.com/')
|
||||
>>> r2 = proxy.request('GET', 'http://httpbin.org/')
|
||||
>>> len(proxy.pools)
|
||||
1
|
||||
>>> r3 = proxy.request('GET', 'https://httpbin.org/')
|
||||
>>> r4 = proxy.request('GET', 'https://twitter.com/')
|
||||
>>> len(proxy.pools)
|
||||
3
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, proxy_pool):
|
||||
self.proxy_pool = proxy_pool
|
||||
def __init__(self, proxy_url, num_pools=10, headers=None,
|
||||
proxy_headers=None, **connection_pool_kw):
|
||||
|
||||
def _set_proxy_headers(self, headers=None):
|
||||
if isinstance(proxy_url, HTTPConnectionPool):
|
||||
proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host,
|
||||
proxy_url.port)
|
||||
proxy = parse_url(proxy_url)
|
||||
if not proxy.port:
|
||||
port = port_by_scheme.get(proxy.scheme, 80)
|
||||
proxy = proxy._replace(port=port)
|
||||
self.proxy = proxy
|
||||
self.proxy_headers = proxy_headers or {}
|
||||
assert self.proxy.scheme in ("http", "https"), \
|
||||
'Not supported proxy scheme %s' % self.proxy.scheme
|
||||
connection_pool_kw['_proxy'] = self.proxy
|
||||
connection_pool_kw['_proxy_headers'] = self.proxy_headers
|
||||
super(ProxyManager, self).__init__(
|
||||
num_pools, headers, **connection_pool_kw)
|
||||
|
||||
def connection_from_host(self, host, port=None, scheme='http'):
|
||||
if scheme == "https":
|
||||
return super(ProxyManager, self).connection_from_host(
|
||||
host, port, scheme)
|
||||
|
||||
return super(ProxyManager, self).connection_from_host(
|
||||
self.proxy.host, self.proxy.port, self.proxy.scheme)
|
||||
|
||||
def _set_proxy_headers(self, url, headers=None):
|
||||
"""
|
||||
Sets headers needed by proxies: specifically, the Accept and Host
|
||||
headers. Only sets headers not provided by the user.
|
||||
"""
|
||||
headers_ = {'Accept': '*/*'}
|
||||
|
||||
netloc = parse_url(url).netloc
|
||||
if netloc:
|
||||
headers_['Host'] = netloc
|
||||
|
||||
if headers:
|
||||
headers_.update(headers)
|
||||
|
||||
return headers_
|
||||
|
||||
def urlopen(self, method, url, **kw):
|
||||
def urlopen(self, method, url, redirect=True, **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)
|
||||
u = parse_url(url)
|
||||
|
||||
if u.scheme == "http":
|
||||
# It's too late to set proxy headers on per-request basis for
|
||||
# tunnelled HTTPS connections, should use
|
||||
# constructor's proxy_headers instead.
|
||||
kw['headers'] = self._set_proxy_headers(url, kw.get('headers',
|
||||
self.headers))
|
||||
kw['headers'].update(self.proxy_headers)
|
||||
|
||||
return super(ProxyManager, self).urlopen(method, url, redirect, **kw)
|
||||
|
||||
|
||||
def proxy_from_url(url, **pool_kw):
|
||||
proxy_pool = connection_from_url(url, **pool_kw)
|
||||
return ProxyManager(proxy_pool)
|
||||
def proxy_from_url(url, **kw):
|
||||
return ProxyManager(proxy_url=url, **kw)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/request.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
@@ -30,7 +30,7 @@ class RequestMethods(object):
|
||||
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
|
||||
encoded in the *body* of the request using multipart or www-form-urlencoded
|
||||
(such as for POST, PUT, PATCH).
|
||||
|
||||
:meth:`.request` is for making any kind of request, it will look up the
|
||||
|
||||
@@ -1,35 +1,56 @@
|
||||
# urllib3/response.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
import io
|
||||
|
||||
from .exceptions import DecodeError
|
||||
from .packages.six import string_types as basestring
|
||||
from .packages.six import string_types as basestring, binary_type
|
||||
from .util import is_fp_closed
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def decode_gzip(data):
|
||||
gzipper = gzip.GzipFile(fileobj=BytesIO(data))
|
||||
return gzipper.read()
|
||||
class DeflateDecoder(object):
|
||||
|
||||
def __init__(self):
|
||||
self._first_try = True
|
||||
self._data = binary_type()
|
||||
self._obj = zlib.decompressobj()
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._obj, name)
|
||||
|
||||
def decompress(self, data):
|
||||
if not self._first_try:
|
||||
return self._obj.decompress(data)
|
||||
|
||||
self._data += data
|
||||
try:
|
||||
return self._obj.decompress(data)
|
||||
except zlib.error:
|
||||
self._first_try = False
|
||||
self._obj = zlib.decompressobj(-zlib.MAX_WBITS)
|
||||
try:
|
||||
return self.decompress(self._data)
|
||||
finally:
|
||||
self._data = None
|
||||
|
||||
|
||||
def decode_deflate(data):
|
||||
try:
|
||||
return zlib.decompress(data)
|
||||
except zlib.error:
|
||||
return zlib.decompress(data, -zlib.MAX_WBITS)
|
||||
def _get_decoder(mode):
|
||||
if mode == 'gzip':
|
||||
return zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
|
||||
return DeflateDecoder()
|
||||
|
||||
|
||||
class HTTPResponse(object):
|
||||
class HTTPResponse(io.IOBase):
|
||||
"""
|
||||
HTTP Response container.
|
||||
|
||||
@@ -52,10 +73,8 @@ class HTTPResponse(object):
|
||||
otherwise unused.
|
||||
"""
|
||||
|
||||
CONTENT_DECODERS = {
|
||||
'gzip': decode_gzip,
|
||||
'deflate': decode_deflate,
|
||||
}
|
||||
CONTENT_DECODERS = ['gzip', 'deflate']
|
||||
REDIRECT_STATUSES = [301, 302, 303, 307, 308]
|
||||
|
||||
def __init__(self, body='', headers=None, status=0, version=0, reason=None,
|
||||
strict=0, preload_content=True, decode_content=True,
|
||||
@@ -65,8 +84,9 @@ class HTTPResponse(object):
|
||||
self.version = version
|
||||
self.reason = reason
|
||||
self.strict = strict
|
||||
self.decode_content = decode_content
|
||||
|
||||
self._decode_content = decode_content
|
||||
self._decoder = None
|
||||
self._body = body if body and isinstance(body, basestring) else None
|
||||
self._fp = None
|
||||
self._original_response = original_response
|
||||
@@ -88,7 +108,7 @@ class HTTPResponse(object):
|
||||
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]:
|
||||
if self.status in self.REDIRECT_STATUSES:
|
||||
return self.headers.get('location')
|
||||
|
||||
return False
|
||||
@@ -115,13 +135,13 @@ class HTTPResponse(object):
|
||||
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.
|
||||
How much of the content to read. If specified, caching is skipped
|
||||
because it doesn't 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.)
|
||||
'content-encoding' header.
|
||||
|
||||
:param cache_content:
|
||||
If True, will save the returned data such that the same result is
|
||||
@@ -133,26 +153,48 @@ class HTTPResponse(object):
|
||||
# Note: content-encoding value should be case-insensitive, per RFC 2616
|
||||
# Section 3.5
|
||||
content_encoding = self.headers.get('content-encoding', '').lower()
|
||||
decoder = self.CONTENT_DECODERS.get(content_encoding)
|
||||
if self._decoder is None:
|
||||
if content_encoding in self.CONTENT_DECODERS:
|
||||
self._decoder = _get_decoder(content_encoding)
|
||||
if decode_content is None:
|
||||
decode_content = self._decode_content
|
||||
decode_content = self.decode_content
|
||||
|
||||
if self._fp is None:
|
||||
return
|
||||
|
||||
flush_decoder = False
|
||||
|
||||
try:
|
||||
if amt is None:
|
||||
# cStringIO doesn't like amt=None
|
||||
data = self._fp.read()
|
||||
flush_decoder = True
|
||||
else:
|
||||
return self._fp.read(amt)
|
||||
cache_content = False
|
||||
data = self._fp.read(amt)
|
||||
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
|
||||
# Close the connection when no data is returned
|
||||
#
|
||||
# This is redundant to what httplib/http.client _should_
|
||||
# already do. However, versions of python released before
|
||||
# December 15, 2012 (http://bugs.python.org/issue16298) do not
|
||||
# properly close the connection in all cases. There is no harm
|
||||
# in redundantly calling close.
|
||||
self._fp.close()
|
||||
flush_decoder = True
|
||||
|
||||
try:
|
||||
if decode_content and decoder:
|
||||
data = decoder(data)
|
||||
except (IOError, zlib.error):
|
||||
raise DecodeError("Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding)
|
||||
if decode_content and self._decoder:
|
||||
data = self._decoder.decompress(data)
|
||||
except (IOError, zlib.error) as e:
|
||||
raise DecodeError(
|
||||
"Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding,
|
||||
e)
|
||||
|
||||
if flush_decoder and decode_content and self._decoder:
|
||||
buf = self._decoder.decompress(binary_type())
|
||||
data += buf + self._decoder.flush()
|
||||
|
||||
if cache_content:
|
||||
self._body = data
|
||||
@@ -163,6 +205,29 @@ class HTTPResponse(object):
|
||||
if self._original_response and self._original_response.isclosed():
|
||||
self.release_conn()
|
||||
|
||||
def stream(self, amt=2**16, decode_content=None):
|
||||
"""
|
||||
A generator wrapper for the read() method. A call will block until
|
||||
``amt`` bytes have been read from the connection or until the
|
||||
connection is closed.
|
||||
|
||||
:param amt:
|
||||
How much of the content to read. The generator will return up to
|
||||
much data per iteration, but may return less. This is particularly
|
||||
likely when using compressed data. However, the empty string will
|
||||
never be returned.
|
||||
|
||||
:param decode_content:
|
||||
If True, will attempt to decode the body based on the
|
||||
'content-encoding' header.
|
||||
"""
|
||||
while not is_fp_closed(self._fp):
|
||||
data = self.read(amt=amt, decode_content=decode_content)
|
||||
|
||||
if data:
|
||||
yield data
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_httplib(ResponseCls, r, **response_kw):
|
||||
"""
|
||||
@@ -202,3 +267,35 @@ class HTTPResponse(object):
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
return self.headers.get(name, default)
|
||||
|
||||
# Overrides from io.IOBase
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
self._fp.close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
if self._fp is None:
|
||||
return True
|
||||
elif hasattr(self._fp, 'closed'):
|
||||
return self._fp.closed
|
||||
elif hasattr(self._fp, 'isclosed'): # Python 2
|
||||
return self._fp.isclosed()
|
||||
else:
|
||||
return True
|
||||
|
||||
def fileno(self):
|
||||
if self._fp is None:
|
||||
raise IOError("HTTPResponse has no file to get a fileno from")
|
||||
elif hasattr(self._fp, "fileno"):
|
||||
return self._fp.fileno()
|
||||
else:
|
||||
raise IOError("The file-like object this HTTPResponse is wrapped "
|
||||
"around has no file descriptor")
|
||||
|
||||
def flush(self):
|
||||
if self._fp is not None and hasattr(self._fp, 'flush'):
|
||||
return self._fp.flush()
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# urllib3/util.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2013 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
|
||||
from binascii import hexlify, unhexlify
|
||||
from collections import namedtuple
|
||||
from socket import error as SocketError
|
||||
from hashlib import md5, sha1
|
||||
from socket import error as SocketError, _GLOBAL_DEFAULT_TIMEOUT
|
||||
import time
|
||||
|
||||
try:
|
||||
from select import poll, POLLIN
|
||||
@@ -22,15 +25,241 @@ try: # Test for SSL features
|
||||
SSLContext = None
|
||||
HAS_SNI = False
|
||||
|
||||
from ssl import wrap_socket, CERT_NONE, SSLError, PROTOCOL_SSLv23
|
||||
import ssl
|
||||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
|
||||
from ssl import SSLContext # Modern SSL?
|
||||
from ssl import HAS_SNI # Has SNI?
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
from .packages import six
|
||||
from .exceptions import LocationParseError
|
||||
from .exceptions import LocationParseError, SSLError, TimeoutStateError
|
||||
|
||||
|
||||
_Default = object()
|
||||
# The default timeout to use for socket connections. This is the attribute used
|
||||
# by httplib to define the default timeout
|
||||
|
||||
|
||||
def current_time():
|
||||
"""
|
||||
Retrieve the current time, this function is mocked out in unit testing.
|
||||
"""
|
||||
return time.time()
|
||||
|
||||
|
||||
class Timeout(object):
|
||||
"""
|
||||
Utility object for storing timeout values.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
timeout = urllib3.util.Timeout(connect=2.0, read=7.0)
|
||||
pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout)
|
||||
pool.request(...) # Etc, etc
|
||||
|
||||
:param connect:
|
||||
The maximum amount of time to wait for a connection attempt to a server
|
||||
to succeed. Omitting the parameter will default the connect timeout to
|
||||
the system default, probably `the global default timeout in socket.py
|
||||
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_.
|
||||
None will set an infinite timeout for connection attempts.
|
||||
|
||||
:type connect: integer, float, or None
|
||||
|
||||
:param read:
|
||||
The maximum amount of time to wait between consecutive
|
||||
read operations for a response from the server. Omitting
|
||||
the parameter will default the read timeout to the system
|
||||
default, probably `the global default timeout in socket.py
|
||||
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_.
|
||||
None will set an infinite timeout.
|
||||
|
||||
:type read: integer, float, or None
|
||||
|
||||
:param total:
|
||||
The maximum amount of time to wait for an HTTP request to connect and
|
||||
return. This combines the connect and read timeouts into one. In the
|
||||
event that both a connect timeout and a total are specified, or a read
|
||||
timeout and a total are specified, the shorter timeout will be applied.
|
||||
|
||||
Defaults to None.
|
||||
|
||||
|
||||
:type total: integer, float, or None
|
||||
|
||||
.. note::
|
||||
|
||||
Many factors can affect the total amount of time for urllib3 to return
|
||||
an HTTP response. Specifically, Python's DNS resolver does not obey the
|
||||
timeout specified on the socket. Other factors that can affect total
|
||||
request time include high CPU load, high swap, the program running at a
|
||||
low priority level, or other behaviors. The observed running time for
|
||||
urllib3 to return a response may be greater than the value passed to
|
||||
`total`.
|
||||
|
||||
In addition, the read and total timeouts only measure the time between
|
||||
read operations on the socket connecting the client and the server, not
|
||||
the total amount of time for the request to return a complete response.
|
||||
As an example, you may want a request to return within 7 seconds or
|
||||
fail, so you set the ``total`` timeout to 7 seconds. If the server
|
||||
sends one byte to you every 5 seconds, the request will **not** trigger
|
||||
time out. This case is admittedly rare.
|
||||
"""
|
||||
|
||||
#: A sentinel object representing the default timeout value
|
||||
DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT
|
||||
|
||||
def __init__(self, connect=_Default, read=_Default, total=None):
|
||||
self._connect = self._validate_timeout(connect, 'connect')
|
||||
self._read = self._validate_timeout(read, 'read')
|
||||
self.total = self._validate_timeout(total, 'total')
|
||||
self._start_connect = None
|
||||
|
||||
def __str__(self):
|
||||
return '%s(connect=%r, read=%r, total=%r)' % (
|
||||
type(self).__name__, self._connect, self._read, self.total)
|
||||
|
||||
|
||||
@classmethod
|
||||
def _validate_timeout(cls, value, name):
|
||||
""" Check that a timeout attribute is valid
|
||||
|
||||
:param value: The timeout value to validate
|
||||
:param name: The name of the timeout attribute to validate. This is used
|
||||
for clear error messages
|
||||
:return: the value
|
||||
:raises ValueError: if the type is not an integer or a float, or if it
|
||||
is a numeric value less than zero
|
||||
"""
|
||||
if value is _Default:
|
||||
return cls.DEFAULT_TIMEOUT
|
||||
|
||||
if value is None or value is cls.DEFAULT_TIMEOUT:
|
||||
return value
|
||||
|
||||
try:
|
||||
float(value)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("Timeout value %s was %s, but it must be an "
|
||||
"int or float." % (name, value))
|
||||
|
||||
try:
|
||||
if value < 0:
|
||||
raise ValueError("Attempted to set %s timeout to %s, but the "
|
||||
"timeout cannot be set to a value less "
|
||||
"than 0." % (name, value))
|
||||
except TypeError: # Python 3
|
||||
raise ValueError("Timeout value %s was %s, but it must be an "
|
||||
"int or float." % (name, value))
|
||||
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def from_float(cls, timeout):
|
||||
""" Create a new Timeout from a legacy timeout value.
|
||||
|
||||
The timeout value used by httplib.py sets the same timeout on the
|
||||
connect(), and recv() socket requests. This creates a :class:`Timeout`
|
||||
object that sets the individual timeouts to the ``timeout`` value passed
|
||||
to this function.
|
||||
|
||||
:param timeout: The legacy timeout value
|
||||
:type timeout: integer, float, sentinel default object, or None
|
||||
:return: a Timeout object
|
||||
:rtype: :class:`Timeout`
|
||||
"""
|
||||
return Timeout(read=timeout, connect=timeout)
|
||||
|
||||
def clone(self):
|
||||
""" Create a copy of the timeout object
|
||||
|
||||
Timeout properties are stored per-pool but each request needs a fresh
|
||||
Timeout object to ensure each one has its own start/stop configured.
|
||||
|
||||
:return: a copy of the timeout object
|
||||
:rtype: :class:`Timeout`
|
||||
"""
|
||||
# We can't use copy.deepcopy because that will also create a new object
|
||||
# for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to
|
||||
# detect the user default.
|
||||
return Timeout(connect=self._connect, read=self._read,
|
||||
total=self.total)
|
||||
|
||||
def start_connect(self):
|
||||
""" Start the timeout clock, used during a connect() attempt
|
||||
|
||||
:raises urllib3.exceptions.TimeoutStateError: if you attempt
|
||||
to start a timer that has been started already.
|
||||
"""
|
||||
if self._start_connect is not None:
|
||||
raise TimeoutStateError("Timeout timer has already been started.")
|
||||
self._start_connect = current_time()
|
||||
return self._start_connect
|
||||
|
||||
def get_connect_duration(self):
|
||||
""" Gets the time elapsed since the call to :meth:`start_connect`.
|
||||
|
||||
:return: the elapsed time
|
||||
:rtype: float
|
||||
:raises urllib3.exceptions.TimeoutStateError: if you attempt
|
||||
to get duration for a timer that hasn't been started.
|
||||
"""
|
||||
if self._start_connect is None:
|
||||
raise TimeoutStateError("Can't get connect duration for timer "
|
||||
"that has not started.")
|
||||
return current_time() - self._start_connect
|
||||
|
||||
@property
|
||||
def connect_timeout(self):
|
||||
""" Get the value to use when setting a connection timeout.
|
||||
|
||||
This will be a positive float or integer, the value None
|
||||
(never timeout), or the default system timeout.
|
||||
|
||||
:return: the connect timeout
|
||||
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
|
||||
"""
|
||||
if self.total is None:
|
||||
return self._connect
|
||||
|
||||
if self._connect is None or self._connect is self.DEFAULT_TIMEOUT:
|
||||
return self.total
|
||||
|
||||
return min(self._connect, self.total)
|
||||
|
||||
@property
|
||||
def read_timeout(self):
|
||||
""" Get the value for the read timeout.
|
||||
|
||||
This assumes some time has elapsed in the connection timeout and
|
||||
computes the read timeout appropriately.
|
||||
|
||||
If self.total is set, the read timeout is dependent on the amount of
|
||||
time taken by the connect timeout. If the connection time has not been
|
||||
established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be
|
||||
raised.
|
||||
|
||||
:return: the value to use for the read timeout
|
||||
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
|
||||
:raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect`
|
||||
has not yet been called on this object.
|
||||
"""
|
||||
if (self.total is not None and
|
||||
self.total is not self.DEFAULT_TIMEOUT and
|
||||
self._read is not None and
|
||||
self._read is not self.DEFAULT_TIMEOUT):
|
||||
# in case the connect timeout has not yet been established.
|
||||
if self._start_connect is None:
|
||||
return self._read
|
||||
return max(0, min(self.total - self.get_connect_duration(),
|
||||
self._read))
|
||||
elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT:
|
||||
return max(0, self.total - self.get_connect_duration())
|
||||
else:
|
||||
return self._read
|
||||
|
||||
|
||||
class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])):
|
||||
@@ -58,6 +287,13 @@ class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query',
|
||||
|
||||
return uri
|
||||
|
||||
@property
|
||||
def netloc(self):
|
||||
"""Network location including host and port"""
|
||||
if self.port:
|
||||
return '%s:%d' % (self.host, self.port)
|
||||
return self.host
|
||||
|
||||
|
||||
def split_first(s, delims):
|
||||
"""
|
||||
@@ -111,7 +347,7 @@ def parse_url(url):
|
||||
|
||||
# While this code has overlap with stdlib's urlparse, it is much
|
||||
# simplified for our needs and less annoying.
|
||||
# Additionally, this imeplementations does silly things to be optimal
|
||||
# Additionally, this implementations does silly things to be optimal
|
||||
# on CPython.
|
||||
|
||||
scheme = None
|
||||
@@ -140,7 +376,8 @@ def parse_url(url):
|
||||
|
||||
# IPv6
|
||||
if url and url[0] == '[':
|
||||
host, url = url[1:].split(']', 1)
|
||||
host, url = url.split(']', 1)
|
||||
host += ']'
|
||||
|
||||
# Port
|
||||
if ':' in url:
|
||||
@@ -231,7 +468,7 @@ def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
|
||||
return headers
|
||||
|
||||
|
||||
def is_connection_dropped(conn):
|
||||
def is_connection_dropped(conn): # Platform-specific
|
||||
"""
|
||||
Returns True if the connection is dropped and should be closed.
|
||||
|
||||
@@ -245,7 +482,7 @@ def is_connection_dropped(conn):
|
||||
if not sock: # Platform-specific: AppEngine
|
||||
return False
|
||||
|
||||
if not poll: # Platform-specific
|
||||
if not poll:
|
||||
if not select: # Platform-specific: AppEngine
|
||||
return False
|
||||
|
||||
@@ -263,10 +500,100 @@ def is_connection_dropped(conn):
|
||||
return True
|
||||
|
||||
|
||||
def resolve_cert_reqs(candidate):
|
||||
"""
|
||||
Resolves the argument to a numeric constant, which can be passed to
|
||||
the wrap_socket function/method from the ssl module.
|
||||
Defaults to :data:`ssl.CERT_NONE`.
|
||||
If given a string it is assumed to be the name of the constant in the
|
||||
:mod:`ssl` module or its abbrevation.
|
||||
(So you can specify `REQUIRED` instead of `CERT_REQUIRED`.
|
||||
If it's neither `None` nor a string we assume it is already the numeric
|
||||
constant which can directly be passed to wrap_socket.
|
||||
"""
|
||||
if candidate is None:
|
||||
return CERT_NONE
|
||||
|
||||
if isinstance(candidate, str):
|
||||
res = getattr(ssl, candidate, None)
|
||||
if res is None:
|
||||
res = getattr(ssl, 'CERT_' + candidate)
|
||||
return res
|
||||
|
||||
return candidate
|
||||
|
||||
|
||||
def resolve_ssl_version(candidate):
|
||||
"""
|
||||
like resolve_cert_reqs
|
||||
"""
|
||||
if candidate is None:
|
||||
return PROTOCOL_SSLv23
|
||||
|
||||
if isinstance(candidate, str):
|
||||
res = getattr(ssl, candidate, None)
|
||||
if res is None:
|
||||
res = getattr(ssl, 'PROTOCOL_' + candidate)
|
||||
return res
|
||||
|
||||
return candidate
|
||||
|
||||
|
||||
def assert_fingerprint(cert, fingerprint):
|
||||
"""
|
||||
Checks if given fingerprint matches the supplied certificate.
|
||||
|
||||
:param cert:
|
||||
Certificate as bytes object.
|
||||
:param fingerprint:
|
||||
Fingerprint as string of hexdigits, can be interspersed by colons.
|
||||
"""
|
||||
|
||||
# Maps the length of a digest to a possible hash function producing
|
||||
# this digest.
|
||||
hashfunc_map = {
|
||||
16: md5,
|
||||
20: sha1
|
||||
}
|
||||
|
||||
fingerprint = fingerprint.replace(':', '').lower()
|
||||
|
||||
digest_length, rest = divmod(len(fingerprint), 2)
|
||||
|
||||
if rest or digest_length not in hashfunc_map:
|
||||
raise SSLError('Fingerprint is of invalid length.')
|
||||
|
||||
# We need encode() here for py32; works on py2 and p33.
|
||||
fingerprint_bytes = unhexlify(fingerprint.encode())
|
||||
|
||||
hashfunc = hashfunc_map[digest_length]
|
||||
|
||||
cert_digest = hashfunc(cert).digest()
|
||||
|
||||
if not cert_digest == fingerprint_bytes:
|
||||
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
|
||||
.format(hexlify(fingerprint_bytes),
|
||||
hexlify(cert_digest)))
|
||||
|
||||
def is_fp_closed(obj):
|
||||
"""
|
||||
Checks whether a given file-like object is closed.
|
||||
|
||||
:param obj:
|
||||
The file-like object to check.
|
||||
"""
|
||||
if hasattr(obj, 'fp'):
|
||||
# Object is a container for another file-like object that gets released
|
||||
# on exhaustion (e.g. HTTPResponse)
|
||||
return obj.fp is None
|
||||
|
||||
return obj.closed
|
||||
|
||||
|
||||
if SSLContext is not None: # Python 3.2+
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=CERT_NONE,
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=PROTOCOL_SSLv23):
|
||||
ssl_version=None):
|
||||
"""
|
||||
All arguments except `server_hostname` have the same meaning as for
|
||||
:func:`ssl.wrap_socket`
|
||||
@@ -279,8 +606,9 @@ if SSLContext is not None: # Python 3.2+
|
||||
if ca_certs:
|
||||
try:
|
||||
context.load_verify_locations(ca_certs)
|
||||
except TypeError as e: # Reraise as SSLError
|
||||
# FIXME: This block needs a test.
|
||||
# Py32 raises IOError
|
||||
# Py33 raises FileNotFoundError
|
||||
except Exception as e: # Reraise as SSLError
|
||||
raise SSLError(e)
|
||||
if certfile:
|
||||
# FIXME: This block needs a test.
|
||||
@@ -290,9 +618,9 @@ if SSLContext is not None: # Python 3.2+
|
||||
return context.wrap_socket(sock)
|
||||
|
||||
else: # Python 3.1 and earlier
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=CERT_NONE,
|
||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||
ca_certs=None, server_hostname=None,
|
||||
ssl_version=PROTOCOL_SSLv23):
|
||||
ssl_version=None):
|
||||
return wrap_socket(sock, keyfile=keyfile, certfile=certfile,
|
||||
ca_certs=ca_certs, cert_reqs=cert_reqs,
|
||||
ssl_version=ssl_version)
|
||||
|
||||
@@ -9,67 +9,72 @@ requests (cookies, auth, proxies).
|
||||
|
||||
"""
|
||||
import os
|
||||
from collections import Mapping
|
||||
from datetime import datetime
|
||||
|
||||
from .compat import cookielib
|
||||
from .cookies import cookiejar_from_dict
|
||||
from .models import Request
|
||||
from .hooks import dispatch_hook, default_hooks
|
||||
from .utils import from_key_val_list, default_headers
|
||||
from .compat import cookielib, OrderedDict, urljoin, urlparse
|
||||
from .cookies import cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar
|
||||
from .models import Request, PreparedRequest
|
||||
from .hooks import default_hooks, dispatch_hook
|
||||
from .utils import to_key_val_list, default_headers
|
||||
from .exceptions import TooManyRedirects, InvalidSchema
|
||||
from .structures import CaseInsensitiveDict
|
||||
|
||||
from .compat import urlparse, urljoin
|
||||
from .adapters import HTTPAdapter
|
||||
|
||||
from .utils import requote_uri, get_environ_proxies, get_netrc_auth
|
||||
|
||||
from .status_codes import codes
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
REDIRECT_STATI = (
|
||||
codes.moved, # 301
|
||||
codes.found, # 302
|
||||
codes.other, # 303
|
||||
codes.temporary_moved, # 307
|
||||
)
|
||||
DEFAULT_REDIRECT_LIMIT = 30
|
||||
|
||||
|
||||
def merge_kwargs(local_kwarg, default_kwarg):
|
||||
"""Merges kwarg dictionaries.
|
||||
|
||||
If a local key in the dictionary is set to None, it will be removed.
|
||||
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||
"""
|
||||
Determines appropriate setting for a given request, taking into account the
|
||||
explicit setting on that request, and the setting in the session. If a
|
||||
setting is a dictionary, they will be merged together using `dict_class`
|
||||
"""
|
||||
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
if session_setting is None:
|
||||
return request_setting
|
||||
|
||||
if isinstance(local_kwarg, str):
|
||||
return local_kwarg
|
||||
if request_setting is None:
|
||||
return session_setting
|
||||
|
||||
if local_kwarg is None:
|
||||
return default_kwarg
|
||||
# Bypass if not a dictionary (e.g. verify)
|
||||
if not (
|
||||
isinstance(session_setting, Mapping) and
|
||||
isinstance(request_setting, Mapping)
|
||||
):
|
||||
return request_setting
|
||||
|
||||
# Bypass if not a dictionary (e.g. timeout)
|
||||
if not hasattr(default_kwarg, 'items'):
|
||||
return local_kwarg
|
||||
|
||||
default_kwarg = from_key_val_list(default_kwarg)
|
||||
local_kwarg = from_key_val_list(local_kwarg)
|
||||
|
||||
# Update new values.
|
||||
kwargs = default_kwarg.copy()
|
||||
kwargs.update(local_kwarg)
|
||||
merged_setting = dict_class(to_key_val_list(session_setting))
|
||||
merged_setting.update(to_key_val_list(request_setting))
|
||||
|
||||
# Remove keys that are set to None.
|
||||
for (k, v) in local_kwarg.items():
|
||||
for (k, v) in request_setting.items():
|
||||
if v is None:
|
||||
del kwargs[k]
|
||||
del merged_setting[k]
|
||||
|
||||
return kwargs
|
||||
return merged_setting
|
||||
|
||||
|
||||
class SessionRedirectMixin(object):
|
||||
|
||||
def resolve_redirects(self, resp, req, stream=False, timeout=None, verify=True, cert=None, proxies=None):
|
||||
def resolve_redirects(self, resp, req, stream=False, timeout=None,
|
||||
verify=True, cert=None, proxies=None):
|
||||
"""Receives a Response. Returns a generator of Responses."""
|
||||
|
||||
i = 0
|
||||
|
||||
# ((resp.status_code is codes.see_other))
|
||||
while (('location' in resp.headers and resp.status_code in REDIRECT_STATI)):
|
||||
prepared_request = req.copy()
|
||||
|
||||
resp.content # Consume socket so it can be released
|
||||
|
||||
@@ -87,41 +92,59 @@ class SessionRedirectMixin(object):
|
||||
parsed_rurl = urlparse(resp.url)
|
||||
url = '%s:%s' % (parsed_rurl.scheme, url)
|
||||
|
||||
# The scheme should be lower case...
|
||||
if '://' in url:
|
||||
scheme, uri = url.split('://', 1)
|
||||
url = '%s://%s' % (scheme.lower(), uri)
|
||||
|
||||
# Facilitate non-RFC2616-compliant 'location' headers
|
||||
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
|
||||
# Compliant with RFC3986, we percent encode the url.
|
||||
if not urlparse(url).netloc:
|
||||
# Compliant with RFC3986, we percent encode the url.
|
||||
url = urljoin(resp.url, requote_uri(url))
|
||||
else:
|
||||
url = requote_uri(url)
|
||||
|
||||
prepared_request.url = url
|
||||
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
|
||||
if resp.status_code is codes.see_other and req.method != 'HEAD':
|
||||
if (resp.status_code == codes.see_other and
|
||||
method != 'HEAD'):
|
||||
method = 'GET'
|
||||
|
||||
# Do what the browsers do, despite standards...
|
||||
if resp.status_code in (codes.moved, codes.found) and req.method == 'POST':
|
||||
if (resp.status_code in (codes.moved, codes.found) and
|
||||
method not in ('GET', 'HEAD')):
|
||||
method = 'GET'
|
||||
|
||||
# Remove the cookie headers that were sent.
|
||||
headers = req.headers
|
||||
prepared_request.method = method
|
||||
|
||||
# https://github.com/kennethreitz/requests/issues/1084
|
||||
if resp.status_code not in (codes.temporary, codes.resume):
|
||||
if 'Content-Length' in prepared_request.headers:
|
||||
del prepared_request.headers['Content-Length']
|
||||
|
||||
prepared_request.body = None
|
||||
|
||||
headers = prepared_request.headers
|
||||
try:
|
||||
del headers['Cookie']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
resp = self.request(
|
||||
url=url,
|
||||
method=method,
|
||||
headers=headers,
|
||||
params=req.params,
|
||||
auth=req.auth,
|
||||
cookies=req.cookies,
|
||||
allow_redirects=False,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
proxies=proxies
|
||||
)
|
||||
prepared_request.prepare_cookies(self.cookies)
|
||||
|
||||
resp = self.send(
|
||||
prepared_request,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
proxies=proxies,
|
||||
allow_redirects=False,
|
||||
)
|
||||
|
||||
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
|
||||
|
||||
i += 1
|
||||
yield resp
|
||||
@@ -130,7 +153,7 @@ class SessionRedirectMixin(object):
|
||||
class Session(SessionRedirectMixin):
|
||||
"""A Requests session.
|
||||
|
||||
Provides cookie persistience, connection-pooling, and configuration.
|
||||
Provides cookie persistence, connection-pooling, and configuration.
|
||||
|
||||
Basic Usage::
|
||||
|
||||
@@ -140,6 +163,11 @@ class Session(SessionRedirectMixin):
|
||||
200
|
||||
"""
|
||||
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
||||
'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream',
|
||||
'trust_env', 'max_redirects']
|
||||
|
||||
def __init__(self):
|
||||
|
||||
#: A case-insensitive dictionary of headers to be sent on each
|
||||
@@ -173,19 +201,23 @@ class Session(SessionRedirectMixin):
|
||||
#: SSL certificate default.
|
||||
self.cert = None
|
||||
|
||||
#: Maximum number of redirects to follow.
|
||||
#: Maximum number of redirects allowed. If the request exceeds this
|
||||
#: limit, a :class:`TooManyRedirects` exception is raised.
|
||||
self.max_redirects = DEFAULT_REDIRECT_LIMIT
|
||||
|
||||
#: Should we trust the environment?
|
||||
self.trust_env = True
|
||||
|
||||
# Set up a CookieJar to be used by default
|
||||
#: A CookieJar containing all currently outstanding cookies set on this
|
||||
#: session. By default it is a
|
||||
#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
|
||||
#: may be any other ``cookielib.CookieJar`` compatible object.
|
||||
self.cookies = cookiejar_from_dict({})
|
||||
|
||||
# Default connection adapters.
|
||||
self.adapters = {}
|
||||
self.mount('http://', HTTPAdapter())
|
||||
self.adapters = OrderedDict()
|
||||
self.mount('https://', HTTPAdapter())
|
||||
self.mount('http://', HTTPAdapter())
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -193,6 +225,46 @@ class Session(SessionRedirectMixin):
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
def prepare_request(self, request):
|
||||
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for
|
||||
transmission and returns it. The :class:`PreparedRequest` has settings
|
||||
merged from the :class:`Request <Request>` instance and those of the
|
||||
:class:`Session`.
|
||||
|
||||
:param request: :class:`Request` instance to prepare with this
|
||||
session's settings.
|
||||
"""
|
||||
cookies = request.cookies or {}
|
||||
|
||||
# Bootstrap CookieJar.
|
||||
if not isinstance(cookies, cookielib.CookieJar):
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Merge with session cookies
|
||||
merged_cookies = RequestsCookieJar()
|
||||
merged_cookies.update(self.cookies)
|
||||
merged_cookies.update(cookies)
|
||||
|
||||
|
||||
# Set environment's basic authentication if not explicitly set.
|
||||
auth = request.auth
|
||||
if self.trust_env and not auth and not self.auth:
|
||||
auth = get_netrc_auth(request.url)
|
||||
|
||||
p = PreparedRequest()
|
||||
p.prepare(
|
||||
method=request.method.upper(),
|
||||
url=request.url,
|
||||
files=request.files,
|
||||
data=request.data,
|
||||
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
|
||||
params=merge_setting(request.params, self.params),
|
||||
auth=merge_setting(auth, self.auth),
|
||||
cookies=merged_cookies,
|
||||
hooks=merge_setting(request.hooks, self.hooks),
|
||||
)
|
||||
return p
|
||||
|
||||
def request(self, method, url,
|
||||
params=None,
|
||||
data=None,
|
||||
@@ -207,18 +279,51 @@ class Session(SessionRedirectMixin):
|
||||
stream=None,
|
||||
verify=None,
|
||||
cert=None):
|
||||
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
: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 'filename': file-like-objects
|
||||
for multipart encoding upload.
|
||||
: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 by default.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of
|
||||
the proxy.
|
||||
:param stream: (optional) whether to immediately download the response
|
||||
content. Defaults to ``False``.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified.
|
||||
A CA_BUNDLE path can also be provided.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem).
|
||||
If Tuple, ('cert', 'key') pair.
|
||||
"""
|
||||
# Create the Request.
|
||||
req = Request(
|
||||
method = method.upper(),
|
||||
url = url,
|
||||
headers = headers,
|
||||
files = files,
|
||||
data = data or {},
|
||||
params = params or {},
|
||||
auth = auth,
|
||||
cookies = cookies,
|
||||
hooks = hooks,
|
||||
)
|
||||
prep = self.prepare_request(req)
|
||||
|
||||
cookies = cookies or {}
|
||||
proxies = proxies or {}
|
||||
|
||||
# Bootstrap CookieJar.
|
||||
if not isinstance(cookies, cookielib.CookieJar):
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Bubble down session cookies.
|
||||
for cookie in self.cookies:
|
||||
cookies.set_cookie(cookie)
|
||||
|
||||
# Gather clues from the surrounding environment.
|
||||
if self.trust_env:
|
||||
# Set environment's proxies.
|
||||
@@ -226,10 +331,6 @@ class Session(SessionRedirectMixin):
|
||||
for (k, v) in env_proxies.items():
|
||||
proxies.setdefault(k, v)
|
||||
|
||||
# Set environment's basic authentication.
|
||||
if not auth:
|
||||
auth = get_netrc_auth(url)
|
||||
|
||||
# Look for configuration.
|
||||
if not verify and verify is not False:
|
||||
verify = os.environ.get('REQUESTS_CA_BUNDLE')
|
||||
@@ -238,54 +339,22 @@ class Session(SessionRedirectMixin):
|
||||
if not verify and verify is not False:
|
||||
verify = os.environ.get('CURL_CA_BUNDLE')
|
||||
|
||||
|
||||
# Merge all the kwargs.
|
||||
params = merge_kwargs(params, self.params)
|
||||
headers = merge_kwargs(headers, self.headers)
|
||||
auth = merge_kwargs(auth, self.auth)
|
||||
proxies = merge_kwargs(proxies, self.proxies)
|
||||
hooks = merge_kwargs(hooks, self.hooks)
|
||||
stream = merge_kwargs(stream, self.stream)
|
||||
verify = merge_kwargs(verify, self.verify)
|
||||
cert = merge_kwargs(cert, self.cert)
|
||||
|
||||
|
||||
# Create the Request.
|
||||
req = Request()
|
||||
req.method = method.upper()
|
||||
req.url = url
|
||||
req.headers = headers
|
||||
req.files = files
|
||||
req.data = data
|
||||
req.params = params
|
||||
req.auth = auth
|
||||
req.cookies = cookies
|
||||
req.hooks = hooks
|
||||
|
||||
# Prepare the Request.
|
||||
prep = req.prepare()
|
||||
proxies = merge_setting(proxies, self.proxies)
|
||||
stream = merge_setting(stream, self.stream)
|
||||
verify = merge_setting(verify, self.verify)
|
||||
cert = merge_setting(cert, self.cert)
|
||||
|
||||
# Send the request.
|
||||
resp = self.send(prep, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
|
||||
|
||||
# Persist cookies.
|
||||
for cookie in resp.cookies:
|
||||
self.cookies.set_cookie(cookie)
|
||||
|
||||
# Redirect resolving generator.
|
||||
gen = self.resolve_redirects(resp, req, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
|
||||
|
||||
# Resolve redirects if allowed.
|
||||
history = [r for r in gen] if allow_redirects else []
|
||||
|
||||
# Shuffle things around if there's history.
|
||||
if history:
|
||||
history.insert(0, resp)
|
||||
resp = history.pop()
|
||||
resp.history = tuple(history)
|
||||
|
||||
# Response manipulation hook.
|
||||
self.response = dispatch_hook('response', hooks, resp)
|
||||
send_kwargs = {
|
||||
'stream': stream,
|
||||
'timeout': timeout,
|
||||
'verify': verify,
|
||||
'cert': cert,
|
||||
'proxies': proxies,
|
||||
'allow_redirects': allow_redirects,
|
||||
}
|
||||
resp = self.send(prep, **send_kwargs)
|
||||
|
||||
return resp
|
||||
|
||||
@@ -360,15 +429,71 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
def send(self, request, **kwargs):
|
||||
"""Send a given PreparedRequest."""
|
||||
# Set defaults that the hooks can utilize to ensure they always have
|
||||
# the correct parameters to reproduce the previous request.
|
||||
kwargs.setdefault('stream', self.stream)
|
||||
kwargs.setdefault('verify', self.verify)
|
||||
kwargs.setdefault('cert', self.cert)
|
||||
kwargs.setdefault('proxies', self.proxies)
|
||||
|
||||
# It's possible that users might accidentally send a Request object.
|
||||
# Guard against that specific failure case.
|
||||
if not isinstance(request, PreparedRequest):
|
||||
raise ValueError('You can only send PreparedRequests.')
|
||||
|
||||
# Set up variables needed for resolve_redirects and dispatching of
|
||||
# hooks
|
||||
allow_redirects = kwargs.pop('allow_redirects', True)
|
||||
stream = kwargs.get('stream')
|
||||
timeout = kwargs.get('timeout')
|
||||
verify = kwargs.get('verify')
|
||||
cert = kwargs.get('cert')
|
||||
proxies = kwargs.get('proxies')
|
||||
hooks = request.hooks
|
||||
|
||||
# Get the appropriate adapter to use
|
||||
adapter = self.get_adapter(url=request.url)
|
||||
|
||||
# Start time (approximately) of the request
|
||||
start = datetime.utcnow()
|
||||
# Send the request
|
||||
r = adapter.send(request, **kwargs)
|
||||
# Total elapsed time of the request (approximately)
|
||||
r.elapsed = datetime.utcnow() - start
|
||||
|
||||
# Response manipulation hooks
|
||||
r = dispatch_hook('response', hooks, r, **kwargs)
|
||||
|
||||
# Persist cookies
|
||||
if r.history:
|
||||
# If the hooks create history then we want those cookies too
|
||||
for resp in r.history:
|
||||
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
|
||||
extract_cookies_to_jar(self.cookies, request, r.raw)
|
||||
|
||||
# Redirect resolving generator.
|
||||
gen = self.resolve_redirects(r, request, stream=stream,
|
||||
timeout=timeout, verify=verify, cert=cert,
|
||||
proxies=proxies)
|
||||
|
||||
# Resolve redirects if allowed.
|
||||
history = [resp for resp in gen] if allow_redirects else []
|
||||
|
||||
# Shuffle things around if there's history.
|
||||
if history:
|
||||
# Insert the first (original) request at the start
|
||||
history.insert(0, r)
|
||||
# Get the last request made
|
||||
r = history.pop()
|
||||
r.history = tuple(history)
|
||||
|
||||
return r
|
||||
|
||||
def get_adapter(self, url):
|
||||
"""Returns the appropriate connnection adapter for the given URL."""
|
||||
for (prefix, adapter) in self.adapters.items():
|
||||
|
||||
if url.startswith(prefix):
|
||||
if url.lower().startswith(prefix):
|
||||
return adapter
|
||||
|
||||
# Nothing matches :-/
|
||||
@@ -376,12 +501,17 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
def close(self):
|
||||
"""Closes all adapters and as such the session"""
|
||||
for _, v in self.adapters.items():
|
||||
for v in self.adapters.values():
|
||||
v.close()
|
||||
|
||||
def mount(self, prefix, adapter):
|
||||
"""Registers a connection adapter to a prefix."""
|
||||
"""Registers a connection adapter to a prefix.
|
||||
|
||||
Adapters are sorted in descending order by key length."""
|
||||
self.adapters[prefix] = adapter
|
||||
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
|
||||
for key in keys_to_move:
|
||||
self.adapters[key] = self.adapters.pop(key)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
|
||||
@@ -18,7 +18,8 @@ _codes = {
|
||||
205: ('reset_content', 'reset'),
|
||||
206: ('partial_content', 'partial'),
|
||||
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
|
||||
208: ('im_used',),
|
||||
208: ('already_reported',),
|
||||
226: ('im_used',),
|
||||
|
||||
# Redirection.
|
||||
300: ('multiple_choices',),
|
||||
@@ -62,6 +63,7 @@ _codes = {
|
||||
444: ('no_response', 'none'),
|
||||
449: ('retry_with', 'retry'),
|
||||
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
|
||||
451: ('unavailable_for_legal_reasons', 'legal_reasons'),
|
||||
499: ('client_closed_request',),
|
||||
|
||||
# Server Error.
|
||||
|
||||
@@ -8,44 +8,105 @@ Data structures that power Requests.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import collections
|
||||
from itertools import islice
|
||||
|
||||
class CaseInsensitiveDict(dict):
|
||||
"""Case-insensitive Dictionary
|
||||
|
||||
class IteratorProxy(object):
|
||||
"""docstring for IteratorProxy"""
|
||||
def __init__(self, i):
|
||||
self.i = i
|
||||
# self.i = chain.from_iterable(i)
|
||||
|
||||
def __iter__(self):
|
||||
return self.i
|
||||
|
||||
def __len__(self):
|
||||
if hasattr(self.i, '__len__'):
|
||||
return len(self.i)
|
||||
if hasattr(self.i, 'len'):
|
||||
return self.i.len
|
||||
if hasattr(self.i, 'fileno'):
|
||||
return os.fstat(self.i.fileno()).st_size
|
||||
|
||||
def read(self, n):
|
||||
return "".join(islice(self.i, None, n))
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
"""
|
||||
A case-insensitive ``dict``-like object.
|
||||
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
provides ``lower_items``.
|
||||
|
||||
All keys are expected to be strings. The structure remembers the
|
||||
case of the last key to be set, and ``iter(instance)``,
|
||||
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
|
||||
will contain case-sensitive keys. However, querying and contains
|
||||
testing is case insensitive:
|
||||
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['Accept'] = 'application/json'
|
||||
cid['aCCEPT'] == 'application/json' # True
|
||||
list(cid) == ['Accept'] # True
|
||||
|
||||
For example, ``headers['content-encoding']`` will return the
|
||||
value of a ``'Content-Encoding'`` response header."""
|
||||
value of a ``'Content-Encoding'`` response header, regardless
|
||||
of how the header name was originally stored.
|
||||
|
||||
@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 list(self.keys()))
|
||||
return self._lower_keys
|
||||
If the constructor, ``.update``, or equality comparison
|
||||
operations are given keys that have equal ``.lower()``s, the
|
||||
behavior is undefined.
|
||||
|
||||
def _clear_lower_keys(self):
|
||||
if hasattr(self, '_lower_keys'):
|
||||
self._lower_keys.clear()
|
||||
"""
|
||||
def __init__(self, data=None, **kwargs):
|
||||
self._store = dict()
|
||||
if data is None:
|
||||
data = {}
|
||||
self.update(data, **kwargs)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
dict.__setitem__(self, key, value)
|
||||
self._clear_lower_keys()
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, self.lower_keys.get(key.lower(), key))
|
||||
self._lower_keys.clear()
|
||||
|
||||
def __contains__(self, key):
|
||||
return key.lower() in self.lower_keys
|
||||
# Use the lowercased key for lookups, but store the actual
|
||||
# key alongside the value.
|
||||
self._store[key.lower()] = (key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# We allow fall-through here, so values default to None
|
||||
if key in self:
|
||||
return dict.__getitem__(self, self.lower_keys[key.lower()])
|
||||
return self._store[key.lower()][1]
|
||||
|
||||
def get(self, key, default=None):
|
||||
if key in self:
|
||||
return self[key]
|
||||
def __delitem__(self, key):
|
||||
del self._store[key.lower()]
|
||||
|
||||
def __iter__(self):
|
||||
return (casedkey for casedkey, mappedvalue in self._store.values())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._store)
|
||||
|
||||
def lower_items(self):
|
||||
"""Like iteritems(), but with all lowercase keys."""
|
||||
return (
|
||||
(lowerkey, keyval[1])
|
||||
for (lowerkey, keyval)
|
||||
in self._store.items()
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, collections.Mapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return default
|
||||
return NotImplemented
|
||||
# Compare insensitively
|
||||
return dict(self.lower_items()) == dict(other.lower_items())
|
||||
|
||||
# Copy is required
|
||||
def copy(self):
|
||||
return CaseInsensitiveDict(self._store.values())
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
|
||||
|
||||
|
||||
class LookupDict(dict):
|
||||
|
||||
@@ -11,57 +11,27 @@ that are also useful for external consumption.
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import collections
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import zlib
|
||||
from netrc import netrc, NetrcParseError
|
||||
|
||||
from . import __version__
|
||||
from . import certs
|
||||
from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import quote, urlparse, bytes, str, OrderedDict
|
||||
from .compat import (quote, urlparse, bytes, str, OrderedDict, urlunparse,
|
||||
is_py2, is_py3, builtin_str, getproxies, proxy_bypass)
|
||||
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .exceptions import MissingSchema, InvalidURL
|
||||
|
||||
_hush_pyflakes = (RequestsCookieJar,)
|
||||
|
||||
CERTIFI_BUNDLE_PATH = None
|
||||
try:
|
||||
# see if requests's own CA certificate bundle is installed
|
||||
from . import certs
|
||||
path = certs.where()
|
||||
if os.path.exists(path):
|
||||
CERTIFI_BUNDLE_PATH = certs.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',
|
||||
# openSUSE (provided by the ca-certificates package), the 'certs' directory is the
|
||||
# preferred way but may not be supported by the SSL module, thus it has 'ca-bundle.pem'
|
||||
# as a fallback (which is generated from pem files in the 'certs' directory):
|
||||
'/etc/ssl/ca-bundle.pem',
|
||||
]
|
||||
|
||||
|
||||
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()
|
||||
DEFAULT_CA_BUNDLE_PATH = certs.where()
|
||||
|
||||
|
||||
def dict_to_sequence(d):
|
||||
@@ -73,6 +43,15 @@ def dict_to_sequence(d):
|
||||
return d
|
||||
|
||||
|
||||
def super_len(o):
|
||||
if hasattr(o, '__len__'):
|
||||
return len(o)
|
||||
if hasattr(o, 'len'):
|
||||
return o.len
|
||||
if hasattr(o, 'fileno'):
|
||||
return os.fstat(o.fileno()).st_size
|
||||
|
||||
|
||||
def get_netrc_auth(url):
|
||||
"""Returns the Requests tuple auth for a given url from netrc."""
|
||||
|
||||
@@ -113,7 +92,7 @@ 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
|
||||
return os.path.basename(name)
|
||||
|
||||
|
||||
def from_key_val_list(value):
|
||||
@@ -158,7 +137,7 @@ def to_key_val_list(value):
|
||||
if isinstance(value, (str, bytes, bool, int)):
|
||||
raise ValueError('cannot encode objects that are not 2-tuples')
|
||||
|
||||
if isinstance(value, dict):
|
||||
if isinstance(value, collections.Mapping):
|
||||
value = value.items()
|
||||
|
||||
return list(value)
|
||||
@@ -276,8 +255,7 @@ def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
"""
|
||||
|
||||
cj2 = cookiejar_from_dict(cookie_dict)
|
||||
for cookie in cj2:
|
||||
cj.set_cookie(cookie)
|
||||
cj.update(cj2)
|
||||
return cj
|
||||
|
||||
|
||||
@@ -288,8 +266,12 @@ def get_encodings_from_content(content):
|
||||
"""
|
||||
|
||||
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
|
||||
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
|
||||
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
|
||||
|
||||
return charset_re.findall(content)
|
||||
return (charset_re.findall(content) +
|
||||
pragma_re.findall(content) +
|
||||
xml_re.findall(content))
|
||||
|
||||
|
||||
def get_encoding_from_headers(headers):
|
||||
@@ -325,7 +307,7 @@ def stream_decode_response_unicode(iterator, r):
|
||||
rv = decoder.decode(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
rv = decoder.decode('', final=True)
|
||||
rv = decoder.decode(b'', final=True)
|
||||
if rv:
|
||||
yield rv
|
||||
|
||||
@@ -371,48 +353,6 @@ def get_unicode_from_response(r):
|
||||
return r.content
|
||||
|
||||
|
||||
def stream_decompress(iterator, mode='gzip'):
|
||||
"""Stream decodes an iterator over compressed data
|
||||
|
||||
:param iterator: An iterator over compressed data
|
||||
:param mode: 'gzip' or 'deflate'
|
||||
:return: An iterator over decompressed data
|
||||
"""
|
||||
|
||||
if mode not in ['gzip', 'deflate']:
|
||||
raise ValueError('stream_decompress mode must be gzip or deflate')
|
||||
|
||||
zlib_mode = 16 + zlib.MAX_WBITS if mode == 'gzip' else -zlib.MAX_WBITS
|
||||
dec = zlib.decompressobj(zlib_mode)
|
||||
try:
|
||||
for chunk in iterator:
|
||||
rv = dec.decompress(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
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
|
||||
|
||||
|
||||
def stream_untransfer(gen, resp):
|
||||
ce = resp.headers.get('content-encoding', '').lower()
|
||||
if 'gzip' in ce:
|
||||
gen = stream_decompress(gen, mode='gzip')
|
||||
elif 'deflate' in ce:
|
||||
gen = stream_decompress(gen, mode='deflate')
|
||||
|
||||
return gen
|
||||
|
||||
|
||||
# The unreserved URI characters (RFC 3986)
|
||||
UNRESERVED_SET = frozenset(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
@@ -427,7 +367,11 @@ def unquote_unreserved(uri):
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2 and h.isalnum():
|
||||
c = chr(int(h, 16))
|
||||
try:
|
||||
c = chr(int(h, 16))
|
||||
except ValueError:
|
||||
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
|
||||
|
||||
if c in UNRESERVED_SET:
|
||||
parts[i] = c + parts[i][2:]
|
||||
else:
|
||||
@@ -452,25 +396,17 @@ def requote_uri(uri):
|
||||
def get_environ_proxies(url):
|
||||
"""Return a dict of environment proxies."""
|
||||
|
||||
proxy_keys = [
|
||||
'all',
|
||||
'http',
|
||||
'https',
|
||||
'ftp',
|
||||
'socks'
|
||||
]
|
||||
|
||||
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
|
||||
|
||||
# First check whether no_proxy is defined. If it is, check that the URL
|
||||
# we're getting isn't in the no_proxy list.
|
||||
no_proxy = get_proxy('no_proxy')
|
||||
netloc = urlparse(url).netloc
|
||||
|
||||
if no_proxy:
|
||||
# We need to check whether we match here. We need to see if we match
|
||||
# the end of the netloc, both with and without the port.
|
||||
no_proxy = no_proxy.split(',')
|
||||
netloc = urlparse(url).netloc
|
||||
no_proxy = no_proxy.replace(' ', '').split(',')
|
||||
|
||||
for host in no_proxy:
|
||||
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
|
||||
@@ -478,10 +414,15 @@ def get_environ_proxies(url):
|
||||
# to apply the proxies on this URL.
|
||||
return {}
|
||||
|
||||
# If the system proxy settings indicate that this URL should be bypassed,
|
||||
# don't proxy.
|
||||
if proxy_bypass(netloc):
|
||||
return {}
|
||||
|
||||
# If we get here, we either didn't have no_proxy set or we're not going
|
||||
# anywhere that no_proxy applies to.
|
||||
proxies = [(key, get_proxy(key + '_proxy')) for key in proxy_keys]
|
||||
return dict([(key, val) for (key, val) in proxies if val])
|
||||
# anywhere that no_proxy applies to, and the system settings don't require
|
||||
# bypassing the proxy for the current URL.
|
||||
return getproxies()
|
||||
|
||||
|
||||
def default_user_agent():
|
||||
@@ -491,11 +432,9 @@ def default_user_agent():
|
||||
if _implementation == 'CPython':
|
||||
_implementation_version = platform.python_version()
|
||||
elif _implementation == 'PyPy':
|
||||
_implementation_version = '%s.%s.%s' % (
|
||||
sys.pypy_version_info.major,
|
||||
_implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
|
||||
sys.pypy_version_info.minor,
|
||||
sys.pypy_version_info.micro
|
||||
)
|
||||
sys.pypy_version_info.micro)
|
||||
if sys.pypy_version_info.releaselevel != 'final':
|
||||
_implementation_version = ''.join([_implementation_version, sys.pypy_version_info.releaselevel])
|
||||
elif _implementation == 'Jython':
|
||||
@@ -512,18 +451,17 @@ def default_user_agent():
|
||||
p_system = 'Unknown'
|
||||
p_release = 'Unknown'
|
||||
|
||||
return " ".join([
|
||||
'python-requests/%s' % __version__,
|
||||
'%s/%s' % (_implementation, _implementation_version),
|
||||
'%s/%s' % (p_system, p_release),
|
||||
])
|
||||
return " ".join(['python-requests/%s' % __version__,
|
||||
'%s/%s' % (_implementation, _implementation_version),
|
||||
'%s/%s' % (p_system, p_release)])
|
||||
|
||||
|
||||
def default_headers():
|
||||
return {
|
||||
return CaseInsensitiveDict({
|
||||
'User-Agent': default_user_agent(),
|
||||
'Accept-Encoding': ', '.join(('gzip', 'deflate', 'compress')),
|
||||
'Accept': '*/*'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def parse_header_links(value):
|
||||
@@ -549,7 +487,7 @@ def parse_header_links(value):
|
||||
|
||||
for param in params.split(";"):
|
||||
try:
|
||||
key,value = param.split("=")
|
||||
key, value = param.split("=")
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
@@ -593,3 +531,41 @@ def guess_json_utf(data):
|
||||
return 'utf-32-le'
|
||||
# Did not detect a valid UTF-32 ascii-range character
|
||||
return None
|
||||
|
||||
|
||||
def except_on_missing_scheme(url):
|
||||
"""Given a URL, raise a MissingSchema exception if the scheme is missing.
|
||||
"""
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
||||
|
||||
if not scheme:
|
||||
raise MissingSchema('Proxy URLs must have explicit schemes.')
|
||||
|
||||
|
||||
def get_auth_from_url(url):
|
||||
"""Given a url with authentication components, extract them into a tuple of
|
||||
username,password."""
|
||||
if url:
|
||||
parsed = urlparse(url)
|
||||
return (parsed.username, parsed.password)
|
||||
else:
|
||||
return ('', '')
|
||||
|
||||
|
||||
def to_native_string(string, encoding='ascii'):
|
||||
"""
|
||||
Given a string object, regardless of type, returns a representation of that
|
||||
string in the native string type, encoding and decoding where necessary.
|
||||
This assumes ASCII unless told otherwise.
|
||||
"""
|
||||
out = None
|
||||
|
||||
if isinstance(string, builtin_str):
|
||||
out = string
|
||||
else:
|
||||
if is_py2:
|
||||
out = string.encode(encoding)
|
||||
else:
|
||||
out = string.decode(encoding)
|
||||
|
||||
return out
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pytest
|
||||
sphinx
|
||||
py==1.4.12
|
||||
pytest==2.3.4
|
||||
invoke==0.2.0
|
||||
7
setup.py
7
setup.py
@@ -17,9 +17,10 @@ if sys.argv[-1] == 'publish':
|
||||
packages = [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.charade',
|
||||
'requests.packages.charade',
|
||||
'requests.packages.urllib3',
|
||||
'requests.packages.urllib3.packages',
|
||||
'requests.packages.urllib3.contrib',
|
||||
'requests.packages.urllib3.packages.ssl_match_hostname'
|
||||
]
|
||||
|
||||
@@ -50,9 +51,7 @@ setup(
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
# 'Programming Language :: Python :: 3.0',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
32
tasks.py
Normal file
32
tasks.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests
|
||||
from invoke import run, task
|
||||
|
||||
@task
|
||||
def test():
|
||||
run('py.test', pty=True)
|
||||
|
||||
@task
|
||||
def deps():
|
||||
print('Vendoring urllib3...')
|
||||
|
||||
run('rm -fr requests/packages/urllib3')
|
||||
run('git clone https://github.com/shazow/urllib3.git')
|
||||
run('mv urllib3/urllib3 requests/packages/')
|
||||
run('rm -fr urllib3')
|
||||
|
||||
print('Vendoring Charade...')
|
||||
|
||||
run('rm -fr requests/packages/charade')
|
||||
run('git clone https://github.com/sigmavirus24/charade.git')
|
||||
run('mv charade/charade requests/packages/')
|
||||
run('rm -fr charade')
|
||||
|
||||
@task
|
||||
def certs():
|
||||
print('Grabbing latest CA Bundle...')
|
||||
r = requests.get('https://raw.github.com/kennethreitz/certifi/master/certifi/cacert.pem')
|
||||
|
||||
with open('requests/cacert.pem', 'w') as f:
|
||||
f.write(r.content)
|
||||
646
test_requests.py
Normal file → Executable file
646
test_requests.py
Normal file → Executable file
@@ -3,18 +3,35 @@
|
||||
|
||||
"""Tests for Requests."""
|
||||
|
||||
from __future__ import division
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
import pickle
|
||||
|
||||
import requests
|
||||
import pytest
|
||||
from requests.auth import HTTPDigestAuth
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.compat import str, cookielib, getproxies, urljoin, urlparse
|
||||
from requests.cookies import cookiejar_from_dict
|
||||
from requests.exceptions import InvalidURL, MissingSchema
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
try:
|
||||
import StringIO
|
||||
except ImportError:
|
||||
import io as StringIO
|
||||
|
||||
HTTPBIN = os.environ.get('HTTPBIN_URL', 'http://httpbin.org/')
|
||||
# Issue #1483: Make sure the URL always has a trailing slash
|
||||
HTTPBIN = HTTPBIN.rstrip('/') + '/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
return HTTPBIN + '/'.join(suffix)
|
||||
return urljoin(HTTPBIN, '/'.join(suffix))
|
||||
|
||||
|
||||
class RequestsTestCase(unittest.TestCase):
|
||||
|
||||
@@ -28,9 +45,6 @@ class RequestsTestCase(unittest.TestCase):
|
||||
"""Teardown."""
|
||||
pass
|
||||
|
||||
def test_assertion(self):
|
||||
assert 1
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
@@ -43,7 +57,8 @@ class RequestsTestCase(unittest.TestCase):
|
||||
requests.post
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
|
||||
self.assertRaises(MissingSchema, requests.get, 'hiwpefhipowhefopw')
|
||||
self.assertRaises(InvalidURL, requests.get, 'http://')
|
||||
|
||||
def test_basic_building(self):
|
||||
req = requests.Request()
|
||||
@@ -54,6 +69,11 @@ class RequestsTestCase(unittest.TestCase):
|
||||
assert pr.url == req.url
|
||||
assert pr.body == 'life=42'
|
||||
|
||||
def test_no_content_length(self):
|
||||
get_req = requests.Request('GET', httpbin('get')).prepare()
|
||||
self.assertTrue('Content-Length' not in get_req.headers)
|
||||
head_req = requests.Request('HEAD', httpbin('head')).prepare()
|
||||
self.assertTrue('Content-Length' not in head_req.headers)
|
||||
|
||||
def test_path_is_not_double_encoded(self):
|
||||
request = requests.Request('GET', "http://0.0.0.0/get/test case").prepare()
|
||||
@@ -70,13 +90,23 @@ class RequestsTestCase(unittest.TestCase):
|
||||
self.assertEqual(request.url,
|
||||
"http://example.com/path?key=value&a=b#fragment")
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = requests.get(httpbin('get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
def test_mixed_case_scheme_acceptable(self):
|
||||
s = requests.Session()
|
||||
s.proxies = getproxies()
|
||||
parts = urlparse(httpbin('get'))
|
||||
schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://',
|
||||
'https://', 'HTTPS://', 'hTTps://', 'HttPs://']
|
||||
for scheme in schemes:
|
||||
url = scheme + parts.netloc + parts.path
|
||||
r = requests.Request('GET', url)
|
||||
r = s.send(r.prepare())
|
||||
self.assertEqual(r.status_code, 200,
|
||||
"failed for scheme %s" % scheme)
|
||||
|
||||
def test_HTTP_200_OK_GET_ALTERNATIVE(self):
|
||||
r = requests.Request('GET', httpbin('get'))
|
||||
s = requests.Session()
|
||||
s.proxies = getproxies()
|
||||
|
||||
r = s.send(r.prepare())
|
||||
|
||||
@@ -86,9 +116,9 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_POST(self):
|
||||
r = requests.post(httpbin('status', '302'), data={'some': 'data'})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# def test_HTTP_302_ALLOW_REDIRECT_POST(self):
|
||||
# r = requests.post(httpbin('status', '302'), data={'some': 'data'})
|
||||
# self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
@@ -104,19 +134,72 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_set_cookie_on_301(self):
|
||||
s = requests.session()
|
||||
url = httpbin('cookies/set?foo=bar')
|
||||
r = s.get(url)
|
||||
self.assertTrue(s.cookies['foo'] == 'bar')
|
||||
|
||||
def test_cookie_sent_on_redirect(self):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies/set?foo=bar'))
|
||||
r = s.get(httpbin('redirect/1')) # redirects to httpbin('get')
|
||||
self.assertTrue("Cookie" in r.json()["headers"])
|
||||
|
||||
def test_cookie_removed_on_expire(self):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies/set?foo=bar'))
|
||||
self.assertTrue(s.cookies['foo'] == 'bar')
|
||||
s.get(
|
||||
httpbin('response-headers'),
|
||||
params={
|
||||
'Set-Cookie':
|
||||
'foo=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT'
|
||||
}
|
||||
)
|
||||
assert 'foo' not in s.cookies
|
||||
|
||||
def test_cookie_quote_wrapped(self):
|
||||
s = requests.session()
|
||||
s.get(httpbin('cookies/set?foo="bar:baz"'))
|
||||
self.assertTrue(s.cookies['foo'] == '"bar:baz"')
|
||||
|
||||
def test_request_cookie_overrides_session_cookie(self):
|
||||
s = requests.session()
|
||||
s.cookies['foo'] = 'bar'
|
||||
r = s.get(httpbin('cookies'), cookies={'foo': 'baz'})
|
||||
assert r.json()['cookies']['foo'] == 'baz'
|
||||
# Session cookie should not be modified
|
||||
assert s.cookies['foo'] == 'bar'
|
||||
|
||||
def test_generic_cookiejar_works(self):
|
||||
cj = cookielib.CookieJar()
|
||||
cookiejar_from_dict({'foo': 'bar'}, cj)
|
||||
s = requests.session()
|
||||
s.cookies = cj
|
||||
r = s.get(httpbin('cookies'))
|
||||
# Make sure the cookie was sent
|
||||
assert r.json()['cookies']['foo'] == 'bar'
|
||||
# Make sure the session cj is still the custom one
|
||||
assert s.cookies is cj
|
||||
|
||||
def test_requests_in_history_are_not_overridden(self):
|
||||
resp = requests.get(httpbin('redirect/3'))
|
||||
urls = [r.url for r in resp.history]
|
||||
req_urls = [r.request.url for r in resp.history]
|
||||
self.assertEquals(urls, req_urls)
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
'User-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
self.assertTrue(heads['User-agent'] in r.text)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
'user-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
@@ -131,8 +214,6 @@ class RequestsTestCase(unittest.TestCase):
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = httpbin('basic-auth', 'user', 'pass')
|
||||
|
||||
@@ -147,6 +228,34 @@ class RequestsTestCase(unittest.TestCase):
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_basicauth_with_netrc(self):
|
||||
auth = ('user', 'pass')
|
||||
wrong_auth = ('wronguser', 'wrongpass')
|
||||
url = httpbin('basic-auth', 'user', 'pass')
|
||||
|
||||
def get_netrc_auth_mock(url):
|
||||
return auth
|
||||
requests.sessions.get_netrc_auth = get_netrc_auth_mock
|
||||
|
||||
# Should use netrc and work.
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Given auth should override and fail.
|
||||
r = requests.get(url, auth=wrong_auth)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session()
|
||||
|
||||
# Should use netrc and work.
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Given auth should override and fail.
|
||||
s.auth = wrong_auth
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
def test_DIGEST_HTTP_200_OK_GET(self):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
@@ -159,10 +268,38 @@ class RequestsTestCase(unittest.TestCase):
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session()
|
||||
s.auth = auth
|
||||
s.auth = HTTPDigestAuth('user', 'pass')
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_DIGEST_AUTH_RETURNS_COOKIE(self):
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
r = requests.get(url)
|
||||
assert r.cookies['fake'] == 'fake_value'
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self):
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
s = requests.Session()
|
||||
s.get(url, auth=auth)
|
||||
assert s.cookies['fake'] == 'fake_value'
|
||||
|
||||
def test_DIGEST_STREAM(self):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = httpbin('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth, stream=True)
|
||||
self.assertNotEqual(r.raw.read(), b'')
|
||||
|
||||
r = requests.get(url, auth=auth, stream=False)
|
||||
self.assertEqual(r.raw.read(), b'')
|
||||
|
||||
|
||||
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self):
|
||||
|
||||
auth = HTTPDigestAuth('user', 'wrongpass')
|
||||
@@ -219,6 +356,12 @@ class RequestsTestCase(unittest.TestCase):
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def test_conflicting_post_params(self):
|
||||
url = httpbin('post')
|
||||
with open('requirements.txt') as f:
|
||||
pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})")
|
||||
pytest.raises(ValueError, "requests.post(url, data=u'[{\"some\": \"data\"}]', files={'some': f})")
|
||||
|
||||
def test_request_ok_set(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
self.assertEqual(r.ok, False)
|
||||
@@ -242,6 +385,9 @@ class RequestsTestCase(unittest.TestCase):
|
||||
requests.get(url, params={'foo': 'foo'})
|
||||
requests.get(httpbin('ø'), params={'foo': 'foo'})
|
||||
|
||||
def test_unicode_header_name(self):
|
||||
requests.put(httpbin('put'), headers={str('Content-Type'): 'application/octet-stream'}, data='\xff') # compat.str is unicode.
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz']))
|
||||
@@ -255,6 +401,470 @@ class RequestsTestCase(unittest.TestCase):
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_unicode_multipart_post(self):
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': u'ëlïxr'},
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': u'ëlïxr'.encode('utf-8')},
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': 'elixr'},
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': 'elixr'.encode('utf-8')},
|
||||
files={'file': ('test_requests.py', open(__file__, 'rb'))})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_unicode_multipart_post_fieldnames(self):
|
||||
filename = os.path.splitext(__file__)[0] + '.py'
|
||||
r = requests.Request(method='POST',
|
||||
url=httpbin('post'),
|
||||
data={'stuff'.encode('utf-8'): 'elixr'},
|
||||
files={'file': ('test_requests.py',
|
||||
open(filename, 'rb'))})
|
||||
prep = r.prepare()
|
||||
self.assertTrue(b'name="stuff"' in prep.body)
|
||||
self.assertFalse(b'name="b\'stuff\'"' in prep.body)
|
||||
|
||||
def test_custom_content_type(self):
|
||||
r = requests.post(httpbin('post'),
|
||||
data={'stuff': json.dumps({'a': 123})},
|
||||
files={'file1': ('test_requests.py', open(__file__, 'rb')),
|
||||
'file2': ('test_requests', open(__file__, 'rb'),
|
||||
'text/py-content-type')})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(b"text/py-content-type" in r.request.body)
|
||||
|
||||
def test_hook_receives_request_arguments(self):
|
||||
def hook(resp, **kwargs):
|
||||
assert resp is not None
|
||||
assert kwargs != {}
|
||||
|
||||
requests.Request('GET', HTTPBIN, hooks={'response': hook})
|
||||
|
||||
def test_prepared_request_hook(self):
|
||||
def hook(resp, **kwargs):
|
||||
resp.hook_working = True
|
||||
return resp
|
||||
|
||||
req = requests.Request('GET', HTTPBIN, hooks={'response': hook})
|
||||
prep = req.prepare()
|
||||
|
||||
s = requests.Session()
|
||||
s.proxies = getproxies()
|
||||
resp = s.send(prep)
|
||||
|
||||
self.assertTrue(hasattr(resp, 'hook_working'))
|
||||
|
||||
def test_prepared_from_session(self):
|
||||
class DummyAuth(requests.auth.AuthBase):
|
||||
def __call__(self, r):
|
||||
r.headers['Dummy-Auth-Test'] = 'dummy-auth-test-ok'
|
||||
return r
|
||||
|
||||
req = requests.Request('GET', httpbin('headers'))
|
||||
self.assertEqual(req.auth, None)
|
||||
|
||||
s = requests.Session()
|
||||
s.auth = DummyAuth()
|
||||
|
||||
prep = s.prepare_request(req)
|
||||
resp = s.send(prep)
|
||||
|
||||
self.assertTrue(resp.json()['headers']['Dummy-Auth-Test'], 'dummy-auth-test-ok')
|
||||
|
||||
def test_links(self):
|
||||
r = requests.Response()
|
||||
r.headers = {
|
||||
'cache-control': 'public, max-age=60, s-maxage=60',
|
||||
'connection': 'keep-alive',
|
||||
'content-encoding': 'gzip',
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
'date': 'Sat, 26 Jan 2013 16:47:56 GMT',
|
||||
'etag': '"6ff6a73c0e446c1f61614769e3ceb778"',
|
||||
'last-modified': 'Sat, 26 Jan 2013 16:22:39 GMT',
|
||||
'link': ('<https://api.github.com/users/kennethreitz/repos?'
|
||||
'page=2&per_page=10>; rel="next", <https://api.github.'
|
||||
'com/users/kennethreitz/repos?page=7&per_page=10>; '
|
||||
' rel="last"'),
|
||||
'server': 'GitHub.com',
|
||||
'status': '200 OK',
|
||||
'vary': 'Accept',
|
||||
'x-content-type-options': 'nosniff',
|
||||
'x-github-media-type': 'github.beta',
|
||||
'x-ratelimit-limit': '60',
|
||||
'x-ratelimit-remaining': '57'
|
||||
}
|
||||
self.assertEqual(r.links['next']['rel'], 'next')
|
||||
|
||||
def test_cookie_parameters(self):
|
||||
key = 'some_cookie'
|
||||
value = 'some_value'
|
||||
secure = True
|
||||
domain = 'test.com'
|
||||
rest = {'HttpOnly': True}
|
||||
|
||||
jar = requests.cookies.RequestsCookieJar()
|
||||
jar.set(key, value, secure=secure, domain=domain, rest=rest)
|
||||
|
||||
self.assertEqual(len(jar), 1)
|
||||
self.assertTrue('some_cookie' in jar)
|
||||
|
||||
cookie = list(jar)[0]
|
||||
self.assertEqual(cookie.secure, secure)
|
||||
self.assertEqual(cookie.domain, domain)
|
||||
self.assertEqual(cookie._rest['HttpOnly'], rest['HttpOnly'])
|
||||
|
||||
def test_time_elapsed_blank(self):
|
||||
r = requests.get(httpbin('get'))
|
||||
td = r.elapsed
|
||||
total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600)
|
||||
* 10**6) / 10**6)
|
||||
self.assertTrue(total_seconds > 0.0)
|
||||
|
||||
def test_response_is_iterable(self):
|
||||
r = requests.Response()
|
||||
io = StringIO.StringIO('abc')
|
||||
read_ = io.read
|
||||
|
||||
def read_mock(amt, decode_content=None):
|
||||
return read_(amt)
|
||||
setattr(io, 'read', read_mock)
|
||||
r.raw = io
|
||||
self.assertTrue(next(iter(r)))
|
||||
io.close()
|
||||
|
||||
def test_get_auth_from_url(self):
|
||||
url = 'http://user:pass@complex.url.com/path?query=yes'
|
||||
self.assertEqual(('user', 'pass'),
|
||||
requests.utils.get_auth_from_url(url))
|
||||
|
||||
def test_cannot_send_unprepared_requests(self):
|
||||
r = requests.Request(url=HTTPBIN)
|
||||
self.assertRaises(ValueError, requests.Session().send, r)
|
||||
|
||||
def test_http_error(self):
|
||||
error = requests.exceptions.HTTPError()
|
||||
self.assertEqual(error.response, None)
|
||||
response = requests.Response()
|
||||
error = requests.exceptions.HTTPError(response=response)
|
||||
self.assertEqual(error.response, response)
|
||||
error = requests.exceptions.HTTPError('message', response=response)
|
||||
self.assertEqual(str(error), 'message')
|
||||
self.assertEqual(error.response, response)
|
||||
|
||||
def test_session_pickling(self):
|
||||
r = requests.Request('GET', httpbin('get'))
|
||||
s = requests.Session()
|
||||
|
||||
s = pickle.loads(pickle.dumps(s))
|
||||
s.proxies = getproxies()
|
||||
|
||||
r = s.send(r.prepare())
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_fixes_1329(self):
|
||||
"""
|
||||
Ensure that header updates are done case-insensitively.
|
||||
"""
|
||||
s = requests.Session()
|
||||
s.headers.update({'ACCEPT': 'BOGUS'})
|
||||
s.headers.update({'accept': 'application/json'})
|
||||
r = s.get(httpbin('get'))
|
||||
headers = r.request.headers
|
||||
self.assertEqual(
|
||||
headers['accept'],
|
||||
'application/json'
|
||||
)
|
||||
self.assertEqual(
|
||||
headers['Accept'],
|
||||
'application/json'
|
||||
)
|
||||
self.assertEqual(
|
||||
headers['ACCEPT'],
|
||||
'application/json'
|
||||
)
|
||||
|
||||
def test_uppercase_scheme_redirect(self):
|
||||
parts = urlparse(httpbin('html'))
|
||||
url = "HTTP://" + parts.netloc + parts.path
|
||||
r = requests.get(httpbin('redirect-to'), params={'url': url})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.url.lower(), url.lower())
|
||||
|
||||
def test_transport_adapter_ordering(self):
|
||||
s = requests.Session()
|
||||
order = ['https://', 'http://']
|
||||
self.assertEqual(order, list(s.adapters))
|
||||
s.mount('http://git', HTTPAdapter())
|
||||
s.mount('http://github', HTTPAdapter())
|
||||
s.mount('http://github.com', HTTPAdapter())
|
||||
s.mount('http://github.com/about/', HTTPAdapter())
|
||||
order = [
|
||||
'http://github.com/about/',
|
||||
'http://github.com',
|
||||
'http://github',
|
||||
'http://git',
|
||||
'https://',
|
||||
'http://',
|
||||
]
|
||||
self.assertEqual(order, list(s.adapters))
|
||||
s.mount('http://gittip', HTTPAdapter())
|
||||
s.mount('http://gittip.com', HTTPAdapter())
|
||||
s.mount('http://gittip.com/about/', HTTPAdapter())
|
||||
order = [
|
||||
'http://github.com/about/',
|
||||
'http://gittip.com/about/',
|
||||
'http://github.com',
|
||||
'http://gittip.com',
|
||||
'http://github',
|
||||
'http://gittip',
|
||||
'http://git',
|
||||
'https://',
|
||||
'http://',
|
||||
]
|
||||
self.assertEqual(order, list(s.adapters))
|
||||
s2 = requests.Session()
|
||||
s2.adapters = {'http://': HTTPAdapter()}
|
||||
s2.mount('https://', HTTPAdapter())
|
||||
self.assertTrue('http://' in s2.adapters)
|
||||
self.assertTrue('https://' in s2.adapters)
|
||||
|
||||
def test_header_remove_is_case_insensitive(self):
|
||||
# From issue #1321
|
||||
s = requests.Session()
|
||||
s.headers['foo'] = 'bar'
|
||||
r = s.get(httpbin('get'), headers={'FOO': None})
|
||||
assert 'foo' not in r.request.headers
|
||||
|
||||
def test_params_are_merged_case_sensitive(self):
|
||||
s = requests.Session()
|
||||
s.params['foo'] = 'bar'
|
||||
r = s.get(httpbin('get'), params={'FOO': 'bar'})
|
||||
assert r.json()['args'] == {'foo': 'bar', 'FOO': 'bar'}
|
||||
|
||||
|
||||
def test_long_authinfo_in_url(self):
|
||||
url = 'http://{0}:{1}@{2}:9000/path?query#frag'.format(
|
||||
'E8A3BE87-9E3F-4620-8858-95478E385B5B',
|
||||
'EA770032-DA4D-4D84-8CE9-29C6D910BF1E',
|
||||
'exactly-------------sixty-----------three------------characters',
|
||||
)
|
||||
r = requests.Request('GET', url).prepare()
|
||||
self.assertEqual(r.url, url)
|
||||
|
||||
def test_header_keys_are_native(self):
|
||||
headers = {u'unicode': 'blah', 'byte'.encode('ascii'): 'blah'}
|
||||
r = requests.Request('GET', httpbin('get'), headers=headers)
|
||||
p = r.prepare()
|
||||
|
||||
# This is testing that they are builtin strings. A bit weird, but there
|
||||
# we go.
|
||||
self.assertTrue('unicode' in p.headers.keys())
|
||||
self.assertTrue('byte' in p.headers.keys())
|
||||
|
||||
def test_can_send_nonstring_objects_with_files(self):
|
||||
data = {'a': 0.0}
|
||||
files = {'b': 'foo'}
|
||||
r = requests.Request('POST', httpbin('post'), data=data, files=files)
|
||||
p = r.prepare()
|
||||
|
||||
self.assertTrue('multipart/form-data' in p.headers['Content-Type'])
|
||||
|
||||
|
||||
class TestContentEncodingDetection(unittest.TestCase):
|
||||
|
||||
def test_none(self):
|
||||
encodings = requests.utils.get_encodings_from_content('')
|
||||
self.assertEqual(len(encodings), 0)
|
||||
|
||||
def test_html_charset(self):
|
||||
"""HTML5 meta charset attribute"""
|
||||
content = '<meta charset="UTF-8">'
|
||||
encodings = requests.utils.get_encodings_from_content(content)
|
||||
self.assertEqual(len(encodings), 1)
|
||||
self.assertEqual(encodings[0], 'UTF-8')
|
||||
|
||||
def test_html4_pragma(self):
|
||||
"""HTML4 pragma directive"""
|
||||
content = '<meta http-equiv="Content-type" content="text/html;charset=UTF-8">'
|
||||
encodings = requests.utils.get_encodings_from_content(content)
|
||||
self.assertEqual(len(encodings), 1)
|
||||
self.assertEqual(encodings[0], 'UTF-8')
|
||||
|
||||
def test_xhtml_pragma(self):
|
||||
"""XHTML 1.x served with text/html MIME type"""
|
||||
content = '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />'
|
||||
encodings = requests.utils.get_encodings_from_content(content)
|
||||
self.assertEqual(len(encodings), 1)
|
||||
self.assertEqual(encodings[0], 'UTF-8')
|
||||
|
||||
def test_xml(self):
|
||||
"""XHTML 1.x served as XML"""
|
||||
content = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
encodings = requests.utils.get_encodings_from_content(content)
|
||||
self.assertEqual(len(encodings), 1)
|
||||
self.assertEqual(encodings[0], 'UTF-8')
|
||||
|
||||
def test_precedence(self):
|
||||
content = '''
|
||||
<?xml version="1.0" encoding="XML"?>
|
||||
<meta charset="HTML5">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=HTML4" />
|
||||
'''.strip()
|
||||
encodings = requests.utils.get_encodings_from_content(content)
|
||||
self.assertEqual(encodings, ['HTML5', 'HTML4', 'XML'])
|
||||
|
||||
|
||||
class TestCaseInsensitiveDict(unittest.TestCase):
|
||||
|
||||
def test_mapping_init(self):
|
||||
cid = CaseInsensitiveDict({'Foo': 'foo','BAr': 'bar'})
|
||||
self.assertEqual(len(cid), 2)
|
||||
self.assertTrue('foo' in cid)
|
||||
self.assertTrue('bar' in cid)
|
||||
|
||||
def test_iterable_init(self):
|
||||
cid = CaseInsensitiveDict([('Foo', 'foo'), ('BAr', 'bar')])
|
||||
self.assertEqual(len(cid), 2)
|
||||
self.assertTrue('foo' in cid)
|
||||
self.assertTrue('bar' in cid)
|
||||
|
||||
def test_kwargs_init(self):
|
||||
cid = CaseInsensitiveDict(FOO='foo', BAr='bar')
|
||||
self.assertEqual(len(cid), 2)
|
||||
self.assertTrue('foo' in cid)
|
||||
self.assertTrue('bar' in cid)
|
||||
|
||||
def test_docstring_example(self):
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['Accept'] = 'application/json'
|
||||
self.assertEqual(cid['aCCEPT'], 'application/json')
|
||||
self.assertEqual(list(cid), ['Accept'])
|
||||
|
||||
def test_len(self):
|
||||
cid = CaseInsensitiveDict({'a': 'a', 'b': 'b'})
|
||||
cid['A'] = 'a'
|
||||
self.assertEqual(len(cid), 2)
|
||||
|
||||
def test_getitem(self):
|
||||
cid = CaseInsensitiveDict({'Spam': 'blueval'})
|
||||
self.assertEqual(cid['spam'], 'blueval')
|
||||
self.assertEqual(cid['SPAM'], 'blueval')
|
||||
|
||||
def test_fixes_649(self):
|
||||
"""__setitem__ should behave case-insensitively."""
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['spam'] = 'oneval'
|
||||
cid['Spam'] = 'twoval'
|
||||
cid['sPAM'] = 'redval'
|
||||
cid['SPAM'] = 'blueval'
|
||||
self.assertEqual(cid['spam'], 'blueval')
|
||||
self.assertEqual(cid['SPAM'], 'blueval')
|
||||
self.assertEqual(list(cid.keys()), ['SPAM'])
|
||||
|
||||
def test_delitem(self):
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['Spam'] = 'someval'
|
||||
del cid['sPam']
|
||||
self.assertFalse('spam' in cid)
|
||||
self.assertEqual(len(cid), 0)
|
||||
|
||||
def test_contains(self):
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['Spam'] = 'someval'
|
||||
self.assertTrue('Spam' in cid)
|
||||
self.assertTrue('spam' in cid)
|
||||
self.assertTrue('SPAM' in cid)
|
||||
self.assertTrue('sPam' in cid)
|
||||
self.assertFalse('notspam' in cid)
|
||||
|
||||
def test_get(self):
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['spam'] = 'oneval'
|
||||
cid['SPAM'] = 'blueval'
|
||||
self.assertEqual(cid.get('spam'), 'blueval')
|
||||
self.assertEqual(cid.get('SPAM'), 'blueval')
|
||||
self.assertEqual(cid.get('sPam'), 'blueval')
|
||||
self.assertEqual(cid.get('notspam', 'default'), 'default')
|
||||
|
||||
def test_update(self):
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['spam'] = 'blueval'
|
||||
cid.update({'sPam': 'notblueval'})
|
||||
self.assertEqual(cid['spam'], 'notblueval')
|
||||
cid = CaseInsensitiveDict({'Foo': 'foo','BAr': 'bar'})
|
||||
cid.update({'fOO': 'anotherfoo', 'bAR': 'anotherbar'})
|
||||
self.assertEqual(len(cid), 2)
|
||||
self.assertEqual(cid['foo'], 'anotherfoo')
|
||||
self.assertEqual(cid['bar'], 'anotherbar')
|
||||
|
||||
def test_update_retains_unchanged(self):
|
||||
cid = CaseInsensitiveDict({'foo': 'foo', 'bar': 'bar'})
|
||||
cid.update({'foo': 'newfoo'})
|
||||
self.assertEquals(cid['bar'], 'bar')
|
||||
|
||||
def test_iter(self):
|
||||
cid = CaseInsensitiveDict({'Spam': 'spam', 'Eggs': 'eggs'})
|
||||
keys = frozenset(['Spam', 'Eggs'])
|
||||
self.assertEqual(frozenset(iter(cid)), keys)
|
||||
|
||||
def test_equality(self):
|
||||
cid = CaseInsensitiveDict({'SPAM': 'blueval', 'Eggs': 'redval'})
|
||||
othercid = CaseInsensitiveDict({'spam': 'blueval', 'eggs': 'redval'})
|
||||
self.assertEqual(cid, othercid)
|
||||
del othercid['spam']
|
||||
self.assertNotEqual(cid, othercid)
|
||||
self.assertEqual(cid, {'spam': 'blueval', 'eggs': 'redval'})
|
||||
|
||||
def test_setdefault(self):
|
||||
cid = CaseInsensitiveDict({'Spam': 'blueval'})
|
||||
self.assertEqual(
|
||||
cid.setdefault('spam', 'notblueval'),
|
||||
'blueval'
|
||||
)
|
||||
self.assertEqual(
|
||||
cid.setdefault('notspam', 'notblueval'),
|
||||
'notblueval'
|
||||
)
|
||||
|
||||
def test_lower_items(self):
|
||||
cid = CaseInsensitiveDict({
|
||||
'Accept': 'application/json',
|
||||
'user-Agent': 'requests',
|
||||
})
|
||||
keyset = frozenset(lowerkey for lowerkey, v in cid.lower_items())
|
||||
lowerkeyset = frozenset(['accept', 'user-agent'])
|
||||
self.assertEqual(keyset, lowerkeyset)
|
||||
|
||||
def test_preserve_key_case(self):
|
||||
cid = CaseInsensitiveDict({
|
||||
'Accept': 'application/json',
|
||||
'user-Agent': 'requests',
|
||||
})
|
||||
keyset = frozenset(['Accept', 'user-Agent'])
|
||||
self.assertEqual(frozenset(i[0] for i in cid.items()), keyset)
|
||||
self.assertEqual(frozenset(cid.keys()), keyset)
|
||||
self.assertEqual(frozenset(cid), keyset)
|
||||
|
||||
def test_preserve_last_key_case(self):
|
||||
cid = CaseInsensitiveDict({
|
||||
'Accept': 'application/json',
|
||||
'user-Agent': 'requests',
|
||||
})
|
||||
cid.update({'ACCEPT': 'application/json'})
|
||||
cid['USER-AGENT'] = 'requests'
|
||||
keyset = frozenset(['ACCEPT', 'USER-AGENT'])
|
||||
self.assertEqual(frozenset(i[0] for i in cid.items()), keyset)
|
||||
self.assertEqual(frozenset(cid.keys()), keyset)
|
||||
self.assertEqual(frozenset(cid), keyset)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user