Compare commits
447 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6fc98509 | ||
|
|
9011852db0 | ||
|
|
29548bfab6 | ||
|
|
efc267ce59 | ||
|
|
244172bdd4 | ||
|
|
f1f56c3081 | ||
|
|
bfa010383e | ||
|
|
daf7aeb8c3 | ||
|
|
573ab095e4 | ||
|
|
030d877ed5 | ||
|
|
33dec52b8e | ||
|
|
aa31b6b4dd | ||
|
|
46f0146adf | ||
|
|
b14fa0764b | ||
|
|
15511584d1 | ||
|
|
80cd1ee663 | ||
|
|
3161783e49 | ||
|
|
e9b4defe8d | ||
|
|
a4b87c6fc1 | ||
|
|
cc350d54b7 | ||
|
|
3afd07451c | ||
|
|
27ee6e0f45 | ||
|
|
849934ccd6 | ||
|
|
597f0ae5fd | ||
|
|
2115046bbc | ||
|
|
565745f7b7 | ||
|
|
76b8afee94 | ||
|
|
892fcd6cc0 | ||
|
|
5537902796 | ||
|
|
9518d053a4 | ||
|
|
93c44157ba | ||
|
|
d47522e6fb | ||
|
|
3e233e4856 | ||
|
|
4a7fdef3ea | ||
|
|
98c719f714 | ||
|
|
dda533cf67 | ||
|
|
aa04cc9b42 | ||
|
|
0d6773c68c | ||
|
|
41876fd8c6 | ||
|
|
6f84ce72f2 | ||
|
|
6d25fff3f9 | ||
|
|
9bfe04586d | ||
|
|
1ecb593c7b | ||
|
|
fed17cb58a | ||
|
|
35fc3b4dbb | ||
|
|
dba7a5a7e2 | ||
|
|
2fa180e9ea | ||
|
|
3b9fc987b8 | ||
|
|
e272ec13c3 | ||
|
|
b97b4439c0 | ||
|
|
1d0a5b2665 | ||
|
|
636e61d68b | ||
|
|
a94541d38c | ||
|
|
ce3b85a62e | ||
|
|
281aa5ebc4 | ||
|
|
75027f9557 | ||
|
|
e8dbecb4db | ||
|
|
317c5b693a | ||
|
|
a86af4477e | ||
|
|
59924b9223 | ||
|
|
c8110222a5 | ||
|
|
1143d4237f | ||
|
|
8d5108b142 | ||
|
|
03e18bb7ca | ||
|
|
6a96652396 | ||
|
|
d4bdd84364 | ||
|
|
b45b329861 | ||
|
|
28ee9788a0 | ||
|
|
596aceb185 | ||
|
|
1f3e53b32e | ||
|
|
13db806861 | ||
|
|
d0fdf0eb38 | ||
|
|
c5d787d6fd | ||
|
|
873d128c5a | ||
|
|
7f35ec7665 | ||
|
|
86cc274cf0 | ||
|
|
c19b2c5172 | ||
|
|
4906686186 | ||
|
|
f038ec3b6e | ||
|
|
a609f726e6 | ||
|
|
7bd47777a9 | ||
|
|
21cd47d223 | ||
|
|
882ea76aa9 | ||
|
|
850e1c7d40 | ||
|
|
01411e37d6 | ||
|
|
4922daf498 | ||
|
|
8fbb1e6d97 | ||
|
|
7fc8c7ccfd | ||
|
|
7eb272b299 | ||
|
|
8bfa909ff2 | ||
|
|
3b085ed20f | ||
|
|
8b640979ee | ||
|
|
c58ddff3a4 | ||
|
|
5f16a8a6b1 | ||
|
|
53a53668d9 | ||
|
|
e7d8982583 | ||
|
|
abc5e27931 | ||
|
|
c75eaec852 | ||
|
|
d9a349c1a1 | ||
|
|
9d143ed653 | ||
|
|
ce4927e5d3 | ||
|
|
17c551539b | ||
|
|
2f55393593 | ||
|
|
d26f3333e4 | ||
|
|
820e2c73e6 | ||
|
|
efbbb84315 | ||
|
|
96e7a60b8d | ||
|
|
c22b71fc5b | ||
|
|
007963afde | ||
|
|
0708736c45 | ||
|
|
61d50d531c | ||
|
|
a17110b32a | ||
|
|
571d808d25 | ||
|
|
5e6ecdad9f | - | |
|
|
91e75f7215 | ||
|
|
81d599786a | ||
|
|
9471b0ab88 | ||
|
|
470af42bf2 | ||
|
|
0b34812afc | ||
|
|
47a36de74e | ||
|
|
0c21aa5530 | ||
|
|
fe109b27be | ||
|
|
3f873dc222 | ||
|
|
e0ae2b4155 | ||
|
|
5f603ae787 | ||
|
|
d4502aff87 | ||
|
|
3992ed6ad1 | ||
|
|
b72eb53a73 | ||
|
|
4d0c6cefd0 | ||
|
|
3f8f1dc00c | ||
|
|
008c77c911 | ||
|
|
77b37687b6 | ||
|
|
b7307298d2 | ||
|
|
abcf81e2a8 | ||
|
|
e477fce211 | ||
|
|
467766cfe8 | ||
|
|
66391e5c9d | ||
|
|
f7024a6000 | ||
|
|
ec33ce9d29 | ||
|
|
d0aa0175e6 | ||
|
|
d9e5717377 | ||
|
|
7e255177dc | ||
|
|
b0a0b509da | ||
|
|
8c737069a5 | ||
|
|
51038497f6 | ||
|
|
855c273707 | ||
|
|
281ab5a8f9 | ||
|
|
7836536459 | ||
|
|
2a0eff9c71 | ||
|
|
3e8a14e133 | ||
|
|
8ef0e88220 | ||
|
|
e102c1b4ca | ||
|
|
5ba43d0fe5 | ||
|
|
783e540d6d | ||
|
|
24da41005c | ||
|
|
17d3e1d498 | ||
|
|
8d2ab68a97 | ||
|
|
7322aa5b64 | ||
|
|
6b11cf21f3 | ||
|
|
9322d13f61 | ||
|
|
86a06c5c18 | ||
|
|
d0cb56932c | ||
|
|
fc04d8a368 | ||
|
|
519b9cee96 | ||
|
|
efba606e57 | ||
|
|
5bb18810d4 | ||
|
|
853383a8d3 | ||
|
|
f319006813 | ||
|
|
2fa021582b | ||
|
|
0ed641a26e | ||
|
|
c2fd568695 | ||
|
|
8f72c48548 | ||
|
|
08a821381a | ||
|
|
4625445d2a | ||
|
|
ca428504d4 | ||
|
|
88f79b74ce | ||
|
|
647cdd3847 | ||
|
|
c510d21e4e | ||
|
|
49e5df1ff9 | ||
|
|
2357b13824 | ||
|
|
f137db6803 | ||
|
|
2cf0dad573 | ||
|
|
414b9dde84 | ||
|
|
d50742f059 | ||
|
|
3bc9a181a1 | ||
|
|
975720a30c | ||
|
|
1361506ae6 | ||
|
|
23ff27ea4b | ||
|
|
933cbd735b | ||
|
|
ce1945c780 | ||
|
|
f52a413b6a | ||
|
|
ced96912f3 | ||
|
|
f6e97c322b | ||
|
|
c9d61aefe4 | ||
|
|
85c79658ef | ||
|
|
c4c86fd6e8 | ||
|
|
95dcdd8a05 | ||
|
|
43669183ce | ||
|
|
56e58fdcaf | ||
|
|
47378164ef | ||
|
|
9469b4bb69 | ||
|
|
6cead64345 | ||
|
|
773630b010 | ||
|
|
907e529471 | ||
|
|
2d70647f72 | ||
|
|
10cc4673f9 | ||
|
|
629d730da6 | ||
|
|
d4eabaaacc | ||
|
|
5693314a37 | ||
|
|
2e4724ccea | ||
|
|
e7084940e1 | ||
|
|
b141da36a4 | ||
|
|
bb16fe8115 | ||
|
|
a987729712 | ||
|
|
f7cc7b1778 | ||
|
|
e1909375f5 | ||
|
|
cf94c96a68 | ||
|
|
e7e395549f | ||
|
|
e776242de1 | ||
|
|
7b79cea738 | ||
|
|
32a861b2ed | ||
|
|
2ce26d4a1c | ||
|
|
aa0f78b740 | ||
|
|
e4adef5226 | ||
|
|
7dc581fad9 | ||
|
|
0b6dc091f5 | ||
|
|
e1714e678e | ||
|
|
d10b08ff88 | ||
|
|
18c42e88f5 | ||
|
|
f238dffc18 | ||
|
|
5a7ec361f4 | ||
|
|
291d4742d6 | ||
|
|
95ba6fcab2 | ||
|
|
1cdd1d04ce | ||
|
|
3293c0e8f4 | ||
|
|
074150ff2c | ||
|
|
c3c90bb09c | ||
|
|
87560d65ca | ||
|
|
7e4f7fa600 | ||
|
|
7f9c6233bb | ||
|
|
9b6a190652 | ||
|
|
1e1fb1e7a8 | ||
|
|
080db5ee5d | ||
|
|
2edda29448 | ||
|
|
6ba3523a26 | ||
|
|
87e7a04209 | ||
|
|
260e32a7c3 | ||
|
|
5fff2c757b | ||
|
|
30d699b64f | ||
|
|
7d91d3fb5c | ||
|
|
747f882b0e | ||
|
|
369e7cb38f | ||
|
|
04c456b877 | ||
|
|
f408829d51 | ||
|
|
73b414ab45 | ||
|
|
f18c40fd36 | ||
|
|
891e52d723 | ||
|
|
d2ff0f40eb | ||
|
|
787110c853 | ||
|
|
20319044a3 | ||
|
|
6f08c81698 | ||
|
|
868cd3d1cb | ||
|
|
39edf7a7fa | ||
|
|
4cf4362610 | ||
|
|
0b719f037c | ||
|
|
9603589b06 | ||
|
|
5f38c1209e | ||
|
|
749edb3554 | ||
|
|
882b465968 | ||
|
|
784fe8bbdb | ||
|
|
6253cf4c18 | ||
|
|
8521075868 | ||
|
|
2d98ca7477 | ||
|
|
e2d6a92150 | ||
|
|
162b751f6d | ||
|
|
20aed5488b | ||
|
|
2374d0fd7a | ||
|
|
483d00736c | ||
|
|
15de7d9dba | ||
|
|
34a583b821 | ||
|
|
2da86daa3a | ||
|
|
b4eac4c993 | ||
|
|
b4a4f38cce | ||
|
|
8c81cf64de | ||
|
|
00f066a467 | ||
|
|
bdf9f90c8a | ||
|
|
428678b2e4 | ||
|
|
bcd30f05ab | ||
|
|
bd49da067f | ||
|
|
3e7c682e66 | ||
|
|
bbd1e40bd5 | ||
|
|
a25f5ff1dd | ||
|
|
80d860d2d4 | ||
|
|
f31ade335d | ||
|
|
023ab755e5 | ||
|
|
4320f81bf4 | ||
|
|
47f1f28385 | ||
|
|
3d1444b829 | ||
|
|
4d3a9defd2 | ||
|
|
1f1211747b | ||
|
|
45e30048f0 | ||
|
|
245c6edec3 | ||
|
|
cb0a96fbf3 | ||
|
|
fade9e36b6 | ||
|
|
24e2a05605 | ||
|
|
1749260c97 | ||
|
|
3b86c3c0e3 | ||
|
|
8f3da7b72c | ||
|
|
d6cde6a5df | ||
|
|
54fd24b757 | ||
|
|
2153b21152 | ||
|
|
74013d751c | ||
|
|
1fa1cdd2a3 | ||
|
|
cd0d0a2b17 | ||
|
|
e63569041a | ||
|
|
54449483b6 | ||
|
|
52bedf4c0b | ||
|
|
5962c0ea86 | ||
|
|
9b2d7382bf | ||
|
|
d91af4cadf | ||
|
|
7e3d265b0f | ||
|
|
f990a7b453 | ||
|
|
ef02d02b1f | ||
|
|
04c169ecfe | ||
|
|
5593f12347 | ||
|
|
3189d8690e | ||
|
|
b51e042e73 | ||
|
|
82f1320ed5 | ||
|
|
add6feab02 | ||
|
|
670c4aecdd | ||
|
|
215d3984cc | ||
|
|
0b36d925f6 | ||
|
|
bc290f62d7 | ||
|
|
c0faa9e79c | ||
|
|
f887906ec3 | ||
|
|
982fd9d803 | ||
|
|
d64bf501d0 | ||
|
|
49f1ec1efb | ||
|
|
0741448798 | ||
|
|
8a4d5c4683 | ||
|
|
3cdb777c23 | ||
|
|
ddc5452978 | ||
|
|
8aa6fabd4d | ||
|
|
398f5b501f | ||
|
|
8d9bcefa9d | ||
|
|
0b29964bf8 | ||
|
|
06e662be86 | ||
|
|
e153b94190 | ||
|
|
7d0374df2f | ||
|
|
2f60748c73 | ||
|
|
bbd58b922b | ||
|
|
21e51b504e | ||
|
|
ba79a2eb97 | ||
|
|
ef81c16611 | ||
|
|
da26f1f377 | ||
|
|
c8cc2d0421 | ||
|
|
a00dc1abf3 | ||
|
|
59c747b793 | ||
|
|
baddc5b6ad | ||
|
|
637d0330c2 | ||
|
|
21e2d0c34a | ||
|
|
ec5f227381 | ||
|
|
8ffb2462f9 | ||
|
|
12187155b4 | ||
|
|
d7f49f3aa5 | ||
|
|
461497f5be | ||
|
|
7929bb91be | ||
|
|
6e13e7343d | ||
|
|
f9f3b46ef6 | ||
|
|
cc851818c2 | ||
|
|
ba45b9d890 | ||
|
|
9fd70c209b | ||
|
|
b7970c2a46 | ||
|
|
a7d072a7c0 | ||
|
|
054891ab5e | ||
|
|
d18ea48b95 | ||
|
|
d39cde206b | ||
|
|
58d5e0318e | ||
|
|
3e23e48239 | ||
|
|
f1f5788066 | ||
|
|
928facfcd9 | ||
|
|
70857f3097 | ||
|
|
da726fc030 | ||
|
|
874a874ea6 | ||
|
|
0f76e17258 | ||
|
|
6b36911260 | ||
|
|
1ebf3afb24 | ||
|
|
84544b1d01 | ||
|
|
319b570f82 | ||
|
|
f7481adf45 | ||
|
|
9b165629f9 | ||
|
|
79b43b2b6e | ||
|
|
80b71a03ce | ||
|
|
2546bafdde | ||
|
|
d27e4bff1e | ||
|
|
27dd96cf3d | ||
|
|
3e45e4ba51 | ||
|
|
0ee249247a | ||
|
|
0f520a8c69 | ||
|
|
aa33194606 | ||
|
|
51360e1ede | ||
|
|
af0febe8ad | ||
|
|
155863bf71 | ||
|
|
3cec61481c | ||
|
|
5708fe9565 | ||
|
|
c40deefb8a | ||
|
|
cd8ce63a3b | ||
|
|
35beda21b8 | ||
|
|
85b8b2bd44 | ||
|
|
29aa7736ca | ||
|
|
e8ff405fce | ||
|
|
5172d24e66 | ||
|
|
dfe7d877eb | ||
|
|
0c00a17372 | ||
|
|
fa1db95814 | ||
|
|
d77c0ec138 | ||
|
|
8ba12ff4f3 | ||
|
|
14ef462263 | ||
|
|
1421ffa32a | ||
|
|
62931b60f8 | ||
|
|
b1e28c7fd8 | ||
|
|
9b0bb871d2 | ||
|
|
1971bdbe67 | ||
|
|
912f48eece | ||
|
|
86fb0cb235 | ||
|
|
23ee58d6ec | ||
|
|
9e0dcae0aa | ||
|
|
7215452da5 | ||
|
|
01b22a7e12 | ||
|
|
d28804cc48 | ||
|
|
a046a04aa2 | ||
|
|
59dd3ed459 | ||
|
|
b6f6048cff | ||
|
|
8a4a05aac0 | ||
|
|
64d6c3074f | ||
|
|
45ca6d8a78 | ||
|
|
77f4b8a75e | ||
|
|
122c4f0514 | ||
|
|
83494152fe | ||
|
|
f6f636f74c | ||
|
|
39e9424f01 | ||
|
|
d93731213a | ||
|
|
f31dc8a9ef | ||
|
|
4687951a04 | ||
|
|
576cba81d9 | ||
|
|
d3a46c822f | ||
|
|
ea86602765 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,3 +4,6 @@ coverage.xml
|
||||
nosetests.xml
|
||||
pylint.txt
|
||||
*.pyc
|
||||
docs/_build
|
||||
toy.py
|
||||
.gitignore
|
||||
|
||||
27
AUTHORS
27
AUTHORS
@@ -17,3 +17,30 @@ Patches and Suggestions
|
||||
- Justin Murphy
|
||||
- Rob Madole
|
||||
- Aram Dulyan
|
||||
- Johannes Gorset
|
||||
- 村山めがね (Megane Murayama)
|
||||
- James Rowe
|
||||
- Daniel Schauenberg
|
||||
- Zbigniew Siciarz
|
||||
- Daniele Tricoli 'Eriol'
|
||||
- Richard Boulton
|
||||
- Miguel Olivares <miguel@moliware.com>
|
||||
- Alberto Paro
|
||||
- Jérémy Bethmont
|
||||
- 潘旭 (Xu Pan)
|
||||
- Tamás Gulácsi
|
||||
- Rubén Abad
|
||||
- Peter Manser
|
||||
- Jeremy Selier
|
||||
- Jens Diemer
|
||||
- Alex <@alopatin>
|
||||
- Tom Hogans <tomhsx@gmail.com>
|
||||
- Armin Ronacher
|
||||
- Shrikant Sharat Kandula
|
||||
- Mikko Ohtamaa
|
||||
- Den Shabalin
|
||||
- Daniel Miller <danielm@vs-networks.com>
|
||||
- Alejandro Giacometti
|
||||
- Rick Mak
|
||||
- Johan Bergström
|
||||
- Josselin Jacquard
|
||||
|
||||
15
HACKING
15
HACKING
@@ -1,15 +0,0 @@
|
||||
Where possible, please follow PEP8 with regard to coding style. Sometimes the
|
||||
line length restriction is too hard to follow, so don't bend over backwards
|
||||
there.
|
||||
|
||||
Triple-quotes should always be """, single quotes are ' unless using " would
|
||||
result in less escaping within the string.
|
||||
|
||||
All modules, functions, and methods should be well documented reStructuredText
|
||||
for Sphinx AutoDoc.
|
||||
|
||||
All functionality should be available in pure Python. Optional C (via Cython)
|
||||
implementations may be written for performance reasons, but should never
|
||||
replace the Python implementation.
|
||||
|
||||
Lastly, don't take yourself too seriously :)
|
||||
106
HISTORY.rst
106
HISTORY.rst
@@ -1,6 +1,112 @@
|
||||
History
|
||||
-------
|
||||
|
||||
0.6.4 (2011-10-13)
|
||||
++++++++++++++++++
|
||||
|
||||
* Automatic decoding of unicode, based on HTTP Headers.
|
||||
* New ``decode_unicode`` setting
|
||||
* Removal of ``r.read/close`` methods
|
||||
* New ``r.faw`` interface for advanced response usage.*
|
||||
* Automatic expansion of parameterized headers
|
||||
|
||||
0.6.3 (2011-10-13)
|
||||
++++++++++++++++++
|
||||
|
||||
* Beautiful ``requests.async`` module, for making async requests w/ gevent.
|
||||
|
||||
0.6.2 (2011-10-09)
|
||||
++++++++++++++++++
|
||||
|
||||
* GET/HEAD obeys allow_redirects=False
|
||||
|
||||
|
||||
0.6.1 (2011-08-20)
|
||||
++++++++++++++++++
|
||||
|
||||
* Enhanced status codes experience ``\o/``
|
||||
* Set a maximum number of redirects (``settings.max_redirects``)
|
||||
* Full Unicode URL support
|
||||
* Support for protocol-less redirects.
|
||||
* Allow for arbitrary request types.
|
||||
* Bugfixes
|
||||
|
||||
|
||||
0.6.0 (2011-08-17)
|
||||
++++++++++++++++++
|
||||
|
||||
* New callback hook system
|
||||
* New persistient sessions object and context manager
|
||||
* Transparent Dict-cookie handling
|
||||
* Status code reference object
|
||||
* Removed Response.cached
|
||||
* Added Response.request
|
||||
* All args are kwargs
|
||||
* Relative redirect support
|
||||
* HTTPError handling improvements
|
||||
* Improved https testing
|
||||
* Bugfixes
|
||||
|
||||
|
||||
0.5.1 (2011-07-23)
|
||||
++++++++++++++++++
|
||||
|
||||
* International Domain Name Support!
|
||||
* Access headers without fetching entire body (``read()``)
|
||||
* Use lists as dicts for parameters
|
||||
* Add Forced Basic Authentication
|
||||
* Forced Basic is default authentication type
|
||||
* ``python-requests.org`` default User-Agent header
|
||||
* CaseInsensitiveDict lower-case caching
|
||||
* Response.history bugfix
|
||||
|
||||
|
||||
0.5.0 (2011-06-21)
|
||||
++++++++++++++++++
|
||||
|
||||
* PATCH Support
|
||||
* Support for Proxies
|
||||
* HTTPBin Test Suite
|
||||
* Redirect Fixes
|
||||
* settings.verbose stream writing
|
||||
* Querystrings for all methods
|
||||
* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised
|
||||
``r.requests.get('hwe://blah'); r.raise_for_status()``
|
||||
|
||||
|
||||
0.4.1 (2011-05-22)
|
||||
++++++++++++++++++
|
||||
|
||||
* Improved Redirection Handling
|
||||
* New 'allow_redirects' param for following non-GET/HEAD Redirects
|
||||
* Settings module refactoring
|
||||
|
||||
|
||||
0.4.0 (2011-05-15)
|
||||
++++++++++++++++++
|
||||
|
||||
* Response.history: list of redirected responses
|
||||
* Case-Insensitive Header Dictionaries!
|
||||
* Unicode URLs
|
||||
|
||||
|
||||
0.3.4 (2011-05-14)
|
||||
++++++++++++++++++
|
||||
|
||||
* Urllib2 HTTPAuthentication Recursion fix (Basic/Digest)
|
||||
* Internal Refactor
|
||||
* Bytes data upload Bugfix
|
||||
|
||||
|
||||
|
||||
0.3.3 (2011-05-12)
|
||||
++++++++++++++++++
|
||||
|
||||
* Request timeouts
|
||||
* Unicode url-encoded data
|
||||
* Settings context manager and module
|
||||
|
||||
|
||||
0.3.2 (2011-04-15)
|
||||
++++++++++++++++++
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
include README.rst LICENSE HISTORY.rst
|
||||
include README.rst LICENSE HISTORY.rst test_requests.py
|
||||
|
||||
14
Makefile
Normal file
14
Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
init:
|
||||
pip install -r reqs.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py --processes=30
|
||||
|
||||
ci: init
|
||||
nosetests --search-test --processes=30 --with-nosexunit test_requests.py
|
||||
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
|
||||
|
||||
site:
|
||||
cd docs; make dirhtml
|
||||
|
||||
docs: site
|
||||
124
README.rst
124
README.rst
@@ -1,20 +1,49 @@
|
||||
Requests: The Simple (e.g. usable) HTTP Module
|
||||
==============================================
|
||||
Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging).
|
||||
Requests is an ISC Licensed HTTP library, written in Python, for human
|
||||
beings.
|
||||
|
||||
But this one's different. This one's going to be awesome. And simple.
|
||||
Most existing Python modules for sending HTTP requests are extremely
|
||||
verbose and cumbersome. Python's builtin urllib2 module provides most of
|
||||
the HTTP capabilities you should need, but the api is thoroughly broken.
|
||||
It requires an enormous amount of work (even method overrides) to
|
||||
perform the simplest of tasks.
|
||||
|
||||
Things shouldn't be this way. Not in Python.
|
||||
|
||||
::
|
||||
|
||||
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
|
||||
>>> r.status_code
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
...
|
||||
|
||||
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 urllib2, but it does
|
||||
all the hard work and crazy hacks for you.
|
||||
|
||||
Really simple.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Extremely simple GET, HEAD, POST, PUT, DELETE Requests
|
||||
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests
|
||||
+ Simple HTTP Header Request Attachment
|
||||
+ Simple Data/Params Request Attachment
|
||||
+ Simple Multipart File Uploads
|
||||
+ CookieJar Support
|
||||
+ Redirection History
|
||||
+ Proxy Support
|
||||
+ Redirection Recursion Urllib Fix
|
||||
+ Auto Decompression of GZipped Content
|
||||
+ Unicode URL Support
|
||||
|
||||
- Simple Authentication
|
||||
+ Simple URL + HTTP Auth Registry
|
||||
@@ -31,15 +60,14 @@ It couldn't be simpler. ::
|
||||
|
||||
HTTPS? Basic Authentication? ::
|
||||
|
||||
>>> r = requests.get('https://convore.com/api/account/verify.json')
|
||||
>>> 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. ::
|
||||
|
||||
>>> conv_auth = ('requeststest', 'requeststest')
|
||||
>>> r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth)
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass', auth=('user', 'pass'))
|
||||
|
||||
>>> r.status_code
|
||||
200
|
||||
@@ -48,74 +76,7 @@ Uh oh, we're not authorized! Let's add authentication. ::
|
||||
'application/json'
|
||||
|
||||
>>> r.content
|
||||
'{"username": "requeststest", "url": "/users/requeststest/", "id": "9408", "img": "censored-long-url"}'
|
||||
|
||||
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
**Requests:**
|
||||
|
||||
All request functions return a Response object (see below).
|
||||
|
||||
If a {filename: fileobject} dictionary is passed in (files=...), a multipart_encode upload will be performed.
|
||||
If CookieJar object is is passed in (cookies=...), the cookies will be sent with the request.
|
||||
|
||||
GET Requests
|
||||
>>> request.get(url, params={}, headers={}, cookies=None, auth=None)
|
||||
<request object>
|
||||
|
||||
HEAD Requests
|
||||
>>> request.head(url, params={}, headers={}, cookies=None, auth=None)
|
||||
<request object>
|
||||
|
||||
PUT Requests
|
||||
>>> request.put(url, data='', headers={}, files={}, cookies=None, auth=None)
|
||||
<request object>
|
||||
|
||||
POST Requests
|
||||
>>> request.post(url, data={}, headers={}, files={}, cookies=None, auth=None)
|
||||
<request object>
|
||||
|
||||
DELETE Requests
|
||||
>>> request.delete(url, params={}, headers={}, cookies=None, auth=None)
|
||||
<request object>
|
||||
|
||||
|
||||
**Responses:**
|
||||
|
||||
Response.status_code
|
||||
(Integer) Received HTTP Status Code Response
|
||||
|
||||
Response.headers
|
||||
(Dictionary) Received HTTP Response Headers
|
||||
|
||||
Response.content
|
||||
(Bytes) Received Content
|
||||
|
||||
Response.url
|
||||
(String) URL of response. Useful for detecting redirects.
|
||||
|
||||
Response.ok
|
||||
(Bool) True if no errors occurred during the request, and the status_code is kosher.
|
||||
|
||||
Response.cached
|
||||
(Bool) True if Response.content is stored within the object.
|
||||
|
||||
Response.error
|
||||
(HTTPError) If an HTTPError occurred (e.g. status of 404), Otherwise this is None.
|
||||
|
||||
Response.raise_for_status()
|
||||
Raises HTTPError if a request is not kosher.
|
||||
|
||||
|
||||
**HTTP Authentication Registry:**
|
||||
|
||||
You can register AuthObjects to automatically enable HTTP Authentication on requests that contain a registered base URL string.
|
||||
|
||||
>>> requests.auth_manager.add_auth(url, authobject)
|
||||
|
||||
'{"authenticated": true, "user": "user"}'
|
||||
|
||||
|
||||
Installation
|
||||
@@ -139,14 +100,5 @@ Contribute
|
||||
If you'd like to contribute, simply fork `the repository`_, commit your changes to the **develop** branch (or branch off of it), and send a pull request. Make sure you add yourself to AUTHORS_.
|
||||
|
||||
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
|
||||
- Sphinx Documentation (http://code.kennethreitz.com/requests/)
|
||||
- Exhaustive unit tests
|
||||
- Get rid of Poster (gets really nasty in py3.x)
|
||||
- Python 3.x Support
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/requests
|
||||
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS
|
||||
|
||||
BIN
docs/_static/requests-sidebar.png
vendored
Normal file
BIN
docs/_static/requests-sidebar.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
27
docs/_templates/sidebarintro.html
vendored
Normal file
27
docs/_templates/sidebarintro.html
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Logo"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Requests is an elegant and simple HTTP library for Python, built for
|
||||
human beings. You are currently looking at the documentation of the
|
||||
development release.
|
||||
</p>
|
||||
|
||||
<h3>Feedback</h3>
|
||||
<p>
|
||||
Feedback is greatly appreciated. If you have any questions, comments,
|
||||
random praise, or anonymous threats, <a href="mailto:me@kennethreitz.com">
|
||||
shoot me an email</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://python-requests.org/">The Requests Website</a></li>
|
||||
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
|
||||
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
11
docs/_templates/sidebarlogo.html
vendored
Normal file
11
docs/_templates/sidebarlogo.html
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<p class="logo">
|
||||
<a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Logo"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Requests is an elegant and simple HTTP library for Python, built for
|
||||
human beings. You are currently looking at the documentation of the
|
||||
development release.
|
||||
</p>
|
||||
3
docs/_themes/.gitignore
vendored
Normal file
3
docs/_themes/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
.DS_Store
|
||||
45
docs/_themes/LICENSE
vendored
Normal file
45
docs/_themes/LICENSE
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
Modifications:
|
||||
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
|
||||
|
||||
Original Project:
|
||||
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
25
docs/_themes/README.rst
vendored
Normal file
25
docs/_themes/README.rst
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
krTheme Sphinx Style
|
||||
====================
|
||||
|
||||
This repository contains sphinx styles Kenneth Reitz uses in most of
|
||||
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
1. put this folder as _themes into your docs folder. Alternatively
|
||||
you can also use git submodules to check out the contents there.
|
||||
|
||||
2. add this to your conf.py: ::
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'flask'
|
||||
|
||||
The following themes exist:
|
||||
|
||||
**kr**
|
||||
the standard flask documentation theme for large projects
|
||||
|
||||
**kr_small**
|
||||
small one-page theme. Intended to be used by very small addon libraries.
|
||||
|
||||
86
docs/_themes/flask_theme_support.py
vendored
Normal file
86
docs/_themes/flask_theme_support.py
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
47
docs/_themes/kr/layout.html
vendored
Normal file
47
docs/_themes/kr/layout.html
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
</div>
|
||||
<a href="https://github.com/kennethreitz/requests">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" />
|
||||
</a>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-8742933-11']);
|
||||
_gaq.push(['_setDomainName', 'none']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var t = document.createElement('script');
|
||||
t.type = 'text/javascript';
|
||||
t.async = true;
|
||||
t.id = 'gauges-tracker';
|
||||
t.setAttribute('data-site-id',
|
||||
'4ddc27f6613f5d186d000007');
|
||||
t.src = '//secure.gaug.es/track.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
{%- endblock %}
|
||||
19
docs/_themes/kr/relations.html
vendored
Normal file
19
docs/_themes/kr/relations.html
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
||||
469
docs/_themes/kr/static/flasky.css_t
vendored
Normal file
469
docs/_themes/kr/static/flasky.css_t
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
|
||||
font-size: 17px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0;
|
||||
margin: -10px 0 0 -20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.document {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* scrollbars */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:start:decrement,
|
||||
::-webkit-scrollbar-button:end:increment {
|
||||
display: block;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:vertical:increment {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: #eee;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:vertical {
|
||||
height: 50px;
|
||||
background-color: #ccc;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:horizontal {
|
||||
width: 50px;
|
||||
background-color: #ccc;
|
||||
-webkit-border-radius: 3px;
|
||||
}
|
||||
|
||||
/* misc. */
|
||||
|
||||
.revsys-inline {
|
||||
display: none!important;
|
||||
}
|
||||
70
docs/_themes/kr/static/small_flask.css
vendored
Normal file
70
docs/_themes/kr/static/small_flask.css
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* small_flask.css_t
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
7
docs/_themes/kr/theme.conf
vendored
Normal file
7
docs/_themes/kr/theme.conf
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
touch_icon =
|
||||
22
docs/_themes/kr_small/layout.html
vendored
Normal file
22
docs/_themes/kr_small/layout.html
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "basic/layout.html" %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{# do not display relbars #}
|
||||
{% block relbar1 %}{% endblock %}
|
||||
{% block relbar2 %}
|
||||
{% if theme_github_fork %}
|
||||
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
|
||||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block sidebar1 %}{% endblock %}
|
||||
{% block sidebar2 %}{% endblock %}
|
||||
287
docs/_themes/kr_small/static/flasky.css_t
vendored
Normal file
287
docs/_themes/kr_small/static/flasky.css_t
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- flasky theme based on nature theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 17px;
|
||||
color: #000;
|
||||
background: white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 40px auto 0 auto;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
text-align: right;
|
||||
color: #888;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
width: 650px;
|
||||
margin: 0 auto 40px auto;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
line-height: 32px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body {
|
||||
padding-bottom: 40px; /* saved for footer */
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: white;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 0;
|
||||
margin: 15px -30px;
|
||||
padding: 8px;
|
||||
line-height: 1.3em;
|
||||
padding: 7px 30px;
|
||||
background: #eee;
|
||||
border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
}
|
||||
|
||||
dl pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
10
docs/_themes/kr_small/theme.conf
vendored
Normal file
10
docs/_themes/kr_small/theme.conf
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
nosidebar = true
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
index_logo = ''
|
||||
index_logo_height = 120px
|
||||
github_fork = ''
|
||||
105
docs/api.rst
Normal file
105
docs/api.rst
Normal file
@@ -0,0 +1,105 @@
|
||||
.. _api:
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. module:: requests
|
||||
|
||||
This part of the documentation covers all the interfaces of Requests. For
|
||||
parts where Requests depends on external libraries, we document the most
|
||||
important right here and provide links to the canonical documentation.
|
||||
|
||||
|
||||
Main Interface
|
||||
--------------
|
||||
|
||||
All of Request's functionality can be accessed by these 7 methods.
|
||||
They all return an instance of the :class:`Response <Response>` object.
|
||||
|
||||
.. autofunction:: request
|
||||
.. autofunction:: head
|
||||
.. autofunction:: get
|
||||
.. autofunction:: post
|
||||
.. autofunction:: put
|
||||
.. autofunction:: patch
|
||||
.. autofunction:: delete
|
||||
|
||||
|
||||
-----------
|
||||
|
||||
|
||||
.. autoclass:: Response
|
||||
:inherited-members:
|
||||
|
||||
Async
|
||||
-----
|
||||
|
||||
.. module:: requests.async
|
||||
|
||||
|
||||
.. autofunction:: map
|
||||
.. autofunction:: request
|
||||
.. autofunction:: head
|
||||
.. autofunction:: get
|
||||
.. autofunction:: post
|
||||
.. autofunction:: put
|
||||
.. autofunction:: patch
|
||||
.. autofunction:: delete
|
||||
|
||||
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
These functions are used internally, but may be useful outside of
|
||||
Requests.
|
||||
|
||||
.. module:: requests.utils
|
||||
|
||||
Cookies
|
||||
~~~~~~~
|
||||
|
||||
.. autofunction:: dict_from_cookiejar
|
||||
.. autofunction:: cookiejar_from_dict
|
||||
.. autofunction:: add_dict_to_cookiejar
|
||||
|
||||
|
||||
Encodings
|
||||
~~~~~~~~~
|
||||
|
||||
.. autofunction:: get_encodings_from_content
|
||||
.. autofunction:: get_encoding_from_headers
|
||||
.. autofunction:: get_unicode_from_response
|
||||
.. autofunction:: decode_gzip
|
||||
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
||||
These items are an internal component to Requests, and should never be
|
||||
seen by the end user (developer). This part of the API documentation
|
||||
exists for those who are extending the functionality of Requests.
|
||||
|
||||
Exceptions
|
||||
~~~~~~~~~~
|
||||
|
||||
.. module:: requests
|
||||
|
||||
.. autoexception:: HTTPError
|
||||
|
||||
.. autoexception:: RequestException
|
||||
|
||||
.. autoexception:: AuthenticationError
|
||||
.. autoexception:: URLRequired
|
||||
.. autoexception:: InvalidMethod
|
||||
.. autoexception:: TooManyRedirects
|
||||
|
||||
|
||||
|
||||
Classes
|
||||
~~~~~~~
|
||||
|
||||
.. autoclass:: requests.Request
|
||||
:inherited-members:
|
||||
|
||||
|
||||
84
docs/community/faq.rst
Normal file
84
docs/community/faq.rst
Normal file
@@ -0,0 +1,84 @@
|
||||
.. _faq:
|
||||
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
This part of the documentation answers common questions about Requests.
|
||||
|
||||
Encoded Data?
|
||||
-------------
|
||||
|
||||
Requests automatically decompresses gzip-encoded responses, and does
|
||||
its best to decodes response content to unicode when possible.
|
||||
it's best to decodes response content to unicode when possible.
|
||||
|
||||
You can get direct access to the raw response (and even the socket),
|
||||
if needed as well.
|
||||
|
||||
|
||||
Custom User-Agents?
|
||||
-------------------
|
||||
|
||||
Requests allows you to easily override User-Agent strings, along with
|
||||
any other HTTP Header.
|
||||
|
||||
|
||||
Why not Httplib2?
|
||||
-----------------
|
||||
|
||||
Chris Adams gave an excellent summary on
|
||||
`Hacker News <http://news.ycombinator.com/item?id=2884406>`_:
|
||||
|
||||
httplib2 is part of why you should use requests: it's far more respectable
|
||||
as a client but not as well documented and it still takes way too much code
|
||||
for basic operations. I appreciate what httplib2 is trying to do, that
|
||||
there's a ton of hard low-level annoyances in building a modern HTTP
|
||||
client, but really, just use requests instead. Kenneth Reitz is very
|
||||
motivated and he gets the degree to which simple things should be simple
|
||||
whereas httplib2 feels more like an academic exercise than something
|
||||
people should use to build production systems[1].
|
||||
|
||||
Disclosure: I'm listed in the requests AUTHORS file but can claim credit
|
||||
for, oh, about 0.0001% of the awesomeness.
|
||||
|
||||
1. http://code.google.com/p/httplib2/issues/detail?id=96 is a good example:
|
||||
an annoying bug which affect many people, there was a fix available for
|
||||
months, which worked great when I applied it in a fork and pounded a couple
|
||||
TB of data through it, but it took over a year to make it into trunk and
|
||||
even longer to make it onto PyPI where any other project which required "
|
||||
httplib2" would get the working version.
|
||||
|
||||
|
||||
Python 3 Support?
|
||||
-----------------
|
||||
|
||||
It's on the way. 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
|
||||
* PyPy-c 1.4
|
||||
* PyPy-c 1.5
|
||||
|
||||
|
||||
Support for Python 3.x is coming *very* soon.
|
||||
|
||||
|
||||
Keep-alive Support?
|
||||
-------------------
|
||||
|
||||
It's on the way.
|
||||
|
||||
|
||||
Proxy Support?
|
||||
--------------
|
||||
|
||||
You bet!
|
||||
|
||||
37
docs/community/support.rst
Normal file
37
docs/community/support.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
.. _support:
|
||||
|
||||
Support
|
||||
=======
|
||||
|
||||
If you have a questions or issues about Requests, there are serveral options:
|
||||
|
||||
Send a Tweet
|
||||
------------
|
||||
|
||||
If your question is less than 140 characters, feel free to send a tweet to
|
||||
`@kennethreitz <http://twitter.com/kennethreitz>`_.
|
||||
|
||||
|
||||
File an Issue
|
||||
-------------
|
||||
|
||||
If you notice some unexpected behavior in Requests, or want to see support
|
||||
for a new feature,
|
||||
`file an issue on GitHub <https://github.com/kennethreitz/requests/issues>`_.
|
||||
|
||||
|
||||
E-mail
|
||||
------
|
||||
|
||||
I'm more than happy to answer any personal or in-depth questions about
|
||||
Requests. Feel free to email
|
||||
`requests@kennethreitz.com <mailto:requests@kennethreitz.com>`_.
|
||||
|
||||
|
||||
IRC
|
||||
---
|
||||
|
||||
The official Freenode channel for Requests is
|
||||
`#python-requests <irc://irc.freenode.net/python-requests>`_
|
||||
|
||||
I'm also available as **kennethreitz** on Freenode.
|
||||
30
docs/community/updates.rst
Normal file
30
docs/community/updates.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
.. _updates:
|
||||
|
||||
Updates
|
||||
=======
|
||||
|
||||
If you'd like to stay up to date on the community and development of Requests,
|
||||
there are serveral options:
|
||||
|
||||
GitHub
|
||||
------
|
||||
|
||||
The best way to track the development of Requests is through
|
||||
`the GitHub repo <https://github.com/kennethreitz/requests>`_.
|
||||
|
||||
Twitter
|
||||
-------
|
||||
|
||||
I often tweet about new features and releases of Requests.
|
||||
|
||||
Follow `@kennethreitz <https://twitter.com/kennethreitz>`_ for updates.
|
||||
|
||||
|
||||
Mailing List
|
||||
------------
|
||||
|
||||
There's a low-volume mailing list for Requests. To subscribe to the
|
||||
mailing list, send an email to
|
||||
`requests@librelist.org <mailto:requests@librelist.org>`_.
|
||||
|
||||
|
||||
28
docs/conf.py
28
docs/conf.py
@@ -16,7 +16,9 @@ import sys, os
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
import requests
|
||||
from requests import __version__
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
@@ -25,7 +27,7 @@ import sys, os
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.jsmath', 'sphinx.ext.viewcode']
|
||||
extensions = ['sphinx.ext.autodoc']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -41,14 +43,14 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'2011, Kenneth Reitz'
|
||||
copyright = u'2011. 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
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.3.2'
|
||||
version = __version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
@@ -81,7 +83,7 @@ exclude_patterns = ['_build']
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
@@ -112,6 +114,7 @@ html_theme = 'default'
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
@@ -131,7 +134,12 @@ html_static_path = ['_static']
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
'sourcelink.html', 'searchbox.html']
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
@@ -147,10 +155,10 @@ html_static_path = ['_static']
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
@@ -230,3 +238,7 @@ texinfo_documents = [
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
texinfo_appendices = []
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'kr'
|
||||
5
docs/dev/authors.rst
Normal file
5
docs/dev/authors.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
Authors
|
||||
=======
|
||||
|
||||
|
||||
.. include:: ../../AUTHORS
|
||||
0
docs/dev/internals.rst
Normal file
0
docs/dev/internals.rst
Normal file
15
docs/dev/todo.rst
Normal file
15
docs/dev/todo.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
How to Help
|
||||
===========
|
||||
|
||||
Requests is under active development, and contributions are more than welcome!
|
||||
|
||||
|
||||
What Needs to be Done
|
||||
---------------------
|
||||
|
||||
- OAuth Support (`#65 <https://github.com/kennethreitz/requests/issues/65>`_)
|
||||
- HTTP Cert Checking (`#30 <https://github.com/kennethreitz/requests/issues/30>`_)
|
||||
- Removal of Poster (`#68 <https://github.com/kennethreitz/requests/issues/68>`_)
|
||||
- Python 3.x Support (`#70 <https://github.com/kennethreitz/requests/issues/70>`_)
|
||||
- Kerberos Support (`#47 <https://github.com/kennethreitz/requests/issues/47>`_)
|
||||
- More Flexible Context Manager (`#69 <https://github.com/kennethreitz/requests/issues/69>`_)
|
||||
112
docs/index.rst
112
docs/index.rst
@@ -3,18 +3,114 @@
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to Requests's documentation!
|
||||
====================================
|
||||
Requests: HTTP for Humans
|
||||
=========================
|
||||
|
||||
Contents:
|
||||
Release v\ |version|. (:ref:`Installation <install>`)
|
||||
|
||||
Requests is an :ref:`ISC Licensed <isc>` HTTP library, written in Python, for human beings.
|
||||
|
||||
Most existing Python modules for sending HTTP requests are extremely verbose
|
||||
and cumbersome. Python's builtin **urllib2** module provides most of
|
||||
the HTTP capabilities you should need, but the api is thoroughly **broken**.
|
||||
It requires an *enormous* amount of work (even method overrides) to perform
|
||||
the simplest of tasks.
|
||||
|
||||
Things shouldn’t be this way. Not in Python.
|
||||
|
||||
::
|
||||
|
||||
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
|
||||
>>> r.status_code
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
...
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
|
||||
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
|
||||
multipart files, and parameters with simple Python dictionaries, and access the
|
||||
response data in the same way. It's powered by :py:class:`urllib2`, but it does
|
||||
all the hard work and crazy hacks for you.
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`Twitter, Inc <http://twitter.com>`_,
|
||||
a U.S. Federal Institution,
|
||||
`Readability <http://readability.com>`_, and
|
||||
`Work for Pie <http://workforpie.com>`_
|
||||
use Requests internally.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
right level of abstraction.
|
||||
|
||||
**Daniel Greenfeld**
|
||||
Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to
|
||||
@kennethreitz's request library. Today has been AWESOME.
|
||||
|
||||
**Kenny Meyers**
|
||||
Python HTTP: When in doubt, or when not in doubt, use Requests. Beautiful,
|
||||
simple, Pythonic.
|
||||
|
||||
**Rich Leland**
|
||||
Requests is awesome. That is all.
|
||||
|
||||
|
||||
User Guide
|
||||
----------
|
||||
|
||||
This part of the documentation, which is mostly prose, begins with some
|
||||
background information about Requests, then focuses on step-by-step
|
||||
instructions for getting the most out of Requests.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
user/intro
|
||||
user/install
|
||||
user/quickstart
|
||||
user/advanced
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
Community Guide
|
||||
-----------------
|
||||
|
||||
This part of the documentation, which is mostly prose, details the
|
||||
Requests ecosystem and community.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
community/faq
|
||||
community/support
|
||||
community/updates
|
||||
|
||||
API Documentation
|
||||
-----------------
|
||||
|
||||
If you are looking for information on a specific function, class or method,
|
||||
this part of the documentation is for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
|
||||
|
||||
Developer Guide
|
||||
---------------
|
||||
|
||||
If you want to contribute to the project, this part of the documentation is for
|
||||
you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
dev/internals
|
||||
dev/todo
|
||||
dev/authors
|
||||
|
||||
171
docs/user/advanced.rst
Normal file
171
docs/user/advanced.rst
Normal file
@@ -0,0 +1,171 @@
|
||||
.. _advanced:
|
||||
|
||||
Advanced Usage
|
||||
==============
|
||||
|
||||
This document covers some of Requests more advanced features.
|
||||
|
||||
|
||||
Session Objects
|
||||
---------------
|
||||
|
||||
The Session object allows you to persist certain parameters across
|
||||
requests. It also establishes a CookieJar and passes it along
|
||||
to any requests made from the Session instance.
|
||||
|
||||
A session object has all the methods of the main Requests API.
|
||||
|
||||
Let's persist some cookies across requests::
|
||||
|
||||
with requests.session() as s:
|
||||
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
|
||||
print r.content
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
|
||||
|
||||
Sessions can also be used to provide default data to the request methods::
|
||||
|
||||
headers = {'x-test': 'true'}
|
||||
auth = ('user', 'pass')
|
||||
|
||||
with requests.session(auth=auth, headers=headers) as c:
|
||||
|
||||
# both 'x-test' and 'x-test2' are sent
|
||||
c.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
|
||||
|
||||
|
||||
.. admonition:: Global Settings
|
||||
|
||||
Certain parameters are best set at the ``request.config`` level
|
||||
(e.g.. a global proxy, user agent header).
|
||||
|
||||
|
||||
Asynchronous Requests
|
||||
----------------------
|
||||
|
||||
Requests has first-class support for non-blocking i/o requests, powered
|
||||
by gevent. This allows you to send a bunch of HTTP requests at the same
|
||||
|
||||
First, let's import the async module. Heads up — if you don't have
|
||||
`gevent <gevent>`_ this will fail::
|
||||
|
||||
from requests import async
|
||||
|
||||
The ``async`` module has the exact same api as ``requests``, except it
|
||||
doesn't send the request immediately. Instead, it returns the ``Request``
|
||||
object.
|
||||
|
||||
We can build a list of ``Request`` objects easily::
|
||||
|
||||
urls = [
|
||||
'http://python-requests.org',
|
||||
'http://httpbin.org',
|
||||
'http://python-guide.org',
|
||||
'http://kennethreitz.com'
|
||||
]
|
||||
|
||||
rs = [async.get(u) for u in urls]
|
||||
|
||||
Now we have a list of ``Request`` objects, ready to be sent. We could
|
||||
send them one at a time with ``Request.send()``, but that would take a while.
|
||||
Instead, we'll send them all at the same time with ``async.map()``::
|
||||
|
||||
>>> async.map(rs)
|
||||
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
|
||||
|
||||
|
||||
Event Hooks
|
||||
-----------
|
||||
|
||||
Requests has a hook system that you can use to manipulate portions of
|
||||
the request process, or signal event handling.
|
||||
|
||||
Available hooks:
|
||||
|
||||
``args``:
|
||||
A dictionary of the arguments being sent to Request().
|
||||
|
||||
``pre_request``:
|
||||
The Request object, directly before being sent.
|
||||
|
||||
``post_request``:
|
||||
The Request object, directly after being sent.
|
||||
|
||||
``response``:
|
||||
The response generated from a Request.
|
||||
|
||||
|
||||
You can assign a hook function on a per-request basis by passing a
|
||||
``{hook_name: callback_function}`` dictionary to the ``hooks`` request
|
||||
parameter::
|
||||
|
||||
hooks=dict(args=print_url)
|
||||
|
||||
That ``callback_function`` will receive a chunk of data as its first
|
||||
argument.
|
||||
|
||||
::
|
||||
|
||||
def print_url(args):
|
||||
print args['url']
|
||||
|
||||
If an error occurs while executing your callback, a warning is given.
|
||||
|
||||
If the callback function returns a value, it is assumed that it is to
|
||||
replace the data that was passed in. If the function doesn't return
|
||||
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
|
||||
<Response [200]>
|
||||
|
||||
Let's hijack some arguments this time with a new callback::
|
||||
|
||||
def hack_headers(args):
|
||||
if not args[headers]:
|
||||
args['headers'] = dict()
|
||||
|
||||
args['headers'].update({'X-Testing': 'True'})
|
||||
|
||||
return args
|
||||
|
||||
hooks = dict(args=hack_headers)
|
||||
headers = dict(yo=dawg)
|
||||
|
||||
And give it a try::
|
||||
|
||||
>>> requests.get('http://httpbin/headers', hooks=hooks, headers=headers)
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "",
|
||||
"Accept-Encoding": "gzip",
|
||||
"Yo": "dawg",
|
||||
"X-Forwarded-For": "::ffff:24.127.96.129",
|
||||
"Connection": "close",
|
||||
"User-Agent": "python-requests.org",
|
||||
"Host": "httpbin.org",
|
||||
"X-Testing": "True",
|
||||
"X-Forwarded-Protocol": "",
|
||||
"Content-Type": ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Verbose Logging
|
||||
---------------
|
||||
|
||||
If you want to get a good look at what HTTP requests are being sent
|
||||
by your application, you can turn on verbose logging.
|
||||
|
||||
To do so, just configure Requests with a stream to write to::
|
||||
|
||||
>>> requests.settings.verbose = sys.stderr
|
||||
>>> requests.get('http://httpbin.org/headers')
|
||||
2011-08-17T03:04:23.380175 GET http://httpbin.org/headers
|
||||
<Response [200]>
|
||||
78
docs/user/install.rst
Normal file
78
docs/user/install.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
.. _install:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
This part of the documentation covers the installation of Requests.
|
||||
The first step to using any software package is getting it properly installed.
|
||||
|
||||
|
||||
Distribute & Pip
|
||||
----------------
|
||||
|
||||
Installing requests is simple with `pip <http://www.pip-installer.org/>`_::
|
||||
|
||||
$ pip install requests
|
||||
|
||||
or, with `easy_install <http://pypi.python.org/pypi/setuptools>`_::
|
||||
|
||||
$ easy_install requests
|
||||
|
||||
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/index.html#pip-compared-to-easy-install>`_.
|
||||
|
||||
|
||||
|
||||
Cheeseshop Mirror
|
||||
-----------------
|
||||
|
||||
If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's
|
||||
personal `Cheeseshop mirror <http://pip.kreitz.co/>`_::
|
||||
|
||||
$ pip install -i http://pip.kreitz.co/simple requests
|
||||
|
||||
|
||||
Get the Code
|
||||
------------
|
||||
|
||||
Requests is actively developed on GitHub, where the code is
|
||||
`always available <https://github.com/kennethreitz/requests>`_.
|
||||
|
||||
You can either clone the public repository::
|
||||
|
||||
git clone git://github.com/kennethreitz/requests.git
|
||||
|
||||
Download the `tarball <https://github.com/kennethreitz/requests/tarball/master>`_::
|
||||
|
||||
$ curl -O 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
|
||||
|
||||
|
||||
Once you have a copy of the source, you can embed it in your Python package,
|
||||
or install it into your site-packages easily::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
.. _gevent:
|
||||
|
||||
Installing Gevent
|
||||
-----------------
|
||||
|
||||
If you are using the ``requests.async`` module for making concurrent
|
||||
requests, you need to install gevent.
|
||||
|
||||
To install gevent, you'll need ``libevent``.
|
||||
|
||||
OSX::
|
||||
|
||||
$ brew install libevent
|
||||
|
||||
Ubuntu::
|
||||
|
||||
$ apt-get install libevent-dev
|
||||
|
||||
Once you have ``libevent``, you can install ``gevent`` with ``pip``::
|
||||
|
||||
$ pip install gevent
|
||||
49
docs/user/intro.rst
Normal file
49
docs/user/intro.rst
Normal file
@@ -0,0 +1,49 @@
|
||||
.. _introduction:
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
Requests was developed with a few :pep:`20` idioms in mind.
|
||||
|
||||
|
||||
#. Beautiful is better than ugly.
|
||||
#. Explicit is better than implicit.
|
||||
#. Simple is better than complex.
|
||||
#. Complex is better than complicated.
|
||||
#. Readability counts.
|
||||
|
||||
All contributions to Requests should keep these important rules in mind.
|
||||
|
||||
|
||||
ISC License
|
||||
-----------
|
||||
|
||||
A large number of open source projects you find today are `GPL Licensed`_.
|
||||
While the GPL has its time and place, it should most certainly not be your
|
||||
go-to license for your next open source project.
|
||||
|
||||
A project that is released as GPL cannot be used in any commercial product
|
||||
without the product itself also being offered as open source.
|
||||
|
||||
The MIT, BSD, ISC, and Apache2 licenses are great alternatives to the GPL
|
||||
that allow your open-source software to be used freely in proprietary,
|
||||
closed-source software.
|
||||
|
||||
Requests is released under terms of `The ISC License`_.
|
||||
|
||||
.. _`GPL Licensed`: http://www.opensource.org/licenses/gpl-license.php
|
||||
.. _`The ISC License`: http://www.opensource.org/licenses/isc-license
|
||||
|
||||
|
||||
Requests License
|
||||
----------------
|
||||
|
||||
Copyright (c) 2011, 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 copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
138
docs/user/quickstart.rst
Normal file
138
docs/user/quickstart.rst
Normal file
@@ -0,0 +1,138 @@
|
||||
.. _quickstart:
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. module:: requests.models
|
||||
|
||||
Eager to get started? This page gives a good introduction in how to get started
|
||||
with Requests. This assumes you already have Requests installed. If you do not,
|
||||
head over to the :ref:`Installation <install>` section.
|
||||
|
||||
First, make sure that:
|
||||
|
||||
* Requests is :ref:`installed <install>`
|
||||
* Requests is :ref:`up-to-date <updates>`
|
||||
|
||||
|
||||
Lets gets started with some simple use cases and examples.
|
||||
|
||||
|
||||
Make a GET Request
|
||||
------------------
|
||||
|
||||
Making a standard request with Requests is very simple.
|
||||
|
||||
Let's get GitHub's public timeline ::
|
||||
|
||||
r = requests.get('https://github.com/timeline.json')
|
||||
|
||||
Now, we have a :class:`Response` object called ``r``. We can get all the
|
||||
information we need from this.
|
||||
|
||||
|
||||
Response Content
|
||||
----------------
|
||||
|
||||
We can read the content of the server's response::
|
||||
|
||||
>>> r.content
|
||||
'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
|
||||
Response Status Codes
|
||||
---------------------
|
||||
|
||||
We can check the response status code::
|
||||
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
Requests also comes with a built-in status code lookup object for easy
|
||||
reference::
|
||||
|
||||
>>> r.status_code == requests.codes.ok
|
||||
True
|
||||
|
||||
If we made a bad request, we can raise it with
|
||||
:class:`Response.raise_for_status()`::
|
||||
|
||||
>>> _r = requests.get('http://httpbin.org/status/404')
|
||||
>>> _r.status_code
|
||||
404
|
||||
|
||||
>>> _r.raise_for_status()
|
||||
Traceback (most recent call last):
|
||||
File "requests/models.py", line 394, in raise_for_status
|
||||
raise self.error
|
||||
urllib2.HTTPError: HTTP Error 404: NOT FOUND
|
||||
|
||||
But, since our ``status_code`` was ``200``, when we call it::
|
||||
|
||||
>>> r.raise_for_status()
|
||||
None
|
||||
|
||||
All is well.
|
||||
|
||||
|
||||
Response Headers
|
||||
----------------
|
||||
|
||||
We can view the server's response headers with a simple Python dictionary
|
||||
interface::
|
||||
|
||||
>>> r.headers
|
||||
{
|
||||
'status': '200 OK',
|
||||
'content-encoding': 'gzip',
|
||||
'transfer-encoding': 'chunked',
|
||||
'connection': 'close',
|
||||
'server': 'nginx/1.0.4',
|
||||
'x-runtime': '148ms',
|
||||
'etag': '"e1ca502697e5c9317743dc078f67693f"',
|
||||
'content-type': 'application/json; charset=utf-8'
|
||||
}
|
||||
|
||||
The dictionary is special, though: it's made just for HTTP headers. According to
|
||||
`RFC 2616 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>`_, HTTP
|
||||
Headers are case-insensitive.
|
||||
|
||||
So, we can access the headers using any capitalization we want::
|
||||
|
||||
>>> r.headers['Content-Type']
|
||||
'application/json; charset=utf-8'
|
||||
|
||||
>>> r.headers.get('content-type')
|
||||
'application/json; charset=utf-8'
|
||||
|
||||
If a header doesn't exist in the Response, its value defaults to ``None``::
|
||||
|
||||
>>> r.headers['X-Random']
|
||||
None
|
||||
|
||||
|
||||
Cookies
|
||||
-------
|
||||
|
||||
If a response contains some Cookies, you can get quick access to them::
|
||||
|
||||
>>> url = 'http://httpbin.org/cookies/set/requests-is/awesome'
|
||||
>>> r = requests.get(url)
|
||||
|
||||
>>> print r.cookies
|
||||
{'requests-is': 'awesome'}
|
||||
|
||||
The underlying CookieJar is also available for more advanced handling::
|
||||
|
||||
>>> r.request.cookiejar
|
||||
<cookielib.CookieJar>
|
||||
|
||||
To send your own cookies to the server, you can use the ``cookies``
|
||||
parameter::
|
||||
|
||||
>>> url = 'http://httpbin.org/cookies'
|
||||
>>> cookies = dict(cookies_are='working')
|
||||
|
||||
>>> r = requests.get(url, cookies=cookies)
|
||||
>>> r.content
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
2217
ext/requests-logo.ai
Normal file
2217
ext/requests-logo.ai
Normal file
File diff suppressed because one or more lines are too long
@@ -1,6 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import packages
|
||||
from core import *
|
||||
|
||||
from core import __version__
|
||||
|
||||
159
requests/api.py
Normal file
159
requests/api.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.api
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module impliments the Requests API.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
import config
|
||||
from .models import Request, Response, AuthObject
|
||||
from .status_codes import codes
|
||||
from .hooks import dispatch_hook
|
||||
from .utils import cookiejar_from_dict, header_expand
|
||||
|
||||
|
||||
__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete')
|
||||
|
||||
def request(method, url,
|
||||
params=None, data=None, headers=None, cookies=None, files=None, auth=None,
|
||||
timeout=None, allow_redirects=False, proxies=None, hooks=None, return_response=True):
|
||||
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
"""
|
||||
|
||||
method = str(method).upper()
|
||||
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Expand header values
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
args = dict(
|
||||
method = method,
|
||||
url = url,
|
||||
data = data,
|
||||
params = params,
|
||||
headers = headers,
|
||||
cookiejar = cookies,
|
||||
files = files,
|
||||
auth = auth,
|
||||
hooks = hooks,
|
||||
timeout = timeout or config.settings.timeout,
|
||||
allow_redirects = allow_redirects,
|
||||
proxies = proxies or config.settings.proxies,
|
||||
)
|
||||
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('args', hooks, args)
|
||||
|
||||
r = Request(**args)
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, r)
|
||||
|
||||
# Don't send if asked nicely.
|
||||
if not return_response:
|
||||
return r
|
||||
|
||||
# Send the HTTP Request.
|
||||
r.send()
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', hooks, r)
|
||||
|
||||
# Response manipulation hook.
|
||||
r.response = dispatch_hook('response', hooks, r.response)
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('GET', url, **kwargs)
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('HEAD', url, **kwargs)
|
||||
|
||||
|
||||
def post(url, data='', **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('post', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def put(url, data='', **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(url, data='', **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('patch', url, **kwargs)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
return request('delete', url, **kwargs)
|
||||
@@ -1,48 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.async
|
||||
~~~~~~~~~~~~~~
|
||||
requests.async
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements the main Requests system, after monkey-patching
|
||||
the urllib2 module with eventlet or gevent..
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
This module contains an asynchronous replica of ``requests.api``, powered
|
||||
by gevent. All API methods return a ``Request`` instance (as opposed to
|
||||
``Response``). A list of requests can be sent with ``map()``.
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
from urllib2 import HTTPError
|
||||
|
||||
|
||||
try:
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
import gevent
|
||||
from gevent import monkey as curious_george
|
||||
except ImportError:
|
||||
pass
|
||||
raise RuntimeError('Gevent is required for requests.async.')
|
||||
|
||||
if not 'eventlet' in locals():
|
||||
try:
|
||||
from gevent import monkey
|
||||
monkey.patch_all()
|
||||
except ImportError:
|
||||
pass
|
||||
# Monkey-patch.
|
||||
curious_george.patch_all(thread=False)
|
||||
|
||||
from . import api
|
||||
from .hooks import dispatch_hook
|
||||
|
||||
|
||||
if not 'eventlet' in locals():
|
||||
raise ImportError('No Async adaptations of urllib2 found!')
|
||||
__all__ = (
|
||||
'map',
|
||||
'get', 'head', 'post', 'put', 'patch', 'delete', 'request'
|
||||
)
|
||||
|
||||
|
||||
def _patched(f):
|
||||
"""Patches a given API function to not send."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
return f(*args, return_response=False, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
|
||||
if pools:
|
||||
r._pools = pools
|
||||
|
||||
r.send()
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', r.hooks, r)
|
||||
|
||||
# Response manipulation hook.
|
||||
r.response = dispatch_hook('response', r.hooks, r.response)
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
# Patched requests.api functions.
|
||||
get = _patched(api.get)
|
||||
head = _patched(api.head)
|
||||
post = _patched(api.post)
|
||||
put = _patched(api.put)
|
||||
patch = _patched(api.patch)
|
||||
delete = _patched(api.delete)
|
||||
request = _patched(api.request)
|
||||
|
||||
|
||||
def map(requests, prefetch=True):
|
||||
"""Concurrently converts a list of Requests to Responses.
|
||||
|
||||
:param requests: a collection of Request objects.
|
||||
:param prefetch: If False, the content will not be downloaded immediately.
|
||||
"""
|
||||
|
||||
jobs = [gevent.spawn(_send, r) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
if prefetch:
|
||||
[r.response.content for r in requests]
|
||||
|
||||
return [r.response for r in requests]
|
||||
|
||||
|
||||
from .core import *
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Request', 'Response', 'request', 'get', 'head', 'post', 'put', 'delete',
|
||||
'auth_manager', 'AuthObject','RequestException', 'AuthenticationError',
|
||||
'URLRequired', 'InvalidMethod', 'HTTPError'
|
||||
]
|
||||
|
||||
68
requests/config.py
Normal file
68
requests/config.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.config
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides the Requests settings feature set.
|
||||
|
||||
"""
|
||||
|
||||
class Settings(object):
|
||||
_singleton = {}
|
||||
|
||||
# attributes with defaults
|
||||
__attrs__ = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Settings, self).__init__()
|
||||
|
||||
self.__dict__ = self._singleton
|
||||
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# new instance of class to call
|
||||
r = self.__class__()
|
||||
|
||||
# cache previous settings for __exit__
|
||||
r.__cache = self.__dict__.copy()
|
||||
map(self.__cache.setdefault, self.__attrs__)
|
||||
|
||||
# set new settings
|
||||
self.__dict__.update(*args, **kwargs)
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
|
||||
def __exit__(self, *args):
|
||||
|
||||
# restore cached copy
|
||||
self.__dict__.update(self.__cache.copy())
|
||||
del self.__cache
|
||||
|
||||
|
||||
def __getattribute__(self, key):
|
||||
if key in object.__getattribute__(self, '__attrs__'):
|
||||
try:
|
||||
return object.__getattribute__(self, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
return object.__getattribute__(self, key)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
settings.base_headers = {'User-Agent': 'python-requests.org'}
|
||||
settings.accept_gzip = True
|
||||
settings.proxies = None
|
||||
settings.verbose = None
|
||||
settings.timeout = None
|
||||
settings.max_redirects = 30
|
||||
settings.decode_unicode = True
|
||||
|
||||
#: Use socket.setdefaulttimeout() as fallback?
|
||||
settings.timeout_fallback = True
|
||||
537
requests/core.py
537
requests/core.py
@@ -1,536 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.core
|
||||
~~~~~~~~~~~~~
|
||||
requests.core
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This module implements the main Requests system.
|
||||
This module implements the main Requests system.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import zlib
|
||||
|
||||
from urllib2 import HTTPError
|
||||
from urlparse import urlparse
|
||||
|
||||
from .packages.poster.encode import multipart_encode
|
||||
from .packages.poster.streaminghttp import register_openers, get_handlers
|
||||
|
||||
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.3.2'
|
||||
__build__ = 0x000302
|
||||
__version__ = '0.6.4'
|
||||
__build__ = 0x000604
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
__all__ = [
|
||||
'Request', 'Response', 'request', 'get', 'head', 'post', 'put', 'delete',
|
||||
'auth_manager', 'AuthObject','RequestException', 'AuthenticationError',
|
||||
'URLRequired', 'InvalidMethod', 'HTTPError'
|
||||
]
|
||||
|
||||
from models import HTTPError, Request, Response
|
||||
from api import *
|
||||
from exceptions import *
|
||||
from sessions import session
|
||||
from status_codes import codes
|
||||
from config import settings
|
||||
|
||||
|
||||
class _Request(urllib2.Request):
|
||||
"""Hidden wrapper around the urllib2.Request object. Allows for manual
|
||||
setting of HTTP methods.
|
||||
"""
|
||||
|
||||
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
|
||||
urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
|
||||
self.method = method
|
||||
|
||||
def get_method(self):
|
||||
if self.method:
|
||||
return self.method
|
||||
|
||||
return urllib2.Request.get_method(self)
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""The :class:`Request` object. It carries out all functionality of
|
||||
Requests. Recommended interface is with the Requests functions.
|
||||
"""
|
||||
|
||||
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
|
||||
|
||||
def __init__(self, url=None, headers=dict(), files=None, method=None,
|
||||
data=dict(), auth=None, cookiejar=None):
|
||||
|
||||
self.url = url
|
||||
self.headers = headers
|
||||
self.files = files
|
||||
self.method = method
|
||||
self.data = data
|
||||
|
||||
# url encode data if it's a dict
|
||||
if hasattr(data, 'items'):
|
||||
self._enc_data = urllib.urlencode(data)
|
||||
else:
|
||||
self._enc_data = data
|
||||
|
||||
self.response = Response()
|
||||
|
||||
if isinstance(auth, (list, tuple)):
|
||||
auth = AuthObject(*auth)
|
||||
if not auth:
|
||||
auth = auth_manager.get_auth(self.url)
|
||||
self.auth = auth
|
||||
self.cookiejar = cookiejar
|
||||
self.sent = False
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if (name == 'method') and (value):
|
||||
if not value in self._METHODS:
|
||||
raise InvalidMethod()
|
||||
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
|
||||
def _checks(self):
|
||||
"""Deterministic checks for consistency."""
|
||||
|
||||
if not self.url:
|
||||
raise URLRequired
|
||||
|
||||
|
||||
def _get_opener(self):
|
||||
"""Creates appropriate opener object for urllib2."""
|
||||
|
||||
_handlers = []
|
||||
|
||||
if self.cookiejar is not None:
|
||||
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
|
||||
|
||||
if self.auth:
|
||||
if not isinstance(self.auth.handler, (urllib2.AbstractBasicAuthHandler, urllib2.AbstractDigestAuthHandler)):
|
||||
auth_manager.add_password(self.auth.realm, self.url, self.auth.username, self.auth.password)
|
||||
self.auth.handler = self.auth.handler(auth_manager)
|
||||
auth_manager.add_auth(self.url, self.auth)
|
||||
|
||||
_handlers.append(self.auth.handler)
|
||||
|
||||
if not _handlers:
|
||||
return urllib2.urlopen
|
||||
|
||||
_handlers.extend(get_handlers())
|
||||
opener = urllib2.build_opener(*_handlers)
|
||||
|
||||
if self.headers:
|
||||
# Allow default headers in the opener to be overloaded
|
||||
normal_keys = [k.capitalize() for k in self.headers]
|
||||
for key, val in opener.addheaders[:]:
|
||||
if key not in normal_keys:
|
||||
continue
|
||||
# Remove it, we have a value to take its place
|
||||
opener.addheaders.remove((key, val))
|
||||
|
||||
return opener.open
|
||||
|
||||
def _build_response(self, resp):
|
||||
"""Build internal Response object from given response."""
|
||||
|
||||
self.response.status_code = getattr(resp, 'code', None)
|
||||
self.response.headers = getattr(resp.info(), 'dict', None)
|
||||
self.response.content = resp.read()
|
||||
|
||||
if self.response.headers.get('content-encoding', None) == 'gzip':
|
||||
try:
|
||||
self.response.content = zlib.decompress(self.response.content, 16+zlib.MAX_WBITS)
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
self.response.url = getattr(resp, 'url', None)
|
||||
|
||||
@staticmethod
|
||||
def _build_url(url, data):
|
||||
"""Build URLs."""
|
||||
|
||||
if urlparse(url).query:
|
||||
return '%s&%s' % (url, data)
|
||||
else:
|
||||
if data:
|
||||
return '%s?%s' % (url, data)
|
||||
else:
|
||||
return url
|
||||
|
||||
def send(self, anyway=False):
|
||||
"""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.
|
||||
|
||||
Once a request is successfully sent, `sent` will equal True.
|
||||
|
||||
:param anyway: If True, request will be sent, even if it has
|
||||
already been sent.
|
||||
"""
|
||||
self._checks()
|
||||
success = False
|
||||
|
||||
if self.method in ('GET', 'HEAD', 'DELETE'):
|
||||
req = _Request(self._build_url(self.url, self._enc_data), method=self.method)
|
||||
else:
|
||||
|
||||
if self.files:
|
||||
register_openers()
|
||||
|
||||
if self.data:
|
||||
self.files.update(self.data)
|
||||
|
||||
datagen, headers = multipart_encode(self.files)
|
||||
req = _Request(self.url, data=datagen, headers=headers, method=self.method)
|
||||
|
||||
else:
|
||||
req = _Request(self.url, data=self._enc_data, method=self.method)
|
||||
|
||||
if self.headers:
|
||||
req.headers.update(self.headers)
|
||||
|
||||
if not self.sent or anyway:
|
||||
try:
|
||||
opener = self._get_opener()
|
||||
resp = opener(req)
|
||||
|
||||
if self.cookiejar is not None:
|
||||
self.cookiejar.extract_cookies(resp, req)
|
||||
|
||||
except urllib2.HTTPError, why:
|
||||
self._build_response(why)
|
||||
self.response.error = why
|
||||
else:
|
||||
self._build_response(resp)
|
||||
self.response.ok = True
|
||||
|
||||
self.response.cached = False
|
||||
else:
|
||||
self.response.cached = True
|
||||
|
||||
self.sent = self.response.ok
|
||||
|
||||
return self.sent
|
||||
|
||||
|
||||
def read(self, *args):
|
||||
return self.response.read()
|
||||
|
||||
class Response(object):
|
||||
"""The :class:`Request` object. All :class:`Request` objects contain a
|
||||
:class:`Request.response <response>` attribute, which is an instance of
|
||||
this class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.content = None
|
||||
self.status_code = None
|
||||
self.headers = dict()
|
||||
self.url = None
|
||||
self.ok = False
|
||||
self.error = None
|
||||
self.cached = False
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if status_code is 'OK'."""
|
||||
return not self.error
|
||||
|
||||
|
||||
def raise_for_status(self):
|
||||
"""Raises stored HTTPError if one exists."""
|
||||
if self.error:
|
||||
raise self.error
|
||||
|
||||
def read(self, *args):
|
||||
return self.content
|
||||
|
||||
|
||||
class AuthManager(object):
|
||||
"""Authentication Manager."""
|
||||
|
||||
def __new__(cls):
|
||||
singleton = cls.__dict__.get('__singleton__')
|
||||
if singleton is not None:
|
||||
return singleton
|
||||
|
||||
cls.__singleton__ = singleton = object.__new__(cls)
|
||||
|
||||
return singleton
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.passwd = {}
|
||||
self._auth = {}
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<AuthManager [%s]>' % (self.method)
|
||||
|
||||
|
||||
def add_auth(self, uri, auth):
|
||||
"""Registers AuthObject to AuthManager."""
|
||||
|
||||
uri = self.reduce_uri(uri, False)
|
||||
|
||||
# try to make it an AuthObject
|
||||
if not isinstance(auth, AuthObject):
|
||||
try:
|
||||
auth = AuthObject(*auth)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self._auth[uri] = auth
|
||||
|
||||
def add_password(self, realm, uri, user, passwd):
|
||||
"""Adds password to AuthManager."""
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
reduced_uri = tuple([self.reduce_uri(u, False) for u in uri])
|
||||
|
||||
if reduced_uri not in self.passwd:
|
||||
self.passwd[reduced_uri] = {}
|
||||
self.passwd[reduced_uri] = (user, passwd)
|
||||
|
||||
|
||||
def find_user_password(self, realm, authuri):
|
||||
for uris, authinfo in self.passwd.iteritems():
|
||||
reduced_authuri = self.reduce_uri(authuri, False)
|
||||
for uri in uris:
|
||||
if self.is_suburi(uri, reduced_authuri):
|
||||
return authinfo
|
||||
|
||||
return (None, None)
|
||||
|
||||
|
||||
def get_auth(self, uri):
|
||||
(in_domain, in_path) = self.reduce_uri(uri, False)
|
||||
|
||||
for domain, path, authority in (
|
||||
(i[0][0], i[0][1], i[1]) for i in self._auth.iteritems()
|
||||
):
|
||||
if in_domain == domain:
|
||||
if path in in_path:
|
||||
return authority
|
||||
|
||||
|
||||
def reduce_uri(self, uri, default_port=True):
|
||||
"""Accept authority or URI and extract only the authority and path."""
|
||||
# note HTTP URLs do not have a userinfo component
|
||||
parts = urllib2.urlparse.urlsplit(uri)
|
||||
if parts[1]:
|
||||
# URI
|
||||
scheme = parts[0]
|
||||
authority = parts[1]
|
||||
path = parts[2] or '/'
|
||||
else:
|
||||
# host or host:port
|
||||
scheme = None
|
||||
authority = uri
|
||||
path = '/'
|
||||
host, port = urllib2.splitport(authority)
|
||||
if default_port and port is None and scheme is not None:
|
||||
dport = {"http": 80,
|
||||
"https": 443,
|
||||
}.get(scheme)
|
||||
if dport is not None:
|
||||
authority = "%s:%d" % (host, dport)
|
||||
|
||||
return authority, path
|
||||
|
||||
|
||||
def is_suburi(self, base, test):
|
||||
"""Check if test is below base in a URI tree
|
||||
|
||||
Both args must be URIs in reduced form.
|
||||
"""
|
||||
if base == test:
|
||||
return True
|
||||
if base[0] != test[0]:
|
||||
return False
|
||||
common = urllib2.posixpath.commonprefix((base[1], test[1]))
|
||||
if len(common) == len(base[1]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def empty(self):
|
||||
self.passwd = {}
|
||||
|
||||
|
||||
def remove(self, uri, realm=None):
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
for default_port in True, False:
|
||||
reduced_uri = tuple([self.reduce_uri(u, default_port) for u in uri])
|
||||
del self.passwd[reduced_uri][realm]
|
||||
|
||||
|
||||
def __contains__(self, uri):
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
uri = tuple([self.reduce_uri(u, False) for u in uri])
|
||||
|
||||
if uri in self.passwd:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
auth_manager = AuthManager()
|
||||
|
||||
|
||||
|
||||
class AuthObject(object):
|
||||
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
|
||||
given to a Requests function, it enables Basic HTTP Authentication for that
|
||||
Request. You can also enable Authorization for domain realms with AutoAuth.
|
||||
See AutoAuth for more details.
|
||||
|
||||
:param username: Username to authenticate with.
|
||||
:param password: Password for given username.
|
||||
:param realm: (optional) the realm this auth applies to
|
||||
:param handler: (optional) basic || digest || proxy_basic || proxy_digest
|
||||
"""
|
||||
|
||||
_handlers = {
|
||||
'basic': urllib2.HTTPBasicAuthHandler,
|
||||
'digest': urllib2.HTTPDigestAuthHandler,
|
||||
'proxy_basic': urllib2.ProxyBasicAuthHandler,
|
||||
'proxy_digest': urllib2.ProxyDigestAuthHandler
|
||||
}
|
||||
|
||||
def __init__(self, username, password, handler='basic', realm=None):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
|
||||
if isinstance(handler, basestring):
|
||||
self.handler = self._handlers.get(handler.lower(), urllib2.HTTPBasicAuthHandler)
|
||||
else:
|
||||
self.handler = handler
|
||||
|
||||
|
||||
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
"""Sends a `method` request. Returns :class:`Response` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of GET/HEAD/DELETE Parameters to send with the :class:`Request`.
|
||||
:param data: (optional) Bytes/Dictionary of PUT/POST Data to send with the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
"""
|
||||
data = kwargs.pop('data', dict()) or kwargs.pop('params', dict())
|
||||
|
||||
r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', {}),
|
||||
cookiejar=kwargs.pop('cookies', None), files=kwargs.pop('files', None),
|
||||
auth=kwargs.pop('auth', auth_manager.get_auth(url)))
|
||||
r.send()
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
def get(url, params={}, headers={}, cookies=None, auth=None):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
"""
|
||||
|
||||
return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth)
|
||||
|
||||
|
||||
def head(url, params={}, headers={}, cookies=None, auth=None):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
"""
|
||||
|
||||
return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth)
|
||||
|
||||
|
||||
def post(url, data={}, headers={}, files=None, cookies=None, auth=None):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary of POST data to send with the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
"""
|
||||
|
||||
return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth)
|
||||
|
||||
|
||||
def put(url, data='', headers={}, files={}, cookies=None, auth=None):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Bytes of PUT Data to send with the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
"""
|
||||
|
||||
return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth)
|
||||
|
||||
|
||||
def delete(url, params={}, headers={}, cookies=None, auth=None):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of DELETE Parameters to send with the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
"""
|
||||
|
||||
return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth)
|
||||
|
||||
|
||||
|
||||
class RequestException(Exception):
|
||||
"""There was an ambiguous exception that occured while handling your
|
||||
request."""
|
||||
|
||||
class AuthenticationError(RequestException):
|
||||
"""The authentication credentials provided were invalid."""
|
||||
|
||||
class URLRequired(RequestException):
|
||||
"""A valid URL is required to make a request."""
|
||||
|
||||
class InvalidMethod(RequestException):
|
||||
"""An inappropriate method was attempted."""
|
||||
import utils
|
||||
|
||||
26
requests/exceptions.py
Normal file
26
requests/exceptions.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.exceptions
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
|
||||
class RequestException(Exception):
|
||||
"""There was an ambiguous exception that occured while handling your
|
||||
request."""
|
||||
|
||||
class AuthenticationError(RequestException):
|
||||
"""The authentication credentials provided were invalid."""
|
||||
|
||||
class Timeout(RequestException):
|
||||
"""The request timed out."""
|
||||
|
||||
class URLRequired(RequestException):
|
||||
"""A valid URL is required to make a request."""
|
||||
|
||||
class InvalidMethod(RequestException):
|
||||
"""An inappropriate method was attempted."""
|
||||
|
||||
class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
40
requests/hooks.py
Normal file
40
requests/hooks.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.hooks
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module provides the capabilities for the Requests hooks system.
|
||||
|
||||
Available hooks:
|
||||
|
||||
``args``:
|
||||
A dictionary of the arguments being sent to Request().
|
||||
|
||||
``pre_request``:
|
||||
The Request object, directly before being sent.
|
||||
|
||||
``post_request``:
|
||||
The Request object, directly after being sent.
|
||||
|
||||
``response``:
|
||||
The response generated from a Request.
|
||||
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
"""Dipatches a hook dictionary on a given peice of data."""
|
||||
|
||||
hooks = hooks or dict()
|
||||
|
||||
if key in hooks:
|
||||
try:
|
||||
return hooks.get(key).__call__(hook_data) or hook_data
|
||||
|
||||
except Exception, why:
|
||||
warnings.warn(str(why))
|
||||
|
||||
return hook_data
|
||||
679
requests/models.py
Normal file
679
requests/models.py
Normal file
@@ -0,0 +1,679 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.models
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import socket
|
||||
import codecs
|
||||
import zlib
|
||||
|
||||
|
||||
from urllib2 import HTTPError
|
||||
from urlparse import urlparse, urlunparse, urljoin
|
||||
from datetime import datetime
|
||||
|
||||
from .config import settings
|
||||
from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .packages.poster.encode import multipart_encode
|
||||
from .packages.poster.streaminghttp import register_openers, get_handlers
|
||||
from .utils import dict_from_cookiejar, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip
|
||||
from .status_codes import codes
|
||||
from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod, TooManyRedirects
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
url=None, headers=dict(), files=None, method=None, data=dict(),
|
||||
params=dict(), auth=None, cookiejar=None, timeout=None, redirect=False,
|
||||
allow_redirects=False, proxies=None, hooks=None):
|
||||
|
||||
#: Float describ the timeout of the request.
|
||||
# (Use socket.setdefaulttimeout() as fallback)
|
||||
self.timeout = timeout
|
||||
|
||||
#: Request URL.
|
||||
self.url = url
|
||||
|
||||
#: Dictonary of HTTP Headers to attach to the :class:`Request <Request>`.
|
||||
self.headers = headers
|
||||
|
||||
#: Dictionary of files to multipart upload (``{filename: content}``).
|
||||
self.files = files
|
||||
|
||||
#: HTTP Method to use.
|
||||
self.method = method
|
||||
|
||||
#: Dictionary or byte of request body data to attach to the
|
||||
#: :class:`Request <Request>`.
|
||||
self.data = None
|
||||
|
||||
#: Dictionary or byte of querystring data to attach to the
|
||||
#: :class:`Request <Request>`.
|
||||
self.params = None
|
||||
|
||||
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
|
||||
#: and HTTPError storage).
|
||||
self.redirect = redirect
|
||||
|
||||
#: Set to True if full redirects are allowed (e.g. re-POST-ing of data at new ``Location``)
|
||||
self.allow_redirects = allow_redirects
|
||||
|
||||
# Dictionary mapping protocol to the URL of the proxy (e.g. {'http': 'foo.bar:3128'})
|
||||
self.proxies = proxies
|
||||
|
||||
self.data, self._enc_data = self._encode_params(data)
|
||||
self.params, self._enc_params = self._encode_params(params)
|
||||
|
||||
#: :class:`Response <Response>` instance, containing
|
||||
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
|
||||
self.response = Response()
|
||||
|
||||
if isinstance(auth, (list, tuple)):
|
||||
auth = AuthObject(*auth)
|
||||
if not auth:
|
||||
auth = auth_manager.get_auth(self.url)
|
||||
|
||||
#: :class:`AuthObject` to attach to :class:`Request <Request>`.
|
||||
self.auth = auth
|
||||
|
||||
#: CookieJar to attach to :class:`Request <Request>`.
|
||||
self.cookiejar = cookiejar
|
||||
|
||||
#: True if Request has been sent.
|
||||
self.sent = False
|
||||
|
||||
#: Event-handling hooks.
|
||||
self.hooks = hooks
|
||||
|
||||
# Header manipulation and defaults.
|
||||
|
||||
if settings.accept_gzip:
|
||||
settings.base_headers.update({'Accept-Encoding': 'gzip'})
|
||||
|
||||
if headers:
|
||||
headers = CaseInsensitiveDict(self.headers)
|
||||
else:
|
||||
headers = CaseInsensitiveDict()
|
||||
|
||||
for (k, v) in settings.base_headers.items():
|
||||
if k not in headers:
|
||||
headers[k] = v
|
||||
|
||||
self.headers = headers
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
|
||||
|
||||
def _checks(self):
|
||||
"""Deterministic checks for consistency."""
|
||||
|
||||
if not self.url:
|
||||
raise URLRequired
|
||||
|
||||
|
||||
def _get_opener(self):
|
||||
"""Creates appropriate opener object for urllib2."""
|
||||
|
||||
_handlers = []
|
||||
|
||||
if self.cookiejar is not None:
|
||||
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
|
||||
|
||||
if self.auth:
|
||||
if not isinstance(self.auth.handler,
|
||||
(urllib2.AbstractBasicAuthHandler,
|
||||
urllib2.AbstractDigestAuthHandler)):
|
||||
|
||||
# TODO: REMOVE THIS COMPLETELY
|
||||
auth_manager.add_password(
|
||||
self.auth.realm, self.url,
|
||||
self.auth.username,
|
||||
self.auth.password)
|
||||
|
||||
self.auth.handler = self.auth.handler(auth_manager)
|
||||
auth_manager.add_auth(self.url, self.auth)
|
||||
|
||||
_handlers.append(self.auth.handler)
|
||||
|
||||
if self.proxies:
|
||||
_handlers.append(urllib2.ProxyHandler(self.proxies))
|
||||
|
||||
_handlers.append(HTTPRedirectHandler)
|
||||
|
||||
if not _handlers:
|
||||
return urllib2.urlopen
|
||||
|
||||
if self.data or self.files:
|
||||
_handlers.extend(get_handlers())
|
||||
|
||||
opener = urllib2.build_opener(*_handlers)
|
||||
|
||||
if self.headers:
|
||||
# Allow default headers in the opener to be overloaded
|
||||
normal_keys = [k.capitalize() for k in self.headers]
|
||||
for key, val in opener.addheaders[:]:
|
||||
if key not in normal_keys:
|
||||
continue
|
||||
# Remove it, we have a value to take its place
|
||||
opener.addheaders.remove((key, val))
|
||||
|
||||
return opener.open
|
||||
|
||||
|
||||
def _build_response(self, resp, is_error=False):
|
||||
"""Build internal :class:`Response <Response>` object
|
||||
from given response.
|
||||
"""
|
||||
|
||||
|
||||
def build(resp):
|
||||
|
||||
response = Response()
|
||||
response.status_code = getattr(resp, 'code', None)
|
||||
|
||||
try:
|
||||
response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None))
|
||||
response.raw = resp
|
||||
|
||||
if self.cookiejar:
|
||||
response.cookies = dict_from_cookiejar(self.cookiejar)
|
||||
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if is_error:
|
||||
response.error = resp
|
||||
|
||||
response.url = getattr(resp, 'url', None)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
history = []
|
||||
|
||||
r = build(resp)
|
||||
|
||||
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))
|
||||
):
|
||||
|
||||
r.raw.close()
|
||||
|
||||
if not len(history) < settings.max_redirects:
|
||||
raise TooManyRedirects()
|
||||
|
||||
history.append(r)
|
||||
|
||||
url = r.headers['location']
|
||||
|
||||
# Handle redirection without scheme (see: RFC 1808 Section 4)
|
||||
if url.startswith('//'):
|
||||
parsed_rurl = urlparse(r.url)
|
||||
url = '%s:%s' % (parsed_rurl.scheme, url)
|
||||
|
||||
# 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, urllib.quote(urllib.unquote(url)))
|
||||
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
|
||||
if r.status_code is codes.see_other:
|
||||
method = 'GET'
|
||||
else:
|
||||
method = self.method
|
||||
|
||||
request = Request(
|
||||
url, self.headers, self.files, method,
|
||||
self.data, self.params, self.auth, self.cookiejar,
|
||||
redirect=True
|
||||
)
|
||||
request.send()
|
||||
r = request.response
|
||||
|
||||
r.history = history
|
||||
|
||||
self.response = r
|
||||
self.response.request = self
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _encode_params(data):
|
||||
"""Encode parameters in a piece of data.
|
||||
|
||||
If the data supplied is a dictionary, encodes each parameter in it, and
|
||||
returns a list of tuples containing the encoded parameters, and a urlencoded
|
||||
version of that.
|
||||
|
||||
Otherwise, assumes the data is already encoded appropriately, and
|
||||
returns it twice.
|
||||
"""
|
||||
|
||||
if hasattr(data, 'items'):
|
||||
result = []
|
||||
for k, vs in 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)
|
||||
else:
|
||||
return data, data
|
||||
|
||||
|
||||
def _build_url(self):
|
||||
"""Build the actual URL to use."""
|
||||
|
||||
# Support for unicode domain names and paths.
|
||||
scheme, netloc, path, params, query, fragment = urlparse(self.url)
|
||||
netloc = netloc.encode('idna')
|
||||
if isinstance(path, unicode):
|
||||
path = path.encode('utf-8')
|
||||
path = urllib.quote(urllib.unquote(path))
|
||||
self.url = str(urlunparse([ scheme, netloc, path, params, query, fragment ]))
|
||||
|
||||
if self._enc_params:
|
||||
if urlparse(self.url).query:
|
||||
return '%s&%s' % (self.url, self._enc_params)
|
||||
else:
|
||||
return '%s?%s' % (self.url, self._enc_params)
|
||||
else:
|
||||
return self.url
|
||||
|
||||
|
||||
def send(self, anyway=False):
|
||||
"""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.
|
||||
|
||||
Once a request is successfully sent, `sent` will equal True.
|
||||
|
||||
:param anyway: If True, request will be sent, even if it has
|
||||
already been sent.
|
||||
"""
|
||||
|
||||
self._checks()
|
||||
|
||||
# Logging
|
||||
if settings.verbose:
|
||||
settings.verbose.write('%s %s %s\n' % (
|
||||
datetime.now().isoformat(), self.method, self.url
|
||||
))
|
||||
|
||||
|
||||
url = self._build_url()
|
||||
if self.method in ('GET', 'HEAD', 'DELETE'):
|
||||
req = _Request(url, method=self.method)
|
||||
else:
|
||||
|
||||
if self.files:
|
||||
register_openers()
|
||||
|
||||
if self.data:
|
||||
self.files.update(self.data)
|
||||
|
||||
datagen, headers = multipart_encode(self.files)
|
||||
req = _Request(url, data=datagen, headers=headers, method=self.method)
|
||||
|
||||
else:
|
||||
req = _Request(url, data=self._enc_data, method=self.method)
|
||||
|
||||
if self.headers:
|
||||
for k,v in self.headers.iteritems():
|
||||
req.add_header(k, v)
|
||||
|
||||
if not self.sent or anyway:
|
||||
|
||||
try:
|
||||
opener = self._get_opener()
|
||||
try:
|
||||
|
||||
resp = opener(req, timeout=self.timeout)
|
||||
|
||||
except TypeError, err:
|
||||
# timeout argument is new since Python v2.6
|
||||
if not 'timeout' in str(err):
|
||||
raise
|
||||
|
||||
if settings.timeout_fallback:
|
||||
# fall-back and use global socket timeout (This is not thread-safe!)
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
socket.setdefaulttimeout(self.timeout)
|
||||
|
||||
resp = opener(req)
|
||||
|
||||
if settings.timeout_fallback:
|
||||
# restore gobal timeout
|
||||
socket.setdefaulttimeout(old_timeout)
|
||||
|
||||
if self.cookiejar is not None:
|
||||
self.cookiejar.extract_cookies(resp, req)
|
||||
|
||||
except (urllib2.HTTPError, urllib2.URLError), why:
|
||||
if hasattr(why, 'reason'):
|
||||
if isinstance(why.reason, socket.timeout):
|
||||
why = Timeout(why)
|
||||
elif isinstance(why.reason, socket.error):
|
||||
why = Timeout(why)
|
||||
|
||||
self._build_response(why, is_error=True)
|
||||
|
||||
else:
|
||||
self._build_response(resp)
|
||||
self.response.ok = True
|
||||
|
||||
|
||||
self.sent = self.response.ok
|
||||
|
||||
return self.sent
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""The core :class:`Response <Response>` object. All
|
||||
:class:`Request <Request>` objects contain a
|
||||
:class:`response <Response>` attribute, which is an instance
|
||||
of this class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._content = None
|
||||
self._content_consumed = False
|
||||
|
||||
#: Integer Code of responded HTTP Status.
|
||||
self.status_code = None
|
||||
|
||||
#: Case-insensitive Dictionary of Response Headers.
|
||||
#: For example, ``headers['content-encoding']`` will return the
|
||||
#: value of a ``'Content-Encoding'`` response header.
|
||||
self.headers = CaseInsensitiveDict()
|
||||
|
||||
#: File-like object representation of response (for advanced usage).
|
||||
self.raw = None
|
||||
|
||||
#: Final URL location of Response.
|
||||
self.url = None
|
||||
|
||||
#: True if no :attr:`error` occured.
|
||||
self.ok = False
|
||||
|
||||
#: Resulting :class:`HTTPError` of request, if one occured.
|
||||
self.error = None
|
||||
|
||||
#: A list of :class:`Response <Response>` objects from
|
||||
#: the history of the Request. Any redirect responses will end
|
||||
#: up here.
|
||||
self.history = []
|
||||
|
||||
#: The :class:`Request <Request>` that created the Response.
|
||||
self.request = None
|
||||
|
||||
#: A dictionary of Cookies the server sent back.
|
||||
self.cookies = None
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
|
||||
return not self.error
|
||||
|
||||
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
|
||||
"""Iterates over the response data. This avoids reading the content
|
||||
at once into memory for large responses. The chunk size is the number
|
||||
of bytes it should read into memory. This is not necessarily the
|
||||
length of each item returned as decoding can take place.
|
||||
"""
|
||||
if self._content_consumed:
|
||||
raise RuntimeError('The content for this response was '
|
||||
'already consumed')
|
||||
|
||||
def generate():
|
||||
while 1:
|
||||
chunk = self.raw.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
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 = settings.decode_unicode
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
return gen
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""Content of the response, in bytes or unicode
|
||||
(if available).
|
||||
"""
|
||||
|
||||
if self._content is not None:
|
||||
return self._content
|
||||
|
||||
if self._content_consumed:
|
||||
raise RuntimeError('The content for this response was '
|
||||
'already consumed')
|
||||
|
||||
# Read the contents.
|
||||
self._content = self.raw.read()
|
||||
|
||||
# Decode GZip'd content.
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
try:
|
||||
self._content = decode_gzip(self._content)
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
# Decode unicode content.
|
||||
if settings.decode_unicode:
|
||||
self._content = get_unicode_from_response(self)
|
||||
|
||||
self._content_consumed = True
|
||||
return self._content
|
||||
|
||||
|
||||
def raise_for_status(self):
|
||||
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occured."""
|
||||
if self.error:
|
||||
raise self.error
|
||||
|
||||
|
||||
|
||||
class AuthManager(object):
|
||||
"""Requests Authentication Manager."""
|
||||
|
||||
def __new__(cls):
|
||||
singleton = cls.__dict__.get('__singleton__')
|
||||
if singleton is not None:
|
||||
return singleton
|
||||
|
||||
cls.__singleton__ = singleton = object.__new__(cls)
|
||||
|
||||
return singleton
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.passwd = {}
|
||||
self._auth = {}
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<AuthManager [%s]>' % (self.method)
|
||||
|
||||
|
||||
def add_auth(self, uri, auth):
|
||||
"""Registers AuthObject to AuthManager."""
|
||||
|
||||
uri = self.reduce_uri(uri, False)
|
||||
|
||||
# try to make it an AuthObject
|
||||
if not isinstance(auth, AuthObject):
|
||||
try:
|
||||
auth = AuthObject(*auth)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self._auth[uri] = auth
|
||||
|
||||
|
||||
def add_password(self, realm, uri, user, passwd):
|
||||
"""Adds password to AuthManager."""
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
reduced_uri = tuple([self.reduce_uri(u, False) for u in uri])
|
||||
|
||||
if reduced_uri not in self.passwd:
|
||||
self.passwd[reduced_uri] = {}
|
||||
self.passwd[reduced_uri] = (user, passwd)
|
||||
|
||||
|
||||
def find_user_password(self, realm, authuri):
|
||||
for uris, authinfo in self.passwd.iteritems():
|
||||
reduced_authuri = self.reduce_uri(authuri, False)
|
||||
for uri in uris:
|
||||
if self.is_suburi(uri, reduced_authuri):
|
||||
return authinfo
|
||||
|
||||
return (None, None)
|
||||
|
||||
|
||||
def get_auth(self, uri):
|
||||
(in_domain, in_path) = self.reduce_uri(uri, False)
|
||||
|
||||
for domain, path, authority in (
|
||||
(i[0][0], i[0][1], i[1]) for i in self._auth.iteritems()
|
||||
):
|
||||
if in_domain == domain:
|
||||
if path in in_path:
|
||||
return authority
|
||||
|
||||
|
||||
def reduce_uri(self, uri, default_port=True):
|
||||
"""Accept authority or URI and extract only the authority and path."""
|
||||
|
||||
# note HTTP URLs do not have a userinfo component
|
||||
parts = urllib2.urlparse.urlsplit(uri)
|
||||
|
||||
if parts[1]:
|
||||
# URI
|
||||
scheme = parts[0]
|
||||
authority = parts[1]
|
||||
path = parts[2] or '/'
|
||||
else:
|
||||
# host or host:port
|
||||
scheme = None
|
||||
authority = uri
|
||||
path = '/'
|
||||
|
||||
host, port = urllib2.splitport(authority)
|
||||
|
||||
if default_port and port is None and scheme is not None:
|
||||
dport = {"http": 80,
|
||||
"https": 443,
|
||||
}.get(scheme)
|
||||
if dport is not None:
|
||||
authority = "%s:%d" % (host, dport)
|
||||
|
||||
return authority, path
|
||||
|
||||
|
||||
def is_suburi(self, base, test):
|
||||
"""Check if test is below base in a URI tree
|
||||
|
||||
Both args must be URIs in reduced form.
|
||||
"""
|
||||
if base == test:
|
||||
return True
|
||||
if base[0] != test[0]:
|
||||
return False
|
||||
common = urllib2.posixpath.commonprefix((base[1], test[1]))
|
||||
if len(common) == len(base[1]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def empty(self):
|
||||
self.passwd = {}
|
||||
|
||||
|
||||
def remove(self, uri, realm=None):
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
for default_port in True, False:
|
||||
reduced_uri = tuple([self.reduce_uri(u, default_port) for u in uri])
|
||||
del self.passwd[reduced_uri][realm]
|
||||
|
||||
|
||||
def __contains__(self, uri):
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
uri = tuple([self.reduce_uri(u, False) for u in uri])
|
||||
|
||||
if uri in self.passwd:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
auth_manager = AuthManager()
|
||||
|
||||
|
||||
|
||||
class AuthObject(object):
|
||||
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
|
||||
given to a Requests function, it enables Basic HTTP Authentication for that
|
||||
Request. You can also enable Authorization for domain realms with AutoAuth.
|
||||
See AutoAuth for more details.
|
||||
|
||||
:param username: Username to authenticate with.
|
||||
:param password: Password for given username.
|
||||
:param realm: (optional) the realm this auth applies to
|
||||
:param handler: (optional) basic || digest || proxy_basic || proxy_digest
|
||||
"""
|
||||
|
||||
_handlers = {
|
||||
'basic': HTTPBasicAuthHandler,
|
||||
'forced_basic': HTTPForcedBasicAuthHandler,
|
||||
'digest': HTTPDigestAuthHandler,
|
||||
'proxy_basic': urllib2.ProxyBasicAuthHandler,
|
||||
'proxy_digest': urllib2.ProxyDigestAuthHandler
|
||||
}
|
||||
|
||||
def __init__(self, username, password, handler='forced_basic', realm=None):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
|
||||
if isinstance(handler, basestring):
|
||||
self.handler = self._handlers.get(handler.lower(), HTTPForcedBasicAuthHandler)
|
||||
else:
|
||||
self.handler = handler
|
||||
148
requests/monkeys.py
Normal file
148
requests/monkeys.py
Normal file
@@ -0,0 +1,148 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.monkeys
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Urllib2 Monkey patches.
|
||||
|
||||
"""
|
||||
|
||||
import urllib2
|
||||
import re
|
||||
|
||||
class Request(urllib2.Request):
|
||||
"""Hidden wrapper around the urllib2.Request object. Allows for manual
|
||||
setting of HTTP methods.
|
||||
"""
|
||||
|
||||
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
|
||||
urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
|
||||
self.method = method
|
||||
|
||||
def get_method(self):
|
||||
if self.method:
|
||||
return self.method
|
||||
|
||||
return urllib2.Request.get_method(self)
|
||||
|
||||
|
||||
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
"""HTTP Redirect handler."""
|
||||
def http_error_301(self, req, fp, code, msg, headers):
|
||||
pass
|
||||
|
||||
http_error_302 = http_error_303 = http_error_307 = http_error_301
|
||||
|
||||
|
||||
|
||||
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
|
||||
"""HTTP Basic Auth Handler with authentication loop fixes."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
|
||||
self.retried_req = None
|
||||
self.retried = 0
|
||||
|
||||
|
||||
def reset_retry_count(self):
|
||||
# Python 2.6.5 will call this on 401 or 407 errors and thus loop
|
||||
# forever. We disable reset_retry_count completely and reset in
|
||||
# http_error_auth_reqed instead.
|
||||
pass
|
||||
|
||||
|
||||
def http_error_auth_reqed(self, auth_header, host, req, headers):
|
||||
# Reset the retry counter once for each request.
|
||||
if req is not self.retried_req:
|
||||
self.retried_req = req
|
||||
self.retried = 0
|
||||
|
||||
return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
|
||||
self, auth_header, host, req, headers
|
||||
)
|
||||
|
||||
|
||||
|
||||
class HTTPForcedBasicAuthHandler(HTTPBasicAuthHandler):
|
||||
"""HTTP Basic Auth Handler with forced Authentication."""
|
||||
|
||||
auth_header = 'Authorization'
|
||||
rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+'
|
||||
'realm=(["\'])(.*?)\\2', re.I)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
def http_error_401(self, req, fp, code, msg, headers):
|
||||
url = req.get_full_url()
|
||||
response = self._http_error_auth_reqed('www-authenticate', url, req, headers)
|
||||
self.reset_retry_count()
|
||||
return response
|
||||
|
||||
http_error_404 = http_error_401
|
||||
|
||||
|
||||
def _http_error_auth_reqed(self, authreq, host, req, headers):
|
||||
|
||||
authreq = headers.get(authreq, None)
|
||||
|
||||
if self.retried > 5:
|
||||
# retry sending the username:password 5 times before failing.
|
||||
raise urllib2.HTTPError(req.get_full_url(), 401, "basic auth failed",
|
||||
headers, None)
|
||||
else:
|
||||
self.retried += 1
|
||||
|
||||
if authreq:
|
||||
|
||||
mo = self.rx.search(authreq)
|
||||
|
||||
if mo:
|
||||
scheme, quote, realm = mo.groups()
|
||||
|
||||
if scheme.lower() == 'basic':
|
||||
response = self.retry_http_basic_auth(host, req, realm)
|
||||
|
||||
if response and response.code not in (401, 404):
|
||||
self.retried = 0
|
||||
return response
|
||||
else:
|
||||
response = self.retry_http_basic_auth(host, req, 'Realm')
|
||||
|
||||
if response and response.code not in (401, 404):
|
||||
self.retried = 0
|
||||
return response
|
||||
|
||||
|
||||
|
||||
class HTTPDigestAuthHandler(urllib2.HTTPDigestAuthHandler):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
|
||||
self.retried_req = None
|
||||
|
||||
def reset_retry_count(self):
|
||||
# Python 2.6.5 will call this on 401 or 407 errors and thus loop
|
||||
# forever. We disable reset_retry_count completely and reset in
|
||||
# http_error_auth_reqed instead.
|
||||
pass
|
||||
|
||||
def http_error_auth_reqed(self, auth_header, host, req, headers):
|
||||
# Reset the retry counter once for each request.
|
||||
if req is not self.retried_req:
|
||||
self.retried_req = req
|
||||
self.retried = 0
|
||||
# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
|
||||
# it doesn't know about the auth type requested. This can happen if
|
||||
# somebody is using BasicAuth and types a bad password.
|
||||
|
||||
try:
|
||||
return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
|
||||
self, auth_header, host, req, headers)
|
||||
except ValueError, inst:
|
||||
arg = inst.args[0]
|
||||
if arg.startswith("AbstractDigestAuthHandler doesn't know "):
|
||||
return
|
||||
raise
|
||||
84
requests/sessions.py
Normal file
84
requests/sessions.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.session
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a Session object to manage and persist settings across
|
||||
requests (cookies, auth, proxies).
|
||||
|
||||
"""
|
||||
|
||||
import cookielib
|
||||
|
||||
from . import api
|
||||
from .utils import add_dict_to_cookiejar
|
||||
|
||||
|
||||
|
||||
class Session(object):
|
||||
"""A Requests session."""
|
||||
|
||||
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks']
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
# Set up a CookieJar to be used by default
|
||||
self.cookies = cookielib.FileCookieJar()
|
||||
|
||||
# Map args from kwargs to instance-local variables
|
||||
map(lambda k, v: (k in self.__attrs__) and setattr(self, k, v),
|
||||
kwargs.iterkeys(), kwargs.itervalues())
|
||||
|
||||
# Map and wrap requests.api methods
|
||||
self._map_api_methods()
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<requests-client at 0x%x>' % (id(self))
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
# print args
|
||||
pass
|
||||
|
||||
|
||||
def _map_api_methods(self):
|
||||
"""Reads each available method from requests.api and decorates
|
||||
them with a wrapper, which inserts any instance-local attributes
|
||||
(from __attrs__) that have been set, combining them with **kwargs.
|
||||
"""
|
||||
|
||||
def pass_args(func):
|
||||
def wrapper_func(*args, **kwargs):
|
||||
inst_attrs = dict((k, v) for k, v in self.__dict__.iteritems()
|
||||
if k in self.__attrs__)
|
||||
# Combine instance-local values with kwargs values, with
|
||||
# priority to values in kwargs
|
||||
kwargs = dict(inst_attrs.items() + kwargs.items())
|
||||
|
||||
# If a session request has a cookie_dict, inject the
|
||||
# values into the existing CookieJar instead.
|
||||
if isinstance(kwargs.get('cookies', None), dict):
|
||||
kwargs['cookies'] = add_dict_to_cookiejar(
|
||||
inst_attrs['cookies'], kwargs['cookies']
|
||||
)
|
||||
|
||||
if kwargs.get('headers', None) and inst_attrs.get('headers', None):
|
||||
kwargs['headers'].update(inst_attrs['headers'])
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper_func
|
||||
|
||||
# Map and decorate each function available in requests.api
|
||||
map(lambda fn: setattr(self, fn, pass_args(getattr(api, fn))),
|
||||
api.__all__)
|
||||
|
||||
|
||||
def session(**kwargs):
|
||||
"""Returns a :class:`Session` for context-managment."""
|
||||
|
||||
return Session(**kwargs)
|
||||
83
requests/status_codes.py
Normal file
83
requests/status_codes.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .structures import LookupDict
|
||||
|
||||
_codes = {
|
||||
|
||||
# Informational.
|
||||
100: ('continue',),
|
||||
101: ('switching_protocols',),
|
||||
102: ('processing',),
|
||||
103: ('checkpoint',),
|
||||
122: ('uri_too_long', 'request_uri_too_long'),
|
||||
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/'),
|
||||
201: ('created',),
|
||||
202: ('accepted',),
|
||||
203: ('non_authoritative_info', 'non_authoritative_information'),
|
||||
204: ('no_content',),
|
||||
205: ('reset_content', 'reset'),
|
||||
206: ('partial_content', 'partial'),
|
||||
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
|
||||
208: ('im_used',),
|
||||
|
||||
# Redirection.
|
||||
300: ('multiple_choices',),
|
||||
301: ('moved_permanently', 'moved', '\\o-'),
|
||||
302: ('found',),
|
||||
303: ('see_other', 'other'),
|
||||
304: ('not_modified',),
|
||||
305: ('use_proxy',),
|
||||
306: ('switch_proxy',),
|
||||
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
|
||||
308: ('resume_incomplete', 'resume'),
|
||||
|
||||
# Client Error.
|
||||
400: ('bad_request', 'bad'),
|
||||
401: ('unauthorized',),
|
||||
402: ('payment_required', 'payment'),
|
||||
403: ('forbidden',),
|
||||
404: ('not_found', '-o-'),
|
||||
405: ('method_not_allowed', 'not_allowed'),
|
||||
406: ('not_acceptable',),
|
||||
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
|
||||
408: ('request_timeout', 'timeout'),
|
||||
409: ('conflict',),
|
||||
410: ('gone',),
|
||||
411: ('length_required',),
|
||||
412: ('precondition_failed', 'precondition'),
|
||||
413: ('request_entity_too_large',),
|
||||
414: ('request_uri_too_large',),
|
||||
415: ('unspported_media_type', 'unspported_media', 'media_type'),
|
||||
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
|
||||
417: ('expectation_failed',),
|
||||
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
|
||||
422: ('unprocessable_entity', 'unprocessable'),
|
||||
423: ('locked',),
|
||||
424: ('failed_depdendency', 'depdendency'),
|
||||
425: ('unordered_collection', 'unordered'),
|
||||
426: ('upgrade_required', 'upgrade'),
|
||||
444: ('no_response', 'none'),
|
||||
449: ('retry_with', 'retry'),
|
||||
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
|
||||
499: ('client_closed_request',),
|
||||
|
||||
# Server Error.
|
||||
500: ('internal_server_error', 'server_error', '/o\\'),
|
||||
501: ('not_implemented',),
|
||||
502: ('bad_gateway',),
|
||||
503: ('service_unavailable', 'unavailable'),
|
||||
504: ('gateway_timeout',),
|
||||
505: ('http_version_not_supported', 'http_version'),
|
||||
506: ('variant_also_negotiates',),
|
||||
507: ('insufficient_storage',),
|
||||
509: ('bandwidth_limit_exceeded', 'bandwidth'),
|
||||
510: ('not_extended',),
|
||||
}
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for (code, titles) in _codes.items():
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith('\\'):
|
||||
setattr(codes, title.upper(), code)
|
||||
65
requests/structures.py
Normal file
65
requests/structures.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.structures
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Datastructures that power Requests.
|
||||
|
||||
"""
|
||||
|
||||
class CaseInsensitiveDict(dict):
|
||||
"""Case-insensitive Dictionary
|
||||
|
||||
For example, ``headers['content-encoding']`` will return the
|
||||
value of a ``'Content-Encoding'`` response header."""
|
||||
|
||||
@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())
|
||||
return self._lower_keys
|
||||
|
||||
def _clear_lower_keys(self):
|
||||
if hasattr(self, '_lower_keys'):
|
||||
self._lower_keys.clear()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
dict.__setitem__(self, key, value)
|
||||
self._clear_lower_keys()
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
self._lower_keys.clear()
|
||||
|
||||
def __contains__(self, key):
|
||||
return key.lower() in self.lower_keys
|
||||
|
||||
def __getitem__(self, key):
|
||||
# We allow fall-through here, so values default to None
|
||||
if key in self:
|
||||
return dict.__getitem__(self, self.lower_keys[key.lower()])
|
||||
|
||||
def get(self, key, default=None):
|
||||
if key in self:
|
||||
return self[key]
|
||||
else:
|
||||
return default
|
||||
|
||||
class LookupDict(dict):
|
||||
"""Dictionary lookup object."""
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.name = name
|
||||
super(LookupDict, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return '<lookup \'%s\'>' % (self.name)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# We allow fall-through here, so values default to None
|
||||
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__dict__.get(key, default)
|
||||
254
requests/utils.py
Normal file
254
requests/utils.py
Normal file
@@ -0,0 +1,254 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module provides utlity functions that are used within Requests
|
||||
that are also useful for external consumption.
|
||||
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import cookielib
|
||||
import re
|
||||
import zlib
|
||||
|
||||
|
||||
def header_expand(headers):
|
||||
"""Returns an HTTP Header value string from a dictionary.
|
||||
|
||||
Example expansion::
|
||||
|
||||
{'text/x-dvi': {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}, 'text/x-c': {}}
|
||||
# Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
|
||||
|
||||
(('text/x-dvi', {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}), ('text/x-c', {}))
|
||||
# Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
|
||||
"""
|
||||
|
||||
collector = []
|
||||
|
||||
if isinstance(headers, dict):
|
||||
headers = headers.items()
|
||||
|
||||
elif isinstance(headers, basestring):
|
||||
return headers
|
||||
|
||||
for i, (value, params) in enumerate(headers):
|
||||
|
||||
_params = []
|
||||
|
||||
for (p_k, p_v) in params.items():
|
||||
|
||||
_params.append('%s=%s' % (p_k, p_v))
|
||||
|
||||
collector.append(value)
|
||||
collector.append('; ')
|
||||
|
||||
if len(params):
|
||||
|
||||
collector.append('; '.join(_params))
|
||||
|
||||
if not len(headers) == i+1:
|
||||
collector.append(', ')
|
||||
|
||||
|
||||
# Remove trailing seperators.
|
||||
if collector[-1] in (', ', '; '):
|
||||
del collector[-1]
|
||||
|
||||
return ''.join(collector)
|
||||
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
"""Returns a key/value dictionary from a CookieJar.
|
||||
|
||||
:param cj: CookieJar object to extract cookies from.
|
||||
"""
|
||||
|
||||
cookie_dict = {}
|
||||
|
||||
for _, cookies in cj._cookies.items():
|
||||
for _, cookies in cookies.items():
|
||||
for cookie in cookies.values():
|
||||
# print cookie
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
|
||||
return cookie_dict
|
||||
|
||||
|
||||
def cookiejar_from_dict(cookie_dict):
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
# return cookiejar if one was passed in
|
||||
if isinstance(cookie_dict, cookielib.CookieJar):
|
||||
return cookie_dict
|
||||
|
||||
# create cookiejar
|
||||
cj = cookielib.CookieJar()
|
||||
|
||||
cj = add_dict_to_cookiejar(cj, cookie_dict)
|
||||
|
||||
return cj
|
||||
|
||||
|
||||
def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cj: CookieJar to insert cookies into.
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
for k, v in cookie_dict.items():
|
||||
|
||||
cookie = cookielib.Cookie(
|
||||
version=0,
|
||||
name=k,
|
||||
value=v,
|
||||
port=None,
|
||||
port_specified=False,
|
||||
domain='',
|
||||
domain_specified=False,
|
||||
domain_initial_dot=False,
|
||||
path='/',
|
||||
path_specified=True,
|
||||
secure=False,
|
||||
expires=None,
|
||||
discard=True,
|
||||
comment=None,
|
||||
comment_url=None,
|
||||
rest={'HttpOnly': None},
|
||||
rfc2109=False
|
||||
)
|
||||
|
||||
# add cookie to cookiejar
|
||||
cj.set_cookie(cookie)
|
||||
|
||||
return cj
|
||||
|
||||
|
||||
def get_encodings_from_content(content):
|
||||
"""Returns encodings from given content string.
|
||||
|
||||
:param content: bytestring to extract encodings from.
|
||||
"""
|
||||
|
||||
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
|
||||
|
||||
return charset_re.findall(content)
|
||||
|
||||
|
||||
def get_encoding_from_headers(headers):
|
||||
"""Returns encodings from given HTTP Header Dict.
|
||||
|
||||
:param headers: dictionary to extract encoding from.
|
||||
"""
|
||||
|
||||
content_type = headers.get('content-type')
|
||||
|
||||
if not content_type:
|
||||
return None
|
||||
|
||||
content_type, params = cgi.parse_header(content_type)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def stream_decode_response_unicode(iterator, r):
|
||||
"""Stream decodes a iterator."""
|
||||
encoding = get_encoding_from_headers(r.headers)
|
||||
if encoding is None:
|
||||
for item in iterator:
|
||||
yield item
|
||||
return
|
||||
|
||||
decoder = codecs.getincrementaldecoder(encoding)(errors='replace')
|
||||
for chunk in iterator:
|
||||
rv = decoder.decode(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
rv = decoder.decode('', final=True)
|
||||
if rv:
|
||||
yield rv
|
||||
|
||||
|
||||
def get_unicode_from_response(r):
|
||||
"""Returns the requested content back in unicode.
|
||||
|
||||
:param r: Reponse object to get unicode content from.
|
||||
|
||||
Tried:
|
||||
|
||||
1. charset from content-type
|
||||
|
||||
2. every encodings from ``<meta ... charset=XXX>``
|
||||
|
||||
3. fall back and replace all unicode characters
|
||||
|
||||
"""
|
||||
|
||||
tried_encodings = []
|
||||
|
||||
# Try charset from content-type
|
||||
encoding = get_encoding_from_headers(r.headers)
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
return unicode(r.content, encoding)
|
||||
except UnicodeError:
|
||||
tried_encodings.append(encoding)
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
return unicode(r.content, encoding, errors='replace')
|
||||
except TypeError:
|
||||
return r.content
|
||||
|
||||
|
||||
def decode_gzip(content):
|
||||
"""Return gzip-decoded string.
|
||||
|
||||
:param content: bytestring to gzip-decode.
|
||||
"""
|
||||
|
||||
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
|
||||
|
||||
|
||||
def stream_decode_gzip(iterator):
|
||||
"""Stream decodes a gzip-encoded iterator"""
|
||||
try:
|
||||
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
for chunk in iterator:
|
||||
rv = dec.decompress(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
buf = dec.decompress('')
|
||||
rv = buf + dec.flush()
|
||||
if rv:
|
||||
yield rv
|
||||
except zlib.error:
|
||||
pass
|
||||
9
setup.py
Normal file → Executable file
9
setup.py
Normal file → Executable file
@@ -5,7 +5,10 @@ import os
|
||||
import sys
|
||||
import requests
|
||||
|
||||
from distutils.core import setup
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
||||
|
||||
|
||||
@@ -25,12 +28,12 @@ if sys.version_info[:2] < (2,6):
|
||||
setup(
|
||||
name='requests',
|
||||
version=requests.__version__,
|
||||
description='Awesome Python HTTP Library that\'s actually usable.',
|
||||
description='Python HTTP for Humans.',
|
||||
long_description=open('README.rst').read() + '\n\n' +
|
||||
open('HISTORY.rst').read(),
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='https://github.com/kennethreitz/requests',
|
||||
url='http://python-requests.org',
|
||||
packages= [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
|
||||
456
test_requests.py
456
test_requests.py
@@ -1,143 +1,501 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import unittest
|
||||
import cookielib
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from requests.sessions import Session
|
||||
|
||||
|
||||
HTTPBIN_URL = 'http://httpbin.ep.io/'
|
||||
HTTPSBIN_URL = 'https://httpbin.ep.io/'
|
||||
|
||||
# HTTPBIN_URL = 'http://staging.httpbin.org/'
|
||||
# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
def httpsbin(*suffix):
|
||||
"""Returns url for HTTPSBIN resource."""
|
||||
|
||||
return HTTPSBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, httpsbin)
|
||||
|
||||
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""Teardown."""
|
||||
pass
|
||||
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = requests.get('http://google.com')
|
||||
r = requests.get(httpbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
def test_HTTPS_200_OK_GET(self):
|
||||
r = requests.get('https://google.com')
|
||||
r = requests.get(httpsbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get('http://www.google.com/search', params={'q': 'test'}, headers=heads)
|
||||
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('http://google.com/search?test=true', params={'q': 'test'}, headers=heads)
|
||||
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('http://whatsmyua.com', headers=heads);
|
||||
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('http://google.com')
|
||||
r = requests.head(httpbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTPS_200_OK_HEAD(self):
|
||||
r = requests.head('https://google.com')
|
||||
r = requests.head(httpsbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_AUTH_HTTPS_200_OK_GET(self):
|
||||
auth = ('requeststest', 'requeststest')
|
||||
url = 'https://convore.com/api/account/verify.json'
|
||||
r = requests.get(url, auth=auth)
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = requests.put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
|
||||
def test_HTTPS_200_OK_PUT(self):
|
||||
r = requests.put(httpsbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# reset auto authentication
|
||||
requests.auth_manager.empty()
|
||||
|
||||
def test_HTTP_200_OK_PATCH(self):
|
||||
r = requests.patch(httpbin('patch'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTPS_200_OK_PATCH(self):
|
||||
r = requests.patch(httpsbin('patch'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_AUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
# print r.__dict__
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
bin = requests.post('http://www.postbin.org/')
|
||||
self.assertEqual(bin.status_code, 200)
|
||||
|
||||
post = requests.post(bin.url, data={'some': 'data'})
|
||||
self.assertEqual(post.status_code, 201)
|
||||
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)
|
||||
|
||||
post2 = requests.post(bin.url, files={'some': open('test_requests.py')})
|
||||
self.assertEqual(post2.status_code, 201)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
bin = requests.post('http://www.postbin.org/')
|
||||
|
||||
self.assertEqual(bin.status_code, 200)
|
||||
for service in SERVICES:
|
||||
|
||||
post2 = requests.post(bin.url, files={'some': open('test_requests.py')}, data={'some': 'data'})
|
||||
self.assertEqual(post2.status_code, 201)
|
||||
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):
|
||||
bin = requests.post('http://www.postbin.org/')
|
||||
self.assertEqual(bin.status_code, 200)
|
||||
|
||||
post2 = requests.post(bin.url, files={'some': open('test_requests.py')},
|
||||
headers={'User-Agent': 'requests-tests'})
|
||||
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)
|
||||
|
||||
self.assertEqual(post2.status_code, 201)
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
r = requests.get('http://google.com/some-404-url')
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = requests.get('http://google.com/')
|
||||
self.assertEqual(bool(r), True)
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = requests.get(service('/'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
|
||||
def test_request_ok_set(self):
|
||||
r = requests.get('http://google.com/some-404-url')
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '404'))
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
|
||||
def test_status_raising(self):
|
||||
r = requests.get('http://google.com/some-404-url')
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
self.assertRaises(requests.HTTPError, r.raise_for_status)
|
||||
|
||||
r = requests.get('http://google.com/')
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
def test_cookie_jar(self):
|
||||
"""
|
||||
.. todo:: This really doesn't test to make sure the cookie is working
|
||||
"""
|
||||
|
||||
jar = cookielib.CookieJar()
|
||||
self.assertFalse(jar)
|
||||
|
||||
requests.get('http://google.com', cookies=jar)
|
||||
url = httpbin('cookies', 'set', 'requests_cookie', 'awesome')
|
||||
r = requests.get(url, cookies=jar)
|
||||
self.assertTrue(jar)
|
||||
|
||||
cookie_found = False
|
||||
for cookie in jar:
|
||||
if cookie.name == 'requests_cookie':
|
||||
self.assertEquals(cookie.value, 'awesome')
|
||||
cookie_found = True
|
||||
self.assertTrue(cookie_found)
|
||||
|
||||
r = requests.get(httpbin('cookies'), cookies=jar)
|
||||
self.assertTrue('awesome' in r.content)
|
||||
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags')
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
def test_autoauth(self):
|
||||
|
||||
conv_auth = ('requeststest', 'requeststest')
|
||||
requests.auth_manager.add_auth('convore.com', conv_auth)
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/')
|
||||
|
||||
requests.get(url, params={'foo': u'føø'})
|
||||
requests.get(url, params={u'føø': u'føø'})
|
||||
requests.get(url, params={'føø': 'føø'})
|
||||
requests.get(url, params={'foo': u'foo'})
|
||||
requests.get(service('ø'), params={'foo': u'foo'})
|
||||
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = ('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = requests.get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEquals(r.status_code, 401)
|
||||
|
||||
|
||||
def test_settings(self):
|
||||
|
||||
def test():
|
||||
r = requests.get(httpbin(''))
|
||||
r.raise_for_status()
|
||||
|
||||
with requests.settings(timeout=0.0000001):
|
||||
self.assertRaises(requests.Timeout, test)
|
||||
|
||||
with requests.settings(timeout=100):
|
||||
requests.get(httpbin(''))
|
||||
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
self.assertEquals(rbody.get('form'), None)
|
||||
self.assertEquals(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params='fooaowpeuf')
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'),
|
||||
params='fooaowpeuf', data="foobar")
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), None)
|
||||
self.assertEquals(rbody.get('data'), 'foobar')
|
||||
|
||||
|
||||
def test_idna(self):
|
||||
r = requests.get(u'http://➡.ws/httpbin')
|
||||
assert 'httpbin' in r.url
|
||||
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('get'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test=['foo','baz']),
|
||||
data=dict(test2="foobar",test3=['foo','baz']))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar',test3='foo'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('relative-redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = Session()
|
||||
r = s.get(httpbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_HTTPS_200_OK_GET(self):
|
||||
|
||||
s = Session()
|
||||
r = s.get(httpsbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = 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
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
r = requests.get('https://convore.com/api/account/verify.json')
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user