Compare commits
379 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
775b6f6f00 | ||
|
|
dacc1d8aaf | ||
|
|
18591920f3 | ||
|
|
193eb42891 | ||
|
|
b20d171c1c | ||
|
|
590211e13e | ||
|
|
fff5269d1a | ||
|
|
e831947be4 | ||
|
|
c205ce658f | ||
|
|
13060f89c7 | ||
|
|
3cc6acaca4 | ||
|
|
21ccf8f8c0 | ||
|
|
b973d08b0e | ||
|
|
d24ee0e1dc | ||
|
|
afb1bd103a | ||
|
|
4c1852f928 | ||
|
|
6e26f5754b | ||
|
|
82d1b340dc | ||
|
|
eab2ab9faa | ||
|
|
12427e3434 | ||
|
|
76bf523a1f | ||
|
|
613816dd9d | ||
|
|
f8ec2544ce | ||
|
|
3c2f52d681 | ||
|
|
3baf3ab069 | ||
|
|
5c575b30c2 | ||
|
|
e65f52aae4 | ||
|
|
44277441d6 | ||
|
|
7b68c124d5 | ||
|
|
f2172922c5 | ||
|
|
079a825ac3 | ||
|
|
9cf9d76eb5 | ||
|
|
dc7029ec98 | ||
|
|
f0019c06d7 | ||
|
|
a094ad8f31 | ||
|
|
d6827a37d0 | ||
|
|
5b7a79b0ff | ||
|
|
ee861f4287 | ||
|
|
0c0bab3ad0 | ||
|
|
1c73ec22e9 | ||
|
|
4677ba5cd9 | ||
|
|
093709be4d | ||
|
|
f6ed16bbbe | ||
|
|
fc618aa78a | ||
|
|
82e69de44f | ||
|
|
8f8062723a | ||
|
|
cc4381b91e | ||
|
|
eae641485b | ||
|
|
29309201b6 | ||
|
|
0dd6c40205 | ||
|
|
84d9a1b577 | ||
|
|
17170a2ca7 | ||
|
|
8d34538f4c | ||
|
|
8d1a85a8d0 | ||
|
|
06f048e507 | ||
|
|
83a9f2c740 | ||
|
|
bc5cc0dc92 | ||
|
|
6562137731 | ||
|
|
d078b69aef | ||
|
|
0a1e527aec | ||
|
|
9f55f503b1 | ||
|
|
253cba373b | ||
|
|
9451ae2e98 | ||
|
|
d203349740 | ||
|
|
4aad69594d | ||
|
|
c7f165ae6f | ||
|
|
98cbde1324 | ||
|
|
77e5443f71 | ||
|
|
e15ae8f9a7 | ||
|
|
0eb2509449 | ||
|
|
bd0e002804 | ||
|
|
0e65dff008 | ||
|
|
fba77fc65f | ||
|
|
1cf5ec872c | ||
|
|
21c2146be4 | ||
|
|
2f91d8e773 | ||
|
|
946f7ec579 | ||
|
|
658d609e9f | ||
|
|
abca7f0ad0 | ||
|
|
cf5ddecc4c | ||
|
|
c1a44259d6 | ||
|
|
54b2cb0b27 | ||
|
|
09b6aef835 | ||
|
|
228c5e68ae | ||
|
|
6bb0529bed | ||
|
|
1532effb68 | ||
|
|
65eaebb177 | ||
|
|
ab0714ce9c | ||
|
|
a3a7c504ad | ||
|
|
ca45da39fa | ||
|
|
313ff95ee4 | ||
|
|
97815c9930 | ||
|
|
e2645826be | ||
|
|
7cc898f9ed | ||
|
|
ec979dc60a | ||
|
|
32a6d07036 | ||
|
|
d6c2d8f1dc | ||
|
|
4a5c94f410 | ||
|
|
0eb5b633aa | ||
|
|
56c36ac27d | ||
|
|
508bd26741 | ||
|
|
835053c55e | ||
|
|
f0e0a62bbe | ||
|
|
bdcbabb758 | ||
|
|
7cc2d468c7 | ||
|
|
83f4c01564 | ||
|
|
cfeb485434 | ||
|
|
d3b7f98814 | ||
|
|
f5b0043c08 | ||
|
|
6ce27f7d55 | ||
|
|
531dc35126 | ||
|
|
d974b46dbe | ||
|
|
ea1d5f3648 | ||
|
|
83b4081e85 | ||
|
|
e18e79d820 | ||
|
|
7234c7240a | ||
|
|
7fa6276eb7 | ||
|
|
bbdf81dc1c | ||
|
|
4b0d53dcb7 | ||
|
|
746d2cad5d | ||
|
|
14f57f6585 | ||
|
|
e63dff0a3a | ||
|
|
09c0365255 | ||
|
|
1661bf6543 | ||
|
|
2ef0b1b51d | ||
|
|
3c58600187 | ||
|
|
572c15d049 | ||
|
|
99a6074816 | ||
|
|
8b750ee182 | ||
|
|
c9b4920230 | ||
|
|
793d314c8d | ||
|
|
8c873712fe | ||
|
|
faae6b87b1 | ||
|
|
fd7610dd7a | ||
|
|
b0e1865073 | ||
|
|
de0658f7e8 | ||
|
|
79bb9ee141 | ||
|
|
2f33679cb3 | ||
|
|
6f5735274b | ||
|
|
709828204a | ||
|
|
b355b678a9 | ||
|
|
124c172c72 | ||
|
|
709c8bdcbf | ||
|
|
2c55d269ce | ||
|
|
e0d1b09ffe | ||
|
|
ceb42dd5de | ||
|
|
80fe5dc147 | ||
|
|
d806ffc1cd | ||
|
|
4f75f1f41d | ||
|
|
fe10eeb5be | ||
|
|
cf37f63ccb | ||
|
|
e7efce91fb | ||
|
|
5f834715d0 | ||
|
|
dd89df314d | ||
|
|
fdbb53db19 | ||
|
|
590ce29743 | ||
|
|
7548bc7ccc | ||
|
|
544ebf402f | ||
|
|
640538adcb | ||
|
|
1ded0ad3d5 | ||
|
|
d339b0f64f | ||
|
|
8e2696b385 | ||
|
|
b5212ddc3d | ||
|
|
dacaae520a | ||
|
|
e663c77e70 | ||
|
|
ec109c9ac8 | ||
|
|
0b9a2a1581 | ||
|
|
2759060559 | ||
|
|
00b13673e3 | ||
|
|
91ac5ba110 | ||
|
|
0d8c08cd22 | ||
|
|
ab4c03adc3 | ||
|
|
75b0c4be1b | ||
|
|
4a8e6415c7 | ||
|
|
58bf158840 | ||
|
|
c0f066626e | ||
|
|
69c7a8638e | ||
|
|
1b6f1b091c | ||
|
|
d39cdc45d1 | ||
|
|
7f761f3adf | ||
|
|
5e76943f02 | ||
|
|
b887ea3b60 | ||
|
|
690426ac86 | ||
|
|
b8298b0710 | ||
|
|
3bd5406c06 | ||
|
|
3ae0ff3685 | ||
|
|
80e29bfcab | ||
|
|
d7107c2cdc | ||
|
|
658da765ec | ||
|
|
9e2f5dd0ed | ||
|
|
1ffce4f7dc | ||
|
|
fcac1c3746 | ||
|
|
c0763bb8d5 | ||
|
|
75bd9d0e94 | ||
|
|
08bc1198d3 | ||
|
|
cf54f60367 | ||
|
|
329efcae6e | ||
|
|
5a2a78f7df | ||
|
|
f72c13ffda | ||
|
|
0a17badb21 | ||
|
|
12f9aa3669 | ||
|
|
afa2e86a4d | ||
|
|
b7842ab34b | ||
|
|
f8b3141204 | ||
|
|
c40d6d0509 | ||
|
|
a74481a1d4 | ||
|
|
b898415d0d | ||
|
|
b47effe5d8 | ||
|
|
4f10605389 | ||
|
|
120e62900b | ||
|
|
986704dc62 | ||
|
|
2f13956357 | ||
|
|
8d700dd1e5 | ||
|
|
5be412db13 | ||
|
|
a61a861cfb | ||
|
|
c363ca476f | ||
|
|
319b1ed551 | ||
|
|
67f50a0fe5 | ||
|
|
b98cc8b4e9 | ||
|
|
495646bb6a | ||
|
|
4516bd45b2 | ||
|
|
730c0e2e2b | ||
|
|
3020d0e56a | ||
|
|
49bed18a83 | ||
|
|
e6959412aa | ||
|
|
04c0bcc9b4 | ||
|
|
ec4edae977 | ||
|
|
f478c34671 | ||
|
|
a0ef4f2358 | ||
|
|
ca57e93ebf | ||
|
|
5ca5b7ec65 | ||
|
|
2b202aabd7 | ||
|
|
cb64d31171 | ||
|
|
80eafe3b63 | ||
|
|
16aa444261 | ||
|
|
0bbdbac17f | ||
|
|
c430b60e38 | ||
|
|
02f6555536 | ||
|
|
4293e3fdb2 | ||
|
|
50298bbf97 | ||
|
|
ac82fa47a0 | ||
|
|
976bfbf687 | ||
|
|
1bc377b188 | ||
|
|
a89530aef5 | ||
|
|
c32b1cd6d6 | ||
|
|
90f5c8bd85 | ||
|
|
878bb61d24 | ||
|
|
8493d8329e | ||
|
|
57aeafa115 | ||
|
|
a29e862adf | ||
|
|
5ed62e1137 | ||
|
|
f99a2534c9 | ||
|
|
a939423006 | ||
|
|
4493acc5ee | ||
|
|
c4eb9301bb | ||
|
|
57ba337e79 | ||
|
|
3d542693bc | ||
|
|
5f2607ffca | ||
|
|
ab916a8a8d | ||
|
|
1fb4bd82f7 | ||
|
|
d892e93589 | ||
|
|
38c2532c5f | ||
|
|
18b5ee7d5b | ||
|
|
9fd2d07c1d | ||
|
|
e7bcc3b332 | ||
|
|
b001fcf968 | ||
|
|
125619c783 | ||
|
|
fe6882623c | ||
|
|
11c4315da3 | ||
|
|
672e66ba5f | ||
|
|
bcfddb3d65 | ||
|
|
9cae98c167 | ||
|
|
b0e2e4f284 | ||
|
|
36842db26f | ||
|
|
e84a7c7e6f | ||
|
|
2109afc144 | ||
|
|
786dfdd7e5 | ||
|
|
62d05f842d | ||
|
|
fc3b03a5f5 | ||
|
|
f4f2bd9fab | ||
|
|
b0555b68e7 | ||
|
|
497695864b | ||
|
|
128b49a1fb | ||
|
|
0dbda6665d | ||
|
|
464279f48d | ||
|
|
b14a244ab4 | ||
|
|
2c7e920298 | ||
|
|
b725bfd3fb | ||
|
|
47e96b8040 | ||
|
|
9a1348707c | ||
|
|
ada98899f8 | ||
|
|
16a7a26452 | ||
|
|
2c04f9d199 | ||
|
|
70e98e47cf | ||
|
|
b525dc29f7 | ||
|
|
a3fb5ac326 | ||
|
|
8e6f98f44e | ||
|
|
9ba93b885c | ||
|
|
5f44d79729 | ||
|
|
a3b26c7e5b | ||
|
|
e9be7c1a58 | ||
|
|
6c359b2457 | ||
|
|
7647e52943 | ||
|
|
a0ae2e6c7b | ||
|
|
efd2b57b92 | ||
|
|
47aba0cc20 | ||
|
|
f5a2b75924 | ||
|
|
49a38ac89a | ||
|
|
6d071f4331 | ||
|
|
3873121c01 | ||
|
|
5bff8e362f | ||
|
|
62733146be | ||
|
|
99f6581770 | ||
|
|
b37fbf30a3 | ||
|
|
e65d0ac9e3 | ||
|
|
61a0e85672 | ||
|
|
c18e6459a8 | ||
|
|
6db255d17d | ||
|
|
49d79546e8 | ||
|
|
f3ba022f46 | ||
|
|
17f2002839 | ||
|
|
a6ee9757a7 | ||
|
|
d9cb18ad2a | ||
|
|
557480cfe8 | ||
|
|
5dae0bb9bf | ||
|
|
e38046e6c6 | ||
|
|
97be9ee4cd | ||
|
|
c658b363e3 | ||
|
|
7f9cef0114 | ||
|
|
8efb6cc870 | ||
|
|
1c8a141711 | ||
|
|
4d38790b5b | ||
|
|
5cfbb0aaf5 | ||
|
|
29271a4100 | ||
|
|
df0dc2b67a | ||
|
|
ff286f351c | ||
|
|
430125b396 | ||
|
|
0536e5b708 | ||
|
|
e6990162e5 | ||
|
|
d9b304b906 | ||
|
|
79f5d532c6 | ||
|
|
6af6a08b7a | ||
|
|
96d5044722 | ||
|
|
4258201900 | ||
|
|
11e5126454 | ||
|
|
9e953f3320 | ||
|
|
bbbeedb0a2 | ||
|
|
de17f4e5a4 | ||
|
|
86eba21b8a | ||
|
|
53d679edba | ||
|
|
89c90add2c | ||
|
|
a869be1e5a | ||
|
|
68619b8088 | ||
|
|
534cdd7587 | ||
|
|
e185aec920 | ||
|
|
14e8eea0ee | ||
|
|
2b849545ea | ||
|
|
15396fb2f9 | ||
|
|
1acef81ed3 | ||
|
|
a25141f9f4 | ||
|
|
d339ffe9d9 | ||
|
|
c5ba5888b5 | ||
|
|
3415e6de0b | ||
|
|
638532dd9a | ||
|
|
c6fc7d99b5 | ||
|
|
281e0598bc | ||
|
|
aa39fb0c80 | ||
|
|
459a91c54f | ||
|
|
115b51e68c | ||
|
|
4441697e49 | ||
|
|
5174ab4325 | ||
|
|
95911f059a | ||
|
|
e3288d5e33 | ||
|
|
49140d6880 | ||
|
|
9c6ffc5303 | ||
|
|
f80984f377 | ||
|
|
9c0a0fbd01 | ||
|
|
57dbab2932 | ||
|
|
7fdb541e2d |
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.2
|
||||
env: HTTPBIN_URL=http://httpbin.org/
|
||||
script: make simpleci
|
||||
install:
|
||||
- pip install nose
|
||||
- pip install . --use-mirrors
|
||||
24
AUTHORS.rst
24
AUTHORS.rst
@@ -68,3 +68,27 @@ Patches and Suggestions
|
||||
- Christopher Davis
|
||||
- Ori Livneh
|
||||
- Jason Emerick
|
||||
- Bryan Helmig
|
||||
- Jonas Obrist
|
||||
- Lucian Ursu
|
||||
- Tom Moertel
|
||||
- Frank Kumro Jr
|
||||
- Chase Sterling
|
||||
- Marty Alchin
|
||||
- takluyver
|
||||
- Ben Toews (mastahyeti)
|
||||
- David Kemp
|
||||
- Brendon Crawford
|
||||
- Denis (Telofy)
|
||||
- Cory Benfield (Lukasa)
|
||||
- Matt Giuca
|
||||
- Adam Tauber
|
||||
- Honza Javorek
|
||||
- Brendan Maguire <maguire.brendan@gmail.com>
|
||||
- Chris Dary
|
||||
- Danver Braganza <danverbraganza@gmail.com>
|
||||
- Max Countryman
|
||||
- Nick Chadwick
|
||||
- Jonathan Drosdeck
|
||||
- Jiri Machalek
|
||||
- Steve Pulec
|
||||
|
||||
122
HISTORY.rst
122
HISTORY.rst
@@ -1,6 +1,123 @@
|
||||
History
|
||||
-------
|
||||
|
||||
|
||||
0.11.1 (2012-03-30)
|
||||
+++++++++++++++++++
|
||||
|
||||
* POST redirects now break RFC to do what browsers do: Follow up with a GET.
|
||||
* New ``strict_mode`` configuration to disable new redirect behavior.
|
||||
|
||||
|
||||
0.11.0 (2012-03-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Private SSL Certificate support
|
||||
* Remove select.poll from Gevent monkeypatching
|
||||
* Remove redundant generator for chunked transfer encoding
|
||||
* Fix: Response.ok raises Timeout Exception in safe_mode
|
||||
|
||||
0.10.8 (2012-03-09)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Generate chunked ValueError fix
|
||||
* Proxy configuration by environment variables
|
||||
* Simplification of iter_lines.
|
||||
* New `trust_env` configuration for disabling system/environment hints.
|
||||
* Suppress cookie errors.
|
||||
|
||||
0.10.7 (2012-03-07)
|
||||
+++++++++++++++++++
|
||||
|
||||
* `encode_uri` = False
|
||||
|
||||
0.10.6 (2012-02-25)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Allow '=' in cookies.
|
||||
|
||||
0.10.5 (2012-02-25)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Response body with 0 content-length fix.
|
||||
* New async.imap.
|
||||
* Don't fail on netrc.
|
||||
|
||||
|
||||
0.10.4 (2012-02-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Honor netrc.
|
||||
|
||||
0.10.3 (2012-02-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
* HEAD requests don't follow redirects anymore.
|
||||
* raise_for_status() doesn't raise for 3xx anymore.
|
||||
* Make Session objects picklable.
|
||||
* ValueError for invalid schema URLs.
|
||||
|
||||
0.10.2 (2012-01-15)
|
||||
+++++++++++++++++++
|
||||
|
||||
* Vastly improved URL quoting.
|
||||
* Additional allowed cookie key values.
|
||||
* Attempted fix for "Too many open files" Error
|
||||
* Replace unicode errors on first pass, no need for second pass.
|
||||
* Append '/' to bare-domain urls before query insertion.
|
||||
* Exceptions now inherit from RuntimeError.
|
||||
* Binary uploads + auth fix.
|
||||
* Bugfixes.
|
||||
|
||||
|
||||
0.10.1 (2012-01-23)
|
||||
+++++++++++++++++++
|
||||
|
||||
* PYTHON 3 SUPPORT!
|
||||
* Dropped 2.5 Support. (*Backwards Incompatible*)
|
||||
|
||||
0.10.0 (2012-01-21)
|
||||
+++++++++++++++++++
|
||||
|
||||
* ``Response.content`` is now bytes-only. (*Backwards Incompatible*)
|
||||
* New ``Response.text`` is unicode-only.
|
||||
* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Respoonse.text`` will guess an encoding.
|
||||
* Default to ISO-8859-1 (Western) encoding for "text" subtypes.
|
||||
* Removal of `decode_unicode`. (*Backwards Incompatible*)
|
||||
* New multiple-hooks system.
|
||||
* New ``Response.register_hook`` for registering hooks within the pipeline.
|
||||
* ``Response.url`` is now Unicode.
|
||||
|
||||
0.9.3 (2012-01-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* SSL verify=False bugfix (apparent on windows machines).
|
||||
|
||||
0.9.2 (2012-01-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Asynchronous async.send method.
|
||||
* Support for proper chunk streams with boundaries.
|
||||
* session argument for Session classes.
|
||||
* Print entire hook tracebacks, not just exception instance.
|
||||
* Fix response.iter_lines from pending next line.
|
||||
* Fix but in HTTP-digest auth w/ URI having query strings.
|
||||
* Fix in Event Hooks section.
|
||||
* Urllib3 update.
|
||||
|
||||
|
||||
0.9.1 (2012-01-06)
|
||||
++++++++++++++++++
|
||||
|
||||
* danger_mode for automatic Response.raise_for_status()
|
||||
* Response.iter_lines refactor
|
||||
|
||||
0.9.0 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* verify ssl is default.
|
||||
|
||||
|
||||
0.8.9 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
@@ -50,7 +167,7 @@ History
|
||||
0.8.2 (2011-11-19)
|
||||
++++++++++++++++++
|
||||
|
||||
* New unicode decoding system, based on overridable `Response.encoding`.
|
||||
* New Unicode decoding system, based on over-ridable `Response.encoding`.
|
||||
* Proper URL slash-quote handling.
|
||||
* Cookies with ``[``, ``]``, and ``_`` allowed.
|
||||
|
||||
@@ -74,6 +191,7 @@ History
|
||||
* OPTION method
|
||||
* Async pool size throttling
|
||||
* File uploads send real names
|
||||
* Vendored in urllib3
|
||||
|
||||
0.7.6 (2011-11-07)
|
||||
++++++++++++++++++
|
||||
@@ -89,7 +207,7 @@ History
|
||||
0.7.4 (2011-10-26)
|
||||
++++++++++++++++++
|
||||
|
||||
* Sesion Hooks fix.
|
||||
* Session Hooks fix.
|
||||
|
||||
0.7.3 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
Copyright (c) 2012 Kenneth Reitz.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
20
Makefile
20
Makefile
@@ -1,11 +1,26 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
init:
|
||||
python setup.py develop
|
||||
pip install -r requirements.txt
|
||||
|
||||
test:
|
||||
nosetests --with-color test_requests.py
|
||||
nosetests ./tests/*
|
||||
|
||||
lazy:
|
||||
nosetests --with-color tests/test_requests.py
|
||||
|
||||
simple:
|
||||
nosetests tests/test_requests.py
|
||||
|
||||
server:
|
||||
gunicorn httpbin:app --bind=0.0.0.0:7077 &
|
||||
|
||||
ci: init
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
nosetests tests/test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
simpleci:
|
||||
nosetests tests/test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
stats:
|
||||
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
|
||||
@@ -19,6 +34,7 @@ pyc:
|
||||
deps:
|
||||
rm -fr requests/packages/urllib3
|
||||
git clone https://github.com/shazow/urllib3.git
|
||||
cd urllib3 && git checkout release && cd ..
|
||||
mv urllib3/urllib3 requests/packages/
|
||||
rm -fr urllib3
|
||||
|
||||
|
||||
61
README.rst
61
README.rst
@@ -1,6 +1,8 @@
|
||||
Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
.. image:: https://secure.travis-ci.org/kennethreitz/requests.png?branch=develop
|
||||
|
||||
Requests is an ISC Licensed HTTP library, written in Python, for human
|
||||
beings.
|
||||
|
||||
@@ -19,7 +21,7 @@ Things shouldn't be this way. Not in Python.
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
...
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
@@ -27,53 +29,24 @@ See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
|
||||
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
|
||||
multipart files, and parameters with simple Python dictionaries, and access the
|
||||
response data in the same way. It's powered by httplib, but it does
|
||||
all the hard work and crazy hacks for you.
|
||||
response data in the same way. It's powered by httplib and `urllib3
|
||||
<https://github.com/shazow/urllib3>`_, but it does all the hard work and crazy
|
||||
hacks for you.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Browser standard SSL verification.
|
||||
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests.
|
||||
- Gevent support for Asyncronous Requests.
|
||||
- Sessions with cookie persistence.
|
||||
- Basic, Digest, and Custom Authentication support.
|
||||
- Automatic form-encoding of dictionaries
|
||||
- A simple dictionary interface for request/response cookies.
|
||||
- Multipart file uploads.
|
||||
- Automatc decoding of Unicode, gzip, and deflate responses.
|
||||
- Full support for unicode URLs and domain names.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
It couldn't be simpler. ::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('http://google.com')
|
||||
|
||||
|
||||
HTTPS? Basic Authentication? ::
|
||||
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass')
|
||||
>>> r.status_code
|
||||
401
|
||||
|
||||
|
||||
Uh oh, we're not authorized! Let's add authentication. ::
|
||||
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass', auth=('user', 'pass'))
|
||||
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
|
||||
>>> r.content
|
||||
'{"authenticated": true, "user": "user"}'
|
||||
- International Domains and URLs
|
||||
- Keep-Alive & Connection Pooling
|
||||
- Sessions with Cookie Persistence
|
||||
- Browser-style SSL Verification
|
||||
- Basic/Digest Authentication
|
||||
- Elegant Key/Value Cookies
|
||||
- Automatic Decompression
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
|
||||
|
||||
Installation
|
||||
@@ -100,4 +73,4 @@ Contribute
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/requests
|
||||
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS
|
||||
.. _AUTHORS: https://github.com/kennethreitz/requests/blob/develop/AUTHORS.rst
|
||||
|
||||
8
docs/_themes/kr/layout.html
vendored
8
docs/_themes/kr/layout.html
vendored
@@ -25,6 +25,12 @@
|
||||
})();
|
||||
/* ]]> */
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
new HelloBar(36402,48802);
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
@@ -54,4 +60,6 @@
|
||||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
@@ -51,23 +51,17 @@ Chris Adams gave an excellent summary on
|
||||
Python 3 Support?
|
||||
-----------------
|
||||
|
||||
It's on the way. Here's a list of Python platforms that are officially
|
||||
Yes! Here's a list of Python platforms that are officially
|
||||
supported:
|
||||
|
||||
* cPython 2.5
|
||||
* cPython 2.5.5
|
||||
* cPython 2.5.6
|
||||
* cPython 2.6
|
||||
* cPython 2.6.6
|
||||
* cPython 2.6.7
|
||||
* cPython 2.7
|
||||
* cPython 2.7.1
|
||||
* cPython 2.7.2
|
||||
* cPython 3.1
|
||||
* cPython 3.2
|
||||
* PyPy-c 1.4
|
||||
* PyPy-c 1.5
|
||||
|
||||
|
||||
Support for Python 3.x is coming *very* soon.
|
||||
* PyPy-c 1.6
|
||||
* PyPy-c 1.7
|
||||
|
||||
|
||||
Keep-alive Support?
|
||||
|
||||
@@ -50,9 +50,6 @@ Requests is available installed as a Debian package! Debian Etch Ubuntu, since O
|
||||
|
||||
$ apt-get install python-requests
|
||||
|
||||
Unfortunately, the most recent version available is v0.5.0. If you're on the
|
||||
Debian Python Package team, I'd love an update of that :)
|
||||
|
||||
|
||||
Fedora and RedHat
|
||||
-----------------
|
||||
|
||||
@@ -43,7 +43,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'2011. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
copyright = u'2012. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
@@ -3,6 +3,24 @@ How to Help
|
||||
|
||||
Requests is under active development, and contributions are more than welcome!
|
||||
|
||||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
|
||||
There is a Contributor Friendly tag for issues that should be ideal for people who are not very
|
||||
familiar with the codebase yet.
|
||||
#. Fork `the repository`_ on Github to start making your changes to the **develop**
|
||||
branch (or branch off of it).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :)
|
||||
Make sure to add yourself to AUTHORS_.
|
||||
|
||||
Development dependencies
|
||||
------------------------
|
||||
|
||||
You'll need to install ``gunicorn`` and ``httpbin`` in order to run requests' test suite::
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
|
||||
|
||||
|
||||
What Needs to be Done
|
||||
---------------------
|
||||
|
||||
@@ -23,22 +23,23 @@ Things shouldn’t be this way. Not in Python.
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
...
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
Requests takes all of the work out of Python HTTP — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data.
|
||||
Requests takes all of the work out of Python HTTP — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by `urllib3 <https://github.com/shazow/urllib3>`_, which is embedded within Requests.
|
||||
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`The Washington Post <http://www.washingtonpost.com/>`_,
|
||||
`PayPal <https://www.paypal.com/>`_,
|
||||
`Native Instruments <http://www.native-instruments.com/>`_, `The Washington Post <http://www.washingtonpost.com/>`_,
|
||||
`Twitter, Inc <http://twitter.com>`_,
|
||||
`Readability <http://readability.com>`_, and
|
||||
Federal US Institutions
|
||||
use Requests internally. It has been installed over 60,000 times from PyPI.
|
||||
use Requests internally. It has been installed over 100,000 times from PyPI.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
@@ -72,6 +73,7 @@ Requests is ready for today's web.
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
- ``.netrc`` support
|
||||
|
||||
|
||||
User Guide
|
||||
|
||||
@@ -22,7 +22,7 @@ Let's persist some cookies across requests::
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
|
||||
print r.content
|
||||
print r.text
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ 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. I do have certs for httpbin.org though::
|
||||
I don't have SSL setup on this domain, so it fails. Excellent. Github does though::
|
||||
|
||||
>>> requests.get('https://httpbin.org', verify=True)
|
||||
>>> 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.
|
||||
@@ -134,7 +134,8 @@ one at a time with ``Request.send()``, but that would take a while. Instead,
|
||||
we'll send them all at the same time with ``async.map()``. Using ``async.map()``
|
||||
will also guarantee execution of the ``response`` hook, described below. ::
|
||||
|
||||
>>> async.map(rs)
|
||||
>>> responses = async.map(rs)
|
||||
>>> repsonses
|
||||
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
|
||||
|
||||
.. admonition:: Throttling
|
||||
@@ -187,14 +188,14 @@ anything, nothing else is effected.
|
||||
|
||||
Let's print some request method arguments at runtime::
|
||||
|
||||
>>> requests.get('http://httpbin', hooks=dict(args=print_url))
|
||||
http://httpbin
|
||||
>>> requests.get('http://httpbin.org', hooks=dict(args=print_url))
|
||||
http://httpbin.org
|
||||
<Response [200]>
|
||||
|
||||
Let's hijack some arguments this time with a new callback::
|
||||
|
||||
def hack_headers(args):
|
||||
if not args[headers]:
|
||||
if args.get('headers') is None:
|
||||
args['headers'] = dict()
|
||||
|
||||
args['headers'].update({'X-Testing': 'True'})
|
||||
@@ -206,7 +207,7 @@ Let's hijack some arguments this time with a new callback::
|
||||
|
||||
And give it a try::
|
||||
|
||||
>>> requests.get('http://httpbin/headers', hooks=hooks, headers=headers)
|
||||
>>> requests.get('http://httpbin.org/headers', hooks=hooks, headers=headers)
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "",
|
||||
@@ -291,3 +292,30 @@ To do so, just configure Requests with a stream to write to::
|
||||
>>> requests.get('http://httpbin.org/headers', config=my_config)
|
||||
2011-08-17T03:04:23.380175 GET http://httpbin.org/headers
|
||||
<Response [200]>
|
||||
|
||||
Proxies
|
||||
-------
|
||||
|
||||
If you need to use a proxy, you can configure individual requests with the
|
||||
``proxies`` argument to any request method:
|
||||
|
||||
::
|
||||
|
||||
import requests
|
||||
|
||||
proxies = {
|
||||
"http": "10.10.1.10:3128"
|
||||
"https": "10.10.1.10:1080"
|
||||
}
|
||||
|
||||
requests.get("http://example.org", proxies=proxies)
|
||||
|
||||
You can also configure proxies by environment variables ``HTTP_PROXY`` and ``HTTPS_PROXY``.
|
||||
|
||||
::
|
||||
|
||||
$ export HTTP_PROXY="10.10.1.10:3128"
|
||||
$ export HTTPS_PROXY="10.10.1.10:1080"
|
||||
$ python
|
||||
>>> import requests
|
||||
>>> requests.get("http://example.org")
|
||||
|
||||
@@ -26,9 +26,9 @@ Cheeseshop Mirror
|
||||
-----------------
|
||||
|
||||
If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's
|
||||
personal `Cheeseshop mirror <http://pip.kreitz.co/>`_::
|
||||
personal `Cheeseshop mirror <http://pip.kennethreitz.com/>`_::
|
||||
|
||||
$ pip install -i http://pip.kreitz.co/simple requests
|
||||
$ pip install -i http://pip.kennethreitz.com/simple requests
|
||||
|
||||
|
||||
Get the Code
|
||||
@@ -43,11 +43,11 @@ You can either clone the public repository::
|
||||
|
||||
Download the `tarball <https://github.com/kennethreitz/requests/tarball/master>`_::
|
||||
|
||||
$ curl -O https://github.com/kennethreitz/requests/tarball/master
|
||||
$ curl -OL https://github.com/kennethreitz/requests/tarball/master
|
||||
|
||||
Or, download the `zipball <https://github.com/kennethreitz/requests/zipball/master>`_::
|
||||
|
||||
$ curl -O https://github.com/kennethreitz/requests/zipball/master
|
||||
$ curl -OL https://github.com/kennethreitz/requests/zipball/master
|
||||
|
||||
|
||||
Once you have a copy of the source, you can embed it in your Python package,
|
||||
|
||||
@@ -30,22 +30,81 @@ Let's get GitHub's public timeline ::
|
||||
Now, we have a :class:`Response` object called ``r``. We can get all the
|
||||
information we need from this.
|
||||
|
||||
Typically, you want to send some sort of data in the urls query string.
|
||||
To do this, simply pass a dictionary to the `params` argument. Your
|
||||
dictionary of data will automatically be encoded when the request is made::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
>>> r = requests.get("http://httpbin.org/get", params=payload)
|
||||
>>> print r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"args": {
|
||||
"key2": "value2",
|
||||
"key1": "value1"
|
||||
},
|
||||
"url": "http://httpbin.org/get",
|
||||
"headers": {
|
||||
"Connections": "keep-alive",
|
||||
"Content-Length": "",
|
||||
"Accept-Encoding": "identity, deflate, compress, gzip",
|
||||
"Accept": "*/*",
|
||||
"User-Agent": "python-requests/0.11.0",
|
||||
"Host": httpbin.org",
|
||||
"Content-Type": ""
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
Response Content
|
||||
----------------
|
||||
|
||||
We can read the content of the server's response::
|
||||
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
Requests does its best to decode content from the server. Most unicode
|
||||
charsets, ``gzip``, and ``deflate`` encodings are all seamlessly decoded.
|
||||
Requests will automatically decode content from the server. Most unicode
|
||||
charsets are seamlessly decoded.
|
||||
|
||||
When you make a request, ``r.encoding`` is set, based on the HTTP headers.
|
||||
Requests will attempt to use that encoding when you access ``r.content``. You
|
||||
can manually set ``r.encoding`` to any encoding you'd like (including ``None``),
|
||||
and that charset will be used.
|
||||
Requests will use that encoding when you access ``r.text``. If ``r.encoding``
|
||||
is ``None``, Requests will make an extremely educated guess of the encoding
|
||||
of the response body. You can manually set ``r.encoding`` to any encoding
|
||||
you'd like, and that charset will be used.
|
||||
|
||||
|
||||
Binary Response Content
|
||||
-----------------------
|
||||
|
||||
You can also access the response body as bytes, for non-text requests::
|
||||
|
||||
>>> r.content
|
||||
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
|
||||
|
||||
For example to create an image from binary data returned by a request, you can
|
||||
use the following code:
|
||||
|
||||
>>> from PIL import Image
|
||||
>>> from StringIO import StringIO
|
||||
>>> i = Image.open(StringIO(r.content))
|
||||
|
||||
|
||||
Raw Response Content
|
||||
--------------------
|
||||
|
||||
In the rare case that you'd like to get the absolute raw socket response from the server,
|
||||
you can access ``r.raw``::
|
||||
|
||||
>>> r.raw
|
||||
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
|
||||
|
||||
>>> r.raw.read(10)
|
||||
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
|
||||
|
||||
|
||||
|
||||
Make a POST Request
|
||||
@@ -62,7 +121,7 @@ dictionary of data will automatically be form-encoded when the request is made::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
>>> r = requests.post("http://httpbin.org/post", data=payload)
|
||||
>>> print r.content
|
||||
>>> print r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {},
|
||||
@@ -117,7 +176,7 @@ Requests makes it simple to upload Multipart-encoded files::
|
||||
>>> files = {'report.xls': open('report.xls', 'rb')}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {
|
||||
@@ -143,7 +202,7 @@ Setting filename explicitly::
|
||||
>>> files = {'file': ('report.xls', open('report.xls', 'rb'))}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {
|
||||
@@ -253,7 +312,7 @@ parameter::
|
||||
>>> cookies = dict(cookies_are='working')
|
||||
|
||||
>>> r = requests.get(url, cookies=cookies)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
|
||||
|
||||
@@ -364,6 +423,10 @@ If a request exceeds the configured number of maximum redirections, a :class:`To
|
||||
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.
|
||||
|
||||
@@ -9,18 +9,17 @@
|
||||
requests
|
||||
~~~~~~~~
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.8.9'
|
||||
__build__ = 0x000809
|
||||
__version__ = '0.11.1'
|
||||
__build__ = 0x001101
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
__copyright__ = 'Copyright 2012 Kenneth Reitz'
|
||||
|
||||
|
||||
from . import utils
|
||||
|
||||
@@ -6,7 +6,7 @@ requests.api
|
||||
|
||||
This module implements the Requests API.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
@@ -32,9 +32,11 @@ def request(method, url, **kwargs):
|
||||
:param session: (optional) A :class:`Session` object to be used for the request.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
"""
|
||||
|
||||
s = kwargs.get('session') or sessions.session()
|
||||
s = kwargs.pop('session') if 'session' in kwargs else sessions.session()
|
||||
return s.request(method=method, url=url, **kwargs)
|
||||
|
||||
|
||||
@@ -43,7 +45,7 @@ def get(url, **kwargs):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -54,7 +56,7 @@ def options(url, **kwargs):
|
||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -65,10 +67,10 @@ def head(url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return request('head', url, **kwargs)
|
||||
|
||||
|
||||
@@ -77,7 +79,7 @@ def post(url, data=None, **kwargs):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, **kwargs)
|
||||
@@ -88,7 +90,7 @@ def put(url, data=None, **kwargs):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('put', url, data=data, **kwargs)
|
||||
@@ -99,7 +101,7 @@ def patch(url, data=None, **kwargs):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
@@ -109,7 +111,7 @@ def delete(url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('delete', url, **kwargs)
|
||||
|
||||
@@ -17,13 +17,13 @@ except ImportError:
|
||||
raise RuntimeError('Gevent is required for requests.async.')
|
||||
|
||||
# Monkey-patch.
|
||||
curious_george.patch_all(thread=False)
|
||||
curious_george.patch_all(thread=False, select=False)
|
||||
|
||||
from . import api
|
||||
|
||||
|
||||
__all__ = (
|
||||
'map',
|
||||
'map', 'imap',
|
||||
'get', 'options', 'head', 'post', 'put', 'patch', 'delete', 'request'
|
||||
)
|
||||
|
||||
@@ -46,15 +46,15 @@ def patched(f):
|
||||
return wrapped
|
||||
|
||||
|
||||
def send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
def send(r, pool=None, prefetch=False):
|
||||
"""Sends the request object using the specified pool. If a pool isn't
|
||||
specified this method blocks. Pools are useful because you can specify size
|
||||
and can hence limit concurrency."""
|
||||
|
||||
if pools:
|
||||
r._pools = pools
|
||||
if pool != None:
|
||||
return pool.spawn(r.send, prefetch=prefetch)
|
||||
|
||||
r.send()
|
||||
|
||||
return r.response
|
||||
return gevent.spawn(r.send, prefetch=prefetch)
|
||||
|
||||
|
||||
# Patched requests.api functions.
|
||||
@@ -78,19 +78,29 @@ def map(requests, prefetch=True, size=None):
|
||||
|
||||
requests = list(requests)
|
||||
|
||||
if size:
|
||||
pool = Pool(size)
|
||||
pool.map(send, requests)
|
||||
pool.join()
|
||||
else:
|
||||
jobs = [gevent.spawn(send, r) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
if prefetch:
|
||||
[r.response.content for r in requests]
|
||||
pool = Pool(size) if size else None
|
||||
jobs = [send(r, pool, prefetch=prefetch) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
return [r.response for r in requests]
|
||||
|
||||
|
||||
def imap(requests, prefetch=True, size=2):
|
||||
"""Concurrently converts a generator object of Requests to
|
||||
a generator of Responses.
|
||||
|
||||
:param requests: a generator of Request objects.
|
||||
:param prefetch: If False, the content will not be downloaded immediately.
|
||||
:param size: Specifies the number of requests to make at a time. default is 2
|
||||
"""
|
||||
|
||||
pool = Pool(size)
|
||||
|
||||
def send(r):
|
||||
r.send(prefetch)
|
||||
return r.response
|
||||
|
||||
for r in pool.imap_unordered(send, requests):
|
||||
yield r
|
||||
|
||||
pool.join()
|
||||
@@ -11,15 +11,15 @@ import time
|
||||
import hashlib
|
||||
|
||||
from base64 import b64encode
|
||||
from urlparse import urlparse
|
||||
|
||||
from .compat import urlparse, str
|
||||
from .utils import randombytes, parse_dict_header
|
||||
|
||||
|
||||
|
||||
def _basic_auth_str(username, password):
|
||||
"""Returns a Basic Auth string."""
|
||||
return 'Basic %s' % b64encode('%s:%s' % (username, password))
|
||||
|
||||
return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1')
|
||||
|
||||
|
||||
class AuthBase(object):
|
||||
@@ -32,8 +32,8 @@ class AuthBase(object):
|
||||
class HTTPBasicAuth(AuthBase):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object."""
|
||||
def __init__(self, username, password):
|
||||
self.username = str(username)
|
||||
self.password = str(password)
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def __call__(self, r):
|
||||
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
@@ -74,9 +74,17 @@ class HTTPDigestAuth(AuthBase):
|
||||
algorithm = algorithm.upper()
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if algorithm == 'MD5':
|
||||
H = lambda x: hashlib.md5(x).hexdigest()
|
||||
def h(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.md5(x).hexdigest()
|
||||
H = h
|
||||
elif algorithm == 'SHA':
|
||||
H = lambda x: hashlib.sha1(x).hexdigest()
|
||||
def h(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
H = h
|
||||
# XXX MD5-sess
|
||||
KD = lambda s, d: H("%s:%s" % (s, d))
|
||||
|
||||
@@ -86,7 +94,9 @@ class HTTPDigestAuth(AuthBase):
|
||||
# XXX not implemented yet
|
||||
entdig = None
|
||||
p_parsed = urlparse(r.request.url)
|
||||
path = p_parsed.path + p_parsed.query
|
||||
path = p_parsed.path
|
||||
if p_parsed.query:
|
||||
path += '?' + p_parsed.query
|
||||
|
||||
A1 = '%s:%s:%s' % (self.username, realm, self.password)
|
||||
A2 = '%s:%s' % (r.request.method, path)
|
||||
@@ -99,10 +109,12 @@ class HTTPDigestAuth(AuthBase):
|
||||
last_nonce = nonce
|
||||
|
||||
ncvalue = '%08x' % nonce_count
|
||||
cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
|
||||
nonce_count, nonce, time.ctime(), randombytes(8)))
|
||||
.hexdigest()[:16]
|
||||
)
|
||||
s = str(nonce_count).encode('utf-8')
|
||||
s += nonce.encode('utf-8')
|
||||
s += time.ctime().encode('utf-8')
|
||||
s += randombytes(8)
|
||||
|
||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
|
||||
respdig = KD(H(A1), noncebit)
|
||||
elif qop is None:
|
||||
@@ -132,5 +144,5 @@ class HTTPDigestAuth(AuthBase):
|
||||
return r
|
||||
|
||||
def __call__(self, r):
|
||||
r.hooks['response'] = self.handle_401
|
||||
r.register_hook('response', self.handle_401)
|
||||
return r
|
||||
|
||||
105
requests/compat.py
Normal file
105
requests/compat.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
pythoncompat
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
# -------
|
||||
# Pythons
|
||||
# -------
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version_info
|
||||
|
||||
#: Python 2.x?
|
||||
is_py2 = (_ver[0] == 2)
|
||||
|
||||
#: Python 3.x?
|
||||
is_py3 = (_ver[0] == 3)
|
||||
|
||||
#: Python 3.0.x
|
||||
is_py30 = (is_py3 and _ver[1] == 0)
|
||||
|
||||
#: Python 3.1.x
|
||||
is_py31 = (is_py3 and _ver[1] == 1)
|
||||
|
||||
#: Python 3.2.x
|
||||
is_py32 = (is_py3 and _ver[1] == 2)
|
||||
|
||||
#: Python 3.3.x
|
||||
is_py33 = (is_py3 and _ver[1] == 3)
|
||||
|
||||
#: Python 3.4.x
|
||||
is_py34 = (is_py3 and _ver[1] == 4)
|
||||
|
||||
#: Python 2.7.x
|
||||
is_py27 = (is_py2 and _ver[1] == 7)
|
||||
|
||||
#: Python 2.6.x
|
||||
is_py26 = (is_py2 and _ver[1] == 6)
|
||||
|
||||
#: Python 2.5.x
|
||||
is_py25 = (is_py2 and _ver[1] == 5)
|
||||
|
||||
#: Python 2.4.x
|
||||
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
|
||||
|
||||
|
||||
# ---------
|
||||
# Platforms
|
||||
# ---------
|
||||
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version.lower()
|
||||
|
||||
is_pypy = ('pypy' in _ver)
|
||||
is_jython = ('jython' in _ver)
|
||||
is_ironpython = ('iron' in _ver)
|
||||
|
||||
# Assume CPython, if nothing else.
|
||||
is_cpython = not any((is_pypy, is_jython, is_ironpython))
|
||||
|
||||
# Windows-based system.
|
||||
is_windows = 'win32' in str(sys.platform).lower()
|
||||
|
||||
# Standard Linux 2+ system.
|
||||
is_linux = ('linux' in str(sys.platform).lower())
|
||||
is_osx = ('darwin' in str(sys.platform).lower())
|
||||
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
|
||||
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
|
||||
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
|
||||
if is_py2:
|
||||
from urllib import quote, unquote, urlencode
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||
from urllib2 import parse_http_list
|
||||
import cookielib
|
||||
from .packages.oreos.monkeys import SimpleCookie
|
||||
from StringIO import StringIO
|
||||
|
||||
bytes = str
|
||||
str = unicode
|
||||
basestring = basestring
|
||||
|
||||
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
|
||||
from urllib.request import parse_http_list
|
||||
from http import cookiejar as cookielib
|
||||
from http.cookies import SimpleCookie
|
||||
from io import StringIO
|
||||
|
||||
str = str
|
||||
bytes = bytes
|
||||
basestring = (str,bytes)
|
||||
|
||||
@@ -10,17 +10,21 @@ Configurations:
|
||||
|
||||
:base_headers: Default HTTP headers.
|
||||
:verbose: Stream to write request logging to.
|
||||
:timeout: Seconds until request timeout.
|
||||
:max_redirects: Maximum number of redirects allowed within a request.
|
||||
:decode_unicode: Decode unicode responses automatically?
|
||||
:max_redirects: Maximum number of redirects allowed within a request.s
|
||||
:keep_alive: Reuse HTTP Connections?
|
||||
:max_retries: The number of times a request should be retried in the event of a connection failure.
|
||||
:danger_mode: If true, Requests will raise errors immediately.
|
||||
:safe_mode: If true, Requests will catch all errors.
|
||||
:strict_mode: If true, Requests will do its best to follow RFCs (e.g. POST redirects).
|
||||
:pool_maxsize: The maximium size of an HTTP connection pool.
|
||||
:pool_connections: The number of active HTTP connection pools to use.
|
||||
:encode_uri: If true, URIs will automatically be percent-encoded.
|
||||
:trust_env: If true, the surrouding environment will be trusted (environ, netrc).
|
||||
|
||||
"""
|
||||
|
||||
SCHEMAS = ['http', 'https']
|
||||
|
||||
from . import __version__
|
||||
|
||||
defaults = dict()
|
||||
@@ -34,9 +38,14 @@ defaults['base_headers'] = {
|
||||
|
||||
defaults['verbose'] = None
|
||||
defaults['max_redirects'] = 30
|
||||
defaults['decode_unicode'] = True
|
||||
defaults['pool_connections'] = 10
|
||||
defaults['pool_maxsize'] = 10
|
||||
defaults['max_retries'] = 0
|
||||
defaults['danger_mode'] = False
|
||||
defaults['safe_mode'] = False
|
||||
defaults['strict_mode'] = False
|
||||
defaults['keep_alive'] = True
|
||||
defaults['encode_uri'] = True
|
||||
defaults['trust_env'] = True
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ This module contains the set of Requests' exceptions.
|
||||
|
||||
"""
|
||||
|
||||
class RequestException(Exception):
|
||||
class RequestException(RuntimeError):
|
||||
"""There was an ambiguous exception that occurred while handling your
|
||||
request."""
|
||||
|
||||
class HTTPError(RequestException):
|
||||
"""An HTTP error occurred."""
|
||||
response = None
|
||||
|
||||
class ConnectionError(RequestException):
|
||||
"""A Connection error occurred."""
|
||||
@@ -29,3 +30,9 @@ class URLRequired(RequestException):
|
||||
|
||||
class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
|
||||
class MissingSchema(RequestException, ValueError):
|
||||
"""The URL schema (e.g. http or https) is missing."""
|
||||
|
||||
class InvalidSchema(RequestException, ValueError):
|
||||
"""See defaults.py for valid schemas."""
|
||||
@@ -22,7 +22,10 @@ Available hooks:
|
||||
|
||||
"""
|
||||
|
||||
import warnings
|
||||
import traceback
|
||||
|
||||
|
||||
HOOKS = ('args', 'pre_request', 'post_request', 'response')
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
@@ -31,10 +34,15 @@ def dispatch_hook(key, hooks, hook_data):
|
||||
hooks = hooks or dict()
|
||||
|
||||
if key in hooks:
|
||||
try:
|
||||
return hooks.get(key).__call__(hook_data) or hook_data
|
||||
hooks = hooks.get(key)
|
||||
|
||||
except Exception, why:
|
||||
warnings.warn(str(why))
|
||||
if hasattr(hooks, '__call__'):
|
||||
hooks = [hooks]
|
||||
|
||||
for hook in hooks:
|
||||
try:
|
||||
hook_data = hook(hook_data) or hook_data
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
return hook_data
|
||||
|
||||
@@ -8,16 +8,12 @@ This module contains the primary objects that power Requests.
|
||||
"""
|
||||
|
||||
import os
|
||||
import urllib
|
||||
import zlib
|
||||
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||
from datetime import datetime
|
||||
|
||||
from .hooks import dispatch_hook
|
||||
from .hooks import dispatch_hook, HOOKS
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .status_codes import codes
|
||||
from .packages import oreos
|
||||
|
||||
from .auth import HTTPBasicAuth, HTTPProxyAuth
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .packages.urllib3.exceptions import MaxRetryError
|
||||
@@ -25,18 +21,26 @@ from .packages.urllib3.exceptions import SSLError as _SSLError
|
||||
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
||||
from .packages.urllib3 import connectionpool, poolmanager
|
||||
from .packages.urllib3.filepost import encode_multipart_formdata
|
||||
from .defaults import SCHEMAS
|
||||
from .exceptions import (
|
||||
ConnectionError, HTTPError, RequestException, Timeout, TooManyRedirects,
|
||||
URLRequired, SSLError)
|
||||
URLRequired, SSLError, MissingSchema, InvalidSchema)
|
||||
from .utils import (
|
||||
get_encoding_from_headers, stream_decode_response_unicode,
|
||||
decode_gzip, stream_decode_gzip, guess_filename, requote_path)
|
||||
get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri,
|
||||
dict_from_string, stream_decode_response_unicode, get_netrc_auth)
|
||||
from .compat import (
|
||||
urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
|
||||
SimpleCookie, is_py2)
|
||||
|
||||
# Import chardet if it is available.
|
||||
try:
|
||||
import chardet
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""The :class:`Request <Request>` object. It carries out all functionality of
|
||||
Requests. Recommended interface is with the Requests functions.
|
||||
@@ -58,7 +62,12 @@ class Request(object):
|
||||
hooks=None,
|
||||
config=None,
|
||||
_poolmanager=None,
|
||||
verify=None):
|
||||
verify=None,
|
||||
session=None,
|
||||
cert=None):
|
||||
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = dict(config or [])
|
||||
|
||||
#: Float describes the timeout of the request.
|
||||
# (Use socket.setdefaulttimeout() as fallback)
|
||||
@@ -83,7 +92,6 @@ class Request(object):
|
||||
#: Dictionary or byte of querystring data to attach to the
|
||||
#: :class:`Request <Request>`.
|
||||
self.params = None
|
||||
self.params = dict(params or [])
|
||||
|
||||
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
|
||||
#: and HTTPError storage).
|
||||
@@ -95,6 +103,14 @@ class Request(object):
|
||||
# Dictionary mapping protocol to the URL of the proxy (e.g. {'http': 'foo.bar:3128'})
|
||||
self.proxies = dict(proxies or [])
|
||||
|
||||
# If no proxies are given, allow configuration by environment variables
|
||||
# HTTP_PROXY and HTTPS_PROXY.
|
||||
if not self.proxies and self.config.get('trust_env'):
|
||||
if 'HTTP_PROXY' in os.environ:
|
||||
self.proxies['http'] = os.environ['HTTP_PROXY']
|
||||
if 'HTTPS_PROXY' in os.environ:
|
||||
self.proxies['https'] = os.environ['HTTPS_PROXY']
|
||||
|
||||
self.data, self._enc_data = self._encode_params(data)
|
||||
self.params, self._enc_params = self._encode_params(params)
|
||||
|
||||
@@ -108,44 +124,46 @@ class Request(object):
|
||||
#: CookieJar to attach to :class:`Request <Request>`.
|
||||
self.cookies = dict(cookies or [])
|
||||
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = dict(config or [])
|
||||
|
||||
#: True if Request has been sent.
|
||||
self.sent = False
|
||||
|
||||
#: Event-handling hooks.
|
||||
self.hooks = hooks
|
||||
self.hooks = {}
|
||||
|
||||
for event in HOOKS:
|
||||
self.hooks[event] = []
|
||||
|
||||
hooks = hooks or {}
|
||||
|
||||
for (k, v) in list(hooks.items()):
|
||||
self.register_hook(event=k, hook=v)
|
||||
|
||||
#: Session.
|
||||
self.session = None
|
||||
self.session = session
|
||||
|
||||
#: SSL Verification.
|
||||
self.verify = verify
|
||||
|
||||
#: SSL Certificate
|
||||
self.cert = cert
|
||||
|
||||
if headers:
|
||||
headers = CaseInsensitiveDict(self.headers)
|
||||
else:
|
||||
headers = CaseInsensitiveDict()
|
||||
|
||||
# Add configured base headers.
|
||||
for (k, v) in self.config.get('base_headers', {}).items():
|
||||
for (k, v) in list(self.config.get('base_headers', {}).items()):
|
||||
if k not in headers:
|
||||
headers[k] = v
|
||||
|
||||
self.headers = headers
|
||||
self._poolmanager = _poolmanager
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
|
||||
|
||||
def _build_response(self, resp, is_error=False):
|
||||
def _build_response(self, resp):
|
||||
"""Build internal :class:`Response <Response>` object
|
||||
from given response.
|
||||
"""
|
||||
@@ -174,7 +192,7 @@ class Request(object):
|
||||
# Add new cookies from the server.
|
||||
if 'set-cookie' in response.headers:
|
||||
cookie_header = response.headers['set-cookie']
|
||||
cookies = oreos.dict_from_string(cookie_header)
|
||||
cookies = dict_from_string(cookie_header)
|
||||
|
||||
# Save cookies in Response.
|
||||
response.cookies = cookies
|
||||
@@ -184,33 +202,36 @@ class Request(object):
|
||||
|
||||
# Save original response for later.
|
||||
response.raw = resp
|
||||
|
||||
if is_error:
|
||||
response.error = resp
|
||||
|
||||
response.url = self.full_url
|
||||
if isinstance(self.full_url, bytes):
|
||||
response.url = self.full_url.decode('utf-8')
|
||||
else:
|
||||
response.url = self.full_url
|
||||
|
||||
return response
|
||||
|
||||
history = []
|
||||
|
||||
r = build(resp)
|
||||
cookies = self.cookies
|
||||
|
||||
self.cookies.update(r.cookies)
|
||||
|
||||
if r.status_code in REDIRECT_STATI and not self.redirect:
|
||||
|
||||
while (
|
||||
('location' in r.headers) and
|
||||
((r.status_code is codes.see_other) or (self.allow_redirects))
|
||||
):
|
||||
while (('location' in r.headers) and
|
||||
((r.status_code is codes.see_other) or (self.allow_redirects))):
|
||||
|
||||
r.content # Consume socket so it can be released
|
||||
|
||||
if not len(history) < self.config.get('max_redirects'):
|
||||
raise TooManyRedirects()
|
||||
|
||||
# Release the connection back into the pool.
|
||||
r.raw.release_conn()
|
||||
|
||||
history.append(r)
|
||||
|
||||
url = r.headers['location']
|
||||
data = self.data
|
||||
|
||||
# Handle redirection without scheme (see: RFC 1808 Section 4)
|
||||
if url.startswith('//'):
|
||||
@@ -220,14 +241,29 @@ class Request(object):
|
||||
# Facilitate non-RFC2616-compliant 'location' headers
|
||||
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
|
||||
if not urlparse(url).netloc:
|
||||
url = urljoin(r.url, url)
|
||||
url = urljoin(r.url,
|
||||
# Compliant with RFC3986, we percent
|
||||
# encode the url.
|
||||
requote_uri(url))
|
||||
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
|
||||
if r.status_code is codes.see_other:
|
||||
method = 'GET'
|
||||
data = None
|
||||
else:
|
||||
method = self.method
|
||||
|
||||
# Do what the browsers do if strict_mode is off...
|
||||
if (not self.config.get('strict_mode')):
|
||||
|
||||
if r.status_code in (codes.moved, codes.found) and self.method == 'POST':
|
||||
method = 'GET'
|
||||
data = None
|
||||
|
||||
if (r.status_code == 303) and self.method != 'HEAD':
|
||||
method = 'GET'
|
||||
data = None
|
||||
|
||||
# Remove the cookie headers that were sent.
|
||||
headers = self.headers
|
||||
try:
|
||||
@@ -242,17 +278,19 @@ class Request(object):
|
||||
method=method,
|
||||
params=self.session.params,
|
||||
auth=self.auth,
|
||||
cookies=cookies,
|
||||
cookies=self.cookies,
|
||||
redirect=True,
|
||||
data=data,
|
||||
config=self.config,
|
||||
timeout=self.timeout,
|
||||
_poolmanager=self._poolmanager,
|
||||
proxies = self.proxies,
|
||||
verify = self.verify
|
||||
proxies=self.proxies,
|
||||
verify=self.verify,
|
||||
session=self.session,
|
||||
cert=self.cert
|
||||
)
|
||||
|
||||
request.send()
|
||||
cookies.update(request.response.cookies)
|
||||
r = request.response
|
||||
self.cookies.update(r.cookies)
|
||||
|
||||
@@ -262,7 +300,6 @@ class Request(object):
|
||||
self.response.request = self
|
||||
self.response.cookies.update(self.cookies)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _encode_params(data):
|
||||
"""Encode parameters in a piece of data.
|
||||
@@ -275,16 +312,19 @@ class Request(object):
|
||||
returns it twice.
|
||||
"""
|
||||
|
||||
if hasattr(data, '__iter__'):
|
||||
if isinstance(data, bytes):
|
||||
return data, data
|
||||
|
||||
if hasattr(data, '__iter__') and not isinstance(data, str):
|
||||
data = dict(data)
|
||||
|
||||
if hasattr(data, 'items'):
|
||||
result = []
|
||||
for k, vs in data.items():
|
||||
for k, vs in list(data.items()):
|
||||
for v in isinstance(vs, list) and vs or [vs]:
|
||||
result.append((k.encode('utf-8') if isinstance(k, unicode) else k,
|
||||
v.encode('utf-8') if isinstance(v, unicode) else v))
|
||||
return result, urllib.urlencode(result, doseq=True)
|
||||
result.append((k.encode('utf-8') if isinstance(k, str) else k,
|
||||
v.encode('utf-8') if isinstance(v, str) else v))
|
||||
return result, urlencode(result, doseq=True)
|
||||
else:
|
||||
return data, data
|
||||
|
||||
@@ -295,28 +335,49 @@ class Request(object):
|
||||
if not self.url:
|
||||
raise URLRequired()
|
||||
|
||||
url = self.url
|
||||
|
||||
# Support for unicode domain names and paths.
|
||||
scheme, netloc, path, params, query, fragment = urlparse(self.url)
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
||||
|
||||
if not scheme:
|
||||
raise ValueError()
|
||||
raise MissingSchema("Invalid URL %r: No schema supplied" % url)
|
||||
|
||||
netloc = netloc.encode('idna')
|
||||
if not scheme in SCHEMAS:
|
||||
raise InvalidSchema("Invalid scheme %r" % scheme)
|
||||
|
||||
if isinstance(path, unicode):
|
||||
path = path.encode('utf-8')
|
||||
netloc = netloc.encode('idna').decode('utf-8')
|
||||
|
||||
path = requote_path(path)
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
url = str(urlunparse([ scheme, netloc, path, params, query, fragment ]))
|
||||
|
||||
if is_py2:
|
||||
if isinstance(scheme, str):
|
||||
scheme = scheme.encode('utf-8')
|
||||
if isinstance(netloc, str):
|
||||
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):
|
||||
fragment = fragment.encode('utf-8')
|
||||
|
||||
url = (urlunparse([scheme, netloc, path, params, query, fragment]))
|
||||
|
||||
if self._enc_params:
|
||||
if urlparse(url).query:
|
||||
return '%s&%s' % (url, self._enc_params)
|
||||
url = '%s&%s' % (url, self._enc_params)
|
||||
else:
|
||||
return '%s?%s' % (url, self._enc_params)
|
||||
else:
|
||||
return url
|
||||
url = '%s?%s' % (url, self._enc_params)
|
||||
|
||||
if self.config.get('encode_uri', True):
|
||||
url = requote_uri(url)
|
||||
|
||||
return url
|
||||
|
||||
@property
|
||||
def path_url(self):
|
||||
@@ -333,6 +394,7 @@ class Request(object):
|
||||
path = p.path
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
url.append(path)
|
||||
|
||||
query = p.query
|
||||
@@ -342,10 +404,13 @@ class Request(object):
|
||||
|
||||
return ''.join(url)
|
||||
|
||||
def register_hook(self, event, hook):
|
||||
"""Properly register a hook."""
|
||||
|
||||
return self.hooks[event].append(hook)
|
||||
|
||||
def send(self, anyway=False, prefetch=False):
|
||||
"""Sends the request. Returns True of successful, false if not.
|
||||
"""Sends the request. Returns True of successful, False if not.
|
||||
If there was an HTTPError during transmission,
|
||||
self.response.status_code will contain the HTTPError code.
|
||||
|
||||
@@ -370,14 +435,14 @@ class Request(object):
|
||||
|
||||
# Multi-part file uploads.
|
||||
if self.files:
|
||||
if not isinstance(self.data, basestring):
|
||||
if not isinstance(self.data, str):
|
||||
|
||||
try:
|
||||
fields = self.data.copy()
|
||||
except AttributeError:
|
||||
fields = dict(self.data)
|
||||
|
||||
for (k, v) in self.files.items():
|
||||
for (k, v) in list(self.files.items()):
|
||||
# support for explicit filename
|
||||
if isinstance(v, (tuple, list)):
|
||||
fn, fp = v
|
||||
@@ -394,7 +459,7 @@ class Request(object):
|
||||
if self.data:
|
||||
|
||||
body = self._enc_data
|
||||
if isinstance(self.data, basestring):
|
||||
if isinstance(self.data, str):
|
||||
content_type = None
|
||||
else:
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
@@ -403,6 +468,10 @@ class Request(object):
|
||||
if (content_type) and (not 'content-type' in self.headers):
|
||||
self.headers['Content-Type'] = content_type
|
||||
|
||||
# Use .netrc auth if none was provided.
|
||||
if not self.auth and self.config.get('trust_env'):
|
||||
self.auth = get_netrc_auth(url)
|
||||
|
||||
if self.auth:
|
||||
if isinstance(self.auth, tuple) and len(self.auth) == 2:
|
||||
# special-case basic HTTP auth
|
||||
@@ -440,13 +509,12 @@ class Request(object):
|
||||
if self.verify is not True:
|
||||
cert_loc = self.verify
|
||||
|
||||
|
||||
# Look for configuration.
|
||||
if not cert_loc:
|
||||
if not cert_loc and self.config.get('trust_env'):
|
||||
cert_loc = os.environ.get('REQUESTS_CA_BUNDLE')
|
||||
|
||||
# Curl compatiblity.
|
||||
if not cert_loc:
|
||||
if not cert_loc and self.config.get('trust_env'):
|
||||
cert_loc = os.environ.get('CURL_CA_BUNDLE')
|
||||
|
||||
# Use the awesome certifi list.
|
||||
@@ -455,6 +523,16 @@ class Request(object):
|
||||
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
conn.ca_certs = cert_loc
|
||||
else:
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
|
||||
if self.cert and self.verify:
|
||||
if len(self.cert) == 2:
|
||||
conn.cert_file = self.cert[0]
|
||||
conn.key_file = self.cert[1]
|
||||
else:
|
||||
conn.cert_file = self.cert
|
||||
|
||||
if not self.sent or anyway:
|
||||
|
||||
@@ -464,8 +542,8 @@ class Request(object):
|
||||
if 'cookie' not in self.headers:
|
||||
|
||||
# Simple cookie with our dict.
|
||||
c = oreos.monkeys.SimpleCookie()
|
||||
for (k, v) in self.cookies.items():
|
||||
c = SimpleCookie()
|
||||
for (k, v) in list(self.cookies.items()):
|
||||
c[k] = v
|
||||
|
||||
# Turn it into a header.
|
||||
@@ -474,6 +552,10 @@ class Request(object):
|
||||
# Attach Cookie header to request.
|
||||
self.headers['Cookie'] = cookie_header
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', self.hooks, self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
try:
|
||||
# The inner try .. except re-raises certain exceptions as
|
||||
# internal exception types; the outer suppresses exceptions
|
||||
@@ -494,16 +576,16 @@ class Request(object):
|
||||
)
|
||||
self.sent = True
|
||||
|
||||
except MaxRetryError, e:
|
||||
except MaxRetryError as e:
|
||||
raise ConnectionError(e)
|
||||
|
||||
except (_SSLError, _HTTPError), e:
|
||||
except (_SSLError, _HTTPError) as e:
|
||||
if self.verify and isinstance(e, _SSLError):
|
||||
raise SSLError(e)
|
||||
|
||||
raise Timeout('Request timed out.')
|
||||
|
||||
except RequestException, e:
|
||||
except RequestException as e:
|
||||
if self.config.get('safe_mode', False):
|
||||
# In safe mode, catch the exception and attach it to
|
||||
# a blank urllib3.HTTPResponse object.
|
||||
@@ -526,6 +608,9 @@ class Request(object):
|
||||
# Save the response.
|
||||
self.response.content
|
||||
|
||||
if self.config.get('danger_mode'):
|
||||
self.response.raise_for_status()
|
||||
|
||||
return self.sent
|
||||
|
||||
|
||||
@@ -575,10 +660,13 @@ class Response(object):
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = {}
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
return self.ok
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
return self.ok
|
||||
@@ -587,12 +675,11 @@ class Response(object):
|
||||
def ok(self):
|
||||
try:
|
||||
self.raise_for_status()
|
||||
except HTTPError:
|
||||
except RequestException:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
|
||||
def iter_content(self, chunk_size=10 * 1024, 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
|
||||
@@ -611,77 +698,43 @@ class Response(object):
|
||||
yield chunk
|
||||
self._content_consumed = True
|
||||
|
||||
gen = generate()
|
||||
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
gen = stream_decode_gzip(gen)
|
||||
|
||||
if decode_unicode is None:
|
||||
decode_unicode = self.config.get('decode_unicode')
|
||||
gen = stream_untransfer(generate(), self)
|
||||
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
|
||||
return gen
|
||||
|
||||
|
||||
def iter_lines(self, newlines=None, decode_unicode=None):
|
||||
def iter_lines(self, chunk_size=10 * 1024, 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.
|
||||
|
||||
:param newlines: a collection of bytes to seperate lines with.
|
||||
"""
|
||||
|
||||
if newlines is None:
|
||||
newlines = ('\r', '\n', '\r\n')
|
||||
pending = None
|
||||
|
||||
if self._content_consumed:
|
||||
raise RuntimeError(
|
||||
'The content for this response was already consumed'
|
||||
)
|
||||
for chunk in self.iter_content(
|
||||
chunk_size=chunk_size,
|
||||
decode_unicode=decode_unicode):
|
||||
|
||||
def generate():
|
||||
if self.raw is not None:
|
||||
chunk = []
|
||||
if pending is not None:
|
||||
chunk = pending + chunk
|
||||
lines = chunk.splitlines()
|
||||
|
||||
while 1:
|
||||
c = self.raw.read(1)
|
||||
if not c:
|
||||
break
|
||||
if lines[-1][-1] == chunk[-1]:
|
||||
pending = lines.pop()
|
||||
else:
|
||||
pending = None
|
||||
|
||||
if c in newlines:
|
||||
yield ''.join(chunk)
|
||||
chunk = []
|
||||
else:
|
||||
chunk.append(c)
|
||||
|
||||
# Yield the remainder, in case the response
|
||||
# did not terminate with a newline
|
||||
if chunk:
|
||||
yield ''.join(chunk)
|
||||
|
||||
self._content_consumed = True
|
||||
|
||||
gen = generate()
|
||||
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
gen = stream_decode_gzip(gen)
|
||||
|
||||
if decode_unicode is None:
|
||||
decode_unicode = self.config.get('decode_unicode')
|
||||
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
|
||||
return gen
|
||||
for line in lines:
|
||||
yield line
|
||||
|
||||
if pending is not None:
|
||||
yield pending
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""Content of the response, in bytes or unicode
|
||||
(if available).
|
||||
"""
|
||||
"""Content of the response, in bytes."""
|
||||
|
||||
if self._content is None:
|
||||
# Read the contents.
|
||||
@@ -690,53 +743,75 @@ class Response(object):
|
||||
raise RuntimeError(
|
||||
'The content for this response was already consumed')
|
||||
|
||||
self._content = self.raw.read()
|
||||
if self.status_code is 0:
|
||||
self._content = None
|
||||
else:
|
||||
self._content = bytes().join(self.iter_content()) or bytes()
|
||||
|
||||
except AttributeError:
|
||||
self._content = None
|
||||
|
||||
content = self._content
|
||||
|
||||
# Decode GZip'd content.
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
try:
|
||||
content = decode_gzip(self._content)
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
# Decode unicode content.
|
||||
if self.config.get('decode_unicode'):
|
||||
|
||||
# Try charset from content-type
|
||||
|
||||
if self.encoding:
|
||||
try:
|
||||
content = unicode(content, self.encoding)
|
||||
except UnicodeError:
|
||||
pass
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
content = unicode(content, self.encoding, errors='replace')
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self._content_consumed = True
|
||||
return self._content
|
||||
|
||||
def _detected_encoding(self):
|
||||
try:
|
||||
detected = chardet.detect(self.content) or {}
|
||||
return detected.get('encoding')
|
||||
|
||||
# Trust that chardet isn't available or something went terribly wrong.
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Content of the response, in unicode.
|
||||
|
||||
if Response.encoding is None and chardet module is available, encoding
|
||||
will be guessed.
|
||||
"""
|
||||
|
||||
# Try charset from content-type
|
||||
content = None
|
||||
encoding = self.encoding
|
||||
|
||||
# Fallback to auto-detected encoding if chardet is available.
|
||||
if self.encoding is None:
|
||||
encoding = self._detected_encoding()
|
||||
|
||||
# Decode unicode from given encoding.
|
||||
try:
|
||||
content = str(self.content, encoding, errors='replace')
|
||||
except LookupError:
|
||||
# A LookupError is raised if the encoding was not found which could
|
||||
# indicate a misspelling or similar mistake.
|
||||
#
|
||||
# So we try blindly encoding.
|
||||
content = str(self.content, errors='replace')
|
||||
except (UnicodeError, TypeError):
|
||||
pass
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def raise_for_status(self):
|
||||
def raise_for_status(self, allow_redirects=True):
|
||||
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""
|
||||
|
||||
if self.error:
|
||||
raise self.error
|
||||
|
||||
if (self.status_code >= 300) and (self.status_code < 400):
|
||||
raise HTTPError('%s Redirection' % self.status_code)
|
||||
if (self.status_code >= 300) and (self.status_code < 400) and not allow_redirects:
|
||||
http_error = HTTPError('%s Redirection' % self.status_code)
|
||||
http_error.response = self
|
||||
raise http_error
|
||||
|
||||
elif (self.status_code >= 400) and (self.status_code < 500):
|
||||
raise HTTPError('%s Client Error' % self.status_code)
|
||||
http_error = HTTPError('%s Client Error' % self.status_code)
|
||||
http_error.response = self
|
||||
raise http_error
|
||||
|
||||
|
||||
elif (self.status_code >= 500) and (self.status_code < 600):
|
||||
raise HTTPError('%s Server Error' % self.status_code)
|
||||
|
||||
|
||||
http_error = HTTPError('%s Server Error' % self.status_code)
|
||||
http_error.response = self
|
||||
raise http_error
|
||||
|
||||
@@ -249,10 +249,13 @@ class CookieError(Exception):
|
||||
# quoted with a preceeding '\' slash.
|
||||
#
|
||||
# These are taken from RFC2068 and RFC2109.
|
||||
# _RFC2965Forbidden is the list of forbidden chars we accept anyway
|
||||
# _LegalChars is the list of chars which don't require "'s
|
||||
# _Translator hash-table for fast quoting
|
||||
#
|
||||
_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~[]_"
|
||||
_RFC2965Forbidden = "[]:{}="
|
||||
_LegalChars = ( string.ascii_letters + string.digits +
|
||||
"!#$%&'*+-.^_`|~_@" + _RFC2965Forbidden )
|
||||
_Translator = {
|
||||
'\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
|
||||
'\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
|
||||
@@ -318,7 +321,7 @@ _Translator = {
|
||||
'\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
|
||||
}
|
||||
|
||||
_idmap = ''.join(chr(x) for x in xrange(256))
|
||||
_idmap = ''.join(chr(x) for x in range(256))
|
||||
|
||||
def _quote(str, LegalChars=_LegalChars,
|
||||
idmap=_idmap, translate=string.translate):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
oreos.sructures
|
||||
~~~~~~~~~~~~~~~
|
||||
oreos.structures
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The plastic blue packaging.
|
||||
|
||||
@@ -362,7 +362,7 @@ class MultiDict(TypeConversionDict):
|
||||
"""
|
||||
try:
|
||||
return dict.pop(self, key)[0]
|
||||
except KeyError, e:
|
||||
except KeyError as e:
|
||||
if default is not _missing:
|
||||
return default
|
||||
raise KeyError(str(e))
|
||||
@@ -372,7 +372,7 @@ class MultiDict(TypeConversionDict):
|
||||
try:
|
||||
item = dict.popitem(self)
|
||||
return (item[0], item[1][0])
|
||||
except KeyError, e:
|
||||
except KeyError as e:
|
||||
raise KeyError(str(e))
|
||||
|
||||
def poplist(self, key):
|
||||
@@ -389,7 +389,7 @@ class MultiDict(TypeConversionDict):
|
||||
"""Pop a ``(key, list)`` tuple from the dict."""
|
||||
try:
|
||||
return dict.popitem(self)
|
||||
except KeyError, e:
|
||||
except KeyError as e:
|
||||
raise KeyError(str(e))
|
||||
|
||||
def __copy__(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/__init__.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -10,26 +10,20 @@ urllib3 - Thread-safe connection pooling and re-using.
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = '1.0.2'
|
||||
__version__ = '1.3'
|
||||
|
||||
|
||||
from .connectionpool import (
|
||||
HTTPConnectionPool,
|
||||
HTTPSConnectionPool,
|
||||
connection_from_url,
|
||||
get_host,
|
||||
make_headers)
|
||||
|
||||
|
||||
from .exceptions import (
|
||||
HTTPError,
|
||||
MaxRetryError,
|
||||
SSLError,
|
||||
TimeoutError)
|
||||
connection_from_url
|
||||
)
|
||||
|
||||
from . import exceptions
|
||||
from .filepost import encode_multipart_formdata
|
||||
from .poolmanager import PoolManager, ProxyManager, proxy_from_url
|
||||
from .response import HTTPResponse
|
||||
from .filepost import encode_multipart_formdata
|
||||
from .util import make_headers, get_host
|
||||
|
||||
|
||||
# Set default logging handler to avoid "No handler found" warnings.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/_collections.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/connectionpool.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -7,37 +7,64 @@
|
||||
import logging
|
||||
import socket
|
||||
|
||||
|
||||
from httplib import HTTPConnection, HTTPSConnection, HTTPException
|
||||
from Queue import Queue, Empty, Full
|
||||
from select import select
|
||||
from socket import error as SocketError, timeout as SocketTimeout
|
||||
|
||||
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||
try: # Python 3
|
||||
from http.client import HTTPConnection, HTTPException
|
||||
from http.client import HTTP_PORT, HTTPS_PORT
|
||||
except ImportError:
|
||||
from httplib import HTTPConnection, HTTPException
|
||||
from httplib import HTTP_PORT, HTTPS_PORT
|
||||
|
||||
try: # Python 3
|
||||
from queue import LifoQueue, Empty, Full
|
||||
except ImportError:
|
||||
from Queue import LifoQueue, Empty, Full
|
||||
|
||||
|
||||
try: # Compiled with SSL?
|
||||
HTTPSConnection = object
|
||||
BaseSSLError = None
|
||||
ssl = None
|
||||
|
||||
try: # Python 3
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
from httplib import HTTPSConnection
|
||||
|
||||
try:
|
||||
import ssl
|
||||
BaseSSLError = ssl.SSLError
|
||||
except ImportError:
|
||||
ssl = None
|
||||
BaseSSLError = None
|
||||
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
from .request import RequestMethods
|
||||
from .response import HTTPResponse
|
||||
from .util import get_host, is_connection_dropped
|
||||
from .exceptions import (
|
||||
SSLError,
|
||||
MaxRetryError,
|
||||
TimeoutError,
|
||||
HostChangedError,
|
||||
EmptyPoolError,
|
||||
HostChangedError,
|
||||
MaxRetryError,
|
||||
SSLError,
|
||||
TimeoutError,
|
||||
)
|
||||
|
||||
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||
from .packages import six
|
||||
|
||||
|
||||
xrange = six.moves.xrange
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_Default = object()
|
||||
|
||||
port_by_scheme = {
|
||||
'http': HTTP_PORT,
|
||||
'https': HTTPS_PORT,
|
||||
}
|
||||
|
||||
|
||||
## Connection objects (extension of httplib)
|
||||
|
||||
@@ -74,6 +101,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
if self.ca_certs:
|
||||
match_hostname(self.sock.getpeercert(), self.host)
|
||||
|
||||
|
||||
## Pool objects
|
||||
|
||||
class ConnectionPool(object):
|
||||
@@ -81,7 +109,17 @@ class ConnectionPool(object):
|
||||
Base class for all connection pools, such as
|
||||
:class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.
|
||||
"""
|
||||
pass
|
||||
|
||||
scheme = None
|
||||
QueueCls = LifoQueue
|
||||
|
||||
def __init__(self, host, port=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def __str__(self):
|
||||
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
||||
self.host, self.port)
|
||||
|
||||
|
||||
class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
@@ -127,11 +165,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
def __init__(self, host, port=None, strict=False, timeout=None, maxsize=1,
|
||||
block=False, headers=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
super(HTTPConnectionPool, self).__init__(host, port)
|
||||
|
||||
self.strict = strict
|
||||
self.timeout = timeout
|
||||
self.pool = Queue(maxsize)
|
||||
self.pool = self.QueueCls(maxsize)
|
||||
self.block = block
|
||||
self.headers = headers or {}
|
||||
|
||||
@@ -169,14 +207,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
conn = self.pool.get(block=self.block, timeout=timeout)
|
||||
|
||||
# If this is a persistent connection, check if it got disconnected
|
||||
if conn and conn.sock and select([conn.sock], [], [], 0.0)[0]:
|
||||
# Either data is buffered (bad), or the connection is dropped.
|
||||
if conn and is_connection_dropped(conn):
|
||||
log.info("Resetting dropped connection: %s" % self.host)
|
||||
conn.close()
|
||||
|
||||
except Empty:
|
||||
if self.block:
|
||||
raise EmptyPoolError("Pool reached maximum size and no more "
|
||||
raise EmptyPoolError(self,
|
||||
"Pool reached maximum size and no more "
|
||||
"connections are allowed.")
|
||||
pass # Oh well, we'll create a new connection then
|
||||
|
||||
@@ -213,9 +251,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
timeout = self.timeout
|
||||
|
||||
conn.timeout = timeout # This only does anything in Py26+
|
||||
|
||||
conn.request(method, url, **httplib_request_kw)
|
||||
conn.sock.settimeout(timeout)
|
||||
|
||||
# Set timeout
|
||||
sock = getattr(conn, 'sock', False) # AppEngine doesn't have sock attr.
|
||||
if sock:
|
||||
sock.settimeout(timeout)
|
||||
|
||||
httplib_response = conn.getresponse()
|
||||
|
||||
log.debug("\"%s %s %s\" %s %s" %
|
||||
@@ -229,11 +271,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
def is_same_host(self, url):
|
||||
"""
|
||||
Check if the given ``url`` is a member of the same host as this
|
||||
conncetion pool.
|
||||
connection pool.
|
||||
"""
|
||||
# TODO: Add optional support for socket.gethostbyname checking.
|
||||
scheme, host, port = get_host(url)
|
||||
|
||||
if self.port and not port:
|
||||
# Use explicit default port for comparison when none is given.
|
||||
port = port_by_scheme.get(scheme)
|
||||
|
||||
return (url.startswith('/') or
|
||||
get_host(url) == (self.scheme, self.host, self.port))
|
||||
(scheme, host, port) == (self.scheme, self.host, self.port))
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None, retries=3,
|
||||
redirect=True, assert_same_host=True, timeout=_Default,
|
||||
@@ -246,7 +294,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
.. note::
|
||||
|
||||
More commonly, it's appropriate to use a convenience method provided
|
||||
by :class:`.RequestMethods`, such as :meth:`.request`.
|
||||
by :class:`.RequestMethods`, such as :meth:`request`.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -306,7 +354,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
headers = self.headers
|
||||
|
||||
if retries < 0:
|
||||
raise MaxRetryError("Max retries exceeded for url: %s" % url)
|
||||
raise MaxRetryError(self, url)
|
||||
|
||||
if timeout is _Default:
|
||||
timeout = self.timeout
|
||||
@@ -320,8 +368,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
if self.port:
|
||||
host = "%s:%d" % (host, self.port)
|
||||
|
||||
raise HostChangedError("Connection pool with host '%s' tried to "
|
||||
"open a foreign host: %s" % (host, url))
|
||||
raise HostChangedError(self, url, retries - 1)
|
||||
|
||||
conn = None
|
||||
|
||||
@@ -352,27 +399,29 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
# ``response.release_conn()`` is called (implicitly by
|
||||
# ``response.read()``)
|
||||
|
||||
except (Empty), e:
|
||||
except Empty as e:
|
||||
# Timed out by queue
|
||||
raise TimeoutError("Request timed out. (pool_timeout=%s)" %
|
||||
raise TimeoutError(self, "Request timed out. (pool_timeout=%s)" %
|
||||
pool_timeout)
|
||||
|
||||
except (SocketTimeout), e:
|
||||
except SocketTimeout as e:
|
||||
# Timed out by socket
|
||||
raise TimeoutError("Request timed out. (timeout=%s)" %
|
||||
raise TimeoutError(self, "Request timed out. (timeout=%s)" %
|
||||
timeout)
|
||||
|
||||
except (BaseSSLError), e:
|
||||
except BaseSSLError as e:
|
||||
# SSL certificate error
|
||||
raise SSLError(e)
|
||||
|
||||
except (CertificateError), e:
|
||||
except CertificateError as e:
|
||||
# Name mismatch
|
||||
raise SSLError(e)
|
||||
|
||||
except (HTTPException, SocketError), e:
|
||||
|
||||
except (HTTPException, SocketError) as e:
|
||||
# Connection broken, discard. It will be replaced next _get_conn().
|
||||
conn = None
|
||||
# This is necessary so we can access e below
|
||||
err = e
|
||||
|
||||
finally:
|
||||
if conn and release_conn:
|
||||
@@ -381,19 +430,16 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
if not conn:
|
||||
log.warn("Retrying (%d attempts remain) after connection "
|
||||
"broken by '%r': %s" % (retries, e, url))
|
||||
"broken by '%r': %s" % (retries, err, url))
|
||||
return self.urlopen(method, url, body, headers, retries - 1,
|
||||
redirect, assert_same_host) # Try again
|
||||
|
||||
# Handle redirection
|
||||
if (redirect and
|
||||
response.status in [301, 302, 303, 307] and
|
||||
'location' in response.headers): # Redirect, retry
|
||||
log.info("Redirecting %s -> %s" %
|
||||
(url, response.headers.get('location')))
|
||||
return self.urlopen(method, response.headers.get('location'), body,
|
||||
headers, retries - 1, redirect,
|
||||
assert_same_host)
|
||||
# Handle redirect?
|
||||
redirect_location = redirect and response.get_redirect_location()
|
||||
if redirect_location:
|
||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
||||
return self.urlopen(method, redirect_location, body, headers,
|
||||
retries - 1, redirect, assert_same_host)
|
||||
|
||||
return response
|
||||
|
||||
@@ -435,7 +481,11 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
log.info("Starting new HTTPS connection (%d): %s"
|
||||
% (self.num_connections, self.host))
|
||||
|
||||
if not ssl:
|
||||
if not ssl: # Platform-specific: Python compiled without +ssl
|
||||
if not HTTPSConnection or HTTPSConnection is object:
|
||||
raise SSLError("Can't connect to HTTPS URL because the SSL "
|
||||
"module is not available.")
|
||||
|
||||
return HTTPSConnection(host=self.host, port=self.port)
|
||||
|
||||
connection = VerifiedHTTPSConnection(host=self.host, port=self.port)
|
||||
@@ -444,87 +494,6 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||
return connection
|
||||
|
||||
|
||||
## Helpers
|
||||
|
||||
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
|
||||
basic_auth=None):
|
||||
"""
|
||||
Shortcuts for generating request headers.
|
||||
|
||||
:param keep_alive:
|
||||
If ``True``, adds 'connection: keep-alive' header.
|
||||
|
||||
:param accept_encoding:
|
||||
Can be a boolean, list, or string.
|
||||
``True`` translates to 'gzip,deflate'.
|
||||
List will get joined by comma.
|
||||
String will be used as provided.
|
||||
|
||||
:param user_agent:
|
||||
String representing the user-agent you want, such as
|
||||
"python-urllib3/0.6"
|
||||
|
||||
:param basic_auth:
|
||||
Colon-separated username:password string for 'authorization: basic ...'
|
||||
auth header.
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> make_headers(keep_alive=True, user_agent="Batman/1.0")
|
||||
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
|
||||
>>> make_headers(accept_encoding=True)
|
||||
{'accept-encoding': 'gzip,deflate'}
|
||||
"""
|
||||
headers = {}
|
||||
if accept_encoding:
|
||||
if isinstance(accept_encoding, str):
|
||||
pass
|
||||
elif isinstance(accept_encoding, list):
|
||||
accept_encoding = ','.join(accept_encoding)
|
||||
else:
|
||||
accept_encoding = 'gzip,deflate'
|
||||
headers['accept-encoding'] = accept_encoding
|
||||
|
||||
if user_agent:
|
||||
headers['user-agent'] = user_agent
|
||||
|
||||
if keep_alive:
|
||||
headers['connection'] = 'keep-alive'
|
||||
|
||||
if basic_auth:
|
||||
headers['authorization'] = 'Basic ' + \
|
||||
basic_auth.encode('base64').strip()
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
def get_host(url):
|
||||
"""
|
||||
Given a url, return its scheme, host and port (None if it's not there).
|
||||
|
||||
For example: ::
|
||||
|
||||
>>> get_host('http://google.com/mail/')
|
||||
('http', 'google.com', None)
|
||||
>>> get_host('google.com:80')
|
||||
('http', 'google.com', 80)
|
||||
"""
|
||||
# This code is actually similar to urlparse.urlsplit, but much
|
||||
# simplified for our needs.
|
||||
port = None
|
||||
scheme = 'http'
|
||||
if '://' in url:
|
||||
scheme, url = url.split('://', 1)
|
||||
if '/' in url:
|
||||
url, _path = url.split('/', 1)
|
||||
if '@' in url:
|
||||
_auth, url = url.split('@', 1)
|
||||
if ':' in url:
|
||||
url, port = url.split(':', 1)
|
||||
port = int(port)
|
||||
return scheme, url, port
|
||||
|
||||
|
||||
def connection_from_url(url, **kw):
|
||||
"""
|
||||
Given a url, return an :class:`.ConnectionPool` instance of its host.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/contrib/ntlmpool.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -10,7 +10,10 @@ NTLM authenticating pool, contributed by erikcederstran
|
||||
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
|
||||
"""
|
||||
|
||||
import httplib
|
||||
try:
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
from httplib import HTTPSConnection
|
||||
from logging import getLogger
|
||||
from ntlm import ntlm
|
||||
|
||||
@@ -53,7 +56,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
|
||||
req_header = 'Authorization'
|
||||
resp_header = 'www-authenticate'
|
||||
|
||||
conn = httplib.HTTPSConnection(host=self.host, port=self.port)
|
||||
conn = HTTPSConnection(host=self.host, port=self.port)
|
||||
|
||||
# Send negotiation message
|
||||
headers[req_header] = (
|
||||
|
||||
@@ -1,35 +1,67 @@
|
||||
# urllib3/exceptions.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
## Exceptions
|
||||
|
||||
## Base Exceptions
|
||||
|
||||
class HTTPError(Exception):
|
||||
"Base exception used by this module."
|
||||
pass
|
||||
|
||||
|
||||
class SSLError(Exception):
|
||||
class PoolError(HTTPError):
|
||||
"Base exception for errors caused within a pool."
|
||||
def __init__(self, pool, message):
|
||||
self.pool = pool
|
||||
HTTPError.__init__(self, "%s: %s" % (pool, message))
|
||||
|
||||
|
||||
class SSLError(HTTPError):
|
||||
"Raised when SSL certificate fails in an HTTPS connection."
|
||||
pass
|
||||
|
||||
|
||||
class MaxRetryError(HTTPError):
|
||||
## Leaf Exceptions
|
||||
|
||||
class MaxRetryError(PoolError):
|
||||
"Raised when the maximum number of retries is exceeded."
|
||||
pass
|
||||
|
||||
def __init__(self, pool, url):
|
||||
message = "Max retries exceeded with url: %s" % url
|
||||
PoolError.__init__(self, pool, message)
|
||||
|
||||
self.url = url
|
||||
|
||||
|
||||
class TimeoutError(HTTPError):
|
||||
class HostChangedError(PoolError):
|
||||
"Raised when an existing pool gets a request for a foreign host."
|
||||
|
||||
def __init__(self, pool, url, retries=3):
|
||||
message = "Tried to open a foreign host with url: %s" % url
|
||||
PoolError.__init__(self, pool, message)
|
||||
|
||||
self.url = url
|
||||
self.retries = retries
|
||||
|
||||
|
||||
class TimeoutError(PoolError):
|
||||
"Raised when a socket timeout occurs."
|
||||
pass
|
||||
|
||||
|
||||
class HostChangedError(HTTPError):
|
||||
"Raised when an existing pool gets a request for a foreign host."
|
||||
pass
|
||||
|
||||
class EmptyPoolError(HTTPError):
|
||||
class EmptyPoolError(PoolError):
|
||||
"Raised when a pool runs out of connections and no more are allowed."
|
||||
pass
|
||||
|
||||
|
||||
class LocationParseError(ValueError, HTTPError):
|
||||
"Raised when get_host or similar fails to parse the URL input."
|
||||
|
||||
def __init__(self, location):
|
||||
message = "Failed to parse: %s" % location
|
||||
super(LocationParseError, self).__init__(self, message)
|
||||
|
||||
self.location = location
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# urllib3/filepost.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import codecs
|
||||
import mimetools
|
||||
import mimetypes
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
from mimetools import choose_boundary
|
||||
except ImportError:
|
||||
from StringIO import StringIO # pylint: disable-msg=W0404
|
||||
from .packages.mimetools_choose_boundary import choose_boundary
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .packages import six
|
||||
from .packages.six import b
|
||||
|
||||
writer = codecs.lookup('utf-8')[3]
|
||||
|
||||
@@ -21,51 +24,65 @@ def get_content_type(filename):
|
||||
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
|
||||
|
||||
def iter_fields(fields):
|
||||
"""
|
||||
Iterate over fields.
|
||||
|
||||
Supports list of (k, v) tuples and dicts.
|
||||
"""
|
||||
if isinstance(fields, dict):
|
||||
return ((k, v) for k, v in six.iteritems(fields))
|
||||
|
||||
return ((k, v) for k, v in fields)
|
||||
|
||||
|
||||
def encode_multipart_formdata(fields, boundary=None):
|
||||
"""
|
||||
Encode a dictionary of ``fields`` using the multipart/form-data mime format.
|
||||
|
||||
:param fields:
|
||||
Dictionary of fields. The key is treated as the field name, and the
|
||||
value as the body of the form-data. If the value is a tuple of two
|
||||
elements, then the first element is treated as the filename of the
|
||||
form-data section.
|
||||
Dictionary of fields or list of (key, value) field tuples. The key is
|
||||
treated as the field name, and the value as the body of the form-data
|
||||
bytes. If the value is a tuple of two elements, then the first element
|
||||
is treated as the filename of the form-data section.
|
||||
|
||||
Field names and filenames must be unicode.
|
||||
|
||||
:param boundary:
|
||||
If not specified, then a random boundary will be generated using
|
||||
:func:`mimetools.choose_boundary`.
|
||||
"""
|
||||
body = StringIO()
|
||||
body = BytesIO()
|
||||
if boundary is None:
|
||||
boundary = mimetools.choose_boundary()
|
||||
boundary = choose_boundary()
|
||||
|
||||
for fieldname, value in fields.iteritems():
|
||||
body.write('--%s\r\n' % (boundary))
|
||||
for fieldname, value in iter_fields(fields):
|
||||
body.write(b('--%s\r\n' % (boundary)))
|
||||
|
||||
if isinstance(value, tuple):
|
||||
filename, data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"; '
|
||||
'filename="%s"\r\n' % (fieldname, filename))
|
||||
body.write('Content-Type: %s\r\n\r\n' %
|
||||
(get_content_type(filename)))
|
||||
body.write(b('Content-Type: %s\r\n\r\n' %
|
||||
(get_content_type(filename))))
|
||||
else:
|
||||
data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
|
||||
% (fieldname))
|
||||
body.write('Content-Type: text/plain\r\n\r\n')
|
||||
body.write(b'Content-Type: text/plain\r\n\r\n')
|
||||
|
||||
if isinstance(data, int):
|
||||
data = str(data) # Backwards compatibility
|
||||
|
||||
if isinstance(data, unicode):
|
||||
if isinstance(data, six.text_type):
|
||||
writer(body).write(data)
|
||||
else:
|
||||
body.write(data)
|
||||
|
||||
body.write('\r\n')
|
||||
body.write(b'\r\n')
|
||||
|
||||
body.write('--%s--\r\n' % (boundary))
|
||||
body.write(b('--%s--\r\n' % (boundary)))
|
||||
|
||||
content_type = 'multipart/form-data; boundary=%s' % boundary
|
||||
content_type = b('multipart/form-data; boundary=%s' % boundary)
|
||||
|
||||
return body.getvalue(), content_type
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""The function mimetools.choose_boundary() from Python 2.7, which seems to
|
||||
have disappeared in Python 3 (although email.generator._make_boundary() might
|
||||
work as a replacement?).
|
||||
|
||||
Tweaked to use lock from threading rather than thread.
|
||||
"""
|
||||
import os
|
||||
from threading import Lock
|
||||
_counter_lock = Lock()
|
||||
|
||||
_counter = 0
|
||||
def _get_next_counter():
|
||||
global _counter
|
||||
with _counter_lock:
|
||||
_counter += 1
|
||||
return _counter
|
||||
|
||||
_prefix = None
|
||||
|
||||
def choose_boundary():
|
||||
"""Return a string usable as a multipart boundary.
|
||||
|
||||
The string chosen is unique within a single program run, and
|
||||
incorporates the user id (if available), process id (if available),
|
||||
and current time. So it's very unlikely the returned string appears
|
||||
in message text, but there's no guarantee.
|
||||
|
||||
The boundary contains dots so you have to quote it in the header."""
|
||||
|
||||
global _prefix
|
||||
import time
|
||||
if _prefix is None:
|
||||
import socket
|
||||
try:
|
||||
hostid = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
hostid = '127.0.0.1'
|
||||
try:
|
||||
uid = repr(os.getuid())
|
||||
except AttributeError:
|
||||
uid = '1'
|
||||
try:
|
||||
pid = repr(os.getpid())
|
||||
except AttributeError:
|
||||
pid = '1'
|
||||
_prefix = hostid + '.' + uid + '.' + pid
|
||||
return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
|
||||
372
requests/packages/urllib3/packages/six.py
Normal file
372
requests/packages/urllib3/packages/six.py
Normal file
@@ -0,0 +1,372 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
#Copyright (c) 2010-2011 Benjamin Peterson
|
||||
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
#this software and associated documentation files (the "Software"), to deal in
|
||||
#the Software without restriction, including without limitation the rights to
|
||||
#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
#the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
#subject to the following conditions:
|
||||
|
||||
#The above copyright notice and this permission notice shall be included in all
|
||||
#copies or substantial portions of the Software.
|
||||
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.1.0"
|
||||
|
||||
|
||||
# True if we are running on Python 3.
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result)
|
||||
# This is a bit ugly, but it avoids running this again.
|
||||
delattr(tp, self.name)
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
|
||||
class _MovedItems(types.ModuleType):
|
||||
"""Lazy loading of moved objects"""
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
del attr
|
||||
|
||||
moves = sys.modules["six.moves"] = _MovedItems("moves")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
|
||||
_iterkeys = "keys"
|
||||
_itervalues = "values"
|
||||
_iteritems = "items"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
|
||||
_iterkeys = "iterkeys"
|
||||
_itervalues = "itervalues"
|
||||
_iteritems = "iteritems"
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
|
||||
advance_iterator = next
|
||||
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
|
||||
|
||||
def iterkeys(d):
|
||||
"""Return an iterator over the keys of a dictionary."""
|
||||
return getattr(d, _iterkeys)()
|
||||
|
||||
def itervalues(d):
|
||||
"""Return an iterator over the values of a dictionary."""
|
||||
return getattr(d, _itervalues)()
|
||||
|
||||
def iteritems(d):
|
||||
"""Return an iterator over the (key, value) pairs of a dictionary."""
|
||||
return getattr(d, _iteritems)()
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
def u(s):
|
||||
return s
|
||||
if sys.version_info[1] <= 1:
|
||||
def int2byte(i):
|
||||
return bytes((i,))
|
||||
else:
|
||||
# This is about 2x faster than the implementation above on 3.2+
|
||||
int2byte = operator.methodcaller("to_bytes", 1, "big")
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
int2byte = chr
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
if PY3:
|
||||
import builtins
|
||||
exec_ = getattr(builtins, "exec")
|
||||
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(builtins, "print")
|
||||
del builtins
|
||||
|
||||
else:
|
||||
def exec_(code, globs=None, locs=None):
|
||||
"""Execute code in a namespace."""
|
||||
if globs is None:
|
||||
frame = sys._getframe(1)
|
||||
globs = frame.f_globals
|
||||
if locs is None:
|
||||
locs = frame.f_locals
|
||||
del frame
|
||||
elif locs is None:
|
||||
locs = globs
|
||||
exec("""exec code in globs, locs""")
|
||||
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
|
||||
def with_metaclass(meta, base=object):
|
||||
"""Create a base class with a metaclass."""
|
||||
return meta("NewBase", (base,), {})
|
||||
@@ -1,32 +1,27 @@
|
||||
# urllib3/poolmanager.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import logging
|
||||
|
||||
from ._collections import RecentlyUsedContainer
|
||||
from .connectionpool import (
|
||||
HTTPConnectionPool, HTTPSConnectionPool,
|
||||
get_host, connection_from_url,
|
||||
)
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
from .connectionpool import get_host, connection_from_url, port_by_scheme
|
||||
from .exceptions import HostChangedError
|
||||
from .request import RequestMethods
|
||||
|
||||
|
||||
__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url']
|
||||
|
||||
|
||||
from .request import RequestMethods
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
|
||||
|
||||
pool_classes_by_scheme = {
|
||||
'http': HTTPConnectionPool,
|
||||
'https': HTTPSConnectionPool,
|
||||
}
|
||||
|
||||
port_by_scheme = {
|
||||
'http': 80,
|
||||
'https': 443,
|
||||
}
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PoolManager(RequestMethods):
|
||||
@@ -44,11 +39,11 @@ class PoolManager(RequestMethods):
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> manager = PoolManager()
|
||||
>>> manager = PoolManager(num_pools=2)
|
||||
>>> r = manager.urlopen("http://google.com/")
|
||||
>>> r = manager.urlopen("http://google.com/mail")
|
||||
>>> r = manager.urlopen("http://yahoo.com/")
|
||||
>>> len(r.pools)
|
||||
>>> len(manager.pools)
|
||||
2
|
||||
|
||||
"""
|
||||
@@ -105,7 +100,12 @@ class PoolManager(RequestMethods):
|
||||
:class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
|
||||
"""
|
||||
conn = self.connection_from_url(url)
|
||||
return conn.urlopen(method, url, assert_same_host=False, **kw)
|
||||
try:
|
||||
return conn.urlopen(method, url, **kw)
|
||||
|
||||
except HostChangedError as e:
|
||||
kw['retries'] = e.retries # Persist retries countdown
|
||||
return self.urlopen(method, e.url, **kw)
|
||||
|
||||
|
||||
class ProxyManager(RequestMethods):
|
||||
@@ -117,9 +117,19 @@ class ProxyManager(RequestMethods):
|
||||
def __init__(self, proxy_pool):
|
||||
self.proxy_pool = proxy_pool
|
||||
|
||||
def _set_proxy_headers(self, headers=None):
|
||||
headers = headers or {}
|
||||
|
||||
# Same headers are curl passes for --proxy1.0
|
||||
headers['Accept'] = '*/*'
|
||||
headers['Proxy-Connection'] = 'Keep-Alive'
|
||||
|
||||
return headers
|
||||
|
||||
def urlopen(self, method, url, **kw):
|
||||
"Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
|
||||
kw['assert_same_host'] = False
|
||||
kw['headers'] = self._set_proxy_headers(kw.get('headers'))
|
||||
return self.proxy_pool.urlopen(method, url, **kw)
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# urllib3/request.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
from urllib import urlencode
|
||||
try:
|
||||
from urllib.parse import urlencode
|
||||
except ImportError:
|
||||
from urllib import urlencode
|
||||
|
||||
from .filepost import encode_multipart_formdata
|
||||
|
||||
@@ -42,7 +44,7 @@ class RequestMethods(object):
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None,
|
||||
encode_multipart=True, multipart_boundary=None,
|
||||
**kw):
|
||||
**kw): # Abstract
|
||||
raise NotImplemented("Classes extending RequestMethods must implement "
|
||||
"their own ``urlopen`` method.")
|
||||
|
||||
@@ -124,22 +126,3 @@ class RequestMethods(object):
|
||||
|
||||
return self.urlopen(method, url, body=body, headers=headers,
|
||||
**urlopen_kw)
|
||||
|
||||
# Deprecated:
|
||||
|
||||
def get_url(self, url, fields=None, **urlopen_kw):
|
||||
"""
|
||||
.. deprecated:: 1.0
|
||||
Use :meth:`request` instead.
|
||||
"""
|
||||
return self.request_encode_url('GET', url, fields=fields,
|
||||
**urlopen_kw)
|
||||
|
||||
def post_url(self, url, fields=None, headers=None, **urlopen_kw):
|
||||
"""
|
||||
.. deprecated:: 1.0
|
||||
Use :meth:`request` instead.
|
||||
"""
|
||||
return self.request_encode_body('POST', url, fields=fields,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/response.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -8,21 +8,17 @@ import gzip
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO # pylint: disable-msg=W0404
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .exceptions import HTTPError
|
||||
from .packages.six import string_types as basestring
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def decode_gzip(data):
|
||||
gzipper = gzip.GzipFile(fileobj=StringIO(data))
|
||||
gzipper = gzip.GzipFile(fileobj=BytesIO(data))
|
||||
return gzipper.read()
|
||||
|
||||
|
||||
@@ -71,7 +67,7 @@ class HTTPResponse(object):
|
||||
self.strict = strict
|
||||
|
||||
self._decode_content = decode_content
|
||||
self._body = None
|
||||
self._body = body if body and isinstance(body, basestring) else None
|
||||
self._fp = None
|
||||
self._original_response = original_response
|
||||
|
||||
@@ -81,9 +77,22 @@ class HTTPResponse(object):
|
||||
if hasattr(body, 'read'):
|
||||
self._fp = body
|
||||
|
||||
if preload_content:
|
||||
if preload_content and not self._body:
|
||||
self._body = self.read(decode_content=decode_content)
|
||||
|
||||
def get_redirect_location(self):
|
||||
"""
|
||||
Should we redirect and where to?
|
||||
|
||||
:returns: Truthy redirect location string if we got a redirect status
|
||||
code and valid location. ``None`` if redirect status and no
|
||||
location. ``False`` if not a redirect status code.
|
||||
"""
|
||||
if self.status in [301, 302, 303, 307]:
|
||||
return self.headers.get('location')
|
||||
|
||||
return False
|
||||
|
||||
def release_conn(self):
|
||||
if not self._pool or not self._connection:
|
||||
return
|
||||
@@ -98,10 +107,9 @@ class HTTPResponse(object):
|
||||
return self._body
|
||||
|
||||
if self._fp:
|
||||
return self.read(decode_content=self._decode_content,
|
||||
cache_content=True)
|
||||
return self.read(cache_content=True)
|
||||
|
||||
def read(self, amt=None, decode_content=True, cache_content=False):
|
||||
def read(self, amt=None, decode_content=None, cache_content=False):
|
||||
"""
|
||||
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
|
||||
parameters: ``decode_content`` and ``cache_content``.
|
||||
@@ -124,22 +132,22 @@ class HTTPResponse(object):
|
||||
"""
|
||||
content_encoding = self.headers.get('content-encoding')
|
||||
decoder = self.CONTENT_DECODERS.get(content_encoding)
|
||||
if decode_content is None:
|
||||
decode_content = self._decode_content
|
||||
|
||||
data = self._fp and self._fp.read(amt)
|
||||
if self._fp is None:
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
if amt:
|
||||
return data
|
||||
|
||||
if not decode_content or not decoder:
|
||||
if cache_content:
|
||||
self._body = data
|
||||
|
||||
return data
|
||||
if amt is None:
|
||||
# cStringIO doesn't like amt=None
|
||||
data = self._fp.read()
|
||||
else:
|
||||
return self._fp.read(amt)
|
||||
|
||||
try:
|
||||
data = decoder(data)
|
||||
if decode_content and decoder:
|
||||
data = decoder(data)
|
||||
except IOError:
|
||||
raise HTTPError("Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding)
|
||||
@@ -150,12 +158,11 @@ class HTTPResponse(object):
|
||||
return data
|
||||
|
||||
finally:
|
||||
|
||||
if self._original_response and self._original_response.isclosed():
|
||||
self.release_conn()
|
||||
|
||||
@staticmethod
|
||||
def from_httplib(r, **response_kw):
|
||||
@classmethod
|
||||
def from_httplib(ResponseCls, r, **response_kw):
|
||||
"""
|
||||
Given an :class:`httplib.HTTPResponse` instance ``r``, return a
|
||||
corresponding :class:`urllib3.response.HTTPResponse` object.
|
||||
@@ -164,14 +171,28 @@ class HTTPResponse(object):
|
||||
with ``original_response=r``.
|
||||
"""
|
||||
|
||||
return HTTPResponse(body=r,
|
||||
headers=dict(r.getheaders()),
|
||||
status=r.status,
|
||||
version=r.version,
|
||||
reason=r.reason,
|
||||
strict=r.strict,
|
||||
original_response=r,
|
||||
**response_kw)
|
||||
# Normalize headers between different versions of Python
|
||||
headers = {}
|
||||
for k, v in r.getheaders():
|
||||
# Python 3: Header keys are returned capitalised
|
||||
k = k.lower()
|
||||
|
||||
has_value = headers.get(k)
|
||||
if has_value: # Python 3: Repeating header keys are unmerged.
|
||||
v = ', '.join([has_value, v])
|
||||
|
||||
headers[k] = v
|
||||
|
||||
# HTTPResponse objects in Python 3 don't have a .strict attribute
|
||||
strict = getattr(r, 'strict', 0)
|
||||
return ResponseCls(body=r,
|
||||
headers=headers,
|
||||
status=r.status,
|
||||
version=r.version,
|
||||
reason=r.reason,
|
||||
strict=strict,
|
||||
original_response=r,
|
||||
**response_kw)
|
||||
|
||||
# Backwards-compatibility methods for httplib.HTTPResponse
|
||||
def getheaders(self):
|
||||
|
||||
136
requests/packages/urllib3/util.py
Normal file
136
requests/packages/urllib3/util.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# urllib3/util.py
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
try:
|
||||
from select import poll, POLLIN
|
||||
except ImportError: # `poll` doesn't exist on OSX and other platforms
|
||||
poll = False
|
||||
try:
|
||||
from select import select
|
||||
except ImportError: # `select` doesn't exist on AppEngine.
|
||||
select = False
|
||||
|
||||
from .packages import six
|
||||
from .exceptions import LocationParseError
|
||||
|
||||
|
||||
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
|
||||
basic_auth=None):
|
||||
"""
|
||||
Shortcuts for generating request headers.
|
||||
|
||||
:param keep_alive:
|
||||
If ``True``, adds 'connection: keep-alive' header.
|
||||
|
||||
:param accept_encoding:
|
||||
Can be a boolean, list, or string.
|
||||
``True`` translates to 'gzip,deflate'.
|
||||
List will get joined by comma.
|
||||
String will be used as provided.
|
||||
|
||||
:param user_agent:
|
||||
String representing the user-agent you want, such as
|
||||
"python-urllib3/0.6"
|
||||
|
||||
:param basic_auth:
|
||||
Colon-separated username:password string for 'authorization: basic ...'
|
||||
auth header.
|
||||
|
||||
Example: ::
|
||||
|
||||
>>> make_headers(keep_alive=True, user_agent="Batman/1.0")
|
||||
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
|
||||
>>> make_headers(accept_encoding=True)
|
||||
{'accept-encoding': 'gzip,deflate'}
|
||||
"""
|
||||
headers = {}
|
||||
if accept_encoding:
|
||||
if isinstance(accept_encoding, str):
|
||||
pass
|
||||
elif isinstance(accept_encoding, list):
|
||||
accept_encoding = ','.join(accept_encoding)
|
||||
else:
|
||||
accept_encoding = 'gzip,deflate'
|
||||
headers['accept-encoding'] = accept_encoding
|
||||
|
||||
if user_agent:
|
||||
headers['user-agent'] = user_agent
|
||||
|
||||
if keep_alive:
|
||||
headers['connection'] = 'keep-alive'
|
||||
|
||||
if basic_auth:
|
||||
headers['authorization'] = 'Basic ' + \
|
||||
b64encode(six.b(basic_auth)).decode('utf-8')
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
def get_host(url):
|
||||
"""
|
||||
Given a url, return its scheme, host and port (None if it's not there).
|
||||
|
||||
For example: ::
|
||||
|
||||
>>> get_host('http://google.com/mail/')
|
||||
('http', 'google.com', None)
|
||||
>>> get_host('google.com:80')
|
||||
('http', 'google.com', 80)
|
||||
"""
|
||||
|
||||
# This code is actually similar to urlparse.urlsplit, but much
|
||||
# simplified for our needs.
|
||||
port = None
|
||||
scheme = 'http'
|
||||
|
||||
if '://' in url:
|
||||
scheme, url = url.split('://', 1)
|
||||
if '/' in url:
|
||||
url, _path = url.split('/', 1)
|
||||
if '@' in url:
|
||||
_auth, url = url.split('@', 1)
|
||||
if ':' in url:
|
||||
url, port = url.split(':', 1)
|
||||
|
||||
if not port.isdigit():
|
||||
raise LocationParseError("Failed to parse: %s" % url)
|
||||
|
||||
port = int(port)
|
||||
|
||||
return scheme, url, port
|
||||
|
||||
|
||||
|
||||
def is_connection_dropped(conn):
|
||||
"""
|
||||
Returns True if the connection is dropped and should be closed.
|
||||
|
||||
:param conn:
|
||||
``HTTPConnection`` object.
|
||||
|
||||
Note: For platforms like AppEngine, this will always return ``False`` to
|
||||
let the platform handle connection recycling transparently for us.
|
||||
"""
|
||||
sock = getattr(conn, 'sock', False)
|
||||
if not sock: #Platform-specific: AppEngine
|
||||
return False
|
||||
|
||||
if not poll: # Platform-specific
|
||||
if not select: #Platform-specific: AppEngine
|
||||
return False
|
||||
|
||||
return select([sock], [], [], 0.0)[0]
|
||||
|
||||
# This version is better on platforms that support it.
|
||||
p = poll()
|
||||
p.register(sock, POLLIN)
|
||||
for (fno, ev) in p.poll(0.0):
|
||||
if fno == sock.fileno():
|
||||
# Either data is buffered (bad), or the connection is dropped.
|
||||
return True
|
||||
@@ -25,7 +25,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
|
||||
if isinstance(local_kwarg, basestring):
|
||||
if isinstance(local_kwarg, str):
|
||||
return local_kwarg
|
||||
|
||||
if local_kwarg is None:
|
||||
@@ -40,7 +40,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
kwargs.update(local_kwarg)
|
||||
|
||||
# Remove keys that are set to None.
|
||||
for (k,v) in local_kwarg.items():
|
||||
for (k, v) in list(local_kwarg.items()):
|
||||
if v is None:
|
||||
del kwargs[k]
|
||||
|
||||
@@ -52,7 +52,7 @@ class Session(object):
|
||||
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
||||
'params', 'config']
|
||||
'params', 'config', 'verify', 'cert']
|
||||
|
||||
|
||||
def __init__(self,
|
||||
@@ -64,7 +64,9 @@ class Session(object):
|
||||
hooks=None,
|
||||
params=None,
|
||||
config=None,
|
||||
verify=None):
|
||||
prefetch=False,
|
||||
verify=True,
|
||||
cert=None):
|
||||
|
||||
self.headers = headers or {}
|
||||
self.cookies = cookies or {}
|
||||
@@ -74,15 +76,14 @@ class Session(object):
|
||||
self.hooks = hooks or {}
|
||||
self.params = params or {}
|
||||
self.config = config or {}
|
||||
self.prefetch = prefetch
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
for (k, v) in defaults.items():
|
||||
for (k, v) in list(defaults.items()):
|
||||
self.config.setdefault(k, v)
|
||||
|
||||
self.poolmanager = PoolManager(
|
||||
num_pools=self.config.get('pool_connections'),
|
||||
maxsize=self.config.get('pool_maxsize')
|
||||
)
|
||||
self.init_poolmanager()
|
||||
|
||||
# Set up a CookieJar to be used by default
|
||||
self.cookies = {}
|
||||
@@ -91,6 +92,12 @@ class Session(object):
|
||||
if cookies is not None:
|
||||
self.cookies.update(cookies)
|
||||
|
||||
def init_poolmanager(self):
|
||||
self.poolmanager = PoolManager(
|
||||
num_pools=self.config.get('pool_connections'),
|
||||
maxsize=self.config.get('pool_maxsize')
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<requests-client at 0x%x>' % (id(self))
|
||||
|
||||
@@ -108,13 +115,14 @@ class Session(object):
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=False,
|
||||
allow_redirects=True,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
config=None,
|
||||
prefetch=False,
|
||||
verify=None):
|
||||
verify=None,
|
||||
cert=None):
|
||||
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
@@ -128,12 +136,13 @@ class Session(object):
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param allow_redirects: (optional) Boolean. Set to True by default.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||
"""
|
||||
|
||||
method = str(method).upper()
|
||||
@@ -145,17 +154,15 @@ class Session(object):
|
||||
headers = {} if headers is None else headers
|
||||
params = {} if params is None else params
|
||||
hooks = {} if hooks is None else hooks
|
||||
|
||||
if verify is None:
|
||||
verify = self.verify
|
||||
prefetch = self.prefetch or prefetch
|
||||
|
||||
# use session's hooks as defaults
|
||||
for key, cb in self.hooks.iteritems():
|
||||
for key, cb in list(self.hooks.items()):
|
||||
hooks.setdefault(key, cb)
|
||||
|
||||
# Expand header values.
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
for k, v in list(headers.items()) or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
args = dict(
|
||||
@@ -173,6 +180,7 @@ class Session(object):
|
||||
proxies=proxies,
|
||||
config=config,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
_poolmanager=self.poolmanager
|
||||
)
|
||||
|
||||
@@ -210,7 +218,7 @@ class Session(object):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -221,7 +229,7 @@ class Session(object):
|
||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -232,10 +240,10 @@ class Session(object):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
kwargs.setdefault('allow_redirects', False)
|
||||
return self.request('head', url, **kwargs)
|
||||
|
||||
|
||||
@@ -244,7 +252,7 @@ class Session(object):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('post', url, data=data, **kwargs)
|
||||
@@ -255,7 +263,7 @@ class Session(object):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('put', url, data=data, **kwargs)
|
||||
@@ -266,7 +274,7 @@ class Session(object):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('patch', url, data=data, **kwargs)
|
||||
@@ -276,11 +284,20 @@ class Session(object):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('delete', url, **kwargs)
|
||||
|
||||
def __getstate__(self):
|
||||
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
||||
|
||||
def __setstate__(self, state):
|
||||
for attr, value in state.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
self.init_poolmanager()
|
||||
|
||||
|
||||
def session(**kwargs):
|
||||
"""Returns a :class:`Session` for context-management."""
|
||||
|
||||
@@ -79,7 +79,7 @@ _codes = {
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for (code, titles) in _codes.items():
|
||||
for (code, titles) in list(_codes.items()):
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith('\\'):
|
||||
|
||||
@@ -18,7 +18,7 @@ class CaseInsensitiveDict(dict):
|
||||
@property
|
||||
def lower_keys(self):
|
||||
if not hasattr(self, '_lower_keys') or not self._lower_keys:
|
||||
self._lower_keys = dict((k.lower(), k) for k in self.iterkeys())
|
||||
self._lower_keys = dict((k.lower(), k) for k in list(self.keys()))
|
||||
return self._lower_keys
|
||||
|
||||
def _clear_lower_keys(self):
|
||||
@@ -63,4 +63,4 @@ class LookupDict(dict):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__dict__.get(key, default)
|
||||
return self.__dict__.get(key, default)
|
||||
|
||||
@@ -11,14 +11,76 @@ that are also useful for external consumption.
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import cookielib
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import zlib
|
||||
import urllib
|
||||
from netrc import netrc, NetrcParseError
|
||||
|
||||
from urllib2 import parse_http_list as _parse_list_header
|
||||
from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import quote, cookielib, SimpleCookie, is_py2, urlparse
|
||||
from .compat import basestring, bytes, str
|
||||
|
||||
|
||||
NETRC_FILES = ('.netrc', '_netrc')
|
||||
|
||||
|
||||
def dict_to_sequence(d):
|
||||
"""Returns an internal sequence dictionary update."""
|
||||
|
||||
if hasattr(d, 'items'):
|
||||
d = d.items()
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def get_netrc_auth(url):
|
||||
"""Returns the Requests tuple auth for a given url from netrc."""
|
||||
|
||||
locations = (os.path.expanduser('~/{0}'.format(f)) for f in NETRC_FILES)
|
||||
netrc_path = None
|
||||
|
||||
for loc in locations:
|
||||
if os.path.exists(loc) and not netrc_path:
|
||||
netrc_path = loc
|
||||
|
||||
# Abort early if there isn't one.
|
||||
if netrc_path is None:
|
||||
return netrc_path
|
||||
|
||||
ri = urlparse(url)
|
||||
|
||||
# Strip port numbers from netloc
|
||||
host = ri.netloc.split(':')[0]
|
||||
|
||||
try:
|
||||
_netrc = netrc(netrc_path).authenticators(host)
|
||||
if _netrc:
|
||||
# Return with login / password
|
||||
login_i = (0 if _netrc[0] else 1)
|
||||
return (_netrc[login_i], _netrc[2])
|
||||
except (NetrcParseError, IOError, AttributeError):
|
||||
# If there was a parsing error or a permissions issue reading the file,
|
||||
# we'll just skip netrc auth
|
||||
pass
|
||||
|
||||
|
||||
def dict_from_string(s):
|
||||
"""Returns a MultiDict with Cookies."""
|
||||
|
||||
cookies = dict()
|
||||
|
||||
try:
|
||||
c = SimpleCookie()
|
||||
c.load(s)
|
||||
|
||||
for k, v in list(c.items()):
|
||||
cookies.update({k: v.value})
|
||||
# This stuff is not to be trusted.
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return cookies
|
||||
|
||||
|
||||
def guess_filename(obj):
|
||||
@@ -27,6 +89,7 @@ def guess_filename(obj):
|
||||
if name and name[0] != '<' and name[-1] != '>':
|
||||
return name
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def parse_list_header(value):
|
||||
"""Parse lists as described by RFC 2068 Section 2.
|
||||
@@ -132,16 +195,22 @@ def header_expand(headers):
|
||||
collector = []
|
||||
|
||||
if isinstance(headers, dict):
|
||||
headers = headers.items()
|
||||
|
||||
headers = list(headers.items())
|
||||
elif isinstance(headers, basestring):
|
||||
return headers
|
||||
elif isinstance(headers, str):
|
||||
# As discussed in https://github.com/kennethreitz/requests/issues/400
|
||||
# latin-1 is the most conservative encoding used on the web. Anyone
|
||||
# who needs more can encode to a byte-string before calling
|
||||
return headers.encode("latin-1")
|
||||
elif headers is None:
|
||||
return headers
|
||||
|
||||
for i, (value, params) in enumerate(headers):
|
||||
|
||||
_params = []
|
||||
|
||||
for (p_k, p_v) in params.items():
|
||||
for (p_k, p_v) in list(params.items()):
|
||||
|
||||
_params.append('%s=%s' % (p_k, p_v))
|
||||
|
||||
@@ -152,10 +221,9 @@ def header_expand(headers):
|
||||
|
||||
collector.append('; '.join(_params))
|
||||
|
||||
if not len(headers) == i+1:
|
||||
if not len(headers) == i + 1:
|
||||
collector.append(', ')
|
||||
|
||||
|
||||
# Remove trailing separators.
|
||||
if collector[-1] in (', ', '; '):
|
||||
del collector[-1]
|
||||
@@ -163,20 +231,13 @@ def header_expand(headers):
|
||||
return ''.join(collector)
|
||||
|
||||
|
||||
|
||||
def randombytes(n):
|
||||
"""Return n random bytes."""
|
||||
# Use /dev/urandom if it is available. Fall back to random module
|
||||
# if not. It might be worthwhile to extend this function to use
|
||||
# other platform-specific mechanisms for getting random bytes.
|
||||
if os.path.exists("/dev/urandom"):
|
||||
f = open("/dev/urandom")
|
||||
s = f.read(n)
|
||||
f.close()
|
||||
return s
|
||||
else:
|
||||
if is_py2:
|
||||
L = [chr(random.randrange(0, 256)) for i in range(n)]
|
||||
return "".join(L)
|
||||
else:
|
||||
L = [chr(random.randrange(0, 256)).encode('utf-8') for i in range(n)]
|
||||
return b"".join(L)
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
@@ -187,9 +248,9 @@ def dict_from_cookiejar(cj):
|
||||
|
||||
cookie_dict = {}
|
||||
|
||||
for _, cookies in cj._cookies.items():
|
||||
for _, cookies in cookies.items():
|
||||
for cookie in cookies.values():
|
||||
for _, cookies in list(cj._cookies.items()):
|
||||
for _, cookies in list(cookies.items()):
|
||||
for cookie in list(cookies.values()):
|
||||
# print cookie
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
|
||||
@@ -221,7 +282,7 @@ def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
for k, v in cookie_dict.items():
|
||||
for k, v in list(cookie_dict.items()):
|
||||
|
||||
cookie = cookielib.Cookie(
|
||||
version=0,
|
||||
@@ -276,22 +337,8 @@ def get_encoding_from_headers(headers):
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
|
||||
|
||||
def unicode_from_html(content):
|
||||
"""Attempts to decode an HTML string into unicode.
|
||||
If unsuccessful, the original content is returned.
|
||||
"""
|
||||
|
||||
encodings = get_encodings_from_content(content)
|
||||
|
||||
for encoding in encodings:
|
||||
|
||||
try:
|
||||
return unicode(content, encoding)
|
||||
except (UnicodeError, TypeError):
|
||||
pass
|
||||
|
||||
return content
|
||||
if 'text' in content_type:
|
||||
return 'ISO-8859-1'
|
||||
|
||||
|
||||
def stream_decode_response_unicode(iterator, r):
|
||||
@@ -334,48 +381,91 @@ def get_unicode_from_response(r):
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
return unicode(r.content, encoding)
|
||||
return str(r.content, encoding)
|
||||
except UnicodeError:
|
||||
tried_encodings.append(encoding)
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
return unicode(r.content, encoding, errors='replace')
|
||||
return str(r.content, encoding, errors='replace')
|
||||
except TypeError:
|
||||
return r.content
|
||||
|
||||
|
||||
def decode_gzip(content):
|
||||
"""Return gzip-decoded string.
|
||||
def stream_decompress(iterator, mode='gzip'):
|
||||
"""
|
||||
Stream decodes an iterator over compressed data
|
||||
|
||||
:param content: bytestring to gzip-decode.
|
||||
:param iterator: An iterator over compressed data
|
||||
:param mode: 'gzip' or 'deflate'
|
||||
:return: An iterator over decompressed data
|
||||
"""
|
||||
|
||||
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
|
||||
if mode not in ['gzip', 'deflate']:
|
||||
raise ValueError('stream_decompress mode must be gzip or deflate')
|
||||
|
||||
|
||||
def stream_decode_gzip(iterator):
|
||||
"""Stream decodes a gzip-encoded iterator"""
|
||||
zlib_mode = 16 + zlib.MAX_WBITS if mode == 'gzip' else -zlib.MAX_WBITS
|
||||
dec = zlib.decompressobj(zlib_mode)
|
||||
try:
|
||||
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
for chunk in iterator:
|
||||
rv = dec.decompress(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
buf = dec.decompress('')
|
||||
except zlib.error:
|
||||
# If there was an error decompressing, just return the raw chunk
|
||||
yield chunk
|
||||
# Continue to return the rest of the raw data
|
||||
for chunk in iterator:
|
||||
yield chunk
|
||||
else:
|
||||
# Make sure everything has been returned from the decompression object
|
||||
buf = dec.decompress(bytes())
|
||||
rv = buf + dec.flush()
|
||||
if rv:
|
||||
yield rv
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
|
||||
def requote_path(path):
|
||||
"""Re-quote the given URL path component.
|
||||
def stream_untransfer(gen, resp):
|
||||
if 'gzip' in resp.headers.get('content-encoding', ''):
|
||||
gen = stream_decompress(gen, mode='gzip')
|
||||
elif 'deflate' in resp.headers.get('content-encoding', ''):
|
||||
gen = stream_decompress(gen, mode='deflate')
|
||||
|
||||
This function passes the given path through an unquote/quote cycle to
|
||||
return gen
|
||||
|
||||
|
||||
# The unreserved URI characters (RFC 3986)
|
||||
UNRESERVED_SET = frozenset(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
+ "0123456789-._~")
|
||||
|
||||
|
||||
def unquote_unreserved(uri):
|
||||
"""Un-escape any percent-escape sequences in a URI that are unreserved
|
||||
characters.
|
||||
This leaves all reserved, illegal and non-ASCII bytes encoded.
|
||||
"""
|
||||
parts = uri.split('%')
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2:
|
||||
c = chr(int(h, 16))
|
||||
if c in UNRESERVED_SET:
|
||||
parts[i] = c + parts[i][2:]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
return ''.join(parts)
|
||||
|
||||
|
||||
def requote_uri(uri):
|
||||
"""Re-quote the given URI.
|
||||
|
||||
This function passes the given URI through an unquote/quote cycle to
|
||||
ensure that it is fully and consistently quoted.
|
||||
"""
|
||||
parts = path.split("/")
|
||||
parts = (urllib.quote(urllib.unquote(part), safe="") for part in parts)
|
||||
return "/".join(parts)
|
||||
# Unquote only the unreserved characters
|
||||
# Then quote only illegal characters (do not quote reserved, unreserved,
|
||||
# or '%')
|
||||
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
envoy==0.0.2
|
||||
httpbin==0.0.6
|
||||
gunicorn
|
||||
nose
|
||||
pyflakes
|
||||
omnijson
|
||||
rudolf2
|
||||
rudolf2
|
||||
|
||||
34
setup.py
34
setup.py
@@ -4,6 +4,7 @@
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
from requests.compat import is_py3, is_py2
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
@@ -20,12 +21,22 @@ if sys.argv[-1] == 'test':
|
||||
os.system('python test_requests.py')
|
||||
sys.exit()
|
||||
|
||||
required = [
|
||||
'certifi>=0.0.4'
|
||||
required = ['certifi>=0.0.7',]
|
||||
packages = [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.urllib3',
|
||||
'requests.packages.urllib3.packages',
|
||||
'requests.packages.urllib3.packages.ssl_match_hostname',
|
||||
'requests.packages.urllib3.packages.mimetools_choose_boundary',
|
||||
]
|
||||
|
||||
if sys.version_info[:2] < (2,6):
|
||||
required.append('simplejson')
|
||||
if is_py3:
|
||||
required.append('chardet2')
|
||||
else:
|
||||
required.append('chardet>=1.0.0')
|
||||
packages.append('requests.packages.oreos')
|
||||
|
||||
|
||||
setup(
|
||||
name='requests',
|
||||
@@ -36,14 +47,7 @@ setup(
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='http://python-requests.org',
|
||||
packages=[
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.urllib3',
|
||||
'requests.packages.urllib3.packages',
|
||||
'requests.packages.urllib3.packages.ssl_match_hostname',
|
||||
'requests.packages.oreos'
|
||||
],
|
||||
packages=packages,
|
||||
package_data={'': ['LICENSE', 'NOTICE']},
|
||||
include_package_data=True,
|
||||
install_requires=required,
|
||||
@@ -54,10 +58,10 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: ISC License (ISCL)',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
# 'Programming Language :: Python :: 3.0',
|
||||
# 'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.0',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
),
|
||||
)
|
||||
|
||||
648
test_requests.py
648
test_requests.py
@@ -1,648 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import StringIO
|
||||
import time
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import envoy
|
||||
from requests import HTTPError
|
||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
|
||||
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
|
||||
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
|
||||
global _httpbin
|
||||
|
||||
if not _httpbin:
|
||||
|
||||
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
|
||||
self.httpbin = c
|
||||
_httpbin = True
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""Teardown."""
|
||||
# self.httpbin.kill()
|
||||
pass
|
||||
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = requests.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_response_sent(self):
|
||||
r = requests.get(httpbin('/get'))
|
||||
|
||||
self.assertTrue(r.request.sent)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
|
||||
assert heads['User-agent'] in r.content
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
"""Issue XX"""
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['User-agent'] in r.content)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['user-agent'] in r.content)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = requests.head(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = requests.put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PATCH(self):
|
||||
r = requests.patch(httpbin('patch'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPBasicAuth('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
auth = ('user', 'pass')
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post = requests.post(url).raise_for_status()
|
||||
|
||||
post = requests.post(url, data={'some': 'data'})
|
||||
self.assertEqual(post.status_code, 200)
|
||||
|
||||
post2 = requests.post(url, files={'some': open('test_requests.py')})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = requests.post(url, data='[{"some": "json"}]')
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post = requests.post(url,
|
||||
files={'some': open('test_requests.py')},
|
||||
data={'some': 'data'})
|
||||
|
||||
self.assertEqual(post.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
post2 = requests.post(url,
|
||||
files={'some': open('test_requests.py')},
|
||||
headers = {'User-Agent': 'requests-tests'})
|
||||
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = requests.get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
|
||||
def test_request_ok_set(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '404'))
|
||||
# print r.status_code
|
||||
# r.raise_for_status()
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
|
||||
def test_status_raising(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/get')
|
||||
|
||||
requests.get(url, params={'foo': u'føø'})
|
||||
requests.get(url, params={u'føø': u'føø'})
|
||||
requests.get(url, params={'føø': 'føø'})
|
||||
requests.get(url, params={'foo': u'foo'})
|
||||
requests.get(service('ø'), params={'foo': u'foo'})
|
||||
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = HTTPBasicAuth('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = requests.get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEquals(r.status_code, 401)
|
||||
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
self.assertEquals(rbody.get('form'), {})
|
||||
self.assertEquals(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_postdata(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data="foobar")
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), {})
|
||||
self.assertEquals(rbody.get('data'), 'foobar')
|
||||
|
||||
|
||||
# def test_idna(self):
|
||||
# r = requests.get(u'http://➡.ws/httpbin')
|
||||
# assert 'httpbin' in r.url
|
||||
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('get'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test=['foo','baz']),
|
||||
data=dict(test2="foobar",test3=['foo','baz']))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar',test3=['foo','baz']))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('relative-redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = s.get(httpbin('user-agent'))
|
||||
assert heads['User-agent'] in r1.content
|
||||
|
||||
r2 = s.get(httpbin('user-agent'))
|
||||
assert heads['User-agent'] in r2.content
|
||||
|
||||
new_heads = {'User-agent': 'blah'}
|
||||
r3 = s.get(httpbin('user-agent'), headers=new_heads)
|
||||
assert new_heads['User-agent'] in r3.content
|
||||
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
def test_session_persistent_cookies(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
# Internally dispatched cookies are sent.
|
||||
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
|
||||
r = s.get(httpbin('cookies'), cookies=_c)
|
||||
r = s.get(httpbin('cookies'))
|
||||
|
||||
# Those cookies persist transparently.
|
||||
c = json.loads(r.content).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Double check.
|
||||
r = s.get(httpbin('cookies'), cookies={})
|
||||
c = json.loads(r.content).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Remove a cookie by setting it's value to None.
|
||||
r = s.get(httpbin('cookies'), cookies={'bessie': None})
|
||||
c = json.loads(r.content).get('cookies')
|
||||
del _c['bessie']
|
||||
assert c == _c
|
||||
|
||||
# Test session-level cookies.
|
||||
s = requests.session(cookies=_c)
|
||||
r = s.get(httpbin('cookies'))
|
||||
c = json.loads(r.content).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Have the server set a cookie.
|
||||
r = s.get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True)
|
||||
c = json.loads(r.content).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
# And server-set cookie persistience.
|
||||
r = s.get(httpbin('cookies'))
|
||||
c = json.loads(r.content).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
|
||||
|
||||
def test_session_persistent_params(self):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = s.get(httpbin('get'))
|
||||
assert params['a'] in r1.content
|
||||
|
||||
|
||||
params2 = {'b': 'b_test'}
|
||||
|
||||
r2 = s.get(httpbin('get'), params=params2)
|
||||
assert params['a'] in r2.content
|
||||
assert params2['b'] in r2.content
|
||||
|
||||
|
||||
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
|
||||
|
||||
r3 = s.get(httpbin('get'), params=params3)
|
||||
|
||||
assert not params['a'] in r3.content
|
||||
assert params3['b'] in r3.content
|
||||
assert params3['c'] in r3.content
|
||||
|
||||
def test_invalid_content(self):
|
||||
# WARNING: if you're using a terrible DNS provider (comcast),
|
||||
# this will fail.
|
||||
try:
|
||||
hah = 'http://somedomainthatclearlydoesntexistg.com'
|
||||
r = requests.get(hah, allow_redirects=False)
|
||||
except requests.ConnectionError:
|
||||
pass # \o/
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
config = {'safe_mode': True}
|
||||
r = requests.get(hah, allow_redirects=False, config=config)
|
||||
assert r.content == None
|
||||
|
||||
def test_cached_response(self):
|
||||
|
||||
r1 = requests.get(httpbin('get'), prefetch=False)
|
||||
assert r1.content
|
||||
assert r1.content
|
||||
|
||||
r2 = requests.get(httpbin('get'), prefetch=True)
|
||||
assert r2._content
|
||||
assert r2.content
|
||||
|
||||
def test_iter_lines(self):
|
||||
|
||||
lines = (0, 2, 10, 100)
|
||||
|
||||
for i in lines:
|
||||
|
||||
r = requests.get(httpbin('stream', str(i)), prefetch=False)
|
||||
len_lines = len([l for l in r.iter_lines()])
|
||||
|
||||
self.assertEqual(i, len_lines)
|
||||
|
||||
# Test 'dangling' fragment in responses that do not terminate in
|
||||
# a newline.
|
||||
quote = (
|
||||
'''Why will he not upon our fair request\n'''
|
||||
'''Untent his person and share the air with us?'''
|
||||
)
|
||||
|
||||
# Make a request and monkey-patch its contents
|
||||
r = requests.get(httpbin('get'))
|
||||
r.raw = StringIO.StringIO(quote)
|
||||
|
||||
# Make sure iter_lines doesn't chop the trailing bit
|
||||
lines = '\n'.join(r.iter_lines())
|
||||
self.assertEqual(lines, quote)
|
||||
|
||||
def test_safe_mode(self):
|
||||
|
||||
safe = requests.session(config=dict(safe_mode=True))
|
||||
|
||||
# Safe mode creates empty responses for failed requests.
|
||||
# Iterating on these responses should produce empty sequences
|
||||
r = safe.get('http://_/')
|
||||
self.assertEquals(list(r.iter_lines()), [])
|
||||
self.assertIsInstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
r = safe.get('http://_/')
|
||||
self.assertEquals(list(r.iter_content()), [])
|
||||
self.assertIsInstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
# When not in safe mode, should raise Timeout exception
|
||||
with self.assertRaises(requests.exceptions.Timeout):
|
||||
r = requests.get(httpbin('stream', '1000'), timeout=0.0001)
|
||||
|
||||
# In safe mode, should return a blank response
|
||||
r = requests.get(httpbin('stream', '1000'), timeout=0.0001,
|
||||
config=dict(safe_mode=True))
|
||||
self.assertIsNone(r.content)
|
||||
self.assertIsInstance(r.error, requests.exceptions.Timeout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def test_addition(self):
|
||||
assert (1 + 1) == 2
|
||||
|
||||
def test_ssl_hostname_ok(self):
|
||||
requests.get('https://github.com', verify=True)
|
||||
|
||||
def test_ssl_hostname_not_ok(self):
|
||||
requests.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com', verify=True)
|
||||
|
||||
def test_ssl_hostname_session_not_ok(self):
|
||||
|
||||
s = requests.session(verify=True)
|
||||
|
||||
s.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, s.get, 'https://kennethreitz.com')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
805
tests/test_requests.py
Executable file
805
tests/test_requests.py
Executable file
@@ -0,0 +1,805 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# from __future__ import unicode_literals
|
||||
|
||||
# Path hack.
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import pickle
|
||||
|
||||
import requests
|
||||
from requests.compat import str, StringIO
|
||||
# import envoy
|
||||
from requests import HTTPError
|
||||
from requests import get, post, head, put
|
||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||
|
||||
if 'HTTPBIN_URL' not in os.environ:
|
||||
os.environ['HTTPBIN_URL'] = 'http://httpbin.org/'
|
||||
|
||||
HTTPBIN_URL = os.environ.get('HTTPBIN_URL')
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
|
||||
class TestSetup(object):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
|
||||
global _httpbin
|
||||
|
||||
if (not 'HTTPBIN_URL' in os.environ) and not _httpbin:
|
||||
# c = envoy.connect('httpbin %s' % (PORT))
|
||||
# time.sleep(1)
|
||||
_httpbin = True
|
||||
|
||||
|
||||
class RequestsTestSuite(TestSetup, unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, get, 'hiwpefhipowhefopw')
|
||||
|
||||
def test_path_is_not_double_encoded(self):
|
||||
request = requests.Request("http://0.0.0.0/get/test case")
|
||||
|
||||
self.assertEqual(request.path_url, "/get/test%20case")
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_response_sent(self):
|
||||
r = get(httpbin('/get'))
|
||||
|
||||
self.assertTrue(r.request.sent)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
|
||||
assert heads['User-agent'] in r.text
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# def test_unicode_headers(self):
|
||||
# # Simply calling requests with a unicode instance should simply work
|
||||
# # when the characters are all representable using latin-1:
|
||||
# heads = { u'User-Agent': u'Requests Test Suite' }
|
||||
# requests.get(url=httpbin('get'), headers=heads)
|
||||
|
||||
# # Characters outside latin-1 should raise an exception:
|
||||
# heads = { u'User-Agent': u'\u30cd\u30c3\u30c8\u30ef\u30fc\u30af' }
|
||||
# self.assertRaises(UnicodeEncodeError, requests.get,
|
||||
# url=httpbin('get'), headers=heads)
|
||||
|
||||
# def test_session_with_escaped_url(self):
|
||||
# # Test a URL that contains percent-escaped characters
|
||||
# # This URL should not be modified (double-escaped)
|
||||
# # Tests:
|
||||
# # - Quoted illegal characters ("%20" (' '), "%3C" ('<'), "%3E" ('>'))
|
||||
# # - Quoted reserved characters ("%25" ('%'), "%23" ('#'), "%2F" ('/'))
|
||||
# # - Quoted non-ASCII characters ("%C3%98", "%C3%A5")
|
||||
# path_fully_escaped = '%3Ca%25b%23c%2Fd%3E/%C3%98%20%C3%A5'
|
||||
# url = httpbin('get/' + path_fully_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
|
||||
|
||||
# # Test that illegal characters in a path get properly percent-escaped
|
||||
# # Tests:
|
||||
# # - Bare illegal characters (space, '<')
|
||||
# # - Bare non-ASCII characters ('\u00d8')
|
||||
# path = u'<a%25b%23c%2Fd%3E/\u00d8 %C3%A5'
|
||||
# url = httpbin('get/' + path)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
|
||||
|
||||
# # Test that reserved characters in a path do not get percent-escaped
|
||||
# # Tests:
|
||||
# # - All reserved characters (RFC 3986), except '?', '#', '[' and ']',
|
||||
# # which are not allowed in the path, and ';' which delimits
|
||||
# # parameters.
|
||||
# # All such characters must be allowed bare in path, and must not be
|
||||
# # encoded.
|
||||
# # - Special unreserved characters (RFC 3986), which should not be
|
||||
# # encoded (even though it wouldn't hurt).
|
||||
# path_reserved = '!$&\'()*+,/:=@-._~'
|
||||
# url = httpbin('get/' + path_reserved)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_reserved))
|
||||
|
||||
# # Test that percent-encoded unreserved characters in a path get
|
||||
# # normalised to their un-encoded forms.
|
||||
# path_unreserved = 'ABCDwxyz1234-._~'
|
||||
# path_unreserved_escaped = '%41%42%43%44%77%78%79%7A%31%32%33%34%2D%2E%5F%7E'
|
||||
# url = httpbin('get/' + path_unreserved_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_unreserved))
|
||||
|
||||
# # Re-run all of the same tests on the query part of the URI
|
||||
# query_fully_escaped = '%3Ca%25b%23c%2Fd%3E=%C3%98%20%C3%A5'
|
||||
# url = httpbin('get/?' + query_fully_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
|
||||
|
||||
# query = u'<a%25b%23c%2Fd%3E=\u00d8 %C3%A5'
|
||||
# url = httpbin('get/?' + query)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
|
||||
|
||||
# # The legal characters in query happens to be the same as in path
|
||||
# query_reserved = '!$&\'()*+,/:=@-._~'
|
||||
# url = httpbin('get/?' + query_reserved)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_reserved))
|
||||
|
||||
# query_unreserved = 'ABCDwxyz=1234-._~'
|
||||
# query_unreserved_escaped = '%41%42%43%44%77%78%79%7A=%31%32%33%34%2D%2E%5F%7E'
|
||||
# url = httpbin('get/?' + query_unreserved_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_unreserved))
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
"""Issue XX"""
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
self.assertTrue(heads['User-agent'] in r.text)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
self.assertTrue(heads['user-agent'] in r.text)
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = head(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPBasicAuth('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
auth = ('user', 'pass')
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post1 = post(url).raise_for_status()
|
||||
|
||||
post1 = post(url, data={'some': 'data'})
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
with open(__file__) as f:
|
||||
post2 = post(url, files={'some': f})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = post(url, data='[{"some": "json"}]')
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
with open(__file__) as f:
|
||||
url = service('post')
|
||||
post1 = post(url,
|
||||
files={'some': f},
|
||||
data={'some': 'data'})
|
||||
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
with open(__file__) as f:
|
||||
|
||||
post2 = post(url,
|
||||
files={'some': f},
|
||||
headers={'User-Agent': 'requests-tests'})
|
||||
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
def test_request_ok_set(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('status', '404'))
|
||||
# print r.status_code
|
||||
# r.raise_for_status()
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
def test_status_raising(self):
|
||||
r = get(httpbin('status', '404'))
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
def test_default_status_raising(self):
|
||||
config = {'danger_mode': True}
|
||||
args = [httpbin('status', '404')]
|
||||
kwargs = dict(config=config)
|
||||
self.assertRaises(HTTPError, get, *args, **kwargs)
|
||||
|
||||
r = get(httpbin('status', '200'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
def test_response_has_unicode_url(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('get')
|
||||
|
||||
response = get(url)
|
||||
|
||||
assert isinstance(response.url, str)
|
||||
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/get')
|
||||
|
||||
get(url, params={'foo': 'føø'})
|
||||
get(url, params={'føø': 'føø'})
|
||||
get(url, params={'føø': 'føø'})
|
||||
get(url, params={'foo': 'foo'})
|
||||
get(service('ø'), params={'foo': 'foo'})
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = HTTPBasicAuth('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
|
||||
self.assertEqual(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_nonurlencoded_postdata(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data="foobar")
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), 'foobar')
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('get'), params=dict(test=['foo', 'baz']))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), params=dict(test=['foo', 'baz']))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(
|
||||
service('post'),
|
||||
params=dict(test=['foo', 'baz']),
|
||||
data=dict(test2="foobar", test3=['foo', 'baz']))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
# print(r.text)
|
||||
# print('-----------------------')
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), dict(test2='foobar', test3=['foo', 'baz']))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(r.history), 0)
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(r.history), 0)
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('redirect', '3'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.history), 3)
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('relative-redirect', '3'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.history), 3)
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = requests.session()
|
||||
r = get(httpbin('/get'), session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = get(httpbin('user-agent'), session=s)
|
||||
assert heads['User-agent'] in r1.text
|
||||
|
||||
r2 = get(httpbin('user-agent'), session=s)
|
||||
assert heads['User-agent'] in r2.text
|
||||
|
||||
new_heads = {'User-agent': 'blah'}
|
||||
r3 = get(httpbin('user-agent'), headers=new_heads, session=s)
|
||||
assert new_heads['User-agent'] in r3.text
|
||||
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
def test_single_hook(self):
|
||||
|
||||
def add_foo_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Foo': 'foo'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
for service in SERVICES:
|
||||
url = service('headers')
|
||||
response = get(url=url, hooks={'args': add_foo_header})
|
||||
|
||||
assert 'foo' in response.text
|
||||
|
||||
def test_multiple_hooks(self):
|
||||
|
||||
def add_foo_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Foo': 'foo'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
def add_bar_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Bar': 'bar'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
for service in SERVICES:
|
||||
url = service('headers')
|
||||
|
||||
response = get(url=url,
|
||||
hooks={
|
||||
'args': [add_foo_header, add_bar_header]
|
||||
}
|
||||
)
|
||||
|
||||
assert 'foo' in response.text
|
||||
assert 'bar' in response.text
|
||||
|
||||
def test_session_persistent_cookies(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
# Internally dispatched cookies are sent.
|
||||
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
|
||||
r = get(httpbin('cookies'), cookies=_c, session=s)
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
|
||||
# Those cookies persist transparently.
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Double check.
|
||||
r = get(httpbin('cookies'), cookies={}, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Remove a cookie by setting it's value to None.
|
||||
r = get(httpbin('cookies'), cookies={'bessie': None}, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
del _c['bessie']
|
||||
assert c == _c
|
||||
|
||||
# Test session-level cookies.
|
||||
s = requests.session(cookies=_c)
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Have the server set a cookie.
|
||||
r = get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
# And server-set cookie persistience.
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
def test_session_persistent_params(self):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = get(httpbin('get'), session=s)
|
||||
assert params['a'] in r1.text
|
||||
|
||||
params2 = {'b': 'b_test'}
|
||||
|
||||
r2 = get(httpbin('get'), params=params2, session=s)
|
||||
assert params['a'] in r2.text
|
||||
assert params2['b'] in r2.text
|
||||
|
||||
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
|
||||
|
||||
r3 = get(httpbin('get'), params=params3, session=s)
|
||||
|
||||
assert not params['a'] in r3.text
|
||||
assert params3['b'] in r3.text
|
||||
assert params3['c'] in r3.text
|
||||
|
||||
def test_session_pickling(self):
|
||||
|
||||
s = requests.session(
|
||||
headers={'header': 'value'},
|
||||
cookies={'a-cookie': 'cookie-value'},
|
||||
auth=('username', 'password'))
|
||||
|
||||
ds = pickle.loads(pickle.dumps(s))
|
||||
|
||||
self.assertEqual(s.headers, ds.headers)
|
||||
self.assertEqual(s.cookies, ds.cookies)
|
||||
self.assertEqual(s.auth, ds.auth)
|
||||
|
||||
def test_invalid_content(self):
|
||||
# WARNING: if you're using a terrible DNS provider (comcast),
|
||||
# this will fail.
|
||||
try:
|
||||
hah = 'http://somedomainthatclearlydoesntexistg.com'
|
||||
r = get(hah, allow_redirects=False)
|
||||
except requests.ConnectionError:
|
||||
pass # \o/
|
||||
else:
|
||||
assert False
|
||||
|
||||
config = {'safe_mode': True}
|
||||
r = get(hah, allow_redirects=False, config=config)
|
||||
assert r.content == None
|
||||
|
||||
def test_cached_response(self):
|
||||
|
||||
r1 = get(httpbin('get'), prefetch=False)
|
||||
assert not r1._content
|
||||
assert r1.content
|
||||
assert r1.text
|
||||
|
||||
r2 = get(httpbin('get'), prefetch=True)
|
||||
assert r2._content
|
||||
assert r2.content
|
||||
assert r2.text
|
||||
|
||||
def test_iter_lines(self):
|
||||
|
||||
lines = (0, 2, 10, 100)
|
||||
|
||||
for i in lines:
|
||||
r = get(httpbin('stream', str(i)), prefetch=False)
|
||||
lines = list(r.iter_lines())
|
||||
len_lines = len(lines)
|
||||
|
||||
self.assertEqual(i, len_lines)
|
||||
|
||||
# Tests that trailing whitespaces within lines do not get stripped.
|
||||
# Tests that a trailing non-terminated line does not get stripped.
|
||||
quote = (
|
||||
'''Agamemnon \n'''
|
||||
'''\tWhy will he not upon our fair request\r\n'''
|
||||
'''\tUntent his person and share the air with us?'''
|
||||
)
|
||||
|
||||
# Make a request and monkey-patch its contents
|
||||
r = get(httpbin('get'))
|
||||
r.raw = StringIO(quote)
|
||||
|
||||
lines = list(r.iter_lines())
|
||||
len_lines = len(lines)
|
||||
self.assertEqual(len_lines, 3)
|
||||
|
||||
joined = lines[0] + '\n' + lines[1] + '\r\n' + lines[2]
|
||||
self.assertEqual(joined, quote)
|
||||
|
||||
def test_safe_mode(self):
|
||||
|
||||
safe = requests.session(config=dict(safe_mode=True))
|
||||
|
||||
# Safe mode creates empty responses for failed requests.
|
||||
# Iterating on these responses should produce empty sequences
|
||||
r = get('http://_/', session=safe)
|
||||
self.assertEqual(list(r.iter_lines()), [])
|
||||
assert isinstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
r = get('http://_/', session=safe)
|
||||
self.assertEqual(list(r.iter_content()), [])
|
||||
assert isinstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
# When not in safe mode, should raise Timeout exception
|
||||
self.assertRaises(
|
||||
requests.exceptions.Timeout,
|
||||
get,
|
||||
httpbin('stream', '1000'), timeout=0.0001)
|
||||
|
||||
# In safe mode, should return a blank response
|
||||
r = get(httpbin('stream', '1000'), timeout=0.0001,
|
||||
config=dict(safe_mode=True))
|
||||
assert r.content is None
|
||||
assert isinstance(r.error, requests.exceptions.Timeout)
|
||||
|
||||
def test_upload_binary_data(self):
|
||||
|
||||
requests.get(httpbin('post'), auth=('a', 'b'), data='\xff')
|
||||
|
||||
def test_useful_exception_for_invalid_scheme(self):
|
||||
|
||||
# If we pass a legitimate URL with a scheme not supported
|
||||
# by requests, we should fail.
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
get,
|
||||
'ftp://ftp.kernel.org/pub/')
|
||||
|
||||
def test_can_have_none_in_header_values(self):
|
||||
try:
|
||||
# Don't choke on headers with none in the value.
|
||||
requests.get(httpbin('headers'), headers={'Foo': None})
|
||||
except TypeError:
|
||||
self.fail()
|
||||
|
||||
def test_danger_mode_redirects(self):
|
||||
s = requests.session()
|
||||
s.config['danger_mode'] = True
|
||||
s.get(httpbin('redirect', '4'))
|
||||
|
||||
|
||||
def test_empty_response(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
r.text
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
68
tests/test_requests_async.py
Executable file
68
tests/test_requests_async.py
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import select
|
||||
has_poll = hasattr(select, "poll")
|
||||
|
||||
from requests import async
|
||||
import envoy
|
||||
|
||||
sys.path.append('.')
|
||||
from test_requests import httpbin, RequestsTestSuite, SERVICES
|
||||
|
||||
|
||||
class RequestsTestSuiteUsingAsyncApi(RequestsTestSuite):
|
||||
"""Requests async test cases."""
|
||||
|
||||
def patched(f):
|
||||
"""Automatically send request after creation."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
|
||||
request = f(*args, **kwargs)
|
||||
return async.map([request])[0]
|
||||
|
||||
return wrapped
|
||||
|
||||
# Patched requests.api functions.
|
||||
global request
|
||||
request = patched(async.request)
|
||||
|
||||
global delete, get, head, options, patch, post, put
|
||||
delete = patched(async.delete)
|
||||
get = patched(async.get)
|
||||
head = patched(async.head)
|
||||
options = patched(async.options)
|
||||
patch = patched(async.patch)
|
||||
post = patched(async.post)
|
||||
put = patched(async.put)
|
||||
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
async.request
|
||||
|
||||
async.delete
|
||||
async.get
|
||||
async.head
|
||||
async.options
|
||||
async.patch
|
||||
async.post
|
||||
async.put
|
||||
|
||||
async.map
|
||||
async.send
|
||||
|
||||
def test_select_poll(self):
|
||||
"""Test to make sure we don't overwrite the poll"""
|
||||
self.assertEqual(hasattr(select, "poll"), has_poll)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
112
tests/test_requests_ext.py
Normal file
112
tests/test_requests_ext.py
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
from requests.compat import is_py2, is_py3
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def test_addition(self):
|
||||
assert (1 + 1) == 2
|
||||
|
||||
|
||||
def test_ssl_hostname_ok(self):
|
||||
requests.get('https://github.com', verify=True)
|
||||
|
||||
|
||||
def test_ssl_hostname_not_ok(self):
|
||||
requests.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com')
|
||||
|
||||
|
||||
def test_ssl_hostname_session_not_ok(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, s.get, 'https://kennethreitz.com')
|
||||
|
||||
s.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
|
||||
def test_binary_post(self):
|
||||
'''We need to be careful how we build the utf-8 string since
|
||||
unicode literals are a syntax error in python3
|
||||
'''
|
||||
|
||||
if is_py2:
|
||||
# Blasphemy!
|
||||
utf8_string = eval("u'Smörgås'.encode('utf-8')")
|
||||
elif is_py3:
|
||||
utf8_string = 'Smörgås'.encode('utf-8')
|
||||
else:
|
||||
raise EnvironmentError('Flesh out this test for your environment.')
|
||||
requests.post('http://www.google.com/', data=utf8_string)
|
||||
|
||||
|
||||
|
||||
def test_unicode_error(self):
|
||||
url = 'http://blip.fm/~1abvfu'
|
||||
requests.get(url)
|
||||
|
||||
|
||||
def test_chunked_head_redirect(self):
|
||||
url = "http://t.co/NFrx0zLG"
|
||||
r = requests.head(url, allow_redirects=True)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_unicode_redirect(self):
|
||||
'''This url redirects to a location that has a nonstandard
|
||||
character in it, that breaks requests in python2.7
|
||||
|
||||
After some research, the cause was identified as an unintended
|
||||
sideeffect of overriding of str with unicode.
|
||||
|
||||
In the case that the redirected url is actually a malformed
|
||||
"bytes" object, i.e. a string with character c where
|
||||
ord(c) > 127,
|
||||
then unicode(url) breaks.
|
||||
'''
|
||||
r = requests.get('http://www.marketwire.com/mw/release.' +
|
||||
'do?id=1628202&sourceType=3')
|
||||
assert r.ok
|
||||
|
||||
def test_unicode_url_outright(self):
|
||||
'''This url visits in my browser'''
|
||||
r = requests.get('http://www.marketwire.com/press-release/' +
|
||||
'jp-morgan-behauptet-sich-der-spitze-euro' +
|
||||
'p%C3%A4ischer-anleihe-analysten-laut-umf' +
|
||||
'rageergebnissen-1628202.htm')
|
||||
assert r.ok
|
||||
|
||||
def test_redirect_encoding(self):
|
||||
'''This url redirects to
|
||||
http://www.dealipedia.com/deal_view_investment.php?r=20012'''
|
||||
|
||||
r = requests.get('http://feedproxy.google.com/~r/Dealipedia' +
|
||||
'News/~3/BQtUJRJeZlo/deal_view_investment.' +
|
||||
'php')
|
||||
assert r.ok
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user