Compare commits

...

198 Commits

Author SHA1 Message Date
Kenneth Reitz
9471b0ab88 let's do this 2011-08-17 04:32:46 -04:00
Jeremy Selier
470af42bf2 Fixing myself :) 2011-08-17 11:28:47 +03:00
Kenneth Reitz
0b34812afc readme cleanup 2011-08-17 04:23:16 -04:00
Kenneth Reitz
47a36de74e supported interpreters 2011-08-17 04:17:21 -04:00
Kenneth Reitz
0c21aa5530 doc cleanups 2011-08-17 04:16:19 -04:00
Kenneth Reitz
3f873dc222 hooks in api layer only 2011-08-17 04:05:20 -04:00
Kenneth Reitz
e0ae2b4155 fixes 2011-08-17 04:03:54 -04:00
Kenneth Reitz
5f603ae787 typo 2011-08-17 04:01:11 -04:00
Kenneth Reitz
d4502aff87 consiserererr 2011-08-17 04:00:12 -04:00
Kenneth Reitz
3992ed6ad1 cookies docs 2011-08-17 03:58:55 -04:00
Kenneth Reitz
b72eb53a73 changing history 2011-08-17 03:25:31 -04:00
Kenneth Reitz
4d0c6cefd0 yawns 2011-08-17 03:21:31 -04:00
Kenneth Reitz
3f8f1dc00c cleaner defaults 2011-08-17 03:19:51 -04:00
Kenneth Reitz
008c77c911 hijack arguments 2011-08-17 03:19:46 -04:00
Kenneth Reitz
77b37687b6 advanced docs 2011-08-17 03:07:24 -04:00
Kenneth Reitz
b7307298d2 merge headers, don't overwrite 2011-08-17 02:49:29 -04:00
Kenneth Reitz
abcf81e2a8 docs update 2011-08-17 02:49:13 -04:00
Kenneth Reitz
e477fce211 add_dict_to_cookiejar
Signed-off-by: Kenneth Reitz <me@kennethreitz.com>
2011-08-17 02:25:56 -04:00
Kenneth Reitz
467766cfe8 cookie session persistence 2011-08-17 02:25:41 -04:00
Kenneth Reitz
66391e5c9d ACTIVVVAATTTEEEEEEE 2011-08-17 02:01:19 -04:00
Kenneth Reitz
f7024a6000 super cookiejar_from_dict powwerrrs 2011-08-17 02:01:13 -04:00
Kenneth Reitz
ec33ce9d29 sp. 2011-08-17 01:32:12 -04:00
Kenneth Reitz
d0aa0175e6 v0.6.0 2011-08-17 01:32:04 -04:00
Kenneth Reitz
d9e5717377 cookiejar_from_dict
#12
2011-08-17 01:29:58 -04:00
Kenneth Reitz
7e255177dc get cookies from response
#116
2011-08-17 01:23:49 -04:00
Kenneth Reitz
b0a0b509da session at root package level 2011-08-17 01:00:00 -04:00
Kenneth Reitz
51038497f6 better 2011-08-17 00:40:49 -04:00
Kenneth Reitz
855c273707 whitespace 2011-08-17 00:40:37 -04:00
Kenneth Reitz
281ab5a8f9 move hooks into api layer only 2011-08-17 00:40:28 -04:00
Kenneth Reitz
7836536459 post_request hook 2011-08-17 00:33:28 -04:00
Kenneth Reitz
2a0eff9c71 docstrings 2011-08-17 00:33:21 -04:00
Kenneth Reitz
3e8a14e133 pre_request hook 2011-08-17 00:28:03 -04:00
Kenneth Reitz
8ef0e88220 attach hooks to Request 2011-08-17 00:27:41 -04:00
Kenneth Reitz
e102c1b4ca RTD doesn't work w/ this 2011-08-17 00:17:58 -04:00
Kenneth Reitz
5ba43d0fe5 hook plans 2011-08-17 00:16:57 -04:00
Kenneth Reitz
783e540d6d sigh 2011-08-17 00:09:47 -04:00
Kenneth Reitz
24da41005c hmmm 2011-08-17 00:09:38 -04:00
Kenneth Reitz
17d3e1d498 whitespace 2011-08-17 00:08:39 -04:00
Kenneth Reitz
8d2ab68a97 space 2011-08-17 00:08:06 -04:00
Kenneth Reitz
7322aa5b64 newlines 2011-08-17 00:02:18 -04:00
Kenneth Reitz
6b11cf21f3 todo 2011-08-17 00:00:47 -04:00
Kenneth Reitz
9322d13f61 cleanups 2011-08-16 23:58:55 -04:00
Kenneth Reitz
86a06c5c18 auth_manager internal 2011-08-16 23:58:07 -04:00
Kenneth Reitz
d0cb56932c new hooks system
#118
2011-08-16 23:57:44 -04:00
Kenneth Reitz
fc04d8a368 cleanups 2011-08-16 22:55:18 -04:00
Kenneth Reitz
519b9cee96 relative imports 2011-08-16 22:55:10 -04:00
Kenneth Reitz
efba606e57 doctoring for Session 2011-08-16 22:20:26 -04:00
Kenneth Reitz
5bb18810d4 memory location repr for requests-client 2011-08-16 22:19:49 -04:00
Kenneth Reitz
853383a8d3 docstring fix 2011-08-16 22:16:22 -04:00
Kenneth Reitz
f319006813 session => sessions 2011-08-16 22:15:03 -04:00
Kenneth Reitz
2fa021582b Merge branch 'feature/session' of https://github.com/tomhsx/requests into feature/session
Conflicts:
	AUTHORS
2011-08-16 02:34:44 -04:00
Tom Hogans
0ed641a26e Added docs regarding requests.session to docs/user/advanced.rst 2011-08-16 02:00:11 -04:00
Tom Hogans
c2fd568695 Added tests for requests.session 2011-08-16 01:38:39 -04:00
Kenneth Reitz
8f72c48548 added alex to authors 2011-08-15 22:17:55 -04:00
alopatin
08a821381a Fixed misc spelling mistakes. 2011-08-15 22:53:22 -03:00
Kenneth Reitz
4625445d2a Merge branch 'develop' of https://github.com/moliware/requests into develop 2011-08-15 21:50:00 -04:00
Tom Hogans
ca428504d4 Added request.session functionality 2011-08-15 16:01:26 -04:00
JensDiemer
88f79b74ce use settings.timeout_fallback bool for socket.setdefaulttimeout() fallback 2011-08-15 17:03:37 +02:00
JensDiemer
647cdd3847 Use socket.setdefaulttimeout(timeout) only if timeout argument doesn't exist (Python <2.6) - https://github.com/kennethreitz/requests/issues/112 2011-08-15 16:51:33 +02:00
moliware
c510d21e4e headers.update is not the same that add headers one by one with add_header 2011-08-15 11:58:57 +02:00
Kenneth Reitz
49e5df1ff9 whoops 2011-08-14 22:32:35 -04:00
Kenneth Reitz
2357b13824 Response Headers quickstart 2011-08-14 22:30:30 -04:00
Kenneth Reitz
f137db6803 raise_for_status quick start 2011-08-14 22:20:03 -04:00
Kenneth Reitz
2cf0dad573 models.. 2011-08-14 22:12:50 -04:00
Kenneth Reitz
414b9dde84 not tablib.. 2011-08-14 22:08:01 -04:00
Kenneth Reitz
d50742f059 massive documentation update 2011-08-14 22:07:15 -04:00
Kenneth Reitz
3bc9a181a1 basic FAQ 2011-08-14 21:16:04 -04:00
Kenneth Reitz
975720a30c I'll have to manually add that to sphinx 2011-08-14 15:50:18 -04:00
Kenneth Reitz
1361506ae6 status blocks 2011-08-14 15:44:03 -04:00
Kenneth Reitz
23ff27ea4b Documentation fixes 2011-08-14 11:43:25 -04:00
Kenneth Reitz
933cbd735b emphasize r.content 2011-08-14 11:06:41 -04:00
Kenneth Reitz
ce1945c780 status codes fix 2011-08-14 10:43:02 -04:00
Kenneth Reitz
f52a413b6a Merge branch 'patch-1' of https://github.com/SimonSapin/requests into develop 2011-08-14 10:41:15 -04:00
Kenneth Reitz
ced96912f3 Merge remote-tracking branch 'origin/develop' into develop 2011-08-14 10:40:32 -04:00
Kenneth Reitz
f6e97c322b Merge branch 'develop' of https://github.com/jbochi/requests into develop 2011-08-14 10:39:47 -04:00
Kenneth Reitz
c9d61aefe4 Merge branch 'docs-typos' of https://github.com/markstory/requests into develop 2011-08-14 10:39:35 -04:00
Kenneth Reitz
85c79658ef Merge pull request #108 from zed/patch-1
fix typo
2011-08-14 07:21:52 -07:00
zed
c4c86fd6e8 fix typo 2011-08-14 17:46:43 +04:00
Mark Story
95dcdd8a05 Fixing a small typo. 2011-08-14 09:13:50 -04:00
Juarez Bochi
43669183ce Edited PATCH Requests in README 2011-08-14 07:30:43 -03:00
Simon Sapin
56e58fdcaf Fix a link in the documentation. 2011-08-14 09:50:25 +03:00
Kenneth Reitz
47378164ef \o/ 2011-08-13 14:55:41 -04:00
Kenneth Reitz
9469b4bb69 status_codes lookup dict 2011-08-13 14:50:59 -04:00
Kenneth Reitz
6cead64345 supporting codes class 2011-08-13 14:50:48 -04:00
Kenneth Reitz
773630b010 requests.codes 2011-08-13 14:50:38 -04:00
Kenneth Reitz
907e529471 changing history muahaha 2011-08-13 13:17:41 -04:00
Kenneth Reitz
2d70647f72 attach request object to responses 2011-08-13 13:17:17 -04:00
Kenneth Reitz
10cc4673f9 remove Response.cached 2011-08-13 13:14:10 -04:00
Kenneth Reitz
629d730da6 all main functions are **kwargs now. People shouldn't be relying on the order. 2011-08-13 13:11:16 -04:00
Kenneth Reitz
d4eabaaacc better 2011-08-13 13:04:14 -04:00
Kenneth Reitz
5693314a37 response error handling rework 2011-08-13 13:01:44 -04:00
Kenneth Reitz
2e4724ccea history 2011-08-13 12:34:13 -04:00
Kenneth Reitz
e7084940e1 added Jeremy Selie to AUTHORS 2011-08-13 09:40:24 -04:00
Kenneth Reitz
b141da36a4 test relative redirects 2011-08-13 09:37:14 -04:00
Kenneth Reitz
bb16fe8115 http/https tests 2011-08-13 09:37:07 -04:00
Kenneth Reitz
a987729712 whitespace! 2011-08-13 09:14:49 -04:00
Kenneth Reitz
f7cc7b1778 Merge branch 'patch-1' of https://github.com/jeremys/requests into develop 2011-08-13 08:39:16 -04:00
Zbigniew Siciarz
e4adef5226 Added proxies to requests.settings 2011-08-09 10:03:09 +02:00
Kenneth Reitz
e1714e678e Added Peter Manser to AUTHORS 2011-08-04 22:08:32 -03:00
Kenneth Reitz
d10b08ff88 Merge pull request #99 from petermanser/develop
Fixing minor bug in code example - missing apostrophe
2011-08-04 18:07:49 -07:00
Peter Manser
18c42e88f5 Fixing minor bug in code example - missing apostrophe 2011-08-04 21:47:15 +02:00
Kenneth Reitz
f238dffc18 Merge https://github.com/bclermont/requests into develop 2011-08-02 23:23:41 -04:00
Jeremy Selier
5a7ec361f4 Join with the latest URL not the first one. 2011-08-02 18:51:21 +03:00
Jeremy Selier
291d4742d6 Fixes correct "relative" redirections. Example: http://digg.com/d1qIwX, which redirects to "/news/story/A_Manifesto_How_to_Save_Media", which requests transforms to: http://digg.com//news/story/A_Manifesto_How_to_Save_Media" which results in an endless redirect loop. 2011-08-02 18:05:03 +03:00
Kenneth Reitz
1cdd1d04ce v0.5.1 2011-07-23 22:58:32 -04:00
Kenneth Reitz
3293c0e8f4 Python 2.5 bugfix 2011-07-23 22:49:01 -04:00
Kenneth Reitz
074150ff2c test fix 2011-07-23 22:42:51 -04:00
Kenneth Reitz
c3c90bb09c default headers and content encoding settings
Closes #78
2011-07-23 22:35:46 -04:00
Kenneth Reitz
87560d65ca redirect history length test 2011-07-23 22:16:21 -04:00
Kenneth Reitz
7e4f7fa600 added Rubén Abad to AUTHORS 2011-07-23 22:12:41 -04:00
Kenneth Reitz
7f9c6233bb history bugfix in history 2011-07-23 22:12:34 -04:00
Kenneth Reitz
9b6a190652 Merge branch 'develop' of https://github.com/rabad/requests into develop 2011-07-23 22:11:59 -04:00
Kenneth Reitz
1e1fb1e7a8 Redirect history fix (Closes #91) 2011-07-23 22:11:54 -04:00
Kenneth Reitz
080db5ee5d CaseInsensitiveDict notes 2011-07-23 21:49:08 -04:00
Kenneth Reitz
2edda29448 fixing bad url shortner link 2011-07-23 21:48:28 -04:00
Kenneth Reitz
6ba3523a26 Merge branch 'develop' of https://github.com/tgulacsi/requests into develop 2011-07-23 21:45:45 -04:00
Kenneth Reitz
87e7a04209 v0.5.1 history update 2011-07-23 21:42:12 -04:00
Kenneth Reitz
260e32a7c3 default user agent! 2011-07-23 21:39:14 -04:00
Kenneth Reitz
5fff2c757b forced_basic default handler. 2011-07-23 21:37:17 -04:00
Kenneth Reitz
30d699b64f Forced Basic Auth for 404'd resources 2011-07-23 21:36:24 -04:00
Kenneth Reitz
7d91d3fb5c use patched HTTPBasicAuthHandler 2011-07-23 20:10:37 -04:00
Gulácsi Tamás
747f882b0e structures: CaseInsensitiveDict.__delitem__ missing "value" fix 2011-07-23 22:25:07 +02:00
Gulácsi Tamás
369e7cb38f add myself to AUTHORS 2011-07-23 22:19:28 +02:00
Gulácsi Tamás
04c456b877 structures: little bit more general, lower-key-caching version of CaseInsensitiveDict 2011-07-23 22:16:12 +02:00
Gulácsi Tamás
f408829d51 test_request: simplejson is just as good as omnijson for test 2011-07-23 22:15:28 +02:00
Rubén Abad
73b414ab45 We can represent multivalued params with arrays 2011-07-13 11:08:54 +02:00
Kenneth Reitz
f18c40fd36 👍 2011-07-09 10:41:44 -04:00
Jérémy Bethmont
891e52d723 Added the possibility to access headers without fetching the whole body (fixes #86). 2011-06-29 11:10:39 +02:00
Xu Pan
d2ff0f40eb add doseq to urlencode in model._encode_params to enable list as dict value 2011-06-26 13:32:19 +08:00
Kenneth Reitz
787110c853 international domain name support! 2011-06-24 20:32:28 -04:00
Kenneth Reitz
20319044a3 Added Jérémy Bethmont to AUTHORS. 2011-06-24 18:47:41 -04:00
Kenneth Reitz
6f08c81698 note about indns 2011-06-24 18:24:44 -04:00
Jérémy Bethmont
868cd3d1cb Implemented idna (fixes #76). 2011-06-24 12:21:25 +02:00
Kenneth Reitz
39edf7a7fa raise_for_status +URLError 2011-06-23 06:22:46 -04:00
Kenneth Reitz
4cf4362610 typos 2011-06-23 06:06:56 -04:00
Kenneth Reitz
0b719f037c ! 2011-06-23 05:34:44 -04:00
Kenneth Reitz
9603589b06 oops 2011-06-23 05:32:05 -04:00
Kenneth Reitz
5f38c1209e TURTLES 2011-06-23 05:18:13 -04:00
Kenneth Reitz
749edb3554 new sidebar logo 2011-06-23 05:16:13 -04:00
Kenneth Reitz
882b465968 tox fix 2011-06-23 05:15:09 -04:00
Kenneth Reitz
784fe8bbdb working requests logo 2011-06-23 03:30:32 -04:00
Kenneth Reitz
6253cf4c18 TODO Docs 2011-06-23 03:30:22 -04:00
Kenneth Reitz
8521075868 playing nice. 2011-06-21 22:55:35 -04:00
Kenneth Reitz
e2d6a92150 Timeouts are normal errors now.
--
FIX ALL TEH TESTS!
2011-06-21 22:43:15 -04:00
Kenneth Reitz
162b751f6d Timeouts are normal errors now.
--
BREAK ALL TEH TESTS!
2011-06-21 22:40:27 -04:00
Kenneth Reitz
20aed5488b Release the v0.5.0! 2011-06-21 22:26:06 -04:00
Kenneth Reitz
2374d0fd7a so many paramatersssss 2011-06-21 22:22:46 -04:00
Kenneth Reitz
483d00736c readme update 2011-06-21 22:22:02 -04:00
Kenneth Reitz
15de7d9dba add PATCH to docs 2011-06-21 22:21:54 -04:00
Kenneth Reitz
34a583b821 PATCH in Docs 2011-06-21 22:06:31 -04:00
Kenneth Reitz
2da86daa3a update history 2011-06-21 22:03:56 -04:00
Kenneth Reitz
b4eac4c993 redirect fix 2011-06-21 22:00:04 -04:00
Kenneth Reitz
b4a4f38cce verbose output stream support 2011-06-21 21:59:57 -04:00
Kenneth Reitz
8c81cf64de proper PATCH testing 2011-06-21 18:24:28 -04:00
Kenneth Reitz
00f066a467 PATCH support 2011-06-21 18:03:06 -04:00
Kenneth Reitz
bdf9f90c8a omnijson for tests 2011-06-21 17:59:24 -04:00
Kenneth Reitz
428678b2e4 Added Alberto Paro to AUTHORS 2011-06-21 17:35:29 -04:00
Alberto Paro
bcd30f05ab Added setuptools management to setup.py to allow to install in develop mode 2011-06-21 22:48:31 +02:00
Kenneth Reitz
bd49da067f updated history 2011-06-21 00:07:41 -04:00
moliware
3e7c682e66 Support for proxies 2011-06-19 22:36:50 +02:00
Kenneth Reitz
bbd1e40bd5 no pypy default 2011-06-17 14:09:33 -04:00
Kenneth Reitz
a25f5ff1dd that works 2011-06-17 13:51:29 -04:00
Richard Boulton
80d860d2d4 Allow POST and PUT requests to take both querystring params and request body
data.
2011-06-16 15:48:43 +01:00
Richard Boulton
f31ade335d Change documentation comments for "params" and "data" parameters to refer to
the request body or the query string, as appropriate.  "GET parameters", "POST
data", etc aren't the ideal terminology to use - they come from common usage
with web browsers, rather than the HTTP specification, so are off-putting in
other contexts.
2011-06-16 15:40:14 +01:00
Richard Boulton
023ab755e5 Move the code to encode the request data into a static helper method - I think this makes the code clearer, but it also prepares the way for reusing the encoding code 2011-06-16 13:35:33 +01:00
Richard Boulton
4320f81bf4 Actually use the HTTPSBIN variable for test https requests 2011-06-16 13:06:22 +01:00
Kenneth Reitz
47f1f28385 + Richard Boulton to AUTHOURS 2011-06-16 06:27:13 -04:00
Richard Boulton
3d1444b829 Update documentation of the "data" parameter for requests.post() to indicate that it allows bytes. Update documentation of requests.put() to refer to the "data" parameter rather than the (hidden, but functional) "params" parameter, and note that that also allows both bytes and dictionaries. 2011-06-16 03:18:47 -07:00
Zbigniew Siciarz
4d3a9defd2 Porting cookie test to httpbin.org. 2011-06-16 09:08:32 +02:00
Zbigniew Siciarz
1f1211747b Merge remote-tracking branch 'upstream/develop' into develop 2011-06-16 08:56:38 +02:00
Kenneth Reitz
45e30048f0 port request tests to httpbin.org 2011-06-16 00:11:47 -04:00
Kenneth Reitz
245c6edec3 todo 2011-06-15 22:56:14 -04:00
Kenneth Reitz
cb0a96fbf3 Redirect chain flag default off. 2011-06-15 22:43:26 -04:00
Zbigniew Siciarz
fade9e36b6 Merge branch 'develop' of git://github.com/kennethreitz/requests into develop 2011-06-13 12:41:40 +02:00
Zbigniew Siciarz
24e2a05605 Merge branch 'master' of git://github.com/kennethreitz/requests into develop 2011-06-13 12:41:33 +02:00
Kenneth Reitz
1749260c97 update tests 2011-06-12 22:02:35 -04:00
Kenneth Reitz
3b86c3c0e3 httpbin 2011-06-12 22:00:17 -04:00
Kenneth Reitz
8f3da7b72c added Daniele Tricoli 'Eriol' to authors 2011-06-12 13:14:56 -04:00
Kenneth Reitz
d6cde6a5df add Zbigniew Siciarz to authors 2011-06-09 18:09:39 -04:00
Zbigniew Siciarz
54fd24b757 Testing that cookies actually work. 2011-06-09 12:30:43 +02:00
Kenneth Reitz
2153b21152 less hostility 2011-06-08 19:22:19 -04:00
Kenneth Reitz
74013d751c rackspace cloud's net connection is insane 2011-06-08 19:18:11 -04:00
Kenneth Reitz
1fa1cdd2a3 WHITESPACE 2011-06-08 19:11:08 -04:00
Kenneth Reitz
cd0d0a2b17 whitespace 2011-06-05 21:22:35 -04:00
Kenneth Reitz
e63569041a Update AUTHORS
Refs #46
2011-05-27 08:02:57 -04:00
Kenneth Reitz
54449483b6 Merge branch 'patch-1' of https://github.com/JNRowe/requests into develop 2011-05-27 08:00:22 -04:00
Kenneth Reitz
52bedf4c0b default post data to empty byte string
Refs #45
2011-05-27 07:59:28 -04:00
James Rowe
5962c0ea86 Subclass dict for CaseInsensitiveDict. 2011-05-27 00:17:44 -07:00
Johannes
9b2d7382bf Conform api.* arguments to api.request arguments.
Fixes #40.
Fixes #41.
2011-05-27 08:36:03 +02:00
Bruno Clermont
d91af4cadf add debian packaging 2011-05-26 15:37:31 -04:00
Kenneth Reitz
7e3d265b0f gauge.es 2011-05-24 18:38:45 -04:00
Kenneth Reitz
f990a7b453 Merge branch 'develop' of github.com:kennethreitz/requests into develop 2011-05-24 17:18:28 -04:00
Kenneth Reitz
ef02d02b1f testimonial cleanup 2011-05-24 17:18:19 -04:00
Kenneth Reitz
04c169ecfe Merge pull request #39 from mrtazz/patch-1
e.g. should be i.e. I guess
2011-05-23 08:42:52 -07:00
Daniel Schauenberg
5593f12347 e.g. should be i.e. I guess 2011-05-23 08:39:15 -07:00
Kenneth Reitz
3189d8690e refactor^2 2011-05-22 18:01:21 -04:00
Kenneth Reitz
b51e042e73 Merge pull request #38 from jgorset/requests
---

Ive refactored `api.request`. I think. The new version is more robust in that it no longer accepts arbitrary arguments and raises an exception if one attempts to call it with both `params` and `data` (that should probably be supported, though, but thats a topic for another issue).

Conflicts:
	requests/api.py
2011-05-22 17:54:10 -04:00
Johannes
82f1320ed5 Refactor api.request 2011-05-22 23:49:29 +02:00
41 changed files with 3866 additions and 398 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ nosetests.xml
pylint.txt
*.pyc
docs/_build
toy.py
toy.py
.gitignore

18
AUTHORS
View File

@@ -18,4 +18,20 @@ Patches and Suggestions
- Rob Madole
- Aram Dulyan
- Johannes Gorset
- 村山めがね (Megane Murayama)
- 村山めがね (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>

View File

@@ -1,6 +1,48 @@
History
-------
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)
++++++++++++++++++

View File

@@ -1,21 +1,46 @@
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
@@ -35,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
@@ -52,77 +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
>>> requests.get(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
HEAD Requests
>>> requests.head(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
PUT Requests
>>> requests.put(url, data='', headers={}, files={}, cookies=None, auth=None)
<Response [200]>
POST Requests
>>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None)
<Response [200]>
DELETE Requests
>>> requests.delete(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
**Responses:**
Response.status_code
(Integer) Received HTTP Status Code Response
Response.headers
((CaseInsensitive) Dictionary) Received HTTP Response Headers.
Response.content
(Bytes) Received Content.
Response.history
(List of Responses) Redirection History.
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

5
debian/changelog vendored Normal file
View File

@@ -0,0 +1,5 @@
python-requests (0.4.1-0) testing; urgency=low
* Initial Debian package
-- Bruno Clermont <bruno.clermont@gmail.com> Thu, 26 May 2011 16:25:00 -0500

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
5

13
debian/control vendored Normal file
View File

@@ -0,0 +1,13 @@
Source: python-requests
Section: python
Priority: optional
Maintainer: Bruno Clermont <bruno.clermont@gmail.com>
Homepage: https://github.com/bclermont/requests
Bugs: https://github.com/bclermont/requests/issues
Build-Depends: debhelper, python-support
Package: python-requests
Architecture: all
Depends: ${python:Depends}, python-support
Provides: ${python:Provides}
Description: Python HTTP Requests for Humans.

1
debian/docs vendored Normal file
View File

@@ -0,0 +1 @@
docs/user/*.rst

1
debian/pyversions vendored Normal file
View File

@@ -0,0 +1 @@
2.4-

42
debian/rules vendored Executable file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/make -f
# Verbose mode
#export DH_VERBOSE=1
clean:
dh_testdir
dh_testroot
rm -rf build requests.egg-info
# find django-sentry/ -name *.pyc | xargs rm -f
dh_clean
build:
dh_testdir
python setup.py build
install:
dh_testdir
dh_installdirs
python setup.py install --root $(CURDIR)/debian/python-requests
binary-indep: install
binary-arch: install
dh_install
dh_installdocs
# dh_installchangelogs
dh_compress
dh_fixperms
dh_pysupport
dh_gencontrol
dh_installdeb
dh_md5sums
dh_builddeb -- -Z lzma -z9
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary

BIN
docs/_static/requests-sidebar.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,4 +1,9 @@
<h3>About Requests</h3>
<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

View File

@@ -1,4 +1,9 @@
<h3><a href="http://python-requests.org">About Requests</a></h3>
<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

View File

@@ -30,4 +30,18 @@
})();
</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 %}

View File

@@ -91,8 +91,8 @@ div.sphinxsidebarwrapper {
}
div.sphinxsidebarwrapper p.logo {
padding: 0 0 20px 0;
margin: 0;
padding: 0;
margin: -10px 0 0 -20px;
text-align: center;
}
@@ -461,3 +461,9 @@ a:hover tt {
background-color: #ccc;
-webkit-border-radius: 3px;
}
/* misc. */
.revsys-inline {
display: none!important;
}

View File

@@ -13,14 +13,16 @@ important right here and provide links to the canonical documentation.
Main Interface
--------------
All of Request's functionality can be accessed by these 5 methods. They
All of Request's functionality can be accessed by these 6 methods. They
all return a :class:`Response <models.Response>` object.
.. autofunction:: head
.. autofunction:: get
.. autofunction:: post
.. autofunction:: put
.. autofunction:: patch
.. autofunction:: delete
.. autofunction:: head
.. autofunction:: request
-----------
@@ -48,19 +50,8 @@ 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.
Functions
~~~~~~~~~
.. autofunction:: request
Classes
~~~~~~~
.. autoclass:: requests.models.Request
:inherited-members:
Structures
~~~~~~~~~~
.. autoclass:: requests.structures.CaseInsensitiveDict
:inherited-members:

43
docs/community/faq.rst Normal file
View File

@@ -0,0 +1,43 @@
.. _faq:
Frequently Asked Questions
==========================
This part of the documentation covers common questions about Requests.
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 `supported interpreters <interpreters>`_.
Keep-alive Support?
-------------------
It's on the way.

View 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.

View File

@@ -0,0 +1,31 @@
.. _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>`_.

View File

@@ -155,7 +155,7 @@ html_sidebars = {
#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 = False

5
docs/dev/authors.rst Normal file
View File

@@ -0,0 +1,5 @@
Authors
=======
.. include:: ../../AUTHORS

15
docs/dev/todo.rst Normal file
View 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>`_)

View File

@@ -13,7 +13,8 @@ Requests is an :ref:`ISC Licensed <isc>` HTTP library, written in Python, for hu
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.
It requires an *enormous* amount of work (even method overrides) to perform
the simplest of tasks.
Things shouldnt be this way. Not in Python.
@@ -21,14 +22,16 @@ Things shouldnt be this way. Not in Python.
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
>>> r.status_code
200
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 **GET**, **HEAD**, **PUT**,
**POST**, and **DELETE** HTTP requests. You can add headers, form data,
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.
@@ -36,35 +39,53 @@ all the hard work and crazy hacks for you.
Testimonals
-----------
`Twitter, Inc <http://twitter.com>`_ and `The Library of Congress <http://www.loc.gov/>`_ use Requests internally.
`Twitter, Inc <http://twitter.com>`_ uses Requests internally.
**Daniel Greenfeld**
Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to @kennethreitz's request library. Today has been AWESOME.
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.
Python HTTP: When in doubt, or when not in doubt, use Requests. Beautiful,
simple, Pythonic.
**Rich Leland**
Requests is awesome. That is all.
**Steve Pike**
I can never remember how to do it the regular way. ``import requests; requests.get()`` is just so easy!
I can never remember how to do it the regular way.
``import requests; requests.get()`` is just so easy!
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.
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
user/intro
user/install
.. user/quickstart
user/quickstart
user/advanced
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
-----------------
@@ -87,3 +108,5 @@ you.
:maxdepth: 2
dev/internals
dev/todo
dev/authors

View File

@@ -0,0 +1,137 @@
.. _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', header={'x-test2', 'true'})
.. admonition:: Global Settings
Certain parameters are best set at the ``request.config`` level
(e.g.. a global proxy, user agent header).
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
paramaeter::
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]>

View File

@@ -15,7 +15,7 @@ Installing requests is simple with `pip <http://www.pip-installer.org/>`_::
or, with `easy_install <http://pypi.python.org/pypi/setuptools>`_::
$ easy_install install requests
$ easy_install requests
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/index.html#pip-compared-to-easy-install>`_.
@@ -24,7 +24,7 @@ But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/index
Cheeseshop Mirror
-----------------
If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's personal `Cheeseshop mirror <pip.kreitz.co/>`_::
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
@@ -32,7 +32,7 @@ If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's pe
Get the Code
------------
Requsts is actively developed on GitHub, where the code is
Requests is actively developed on GitHub, where the code is
`always available <https://github.com/kennethreitz/requests>`_.
You can either clone the public repository::

View File

@@ -1,3 +1,5 @@
.. _introduction:
Introduction
============
@@ -40,14 +42,21 @@ Requests License
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.
.. _interpreters:
Python Interpreters
-------------------
At this time, the following Python platforms 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

View File

@@ -1,84 +1,134 @@
Feature Overview
================
Requests is designed to solve a 90% use case — making simple requests. While most
HTTP libraries are extremely extensible, they often attempt to support the entire HTTP Spec.
This often leads to extremely messy and cumbersome APIs, as is the case with urllib2. Requests abandons support for edge-cases, and focuses on the essentials.
.. _features:
Requests Can:
-------------
- Make **GET**, **POST**, **PUT**, **DELETE**, and **HEAD** requests.
- Handle HTTP and HTTPS Requests
- Add Request headers (with a simple dictionary)
- URLEncode your Form Data (with a simple dictionary)
- Add Multi-part File Uploads (with a simple dictionary)
- Handle CookieJars (with a single parameter)
- Add HTTP Authentication (with a single parameter)
- Handle redirects (with history)
- Automatically decompress GZip'd responses
- Support Unicode URLs
- Gracefully timeout
- Interface with Eventlet & Gevent
Requests Can't:
---------------
- Handle Caching
- Handle Keep-Alives
.. _quickstart:
Quickstart
==========
.. module:: requests.models
GET Request
-----------
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>`
Adding Parameters
-----------------
Lets gets started with some simple use cases and examples.
Adding Headers
--------------
HTTP Basic Auth
---------------
Tracking Redirects
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
----------------
HTTP POST (Form Data)
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::
HTTP POST (Binary Data)
-----------------------
>>> 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.
HTTP POST (Multipart Files)
---------------------------
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
HTTP PUT
--------
Cookies
-------
If a response contains some Cookies, you can get quick access to them::
HTTP DELETE
-----------
>>> url = 'http://httpbin.org/cookies/set/requests-is/awesome'
>>> r = requests.get(url)
>>> print r.cookies
{'requests-is': 'awesome'}
HTTP HEAD
---------
The underlying CookieJar is also available for more advanced handing::
>>> 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

File diff suppressed because one or more lines are too long

View File

@@ -11,111 +11,174 @@ This module impliments the Requests API.
"""
import requests
import config
from .models import Request, Response, AuthManager, AuthObject, auth_manager
from .models import Request, Response, AuthObject
from .status_codes import codes
from .hooks import dispatch_hook
from .utils import cookiejar_from_dict
from urlparse import urlparse
__all__ = ('request', 'get', 'head', 'post', 'put', 'delete')
__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):
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <models.Request>`. Returns :class:`Response <models.Response>` object.
"""Constructs and sends a :class:`Request <models.Request>`.
Returns :class:`Response <models.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 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) CookieJar object 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.
"""
data = kwargs.pop('data', dict()) or kwargs.pop('params', dict())
r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', dict()),
cookiejar=kwargs.get('cookies', None),
files=kwargs.get('files', None),
auth=kwargs.get('auth', auth_manager.get_auth(url)),
timeout=kwargs.get('timeout', config.settings.timeout),
allow_redirects=kwargs.get('allow_redirects', None)
if cookies is None:
cookies = {}
cookies = cookiejar_from_dict(cookies)
args = dict(
method = method,
url = url,
data = data,
params = params,
headers = headers,
cookiejar = cookies,
files = files,
auth = auth,
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)
# 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, params={}, headers={}, cookies=None, auth=None, **kwargs):
def get(url, **kwargs):
"""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 params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for 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 cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
"""
return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
return request('GET', url, **kwargs)
def head(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
def head(url, **kwargs):
"""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 params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for 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 cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
"""
return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
return request('HEAD', url, **kwargs)
def post(url, data={}, headers={}, files=None, cookies=None, auth=None, **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 of POST data to send with 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 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 cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
: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 redirect following is allowed.
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
"""
return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs)
return request('POST', url, data=data, **kwargs)
def put(url, data='', headers={}, files={}, cookies=None, auth=None, **kwargs):
def put(url, data='', **kwargs):
"""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 data: (optional) Dictionary or bytes to send in the body of 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 cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
: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 redirect following is allowed.
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
"""
return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs)
return request('PUT', url, data=data, **kwargs)
def delete(url, params={}, headers={}, cookies=None, auth=None, **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 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) Dict or CookieJar object to send with the :class:`Request`.
: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 redirect following is allowed.
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
"""
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 params: (optional) Dictionary of DELETE Parameters to send with the :class:`Request`.
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for 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 cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
: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 redirect following is allowed.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
"""
return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
return request('DELETE', url, **kwargs)

View File

@@ -12,7 +12,7 @@ class Settings(object):
_singleton = {}
# attributes with defaults
__attrs__ = ('timeout',)
__attrs__ = []
def __init__(self, **kwargs):
super(Settings, self).__init__()
@@ -53,4 +53,14 @@ class Settings(object):
return None
return object.__getattribute__(self, key)
settings = Settings()
settings = Settings()
settings.base_headers = {'User-Agent': 'python-requests.org'}
settings.accept_gzip = True
settings.proxies = None
settings.verbose = None
settings.timeout = None
#: Use socket.setdefaulttimeout() as fallback?
settings.timeout_fallback = True

View File

@@ -12,14 +12,16 @@ This module implements the main Requests system.
"""
__title__ = 'requests'
__version__ = '0.4.1'
__build__ = 0x000401
__version__ = '0.6.0'
__build__ = 0x000600
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
from models import HTTPError, auth_manager
from models import HTTPError
from api import *
from exceptions import *
from sessions import session
from status_codes import codes
from config import settings

40
requests/hooks.py Normal file
View 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

View File

@@ -6,61 +6,75 @@ requests.models
"""
import requests
import urllib
import urllib2
import socket
import zlib
from urllib2 import HTTPError
from urlparse import urlparse
from urlparse import urlparse, urlunparse, urljoin
from datetime import datetime
from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler
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
from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod
REDIRECT_STATI = (301, 302, 303, 307)
class Request(object):
"""The :class:`Request <models.Request>` object. It carries out all functionality of
Requests. Recommended interface is with the Requests functions.
"""
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH')
def __init__(self, url=None, headers=dict(), files=None, method=None,
data=dict(), auth=None, cookiejar=None, timeout=None,
redirect=True, allow_redirects=False):
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):
socket.setdefaulttimeout(timeout)
#: 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 <models.Request>`.
self.headers = headers
#: Dictionary of files to multipart upload (``{filename: content}``).
self.files = files
#: HTTP Method to use. Available: GET, HEAD, PUT, POST, DELETE.
self.method = method
#: Form or Byte data to attach to the :class:`Request <models.Request>`.
self.data = dict()
#: Dictionary or byte of request body data to attach to the
#: :class:`Request <models.Request>`.
self.data = None
#: Dictionary or byte of querystring data to attach to the
#: :class:`Request <models.Request>`.
self.params = None
#: True if :class:`Request <models.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
if hasattr(data, 'items'):
for (k, v) in data.items():
self.data.update({
k.encode('utf-8') if isinstance(k, unicode) else k:
v.encode('utf-8') if isinstance(v, unicode) else v
})
self._enc_data = urllib.urlencode(self.data)
else:
self._enc_data = self.data = data
# 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 <models.Response>` instance, containing
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
@@ -70,14 +84,34 @@ class Request(object):
auth = AuthObject(*auth)
if not auth:
auth = auth_manager.get_auth(self.url)
#: :class:`AuthObject` to attach to :class:`Request <models.Request>`.
self.auth = auth
#: CookieJar to attach to :class:`Request <models.Request>`.
self.cookiejar = cookiejar
#: True if Request has been sent.
self.sent = False
# 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)
@@ -107,12 +141,15 @@ class Request(object):
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)
@@ -135,7 +172,8 @@ class Request(object):
return opener.open
def _build_response(self, resp):
def _build_response(self, resp, is_error=False):
"""Build internal :class:`Response <models.Response>` object from given response."""
def build(resp):
@@ -145,15 +183,19 @@ class Request(object):
try:
response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None))
response.content = resp.read()
response.read = resp.read
response.close = resp.close
if self.cookiejar:
response.cookies = dict_from_cookiejar(self.cookiejar)
except AttributeError:
pass
if response.headers['content-encoding'] == 'gzip':
try:
response.content = zlib.decompress(response.content, 16+zlib.MAX_WBITS)
except zlib.error:
pass
if is_error:
response.error = resp
response.url = getattr(resp, 'url', None)
@@ -164,7 +206,7 @@ class Request(object):
r = build(resp)
if self.redirect:
if r.status_code in REDIRECT_STATI and not self.redirect:
while (
('location' in r.headers) and
@@ -177,11 +219,9 @@ class Request(object):
url = r.headers['location']
# Facilitate for non-RFC2616-compliant 'location' headers
# Facilitate non-RFC2616-compliant 'location' headers
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
if not urlparse(url).netloc:
parent_url_components = urlparse(self.url)
url = '%s://%s/%s' % (parent_url_components.scheme, parent_url_components.netloc, url)
url = urljoin(r.url, url)
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
if r.status_code is 303:
@@ -191,7 +231,8 @@ class Request(object):
request = Request(
url, self.headers, self.files, method,
self.data, self.auth, self.cookiejar, redirect=False
self.data, self.params, self.auth, self.cookiejar,
redirect=True
)
request.send()
r = request.response
@@ -199,19 +240,47 @@ class Request(object):
r.history = history
self.response = r
self.response.request = self
@staticmethod
def _build_url(url, data=None):
"""Build URLs."""
def _encode_params(data):
"""Encode parameters in a piece of data.
if urlparse(url).query:
return '%s&%s' % (url, 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:
if data:
return '%s?%s' % (url, data)
return data, data
def _build_url(self):
"""Build the actual URL to use."""
# Support for unicode domain names.
parsed_url = list(urlparse(self.url))
parsed_url[1] = parsed_url[1].encode('idna')
self.url = urlunparse(parsed_url)
if self._enc_params:
if urlparse(self.url).query:
return '%s&%s' % (self.url, self._enc_params)
else:
return url
return '%s?%s' % (self.url, self._enc_params)
else:
return self.url
def send(self, anyway=False):
@@ -224,11 +293,20 @@ class Request(object):
:param anyway: If True, request will be sent, even if it has
already been sent.
"""
self._checks()
success = False
# 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(self._build_url(self.url, self._enc_data), method=self.method)
req = _Request(url, method=self.method)
else:
if self.files:
@@ -238,46 +316,60 @@ class Request(object):
self.files.update(self.data)
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method=self.method)
req = _Request(url, data=datagen, headers=headers, method=self.method)
else:
req = _Request(self.url, data=self._enc_data, method=self.method)
req = _Request(url, data=self._enc_data, method=self.method)
if self.headers:
req.headers.update(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()
resp = opener(req)
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, why:
self._build_response(why)
if not self.redirect:
self.response.error = why
except urllib2.URLError, error:
raise Timeout if isinstance(error.reason, socket.timeout) else error
except (urllib2.HTTPError, urllib2.URLError), why:
if hasattr(why, 'reason'):
if isinstance(why.reason, socket.timeout):
why = Timeout(why)
self._build_response(why, is_error=True)
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 core :class:`Response <models.Response>` object. All
@@ -290,7 +382,7 @@ class Response(object):
#: Raw content of the response, in bytes.
#: If ``content-encoding`` of response was set to ``gzip``, the
#: response data will be automatically deflated.
self.content = None
self._content = None
#: Integer Code of responded HTTP Status.
self.status_code = None
#: Case-insensitive Dictionary of Response Headers.
@@ -303,12 +395,14 @@ class Response(object):
self.ok = False
#: Resulting :class:`HTTPError` of request, if one occured.
self.error = None
#: True, if the response :attr:`content` is cached locally.
self.cached = False
#: A list of :class:`Response <models.Response>` objects from
#: the history of the Request. Any redirect responses will end
#: up here.
self.history = []
#: The Request that created the Response.
self.request = None
#: A dictionary of Cookies the server sent back.
self.cookies = None
def __repr__(self):
@@ -320,17 +414,25 @@ class Response(object):
return not self.error
def __getattr__(self, name):
"""Read and returns the full stream when accessing to :attr: `content`"""
if name == 'content':
if self._content is not None:
return self._content
self._content = self.read()
if self.headers.get('content-encoding', '') == 'gzip':
try:
self._content = zlib.decompress(self._content, 16+zlib.MAX_WBITS)
except zlib.error:
pass
return self._content
def raise_for_status(self):
"""Raises stored :class:`HTTPError`, if one occured."""
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occured."""
if self.error:
raise self.error
def read(self, *args):
"""Returns :attr:`content`. Used for file-like object compatiblity."""
return self.content
class AuthManager(object):
"""Requests Authentication Manager."""
@@ -405,8 +507,10 @@ class AuthManager(object):
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]
@@ -417,7 +521,9 @@ class AuthManager(object):
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,
@@ -487,17 +593,18 @@ class AuthObject(object):
_handlers = {
'basic': HTTPBasicAuthHandler,
'forced_basic': HTTPForcedBasicAuthHandler,
'digest': HTTPDigestAuthHandler,
'proxy_basic': urllib2.ProxyBasicAuthHandler,
'proxy_digest': urllib2.ProxyDigestAuthHandler
}
def __init__(self, username, password, handler='basic', realm=None):
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(), urllib2.HTTPBasicAuthHandler)
self.handler = self._handlers.get(handler.lower(), HTTPForcedBasicAuthHandler)
else:
self.handler = handler

View File

@@ -9,7 +9,7 @@ Urllib2 Monkey patches.
"""
import urllib2
import re
class Request(urllib2.Request):
"""Hidden wrapper around the urllib2.Request object. Allows for manual
@@ -28,7 +28,7 @@ class Request(urllib2.Request):
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
"""HTTP Redirect handler."""
def http_error_301(self, req, fp, code, msg, headers):
pass
@@ -37,10 +37,13 @@ class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
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
@@ -48,6 +51,7 @@ class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
# 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:
@@ -60,6 +64,59 @@ class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
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):

84
requests/sessions.py Normal file
View 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
View 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'),
302: ('found',),
302: ('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',),
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'),
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)

View File

@@ -8,57 +8,58 @@ Datastructures that power Requests.
"""
from UserDict import DictMixin
class CaseInsensitiveDict(DictMixin):
"""Case-insensitive Dictionary for :class:`Response <models.Response>` Headers.
class CaseInsensitiveDict(dict):
"""Case-insensitive Dictionary
For example, ``headers['content-encoding']`` will return the
value of a ``'Content-Encoding'`` response header."""
def __init__(self, *args, **kwargs):
# super(CaseInsensitiveDict, self).__init__()
self.data = dict(*args, **kwargs)
def __repr__(self):
return self.data.__repr__()
def __getstate__(self):
return self.data.copy()
def __setstate__(self, d):
self.data = d
def _lower_keys(self):
return map(str.lower, self.data.keys())
def __contains__(self, key):
return key.lower() in self._lower_keys()
def __getitem__(self, key):
if key.lower() in self:
return self.items()[self._lower_keys().index(key.lower())][1]
@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):
return self.data.__setitem__(key, value)
dict.__setitem__(self, key, value)
self._clear_lower_keys()
def __delitem__(self, key):
return self.data.__delitem__(key)
dict.__delitem__(self, key)
self._lower_keys.clear()
def __contains__(self, key):
return key.lower() in self.lower_keys
def __keys__(self):
return self.data.__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
def __iter__(self):
return self.data.__iter__()
class LookupDict(dict):
"""Dictionary lookup object."""
def __init__(self, name=None):
self.name = name
super(LookupDict, self).__init__()
def iteritems(self):
return self.data.iteritems()
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)

72
requests/utils.py Normal file
View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
"""
requests.utils
~~~~~~~~~~~~~~
This module provides utlity functions that are used within Requests
that are also useful for external consumption.
"""
import cookielib
def dict_from_cookiejar(cookiejar):
"""Returns a key/value dictionary from a CookieJar."""
cookie_dict = {}
for _, cookies in cookiejar._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."""
# 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."""
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

View 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,7 +28,7 @@ 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',

View File

@@ -6,50 +6,91 @@ 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.org/'
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."""
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_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);
r = requests.get(httpbin('user-agent'), headers=heads);
self.assertTrue(heads['User-agent'] in r.content)
heads = {
@@ -57,132 +98,376 @@ class RequestsTestSuite(unittest.TestCase):
'Mozilla/5.0 (github.com/kennethreitz/requests)'
}
r = requests.get('http://whatsmyua.com', headers=heads);
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, 302)
post_url = bin.headers['location']
post = requests.post(post_url, data={'some': 'data'})
self.assertEqual(post.status_code, 201)
for service in SERVICES:
post2 = requests.post(post_url, files={'some': open('test_requests.py')})
self.assertEqual(post2.status_code, 201)
url = service('post')
post = requests.post(url).raise_for_status()
post3 = requests.post(post_url, data='[{"some": "json"}]')
self.assertEqual(post.status_code, 201)
post = requests.post(url, data={'some': 'data'})
self.assertEqual(post.status_code, 200)
post2 = requests.post(url, files={'some': open('test_requests.py')})
self.assertEqual(post2.status_code, 200)
post3 = requests.post(url, data='[{"some": "json"}]')
self.assertEqual(post3.status_code, 200)
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 302)
post_url = bin.headers['location']
for service in SERVICES:
post2 = requests.post(post_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, 302)
post_url = bin.headers['location']
for service in SERVICES:
post2 = requests.post(post_url, files={'some': open('test_requests.py')},
headers = {'User-Agent': 'requests-tests'})
url = service('post')
self.assertEqual(post2.status_code, 201)
post2 = requests.post(url,
files={'some': open('test_requests.py')},
headers = {'User-Agent': 'requests-tests'})
self.assertEqual(post2.status_code, 200)
def test_nonzero_evaluation(self):
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)
r = requests.get('https://convore.com/api/account/verify.json')
self.assertEquals(r.status_code, 200)
def test_unicode_get(self):
requests.get('http://google.com', params={'foo': u'føø'})
requests.get('http://google.com', params={u'føø': u'føø'})
requests.get('http://google.com', params={'føø': 'føø'})
requests.get('http://google.com', params={'foo': u'foo'})
requests.get('http://google.com/ø', params={'foo': u'foo'})
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):
conv_auth = ('requeststest', 'bad_password')
r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth)
self.assertEquals(r.status_code, 401)
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):
with requests.settings(timeout=0.0001):
self.assertRaises(requests.Timeout, requests.get, 'http://google.com')
with requests.settings(timeout=10):
requests.get('http://google.com')
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):
requests.post('http://google.com', data='foo')
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_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)

View File

@@ -1,11 +1,8 @@
[tox]
envlist = py25,py26,py27,pypy
envlist = py25,py26,py27
[testenv]
commands=py.test --junitxml=junit-{envname}.xml
deps =
pytest
[testenv:pypy]
basepython=/usr/bin/pypy
omnijson