Compare commits

...

236 Commits

Author SHA1 Message Date
Kenneth Reitz
8c90c610cd v0.7.6
Fixes #237
2011-11-07 14:20:32 -05:00
Kenneth Reitz
1a85e110b8 Merge branch 'develop' of github.com:kennethreitz/requests into develop 2011-11-07 14:18:20 -05:00
Kenneth Reitz
12fa108ede add query data to digest path 2011-11-07 14:18:05 -05:00
Kenneth Reitz
9c3ca6e740 The Washington Post 2011-11-07 10:42:10 -05:00
Kenneth Reitz
12ada03548 Merge pull request #244 from neogenix/patch-1
Updated your 2001, to 2011... unless you went back in time ;)
2011-11-06 19:58:34 -08:00
Patrick
daa4fbf4e2 Updated your 2001, to 2011... unless you went back in time ;) 2011-11-06 21:57:47 -06:00
Kenneth Reitz
7ce305fa89 french post 2011-11-06 13:25:34 -05:00
Kenneth Reitz
04cdd0f2e7 daikon 2011-11-06 13:24:23 -05:00
Kenneth Reitz
33ee732082 steal @shazow's contribute paragraph 2011-11-04 23:57:43 -04:00
Kenneth Reitz
cf36d8c753 changes! 2011-11-04 23:32:15 -04:00
Kenneth Reitz
bd562a7ab3 Merge branch 'develop' of https://github.com/davidfischer/requests into develop 2011-11-04 23:28:21 -04:00
Kenneth Reitz
72c8163aee davidfischer 2011-11-04 23:27:50 -04:00
Kenneth Reitz
2bae57e0a6 v0.7.5 2011-11-04 23:26:31 -04:00
Kenneth Reitz
f7968b6797 r.content is None of there's an invalid response.
Fixes #236
2011-11-04 23:24:38 -04:00
Kenneth Reitz
4cae63a297 take out weinre 2011-11-04 23:00:38 -04:00
Kenneth Reitz
628325ae90 Official™ mobile-friendly 2011-11-04 22:58:43 -04:00
Kenneth Reitz
b3e2410f44 mobile auto width 2011-11-04 22:15:54 -04:00
Kenneth Reitz
a02189a8e9 hide rtd on mobile docs 2011-11-04 22:14:12 -04:00
Kenneth Reitz
d343fda7f0 Merge remote-tracking branch 'origin/develop' into develop 2011-11-04 21:59:16 -04:00
Kenneth Reitz
f9bcf7c611 revert 2011-11-04 21:59:12 -04:00
Kenneth Reitz
23b0535a0d Update AUTHORS 2011-11-04 11:51:38 -03:00
Kenneth Reitz
32c7cbf1ad Merge pull request #238 from brutasse/docs-typo
Fixed a broken link in the docs
2011-11-04 07:51:07 -07:00
Bruno Renié
8fe6f8c618 Fixed a broken link in the docs 2011-11-04 15:49:29 +01:00
Kenneth Reitz
80c55f5767 python-rexster 2011-11-03 19:03:11 -03:00
David Fischer
3efd5dbab4 A cleaner and more complete fix for auth/redirects 2011-11-03 08:05:55 -07:00
David Fischer
eaf1264879 Fixes an issue with auth and redirects 2011-11-02 23:29:31 -07:00
Kenneth Reitz
f5592ceb48 Merge remote-tracking branch 'origin/develop' into develop 2011-11-02 20:39:15 -04:00
Kenneth Reitz
e32840f631 fix request docs 2011-11-02 20:39:06 -04:00
Kenneth Reitz
4596c02339 Merge pull request #234 from anatomyofashane/develop
ch ch chcnages
2011-10-31 18:53:07 -07:00
Shane McDonald
4fcf14e124 ch ch chcnages
via @kennethreitz
2011-10-31 21:50:57 -04:00
Kenneth Reitz
797301a63b Merge pull request #233 from anatomyofashane/develop
Update docs/_themes/kr/static/flasky.css_t
2011-10-31 18:26:52 -07:00
anatomyofashane
3615cbc9df Update docs/_themes/kr/static/flasky.css_t 2011-10-31 22:24:59 -03:00
Kenneth Reitz
e43b438729 test fix 2011-10-26 18:42:58 -04:00
Kenneth Reitz
8836c4b12a history 2011-10-26 18:38:32 -04:00
Kenneth Reitz
3a4f6ba8b3 v0.7.4 2011-10-26 18:36:14 -04:00
Kenneth Reitz
6ac1e02691 hooks fix #227 2011-10-26 18:35:01 -04:00
Kenneth Reitz
6140fac6aa Default Accept header, a la curl 2011-10-25 16:10:49 -04:00
Kenneth Reitz
73ed950e70 simplify 2011-10-24 20:55:35 -04:00
Kenneth Reitz
6ef5094be6 Merge remote-tracking branch 'origin/develop' into develop 2011-10-24 20:55:01 -04:00
Kenneth Reitz
077758f713 Merge pull request #224 from stoey/84e89429
Syntax error for Python 2.5
2011-10-24 17:54:05 -07:00
Kenneth Reitz
c108c5300f No mo' HttpError 2011-10-23 17:18:46 -04:00
Kenneth Reitz
0008b035e2 v0.7.3 2011-10-23 16:56:47 -04:00
Kenneth Reitz
be69529dca oops, debugging prints 2011-10-23 16:53:21 -04:00
Kenneth Reitz
e257151ffc Merge branch 'master' into develop 2011-10-23 16:10:24 -04:00
Kenneth Reitz
84a2b35b71 Readme update. 2011-10-23 16:03:06 -04:00
Kenneth Reitz
e15834da8e v0.7.2 2011-10-23 15:40:26 -04:00
Kenneth Reitz
f1bb25b1ec bugfix 2011-10-23 15:39:33 -04:00
Kenneth Reitz
6b9cb059f6 Merge branch 'develop' 2011-10-23 15:21:42 -04:00
Kenneth Reitz
02cf3dce37 v0.7.1 2011-10-23 15:18:27 -04:00
Kenneth Reitz
4fb72a2e68 Merge branch 'feature/detach' into develop 2011-10-23 15:18:09 -04:00
Kenneth Reitz
58b1c69b6a Explain custom auth handlers 2011-10-23 15:13:34 -04:00
Kenneth Reitz
8f8dbec0d5 docs for basic/digest auth 2011-10-23 14:59:46 -04:00
Kenneth Reitz
e46251b727 ch ch changes 2011-10-23 14:52:20 -04:00
Kenneth Reitz
82cbae479c history for this 2011-10-23 14:51:22 -04:00
Kenneth Reitz
b88fbb5e0c KILL THE AUTH HANDLERS 2011-10-23 14:49:59 -04:00
Kenneth Reitz
68b4830948 Add argument injection hook back 2011-10-23 14:47:15 -04:00
Kenneth Reitz
9966017a49 Add new utilities from werkzeug 2011-10-23 14:46:58 -04:00
Kenneth Reitz
53c7b77735 Digest Authentication test. 2011-10-23 14:46:44 -04:00
Kenneth Reitz
491a3c075d Move hooks into internal event loop 2011-10-23 14:46:35 -04:00
Kenneth Reitz
4916b1c2ce pep8 2011-10-23 14:44:26 -04:00
Kenneth Reitz
60b37e54b5 Digest authentication support!
Ripped from urllib2
2011-10-23 14:44:20 -04:00
Kenneth Reitz
b7c788cc56 prettier async patching 2011-10-23 14:44:00 -04:00
Kenneth Reitz
12db1576e9 auth tuple fix 2011-10-23 12:26:46 -04:00
Kenneth Reitz
af6aa3e572 Move hooks out of top-level loop 2011-10-23 12:26:41 -04:00
Kenneth Reitz
20e0ee4968 Remove AuthObject entirely. 2011-10-23 11:35:01 -04:00
Kenneth Reitz
4892a305d9 more advanced authentication mechanism 2011-10-23 11:33:03 -04:00
Kenneth Reitz
495c3d5006 Use new authentication style in models
🤘
2011-10-23 11:15:42 -04:00
Kenneth Reitz
06a5aed8e2 assert sessions are using new auth 2011-10-23 11:15:11 -04:00
Kenneth Reitz
0dae52220b fix base64 encoding for httpbasic 2011-10-23 11:14:59 -04:00
Kenneth Reitz
d23c4b94c1 httpbasic authentication cleanup 2011-10-23 11:00:36 -04:00
Kenneth Reitz
94c587d642 Comment out authentication handlers 2011-10-23 11:00:21 -04:00
Kenneth Reitz
340f7a0733 initial auth module w/ httpbasic auth 2011-10-23 10:56:04 -04:00
Kenneth Reitz
b8d4a5ea61 v0.7.1 plans 2011-10-23 10:04:46 -04:00
Kenneth Reitz
5b5b3def00 Merge branch 'develop' 2011-10-22 22:14:15 -04:00
Kenneth Reitz
1a4dfad52d mod string formatting for 2.5 2011-10-22 21:33:00 -04:00
Kenneth Reitz
b66871e871 v0.7.0 2011-10-22 21:30:47 -04:00
Kenneth Reitz
a74568d8d5 simplify request dispatch flow 2011-10-22 21:10:24 -04:00
Kenneth Reitz
302c4d9c41 Don't alter behavior for safe methods anymore. 2011-10-22 21:01:45 -04:00
Kenneth Reitz
97e3c84084 simplify the URL check 2011-10-22 20:57:39 -04:00
Kenneth Reitz
0ee6c1fa68 kill accept_gzip config 2011-10-22 20:55:46 -04:00
Kenneth Reitz
f0fe8b971b Merge branch 'feature/config' into develop 2011-10-22 20:32:11 -04:00
Kenneth Reitz
8c34fa8477 settings => config 2011-10-22 20:31:51 -04:00
Kenneth Reitz
6be69e303b history for config 2011-10-22 20:29:00 -04:00
Kenneth Reitz
2f6cf50a6c deprecate settings 2011-10-22 20:28:25 -04:00
Kenneth Reitz
f9dfbb73dd requests.request uses config 2011-10-22 20:28:19 -04:00
Kenneth Reitz
903f96cc6a use config throughout models 2011-10-22 20:28:06 -04:00
Kenneth Reitz
5c81fba1eb use config object in sessions 2011-10-22 20:26:12 -04:00
Kenneth Reitz
4dc48b6521 _config => defaults 2011-10-22 20:25:27 -04:00
Kenneth Reitz
ab3200b4cc config.py => _config.py 2011-10-22 19:41:39 -04:00
Kenneth Reitz
185450c5e1 test entry points 2011-10-22 19:40:45 -04:00
Kenneth Reitz
958c424c89 I don't even want to know. 2011-10-22 19:39:23 -04:00
Kenneth Reitz
61be59d996 show both session styles 2011-10-22 19:34:03 -04:00
Kenneth Reitz
e007a5a13f docstring 2011-10-22 19:27:44 -04:00
Kenneth Reitz
3d29f6535f kill wrapping of api methods 2011-10-22 19:27:08 -04:00
Kenneth Reitz
98dc08236c loose yourself 2011-10-22 18:18:39 -04:00
Kenneth Reitz
4ab0bfefd3 Merge branch 'master' into develop 2011-10-22 18:17:04 -04:00
Kenneth Reitz
e418631ce8 Merge branch 'feature/primary-sessions' into develop 2011-10-22 18:16:43 -04:00
Kenneth Reitz
4d97cc7506 disable rudolf until it obeys 2011-10-22 18:16:10 -04:00
Kenneth Reitz
2b3bd78685 Add all methods to session 2011-10-22 18:15:28 -04:00
Kenneth Reitz
597ace5718 import cleanups 2011-10-22 18:13:22 -04:00
Kenneth Reitz
f269c6c89a cookiejar => cookies in models 2011-10-22 18:10:47 -04:00
Kenneth Reitz
d72d116214 Move request and get to session 2011-10-22 18:10:32 -04:00
Kenneth Reitz
d552e6c884 pep8 all the things 2011-10-22 18:07:14 -04:00
Kenneth Reitz
77909f1d49 v0.6.7 2011-10-22 18:07:00 -04:00
Kenneth Reitz
dbf70fc40f All methods are valid now 2011-10-22 18:02:34 -04:00
Kenneth Reitz
3a6d9b09db test improvements 2011-10-22 12:27:10 -04:00
Kenneth Reitz
b19d8785a4 Update AUTHORS 2011-10-21 11:25:02 -03:00
Kenneth Reitz
41e60b8a00 Merge pull request #217 from thedaniel/async-doc
Clarify documentation around async.map and Request.send
2011-10-21 07:24:11 -07:00
Daniel Hengeveld
eddf5254fb Clarify documentation around async.map and Request.send 2011-10-20 23:55:03 -07:00
Kenneth Reitz
ba5eddcf5e WhitespaceBot 2011-10-20 23:53:25 -04:00
Kenneth Reitz
d63cd5f547 periods. 2011-10-20 23:53:20 -04:00
Kenneth Reitz
348d2a2d51 v0.6.7 initial history 2011-10-20 22:43:52 -04:00
Kenneth Reitz
194ff1ad89 #160 2011-10-20 22:09:08 -04:00
Kenneth Reitz
3b6904c5ef Merge branch 'master' into develop 2011-10-20 11:42:09 -04:00
Kenneth Reitz
8d0e03ce41 Merge remote-tracking branch 'origin/develop' into develop 2011-10-20 11:42:00 -04:00
Kenneth Reitz
baa3f78430 Merge pull request #214 from zsiciarz/new_http_codes
New http codes
2011-10-20 08:41:43 -07:00
Zbigniew Siciarz
4f984b0fe6 New HTTP status codes (draft only).
See IETF document at
https://tools.ietf.org/html/draft-nottingham-http-new-status-02.
2011-10-20 17:37:16 +02:00
Kenneth Reitz
c5ed4c9953 Update AUTHORS 2011-10-20 09:46:32 -03:00
Kenneth Reitz
681956e0c4 Merge pull request #213 from fredrikmollerstrand/develop
Fix pip-compared-to-easy-install url
2011-10-20 05:46:16 -07:00
Fredrik Möllerstrand
ad6a8d53d6 docs: fix pip-compared-to-easy-install url. 2011-10-20 11:50:27 +01:00
Kenneth Reitz
0e9d7aad27 add back the ascii art 2011-10-19 16:50:20 -04:00
Kenneth Reitz
6dc664caaf move core imports into __init__.py
because you can't splat import relatively. dumb.
2011-10-19 16:47:46 -04:00
Kenneth Reitz
98c5ded4f3 Merge branch 'master' into develop 2011-10-19 16:36:22 -04:00
Kenneth Reitz
5568f77ad5 Merge branch 'master' into develop 2011-10-19 16:36:12 -04:00
Kenneth Reitz
cfd38263d1 imports cleanup for @alex 🍰 2011-10-19 15:54:22 -04:00
Kenneth Reitz
c052029399 Update AUTHORS 2011-10-19 12:04:34 -03:00
Kenneth Reitz
b7243684de Merge pull request #211 from tvaught/master
Minor doc spelling tweaks
2011-10-19 08:04:04 -07:00
travis
055f7c1eb6 Some Doc and doctoring fixes by the Speling Poleece. 2011-10-19 10:00:13 -05:00
Kenneth Reitz
e27241df73 Update docs/index.rst 2011-10-19 11:41:33 -03:00
Kenneth Reitz
23efd56b12 Merge branch 'develop' 2011-10-19 03:40:39 -04:00
Kenneth Reitz
d938f32089 v0.6.6 2011-10-19 03:39:36 -04:00
Kenneth Reitz
7dc75ce3ce test session persistent params 2011-10-19 03:37:52 -04:00
Kenneth Reitz
8c74860fd1 add params to session merge attributes 2011-10-19 03:32:53 -04:00
Kenneth Reitz
492133ba64 white in spaaaaaccceeee 2011-10-19 01:36:38 -04:00
Kenneth Reitz
82e8fdbb29 Merge branch 'develop' 2011-10-19 01:31:32 -04:00
Kenneth Reitz
b37b977d20 v0.6.5 2011-10-19 01:30:45 -04:00
Kenneth Reitz
ec94f5030c testing session method attribute merging 2011-10-19 01:10:06 -04:00
Kenneth Reitz
65a765fe88 new local merging of keyword arguments for sessions 2011-10-19 01:09:55 -04:00
Kenneth Reitz
9645e882b2 "Python on the Web" blog post 2011-10-18 17:26:21 -04:00
Kenneth Reitz
ef1e27ea9f apt-get install python-requests 2011-10-17 20:59:27 -04:00
Kenneth Reitz
6bd8e9e73f sp 2011-10-17 20:58:52 -04:00
Kenneth Reitz
de4035fb96 ignore junit xml 2011-10-17 20:57:04 -04:00
Kenneth Reitz
38bd234936 pycodeconf audio 2011-10-17 20:56:54 -04:00
Kenneth Reitz
7481980c1a descriptions for "out there" 2011-10-17 20:52:55 -04:00
Kenneth Reitz
a4234e97be Add "out there" section to docs 2011-10-17 20:41:40 -04:00
Kenneth Reitz
9b42c954c6 Offline test suite 2011-10-15 13:01:56 -04:00
Kenneth Reitz
d0b9357f1b all teh colors 2011-10-15 12:54:50 -04:00
Kenneth Reitz
a688f026dc settings are being deprecated 2011-10-15 12:37:08 -04:00
Kenneth Reitz
60917e0b9f timeout adjustment 2011-10-15 12:35:47 -04:00
Kenneth Reitz
f0bcdd075e gunicorn 2011-10-15 12:33:14 -04:00
Kenneth Reitz
7a0f3638b5 Test suite cleanup 2011-10-15 12:32:42 -04:00
Kenneth Reitz
12997080a8 use gunicorn to serve tests 2011-10-15 12:30:49 -04:00
Kenneth Reitz
6e840f46e1 make test 2011-10-15 12:16:24 -04:00
Kenneth Reitz
12d6697815 v0.0.4 2011-10-15 12:05:40 -04:00
Kenneth Reitz
d85c5356dc remove idna tests, for now 2011-10-15 12:05:35 -04:00
Kenneth Reitz
ff6dbc556b Merge branch 'feature/httpbin' into develop 2011-10-15 11:56:20 -04:00
Kenneth Reitz
35bfa9e91d HTTP Integration 2011-10-15 11:55:57 -04:00
Kenneth Reitz
d98de70f4a envoy. 2011-10-15 11:05:29 -04:00
Kenneth Reitz
e1dfb7c883 httpbin 2011-10-15 09:55:00 -04:00
Kenneth Reitz
15755be3c7 nosexunit is a steaming pile of trash 2011-10-15 09:52:45 -04:00
Dan Head
84e89429f0 Syntax fix for Python 2.5 2011-10-14 22:24:42 +00:00
Kenneth Reitz
daf9b60550 gah, gevent won't work with pypy 2011-10-13 22:39:13 -04:00
Kenneth Reitz
1382d655ed omnijson for python2.5 2011-10-13 22:37:23 -04:00
Kenneth Reitz
f0cfba708b take out pyflakes.. for now. 2011-10-13 22:34:01 -04:00
Kenneth Reitz
435bd1242a reqs.txt for ci and dev 2011-10-13 22:30:19 -04:00
Kenneth Reitz
4f6fc98509 Merge branch 'release/0.6.4' 2011-10-13 22:23:04 -04:00
Kenneth Reitz
9011852db0 Bunk submodule. 2011-10-13 22:22:38 -04:00
Kenneth Reitz
29548bfab6 Remove hacking file. 2011-10-13 22:20:43 -04:00
Kenneth Reitz
efc267ce59 Remove deprecated tox configuration. 2011-10-13 22:20:23 -04:00
Kenneth Reitz
244172bdd4 v0.6.4 2011-10-13 22:17:54 -04:00
Kenneth Reitz
f1f56c3081 Remove debian sources. 2011-10-13 22:17:24 -04:00
Kenneth Reitz
bfa010383e Merge pull request #202 from kennethreitz/feature/content-stream
Content Streaming
2011-10-13 19:16:26 -07:00
Kenneth Reitz
daf7aeb8c3 Async API 2011-10-13 22:15:55 -04:00
Kenneth Reitz
573ab095e4 fo => raw 2011-10-13 22:11:50 -04:00
Kenneth Reitz
030d877ed5 v0.6.4 2011-10-13 22:11:42 -04:00
Kenneth Reitz
33dec52b8e merge cleanups 2011-10-13 22:05:57 -04:00
Kenneth Reitz
aa31b6b4dd Merge fixes 2011-10-13 22:02:50 -04:00
Kenneth Reitz
46f0146adf Merge branch 'feature-content-streaming' of https://github.com/mitsuhiko/requests into feature/content-stream
Conflicts:
	AUTHORS
	HISTORY.rst
	docs/api.rst
	docs/community/faq.rst
	docs/user/intro.rst
	requests/api.py
	requests/models.py
2011-10-13 21:54:42 -04:00
Kenneth Reitz
b14fa0764b gevent installation documentation 2011-10-13 21:50:04 -04:00
Armin Ronacher
0d6773c68c Fail silently if encoding header is missing 2011-09-03 18:04:51 +02:00
Armin Ronacher
41876fd8c6 Implemented content streaming for responses. 2011-09-03 17:59:30 +02:00
verm666
6f84ce72f2 Fixed logic for auto redirect in GET and HEAD methods. 2011-09-02 15:44:18 -04:00
Kenneth Reitz
6d25fff3f9 Added Josselin Jacquard to AUTHORS 2011-08-30 23:58:31 -04:00
Kenneth Reitz
9bfe04586d Merge branch 'develop' of https://github.com/kigeia/requests into develop 2011-08-30 23:57:24 -04:00
Josselin Jacquard
1ecb593c7b Fix a bug when content-type is empty 2011-08-30 12:04:48 +02:00
Kenneth Reitz
fed17cb58a history 2011-08-28 03:23:54 -04:00
Kenneth Reitz
35fc3b4dbb cleanup 2011-08-28 03:22:46 -04:00
Kenneth Reitz
dba7a5a7e2 shortcut strings 2011-08-28 03:22:20 -04:00
Kenneth Reitz
2fa180e9ea automatic header expansion 2011-08-28 03:22:03 -04:00
Kenneth Reitz
3b9fc987b8 cleanup 2011-08-28 03:09:26 -04:00
Kenneth Reitz
e272ec13c3 tuples too 2011-08-28 03:07:45 -04:00
Kenneth Reitz
b97b4439c0 header expanding 2011-08-28 02:40:19 -04:00
Kenneth Reitz
1d0a5b2665 whitespace 2011-08-23 23:09:51 -04:00
Kenneth Reitz
636e61d68b utils.unicode_from_html 2011-08-23 23:09:09 -04:00
Kenneth Reitz
a94541d38c Only decode unicode from http headers
HTTP client, not HTML
#115
2011-08-23 23:03:18 -04:00
Kenneth Reitz
ce3b85a62e unsuccessful 2011-08-23 23:01:35 -04:00
Kenneth Reitz
281aa5ebc4 updated AUTHORS 2011-08-23 23:01:23 -04:00
jbergstroem
75027f9557 Typos, nits and some 80w fixes 2011-08-23 11:37:51 +02:00
jbergstroem
e8dbecb4db Add test_requests.py to manifest 2011-08-23 11:29:36 +02:00
Kenneth Reitz
317c5b693a _content 2011-08-21 07:12:54 -04:00
Den Shabalin
a86af4477e AUTHORS -= Den Shabalin (I've already been mentioned in the list @ line 41.) 2011-08-21 14:09:03 +03:00
Den Shabalin
59924b9223 Merge branch 'develop' of git://github.com/kennethreitz/requests into develop 2011-08-21 14:06:31 +03:00
Kenneth Reitz
c8110222a5 no need to set content
avoids 2.5 workaround
2011-08-21 07:01:29 -04:00
Kenneth Reitz
1143d4237f AUTHORS += 'Den Shabalin' 2011-08-21 06:59:13 -04:00
Kenneth Reitz
8d5108b142 faq 2011-08-21 06:58:34 -04:00
Den Shabalin
03e18bb7ca Merge branch 'master' of git://github.com/kennethreitz/requests into develop 2011-08-21 12:13:56 +03:00
Den Shabalin
6a96652396 Refactor response.content into a property. This makes content attribute discoverable through dir(response) and also guards from errors like forgetting to raise AttributeError in __getattr__. Also you don't have to create fake self.content to fool Sphinx any more. 2011-08-21 11:57:03 +03:00
Kenneth Reitz
d4bdd84364 new FAQs 2011-08-20 23:00:17 -04:00
Kenneth Reitz
b45b329861 history 2011-08-20 22:55:30 -04:00
Kenneth Reitz
28ee9788a0 Request 2011-08-20 22:51:29 -04:00
Kenneth Reitz
596aceb185 big docs update 2011-08-20 22:47:03 -04:00
Kenneth Reitz
1f3e53b32e yay 2011-08-20 20:05:38 -04:00
Kenneth Reitz
13db806861 docstringin' it up 2011-08-20 20:04:11 -04:00
Kenneth Reitz
d0fdf0eb38 no longer required 2011-08-20 19:59:41 -04:00
Kenneth Reitz
c5d787d6fd cleanups 2011-08-20 19:59:21 -04:00
Kenneth Reitz
873d128c5a docstrings for utils module 2011-08-20 19:58:00 -04:00
Kenneth Reitz
7f35ec7665 r.fo bugfix 2011-08-20 19:52:18 -04:00
Kenneth Reitz
86cc274cf0 remote read/close methods from response 2011-08-20 19:49:23 -04:00
Kenneth Reitz
c19b2c5172 settings.decode_unicode 2011-08-20 19:47:43 -04:00
Kenneth Reitz
4906686186 utils.decode_gzip 2011-08-20 19:46:50 -04:00
Kenneth Reitz
f038ec3b6e may have multiple encoding values 2011-08-20 19:44:24 -04:00
Kenneth Reitz
a609f726e6 settings.allow_unicode 2011-08-20 19:43:36 -04:00
Kenneth Reitz
7bd47777a9 no separate test suite 2011-08-20 19:40:33 -04:00
Kenneth Reitz
21cd47d223 Merge branch 'feature/unicode' into feature/fo
Conflicts:
	AUTHORS
2011-08-20 19:30:46 -04:00
Kenneth Reitz
882ea76aa9 cleanups, response.fo 2011-08-20 19:26:56 -04:00
Kenneth Reitz
850e1c7d40 Merge branch 'develop' of https://github.com/janrito/requests into develop 2011-08-20 19:23:53 -04:00
Kenneth Reitz
01411e37d6 AUTHORS += Alejandro Giacometti 2011-08-20 19:22:48 -04:00
Kenneth Reitz
4922daf498 added Rick Mak to AUTHORS 2011-08-20 19:22:18 -04:00
Kenneth Reitz
8fbb1e6d97 move encoding methods into utils for external consumption 2011-08-20 19:17:41 -04:00
Kenneth Reitz
7fc8c7ccfd move encoding detection out of request object. 2011-08-20 19:17:25 -04:00
Kenneth Reitz
7eb272b299 Merge https://github.com/rickmak/requests into feature/unicode
Conflicts:
	requests/models.py
2011-08-20 18:42:18 -04:00
Kenneth Reitz
8bfa909ff2 merge remnant (oops) 2011-08-20 18:34:33 -04:00
Alejandro Giacometti
5f16a8a6b1 Added readline method to response object. 2011-08-20 23:05:39 +02:00
Rick Mak
d26f3333e4 Fix tried_encodings scope 2011-08-18 14:42:35 +08:00
Rick Mak
820e2c73e6 Import re 2011-08-18 14:22:54 +08:00
Rick Mak
8c737069a5 Return the r.content as unicode. 2011-08-17 12:49:19 +08:00
46 changed files with 1412 additions and 923 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ pylint.txt
docs/_build
toy.py
.gitignore
junit-report.xml

10
AUTHORS
View File

@@ -40,3 +40,13 @@ Patches and Suggestions
- Mikko Ohtamaa
- Den Shabalin
- Daniel Miller <danielm@vs-networks.com>
- Alejandro Giacometti
- Rick Mak
- Johan Bergström
- Josselin Jacquard
- Travis N. Vaught
- Fredrik Möllerstrand
- Daniel Hengeveld
- Dan Head
- Bruno Renié
- David Fischer

View File

@@ -1,40 +0,0 @@
Requests is written and maintained by Kenneth Reitz and
various contributors:
Development Lead
````````````````
- Kenneth Reitz <me@kennethreitz.com>
Patches and Suggestions
```````````````````````
- Various Pocoo Members
- Chris Adams
- Flavio Percoco Premoli
- Dj Gilcrease
- Justin Murphy
- Rob Madole
- Aram Dulyan
- Johannes Gorset
- 村山めがね (Megane Murayama)
- James Rowe
- Daniel Schauenberg
- Zbigniew Siciarz
- Daniele Tricoli 'Eriol'
- Richard Boulton
- Miguel Olivares <miguel@moliware.com>
- Alberto Paro
- Jérémy Bethmont
- 潘旭 (Xu Pan)
- Tamás Gulácsi
- Rubén Abad
- Peter Manser
- Jeremy Selie
<<<<<<< HEAD
- Jens Diemer
- Alex <@alopatin>
=======
- Tom Hogans <tomhsx@gmail.com>
>>>>>>> 0ed641a26ec2200de00e4bbf3d170c767375351e

15
HACKING
View File

@@ -1,15 +0,0 @@
Where possible, please follow PEP8 with regard to coding style. Sometimes the
line length restriction is too hard to follow, so don't bend over backwards
there.
Triple-quotes should always be """, single quotes are ' unless using " would
result in less escaping within the string.
All modules, functions, and methods should be well documented reStructuredText
for Sphinx AutoDoc.
All functionality should be available in pure Python. Optional C (via Cython)
implementations may be written for performance reasons, but should never
replace the Python implementation.
Lastly, don't take yourself too seriously :)

View File

@@ -1,6 +1,74 @@
History
-------
0.7.6 (2011-11-07)
++++++++++++++++++
* Digest authentication bugfix (attach query data to path)
0.7.5 (2011-11-04)
++++++++++++++++++
* Response.content = None if there was an invalid repsonse.
* Redirection auth handling.
0.7.4 (2011-10-26)
++++++++++++++++++
* Sesion Hooks fix.
0.7.3 (2011-10-23)
++++++++++++++++++
* Digest Auth fix.
0.7.2 (2011-10-23)
++++++++++++++++++
* PATCH Fix.
0.7.1 (2011-10-23)
++++++++++++++++++
* Move away from urllib2 authentication handling.
* Fully Remove AuthManager, AuthObject, &c.
* New tuple-based auth system with handler callbacks.
0.7.0 (2011-10-22)
++++++++++++++++++
* Sessions are now the primary interface.
* Deprecated InvalidMethodException.
* PATCH fix.
* New config system (no more global settings).
0.6.6 (2011-10-19)
++++++++++++++++++
* Session parameter bugfix (params merging).
0.6.5 (2011-10-18)
++++++++++++++++++
* Offline (fast) test suite.
* Session dictionary argument merging.
0.6.4 (2011-10-13)
++++++++++++++++++
* Automatic decoding of unicode, based on HTTP Headers.
* New ``decode_unicode`` setting.
* Removal of ``r.read/close`` methods.
* New ``r.faw`` interface for advanced response usage.*
* Automatic expansion of parameterized headers.
0.6.3 (2011-10-13)
++++++++++++++++++
@@ -10,7 +78,7 @@ History
0.6.2 (2011-10-09)
++++++++++++++++++
* GET/HEAD obeys allow_redirects=False
* GET/HEAD obeys allow_redirects=False.
0.6.1 (2011-08-20)

View File

@@ -1 +1 @@
include README.rst LICENSE HISTORY.rst
include README.rst LICENSE HISTORY.rst test_requests.py

View File

@@ -2,10 +2,12 @@ init:
pip install -r reqs.txt
test:
nosetests test_requests.py --processes=30
nosetests test_requests.py
ci: init
nosetests --search-test --processes=30 --with-nosexunit test_requests.py
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
stats:
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
site:

View File

@@ -34,19 +34,15 @@ all the hard work and crazy hacks for you.
Features
--------
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests
+ Simple HTTP Header Request Attachment
+ Simple Data/Params Request Attachment
+ Simple Multipart File Uploads
+ CookieJar Support
+ Redirection History
+ Proxy Support
+ Redirection Recursion Urllib Fix
+ Auto Decompression of GZipped Content
+ Unicode URL Support
- Simple Authentication
+ Simple URL + HTTP Auth Registry
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests.
- Gevent support for Asyncronous Requests.
- Sessions with cookie persistience.
- Basic, Digest, and Custom Authentication support.
- Automatic form-encoding of dictionaries
- A simple dictionary interface for request/response cookies.
- Multipart file uploads.
- Automatc decoding of Unicode, gzip, and deflate responses.
- Full support for unicode URLs and domain names.
Usage
@@ -97,8 +93,10 @@ But, you really shouldn't do that.
Contribute
----------
If you'd like to contribute, simply fork `the repository`_, commit your changes to the **develop** branch (or branch off of it), and send a pull request. Make sure you add yourself to AUTHORS_.
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet.
#. Fork `the repository`_ on Github to start making your changes to the **develop** branch (or branch off of it).
#. Write a test which shows that the bug was fixed or that the feature works as expected.
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
.. _`the repository`: http://github.com/kennethreitz/requests
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS

5
debian/changelog vendored
View File

@@ -1,5 +0,0 @@
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
View File

@@ -1 +0,0 @@
5

13
debian/control vendored
View File

@@ -1,13 +0,0 @@
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
View File

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

1
debian/pyversions vendored
View File

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

42
debian/rules vendored
View File

@@ -1,42 +0,0 @@
#!/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

View File

@@ -2,18 +2,19 @@
{%- block extrahead %}
{{ super() }}
{% if theme_touch_icon %}
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
{% endif %}
<link media="only screen and (max-device-width: 480px)" href="{{
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9">
{% endblock %}
{%- block relbar2 %}{% endblock %}
{%- block footer %}
<div class="footer">
&copy; Copyright {{ copyright }}.
</div>
<a href="https://github.com/kennethreitz/requests">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" />
<a href="https://github.com/kennethreitz/requests" class="github">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" class="github"/>
</a>
<script type="text/javascript">

View File

@@ -393,6 +393,11 @@ a:hover tt {
display: none;
}
div.document {
width: 100%;
}
div.documentwrapper {
margin-left: 0;
margin-top: 0;
@@ -415,6 +420,10 @@ a:hover tt {
width: auto;
}
.footer {
width: auto;
}
.bodywrapper {
margin: 0;
}
@@ -423,7 +432,9 @@ a:hover tt {
width: auto;
}
.github {
display: none;
}
}

View File

@@ -68,3 +68,23 @@ div.body {
min-height: 0;
padding: 0;
}
.rtd_doc_footer {
display: none;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.footer {
width: auto;
}
.github {
display: none;
}

View File

@@ -31,6 +31,21 @@ They all return an instance of the :class:`Response <Response>` object.
.. autoclass:: Response
:inherited-members:
Async
-----
.. module:: requests.async
.. autofunction:: map
.. autofunction:: request
.. autofunction:: head
.. autofunction:: get
.. autofunction:: post
.. autofunction:: put
.. autofunction:: patch
.. autofunction:: delete
Utilities
@@ -48,9 +63,6 @@ Cookies
.. autofunction:: cookiejar_from_dict
.. autofunction:: add_dict_to_cookiejar
Curl
~~~~
.. autofunction:: curl_from_request
Encodings
~~~~~~~~~

View File

@@ -9,7 +9,7 @@ Encoded Data?
-------------
Requests automatically decompresses gzip-encoded responses, and does
its best to decodes response content to unicode when possible.
its best to decode response content to unicode when possible.
You can get direct access to the raw response (and even the socket),
if needed as well.

View File

@@ -0,0 +1,66 @@
Modules
=======
- `requests-oauth-hook <https://github.com/maraujop/requests-oauth-hook>`_, adds OAuth support to Requests.
- `FacePy <https://github.com/jgorset/facepy>`_, a Python wrapper to the Facebook API.
- `robotframework-requests <https://github.com/bulkan/robotframework-requests>`_, a Robot Framework API wrapper.
- `fullerene <https://github.com/bitprophet/fullerene>`_, a Graphite Dashboard.
- `urbanairship-python <https://github.com/benjaminws/urbanairship-python>`_, a fork of the Urban Airship API wrapper.
- `WhitespaceBot <https://github.com/Gunio/WhitespaceBot/>`_, a project that automatically forks repos, strips trailing whitespace, and sends a pull request.
- `python-rexster <https://github.com/CulturePlex/python-rexster>`_, Rexter client that provides a simple interface for graph databases.
- `daikon <https://github.com/neogenix/daikon>`_, a CLI for ElasticSearch.
Articles & Talks
================
- `Python for the Web <http://gun.io/blog/python-for-the-web/>`_ teaches how to use Python to interact with the web, using Requests.
- `Daniel Greenfield's Review of Requests <http://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <http://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
- `Issac Kelly's 'Consuming Web APIs' talk <http://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
- `French blog post introducing Requests <http://www.nicosphere.net/requests-urllib2-de-python-simplifie-2432/>`_
Integrations
============
ScraperWiki
------------
`ScraperWiki <https://scraperwiki.com/>`_ is an excellent service that allows
you to run Python, Ruby, and PHP scraper scripts on the web. Now, Requests
v0.6.1 is available to use in your scrapers!
To give it a try, simply::
import requests
Managed Packages
================
Requests is available in a number of popular package formats. Of course,
the ideal way to install Requests is via The Cheeseshop.
Ubuntu & Debian
---------------
Requests is available installed as a Debian package! Debian Etch Ubuntu, since Oneiric::
$ apt-get install python-requests
Unfortunately, the most recent version available is v0.5.0. If you're on the
Debian Python Package team, I'd love an update of that :)
Fedora and RedHat
-----------------
You can easily install Requests v0.6.1 with yum on rpm-based systems::
$ yum install python-requests

View File

@@ -3,7 +3,7 @@
Support
=======
If you have a questions or issues about Requests, there are serveral options:
If you have a questions or issues about Requests, there are several options:
Send a Tweet
------------

View File

@@ -4,7 +4,7 @@ Updates
=======
If you'd like to stay up to date on the community and development of Requests,
there are serveral options:
there are several options:
GitHub
------

View File

@@ -39,8 +39,9 @@ all the hard work and crazy hacks for you.
Testimonials
------------
`Twitter, Inc <http://twitter.com>`_,
`The Washington Post <http://www.washingtonpost.com/>`_, `Twitter, Inc <http://twitter.com>`_,
a U.S. Federal Institution,
NIH,
`Readability <http://readability.com>`_, and
`Work for Pie <http://workforpie.com>`_
use Requests internally.
@@ -87,6 +88,7 @@ Requests ecosystem and community.
:maxdepth: 2
community/faq
community/out-there.rst
community/support
community/updates

View File

@@ -17,13 +17,13 @@ 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 = requests.session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")
print r.content
# '{"cookies": {"sessioncookie": "123456789"}}'
print r.content
# '{"cookies": {"sessioncookie": "123456789"}}'
Sessions can also be used to provide default data to the request methods::
@@ -39,8 +39,8 @@ Sessions can also be used to provide default data to the request methods::
.. admonition:: Global Settings
Certain parameters are best set at the ``request.config`` level
(e.g.. a global proxy, user agent header).
Certain parameters are best set in the ``config`` dictionary
(e.g. user agent header).
Asynchronous Requests
@@ -50,7 +50,7 @@ Requests has first-class support for non-blocking i/o requests, powered
by gevent. This allows you to send a bunch of HTTP requests at the same
First, let's import the async module. Heads up — if you don't have
**gevent** installed, this will fail.::
`gevent <gevent>`_ this will fail::
from requests import async
@@ -69,9 +69,10 @@ We can build a list of ``Request`` objects easily::
rs = [async.get(u) for u in urls]
Now we have a list of ``Request`` objects, ready to be sent. We could
send them one at a time with ``Request.send()``, but that would take a while.
Instead, we'll send them all at the same time with ``async.map()``::
Now we have a list of ``Request`` objects, ready to be sent. We could send them
one at a time with ``Request.send()``, but that would take a while. Instead,
we'll send them all at the same time with ``async.map()``. Using ``async.map()``
will also guarantee execution of the ``response`` hook, described below. ::
>>> async.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
@@ -156,6 +157,39 @@ And give it a try::
}
Custom Authentication
---------------------
Requests allows you to use specify your own authentication mechanism.
When you pass our authentication tuple to a request method, the first
string is the type of authentication. 'basic' is inferred if none is
provided.
You can pass in a callable object instead of a string for the first item
in the tuple, and it will be used in place of the built in authentication
callbacks.
Let's pretend that we have a web service that will only respond if the
``X-Pizza`` header is set to a password value. Unlikely, but just go with it.
We simply need to define a callback function that will be used to update the
Request object, right before it is dispatched.
::
def pizza_auth(r, username):
"""Attaches HTTP Pizza Authentication to the given Request object.
"""
r.headers['X-Pizza'] = username
return r
Then, we can make a request using our Pizza Auth::
>>> requests.get('http://pizzabin.org/admin', auth=(pizza_auth, 'kenneth'))
<Response [200]>
Verbose Logging
---------------

View File

@@ -18,7 +18,7 @@ or, with `easy_install <http://pypi.python.org/pypi/setuptools>`_::
$ easy_install requests
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/index.html#pip-compared-to-easy-install>`_.
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/other-tools.html#pip-compared-to-easy-install>`_.
@@ -54,3 +54,25 @@ Once you have a copy of the source, you can embed it in your Python package,
or install it into your site-packages easily::
$ python setup.py install
.. _gevent:
Installing Gevent
-----------------
If you are using the ``requests.async`` module for making concurrent
requests, you need to install gevent.
To install gevent, you'll need ``libevent``.
OSX::
$ brew install libevent
Ubuntu::
$ apt-get install libevent-dev
Once you have ``libevent``, you can install ``gevent`` with ``pip``::
$ pip install gevent

View File

@@ -47,5 +47,3 @@ 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.

View File

@@ -136,3 +136,32 @@ parameter::
>>> r = requests.get(url, cookies=cookies)
>>> r.content
'{"cookies": {"cookies_are": "working"}}'
Basic Authentication
--------------------
Most web services require authentication. There many different types of
authentication, but the most common is called HTTP Basic Auth.
Making requests with Basic Auth is easy, with Requests::
>>> requests.get('https://api.github.com/user', auth=('user', 'pass'))
<Response [200]>
Digest Authentication
---------------------
Another popular form of protecting web service is Digest Authentication.
Requests supports it!::
>>> url = 'http://httpbin.org/digest-auth/auth/user/pass'
>>> requests.get(url, auth=('digest', 'user', 'pass'))
<Response [200]>
-----------------------
Ready for more? Check out the :ref:`advanced <advanced>` section.

7
reqs.txt Normal file
View File

@@ -0,0 +1,7 @@
envoy==0.0.2
httpbin==0.0.4
gunicorn
nose
pyflakes
omnijson
rudolf

View File

@@ -1,4 +1,33 @@
# -*- coding: utf-8 -*-
from core import *
from core import __version__
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
# /
"""
requests
~~~~~~~~
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
__title__ = 'requests'
__version__ = '0.7.6'
__build__ = 0x000706
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
from . import utils
from .models import Request, Response
from .api import request, get, head, post, patch, put, delete
from .sessions import session
from .status_codes import codes
from .exceptions import (
RequestException, AuthenticationError, Timeout, URLRequired,
TooManyRedirects
)

View File

@@ -4,28 +4,33 @@
requests.api
~~~~~~~~~~~~
This module impliments the Requests API.
This module implements the Requests API.
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
import config
from .models import Request, Response, AuthObject
from .status_codes import codes
from .hooks import dispatch_hook
from .utils import cookiejar_from_dict
from .sessions import session
__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete')
def request(method, url,
params=None, data=None, headers=None, cookies=None, files=None, auth=None,
timeout=None, allow_redirects=False, proxies=None, hooks=None, return_response=True):
"""Constructs and sends a :class:`Request <models.Request>`.
Returns :class:`Response <models.Response>` object.
def request(method, url,
params=None,
data=None,
headers=None,
cookies=None,
files=None,
auth=None,
timeout=None,
allow_redirects=False,
proxies=None,
hooks=None,
return_response=True,
config=None):
"""Constructs and sends a :class:`Request <Request>`.
Returns :class:`Response <Response>` object.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
@@ -34,68 +39,28 @@ def request(method, url,
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param auth: (optional) Auth typle to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param return_response: (optional) If False, an un-sent Request object will returned.
:param config: (optional) A configuration dictionary.
"""
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,
hooks = hooks,
timeout = timeout or config.settings.timeout,
allow_redirects = allow_redirects,
proxies = proxies or config.settings.proxies,
s = session()
return s.request(
method, url, params, data, headers, cookies, files, auth,
timeout, allow_redirects, proxies, hooks, return_response,
config
)
# Arguments manipulation hook.
args = dispatch_hook('args', hooks, args)
r = Request(**args)
# Pre-request hook.
r = dispatch_hook('pre_request', hooks, r)
# Don't send if asked nicely.
if not return_response:
return r
# Send the HTTP Request.
r.send()
# Post-request hook.
r = dispatch_hook('post_request', hooks, r)
# Response manipulation hook.
r.response = dispatch_hook('response', hooks, r.response)
return r.response
def get(url, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param 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) 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 False to disable redirect following.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param **kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
@@ -103,17 +68,10 @@ def get(url, **kwargs):
def head(url, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param 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) 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 False to disable redirect following.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param **kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
@@ -121,22 +79,14 @@ def head(url, **kwargs):
def post(url, data='', **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param 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.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return request('POST', url, data=data, **kwargs)
return request('post', url, data=data, **kwargs)
def put(url, data='', **kwargs):
@@ -144,17 +94,10 @@ def put(url, data='', **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param 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.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return request('PUT', url, data=data, **kwargs)
return request('put', url, data=data, **kwargs)
def patch(url, data='', **kwargs):
@@ -162,31 +105,17 @@ def patch(url, data='', **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param 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.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return request('PATCH', url, **kwargs)
return request('patch', url, data=data, **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 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) 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.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return request('DELETE', url, **kwargs)
return request('delete', url, **kwargs)

View File

@@ -28,16 +28,19 @@ __all__ = (
)
def _patched(f):
def patched(f):
"""Patches a given API function to not send."""
def wrapped(*args, **kwargs):
return f(*args, return_response=False, **kwargs)
kwargs['return_response'] = False
return f(*args, **kwargs)
return wrapped
def _send(r, pools=None):
def send(r, pools=None):
"""Sends a given Request object."""
if pools:
@@ -45,23 +48,17 @@ def _send(r, pools=None):
r.send()
# Post-request hook.
r = dispatch_hook('post_request', r.hooks, r)
# Response manipulation hook.
r.response = dispatch_hook('response', r.hooks, r.response)
return r.response
# Patched requests.api functions.
get = _patched(api.get)
head = _patched(api.head)
post = _patched(api.post)
put = _patched(api.put)
patch = _patched(api.patch)
delete = _patched(api.delete)
request = _patched(api.request)
get = patched(api.get)
head = patched(api.head)
post = patched(api.post)
put = patched(api.put)
patch = patched(api.patch)
delete = patched(api.delete)
request = patched(api.request)
def map(requests, prefetch=True):
@@ -71,7 +68,7 @@ def map(requests, prefetch=True):
:param prefetch: If False, the content will not be downloaded immediately.
"""
jobs = [gevent.spawn(_send, r) for r in requests]
jobs = [gevent.spawn(send, r) for r in requests]
gevent.joinall(jobs)
if prefetch:

146
requests/auth.py Normal file
View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
"""
requests.auth
~~~~~~~~~~~~~
This module contains the authentication handlers for Requests.
"""
import time
import hashlib
from base64 import b64encode
from urlparse import urlparse
from .utils import randombytes, parse_dict_header
def http_basic(r, username, password):
"""Attaches HTTP Basic Authentication to the given Request object.
Arguments should be considered non-positional.
"""
username = str(username)
password = str(password)
auth_s = b64encode('%s:%s' % (username, password))
r.headers['Authorization'] = ('Basic %s' % auth_s)
return r
def http_digest(r, username, password):
"""Attaches HTTP Digest Authentication to the given Request object.
Arguments should be considered non-positional.
"""
def handle_401(r):
"""Takes the given response and tries digest-auth, if needed."""
s_auth = r.headers.get('www-authenticate', '')
if 'digest' in s_auth.lower():
last_nonce = ''
nonce_count = 0
chal = parse_dict_header(s_auth.replace('Digest ', ''))
realm = chal['realm']
nonce = chal['nonce']
qop = chal.get('qop')
algorithm = chal.get('algorithm', 'MD5')
opaque = chal.get('opaque', None)
algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level
if algorithm == 'MD5':
H = lambda x: hashlib.md5(x).hexdigest()
elif algorithm == 'SHA':
H = lambda x: hashlib.sha1(x).hexdigest()
# XXX MD5-sess
KD = lambda s, d: H("%s:%s" % (s, d))
if H is None:
return None
# XXX not implemented yet
entdig = None
p_parsed = urlparse(r.request.url)
path = p_parsed.path + p_parsed.query
A1 = "%s:%s:%s" % (username, realm, password)
A2 = "%s:%s" % (r.request.method, path)
if qop == 'auth':
if nonce == last_nonce:
nonce_count += 1
else:
nonce_count = 1
last_nonce = nonce
ncvalue = '%08x' % nonce_count
cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
nonce_count, nonce, time.ctime(), randombytes(8)))
.hexdigest()[:16]
)
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
respdig = KD(H(A1), noncebit)
elif qop is None:
respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
else:
# XXX handle auth-int.
return None
# XXX should the partial digests be encoded too?
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
'response="%s"' % (username, realm, nonce, path, respdig)
if opaque:
base += ', opaque="%s"' % opaque
if entdig:
base += ', digest="%s"' % entdig
base += ', algorithm="%s"' % algorithm
if qop:
base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
r.request.headers['Authorization'] = 'Digest %s' % (base)
r.request.send(anyway=True)
_r = r.request.response
_r.history.append(r)
return _r
return r
r.hooks['response'] = handle_401
return r
def dispatch(t):
"""Given an auth tuple, return an expanded version."""
if not t:
return t
else:
t = list(t)
# Make sure they're passing in something.
assert len(t) >= 2
# If only two items are passed in, assume HTTPBasic.
if (len(t) == 2):
t.insert(0, 'basic')
# Allow built-in string referenced auths.
if isinstance(t[0], basestring):
if t[0] in ('basic', 'forced_basic'):
t[0] = http_basic
elif t[0] in ('digest',):
t[0] = http_digest
# Return a custom callable.
return (t[0], tuple(t[1:]))

View File

@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.config
~~~~~~~~~~~~~~~
This module provides the Requests settings feature set.
"""
class Settings(object):
_singleton = {}
# attributes with defaults
__attrs__ = []
def __init__(self, **kwargs):
super(Settings, self).__init__()
self.__dict__ = self._singleton
def __call__(self, *args, **kwargs):
# new instance of class to call
r = self.__class__()
# cache previous settings for __exit__
r.__cache = self.__dict__.copy()
map(self.__cache.setdefault, self.__attrs__)
# set new settings
self.__dict__.update(*args, **kwargs)
return r
def __enter__(self):
pass
def __exit__(self, *args):
# restore cached copy
self.__dict__.update(self.__cache.copy())
del self.__cache
def __getattribute__(self, key):
if key in object.__getattribute__(self, '__attrs__'):
try:
return object.__getattribute__(self, key)
except AttributeError:
return None
return object.__getattribute__(self, key)
settings = Settings()
settings.base_headers = {'User-Agent': 'python-requests.org'}
settings.accept_gzip = True
settings.proxies = None
settings.verbose = None
settings.timeout = None
settings.max_redirects = 30
#: Use socket.setdefaulttimeout() as fallback?
settings.timeout_fallback = True

View File

@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.core
~~~~~~~~~~~~~
This module implements the main Requests system.
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
__title__ = 'requests'
__version__ = '0.6.3'
__build__ = 0x000603
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
from models import HTTPError
from api import *
from exceptions import *
from sessions import session
from status_codes import codes
from config import settings

39
requests/defaults.py Normal file
View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
"""
requests.defaults
~~~~~~~~~~~~~~~~~
This module provides the Requests configuration defaults.
settings parameters:
- :base_headers: - Sets default User-Agent to `python-requests.org`
- :accept_gzip: - Whether or not to accept gzip-compressed data
- :proxies: - http proxies?
- :verbose: - display verbose information?
- :timeout: - timeout time until request terminates
- :max_redirects: - maximum number of allowed redirects?
- :decode_unicode: - whether or not to accept unicode?
"""
from . import __version__
defaults = dict()
defaults['base_headers'] = {
'User-Agent': 'python-requests/%s' % __version__,
'Accept-Encoding': ', '.join(('identity', 'deflate', 'compress', 'gzip')),
'Accept': '*/*'
}
defaults['proxies'] = {}
defaults['verbose'] = None
defaults['timeout'] = None
defaults['max_redirects'] = 30
defaults['decode_unicode'] = True
defaults['timeout_fallback'] = True
# defaults['keep_alive'] = True
# defaults['max_connections'] = 10

View File

@@ -2,25 +2,24 @@
"""
requests.exceptions
~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~
This module contains the set of Requests' exceptions.
"""
class RequestException(Exception):
"""There was an ambiguous exception that occured while handling your
"""There was an ambiguous exception that occurred while handling your
request."""
class AuthenticationError(RequestException):
"""The authentication credentials provided were invalid."""
class Timeout(RequestException):
"""The request timed out."""
class URLRequired(RequestException):
"""A valid URL is required to make a request."""
class InvalidMethod(RequestException):
"""An inappropriate method was attempted."""
class TooManyRedirects(RequestException):
"""Too many redirects."""

View File

@@ -26,7 +26,7 @@ import warnings
def dispatch_hook(key, hooks, hook_data):
"""Dipatches a hook dictionary on a given peice of data."""
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or dict()

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
@@ -11,18 +12,21 @@ import urllib2
import socket
import zlib
from urllib2 import HTTPError
from urlparse import urlparse, urlunparse, urljoin
from datetime import datetime
from .config import settings
from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler
from .auth import dispatch as auth_dispatch
from .hooks import dispatch_hook
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, TooManyRedirects
from .status_codes import codes
from .exceptions import Timeout, URLRequired, TooManyRedirects
from .monkeys import Request as _Request
from .monkeys import HTTPRedirectHandler
from .utils import (
dict_from_cookiejar, get_unicode_from_response,
stream_decode_response_unicode, decode_gzip, stream_decode_gzip)
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
@@ -30,40 +34,51 @@ REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
class Request(object):
"""The :class:`Request <models.Request>` object. It carries out all functionality of
"""The :class:`Request <Request>` object. It carries out all functionality of
Requests. Recommended interface is with the Requests functions.
"""
def __init__(self,
url=None, headers=dict(), files=None, method=None, data=dict(),
params=dict(), auth=None, cookiejar=None, timeout=None, redirect=False,
allow_redirects=False, proxies=None, hooks=None):
url=None,
headers=dict(),
files=None,
method=None,
data=dict(),
params=dict(),
auth=None,
cookies=None,
timeout=None,
redirect=False,
allow_redirects=False,
proxies=None,
hooks=None,
config=None):
#: Float describ the timeout of the request.
#: Float describes 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>`.
#: Dictionary of HTTP Headers to attach to the :class:`Request <Request>`.
self.headers = headers
#: Dictionary of files to multipart upload (``{filename: content}``).
self.files = files
#: HTTP Method to use. Available: GET, HEAD, PUT, POST, DELETE.
#: HTTP Method to use.
self.method = method
#: Dictionary or byte of request body data to attach to the
#: :class:`Request <models.Request>`.
#: :class:`Request <Request>`.
self.data = None
#: Dictionary or byte of querystring data to attach to the
#: :class:`Request <models.Request>`.
#: :class:`Request <Request>`.
self.params = None
#: True if :class:`Request <models.Request>` is part of a redirect chain (disables history
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
#: and HTTPError storage).
self.redirect = redirect
@@ -76,20 +91,19 @@ class Request(object):
self.data, self._enc_data = self._encode_params(data)
self.params, self._enc_params = self._encode_params(params)
#: :class:`Response <models.Response>` instance, containing
#: :class:`Response <Response>` instance, containing
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
self.response = Response()
if isinstance(auth, (list, tuple)):
auth = AuthObject(*auth)
if not auth:
auth = auth_manager.get_auth(self.url)
#: Authentication tuple to attach to :class:`Request <Request>`.
self._auth = auth
self.auth = auth_dispatch(auth)
#: :class:`AuthObject` to attach to :class:`Request <models.Request>`.
self.auth = auth
#: CookieJar to attach to :class:`Request <Request>`.
self.cookies = cookies
#: CookieJar to attach to :class:`Request <models.Request>`.
self.cookiejar = cookiejar
#: Dictionary of configurations for this request.
self.config = config
#: True if Request has been sent.
self.sent = False
@@ -97,50 +111,33 @@ class Request(object):
#: Event-handling hooks.
self.hooks = hooks
# Header manipulation and defaults.
if settings.accept_gzip:
settings.base_headers.update({'Accept-Encoding': 'gzip'})
if headers:
headers = CaseInsensitiveDict(self.headers)
else:
headers = CaseInsensitiveDict()
for (k, v) in settings.base_headers.items():
for (k, v) in self.config.get('base_headers', {}).items():
if k not in headers:
headers[k] = v
self.headers = headers
# Pre-request hook.
r = dispatch_hook('pre_request', hooks, self)
self.__dict__.update(r.__dict__)
def __repr__(self):
return '<Request [%s]>' % (self.method)
def _checks(self):
"""Deterministic checks for consistency."""
if not self.url:
raise URLRequired
def _get_opener(self):
"""Creates appropriate opener object for urllib2."""
_handlers = []
if self.cookiejar is not None:
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
if self.auth:
if not isinstance(self.auth.handler, (urllib2.AbstractBasicAuthHandler, urllib2.AbstractDigestAuthHandler)):
# TODO: REMOVE THIS COMPLETELY
auth_manager.add_password(self.auth.realm, self.url, self.auth.username, self.auth.password)
self.auth.handler = self.auth.handler(auth_manager)
auth_manager.add_auth(self.url, self.auth)
_handlers.append(self.auth.handler)
if self.cookies is not None:
_handlers.append(urllib2.HTTPCookieProcessor(self.cookies))
if self.proxies:
_handlers.append(urllib2.ProxyHandler(self.proxies))
@@ -168,22 +165,23 @@ class Request(object):
def _build_response(self, resp, is_error=False):
"""Build internal :class:`Response <models.Response>` object from given response."""
"""Build internal :class:`Response <Response>` object
from given response.
"""
def build(resp):
response = Response()
response.config = self.config
response.status_code = getattr(resp, 'code', None)
try:
response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None))
response.read = resp.read
response._resp = resp
response._close = resp.close
response.raw = resp
if self.cookiejar:
response.cookies = dict_from_cookiejar(self.cookiejar)
if self.cookies:
response.cookies = dict_from_cookiejar(self.cookies)
except AttributeError:
@@ -208,9 +206,9 @@ class Request(object):
((r.status_code is codes.see_other) or (self.allow_redirects))
):
r.close()
r.raw.close()
if not len(history) < settings.max_redirects:
if not len(history) < self.config.get('max_redirects'):
raise TooManyRedirects()
history.append(r)
@@ -234,9 +232,16 @@ class Request(object):
method = self.method
request = Request(
url, self.headers, self.files, method,
self.data, self.params, self.auth, self.cookiejar,
redirect=True
url=url,
headers=self.headers,
files=self.files,
method=method,
# data=self.data,
# params=self.params,
auth=self._auth,
cookies=self.cookies,
redirect=True,
config=self.config
)
request.send()
r = request.response
@@ -257,8 +262,8 @@ class Request(object):
Otherwise, assumes the data is already encoded appropriately, and
returns it twice.
"""
if hasattr(data, 'items'):
result = []
for k, vs in data.items():
@@ -301,33 +306,44 @@ class Request(object):
already been sent.
"""
self._checks()
success = False
# Some people...
if not self.url:
raise URLRequired
# Logging
if settings.verbose:
settings.verbose.write('%s %s %s\n' % (
if self.config.get('verbose'):
self.config.get('verbose').write('%s %s %s\n' % (
datetime.now().isoformat(), self.method, self.url
))
# Build the URL
url = self._build_url()
if self.method in ('GET', 'HEAD', 'DELETE'):
req = _Request(url, method=self.method)
# Attach uploaded files.
if self.files:
register_openers()
# Add form-data to the multipart.
if self.data:
self.files.update(self.data)
data, headers = multipart_encode(self.files)
else:
data = self._enc_data
headers = {}
if self.files:
register_openers()
if self.auth:
auth_func, auth_args = self.auth
if self.data:
self.files.update(self.data)
r = auth_func(self, *auth_args)
datagen, headers = multipart_encode(self.files)
req = _Request(url, data=datagen, headers=headers, method=self.method)
self.__dict__.update(r.__dict__)
else:
req = _Request(url, data=self._enc_data, method=self.method)
# Build the Urllib2 Request.
req = _Request(url, data=data, headers=headers, method=self.method)
# Add the headers to the request.
if self.headers:
for k,v in self.headers.iteritems():
req.add_header(k, v)
@@ -345,19 +361,19 @@ class Request(object):
if not 'timeout' in str(err):
raise
if settings.timeout_fallback:
if self.config.get('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
if self.config.get('timeout_fallback'):
# restore global timeout
socket.setdefaulttimeout(old_timeout)
if self.cookiejar is not None:
self.cookiejar.extract_cookies(resp, req)
if self.cookies is not None:
self.cookies.extract_cookies(resp, req)
except (urllib2.HTTPError, urllib2.URLError), why:
if hasattr(why, 'reason'):
@@ -368,7 +384,6 @@ class Request(object):
self._build_response(why, is_error=True)
else:
self._build_response(resp)
self.response.ok = True
@@ -376,43 +391,62 @@ class Request(object):
self.sent = self.response.ok
# Response manipulation hook.
self.response = dispatch_hook('response', self.hooks, self.response)
# Post-request hook.
r = dispatch_hook('post_request', self.hooks, self)
self.__dict__.update(r.__dict__)
return self.sent
class Response(object):
"""The core :class:`Response <models.Response>` object. All
:class:`Request <models.Request>` objects contain a
:class:`response <models.Response>` attribute, which is an instance
"""The core :class:`Response <Response>` object. All
:class:`Request <Request>` objects contain a
:class:`response <Response>` attribute, which is an instance
of this class.
"""
def __init__(self):
#: 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_consumed = False
#: Integer Code of responded HTTP Status.
self.status_code = None
#: Case-insensitive Dictionary of Response Headers.
#: For example, ``headers['content-encoding']`` will return the
#: value of a ``'Content-Encoding'`` response header.
self.headers = CaseInsensitiveDict()
#: File-like object representation of response (for advanced usage).
self.raw = None
#: Final URL location of Response.
self.url = None
#: True if no :attr:`error` occured.
#: True if no :attr:`error` occurred.
self.ok = False
#: Resulting :class:`HTTPError` of request, if one occured.
#: Resulting :class:`HTTPError` of request, if one occurred.
self.error = None
#: A list of :class:`Response <models.Response>` objects from
#: A list of :class:`Response <Response>` objects from
#: the history of the Request. Any redirect responses will end
#: up here.
self.history = []
#: The Request that created the Response.
#: The :class:`Request <Request>` that created the Response.
self.request = None
#: A dictionary of Cookies the server sent back.
self.cookies = None
#: Dictionary of configurations for this request.
self.config = None
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
@@ -420,206 +454,72 @@ class Response(object):
def __nonzero__(self):
"""Returns true if :attr:`status_code` is 'OK'."""
return not self.error
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
"""Iterates over the response data. This avoids reading the content
at once into memory for large responses. The chunk size is the number
of bytes it should read into memory. This is not necessarily the
length of each item returned as decoding can take place.
"""
if self._content_consumed:
raise RuntimeError('The content for this response was '
'already consumed')
def __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
def generate():
while 1:
chunk = self.raw.read(chunk_size)
if not chunk:
break
yield chunk
self._content_consumed = True
gen = generate()
if 'gzip' in self.headers.get('content-encoding', ''):
gen = stream_decode_gzip(gen)
if decode_unicode is None:
decode_unicode = self.config.get('decode_unicode')
if decode_unicode:
gen = stream_decode_response_unicode(gen, self)
return gen
@property
def content(self):
"""Content of the response, in bytes or unicode
(if available).
"""
if self._content is not None:
return self._content
else:
raise AttributeError
if self._content_consumed:
raise RuntimeError('The content for this response was '
'already consumed')
# Read the contents.
try:
self._content = self.raw.read()
except AttributeError:
return None
# Decode GZip'd content.
if 'gzip' in self.headers.get('content-encoding', ''):
try:
self._content = decode_gzip(self._content)
except zlib.error:
pass
# Decode unicode content.
if self.config.get('decode_unicode'):
self._content = get_unicode_from_response(self)
self._content_consumed = True
return self._content
def raise_for_status(self):
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occured."""
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""
if self.error:
raise self.error
def close(self):
if self._resp.fp is not None and hasattr(self._resp.fp, '_sock'):
self._resp.fp._sock.recv = None
self._close()
class AuthManager(object):
"""Requests Authentication Manager."""
def __new__(cls):
singleton = cls.__dict__.get('__singleton__')
if singleton is not None:
return singleton
cls.__singleton__ = singleton = object.__new__(cls)
return singleton
def __init__(self):
self.passwd = {}
self._auth = {}
def __repr__(self):
return '<AuthManager [%s]>' % (self.method)
def add_auth(self, uri, auth):
"""Registers AuthObject to AuthManager."""
uri = self.reduce_uri(uri, False)
# try to make it an AuthObject
if not isinstance(auth, AuthObject):
try:
auth = AuthObject(*auth)
except TypeError:
pass
self._auth[uri] = auth
def add_password(self, realm, uri, user, passwd):
"""Adds password to AuthManager."""
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
reduced_uri = tuple([self.reduce_uri(u, False) for u in uri])
if reduced_uri not in self.passwd:
self.passwd[reduced_uri] = {}
self.passwd[reduced_uri] = (user, passwd)
def find_user_password(self, realm, authuri):
for uris, authinfo in self.passwd.iteritems():
reduced_authuri = self.reduce_uri(authuri, False)
for uri in uris:
if self.is_suburi(uri, reduced_authuri):
return authinfo
return (None, None)
def get_auth(self, uri):
(in_domain, in_path) = self.reduce_uri(uri, False)
for domain, path, authority in (
(i[0][0], i[0][1], i[1]) for i in self._auth.iteritems()
):
if in_domain == domain:
if path in in_path:
return authority
def reduce_uri(self, uri, default_port=True):
"""Accept authority or URI and extract only the authority and path."""
# note HTTP URLs do not have a userinfo component
parts = urllib2.urlparse.urlsplit(uri)
if parts[1]:
# URI
scheme = parts[0]
authority = parts[1]
path = parts[2] or '/'
else:
# host or host:port
scheme = None
authority = uri
path = '/'
host, port = urllib2.splitport(authority)
if default_port and port is None and scheme is not None:
dport = {"http": 80,
"https": 443,
}.get(scheme)
if dport is not None:
authority = "%s:%d" % (host, dport)
return authority, path
def is_suburi(self, base, test):
"""Check if test is below base in a URI tree
Both args must be URIs in reduced form.
"""
if base == test:
return True
if base[0] != test[0]:
return False
common = urllib2.posixpath.commonprefix((base[1], test[1]))
if len(common) == len(base[1]):
return True
return False
def empty(self):
self.passwd = {}
def remove(self, uri, realm=None):
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
for default_port in True, False:
reduced_uri = tuple([self.reduce_uri(u, default_port) for u in uri])
del self.passwd[reduced_uri][realm]
def __contains__(self, uri):
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
uri = tuple([self.reduce_uri(u, False) for u in uri])
if uri in self.passwd:
return True
return False
auth_manager = AuthManager()
class AuthObject(object):
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
given to a Requests function, it enables Basic HTTP Authentication for that
Request. You can also enable Authorization for domain realms with AutoAuth.
See AutoAuth for more details.
:param username: Username to authenticate with.
:param password: Password for given username.
:param realm: (optional) the realm this auth applies to
:param handler: (optional) basic || digest || proxy_basic || proxy_digest
"""
_handlers = {
'basic': HTTPBasicAuthHandler,
'forced_basic': HTTPForcedBasicAuthHandler,
'digest': HTTPDigestAuthHandler,
'proxy_basic': urllib2.ProxyBasicAuthHandler,
'proxy_digest': urllib2.ProxyDigestAuthHandler
}
def __init__(self, username, password, handler='forced_basic', realm=None):
self.username = username
self.password = password
self.realm = realm
if isinstance(handler, basestring):
self.handler = self._handlers.get(handler.lower(), HTTPForcedBasicAuthHandler)
else:
self.handler = handler

View File

@@ -29,120 +29,11 @@ class Request(urllib2.Request):
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
"""HTTP Redirect handler."""
def http_error_301(self, req, fp, code, msg, headers):
def _pass(self, req, fp, code, msg, headers):
pass
http_error_302 = http_error_303 = http_error_307 = http_error_301
http_error_302 = _pass
http_error_303 = _pass
http_error_307 = _pass
http_error_301 = _pass
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
"""HTTP Basic Auth Handler with authentication loop fixes."""
def __init__(self, *args, **kwargs):
urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
self.retried_req = None
self.retried = 0
def reset_retry_count(self):
# Python 2.6.5 will call this on 401 or 407 errors and thus loop
# forever. We disable reset_retry_count completely and reset in
# http_error_auth_reqed instead.
pass
def http_error_auth_reqed(self, auth_header, host, req, headers):
# Reset the retry counter once for each request.
if req is not self.retried_req:
self.retried_req = req
self.retried = 0
return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers
)
class HTTPForcedBasicAuthHandler(HTTPBasicAuthHandler):
"""HTTP Basic Auth Handler with forced Authentication."""
auth_header = 'Authorization'
rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+'
'realm=(["\'])(.*?)\\2', re.I)
def __init__(self, *args, **kwargs):
HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
def http_error_401(self, req, fp, code, msg, headers):
url = req.get_full_url()
response = self._http_error_auth_reqed('www-authenticate', url, req, headers)
self.reset_retry_count()
return response
http_error_404 = http_error_401
def _http_error_auth_reqed(self, authreq, host, req, headers):
authreq = headers.get(authreq, None)
if self.retried > 5:
# retry sending the username:password 5 times before failing.
raise urllib2.HTTPError(req.get_full_url(), 401, "basic auth failed",
headers, None)
else:
self.retried += 1
if authreq:
mo = self.rx.search(authreq)
if mo:
scheme, quote, realm = mo.groups()
if scheme.lower() == 'basic':
response = self.retry_http_basic_auth(host, req, realm)
if response and response.code not in (401, 404):
self.retried = 0
return response
else:
response = self.retry_http_basic_auth(host, req, 'Realm')
if response and response.code not in (401, 404):
self.retried = 0
return response
class HTTPDigestAuthHandler(urllib2.HTTPDigestAuthHandler):
def __init__(self, *args, **kwargs):
urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
self.retried_req = None
def reset_retry_count(self):
# Python 2.6.5 will call this on 401 or 407 errors and thus loop
# forever. We disable reset_retry_count completely and reset in
# http_error_auth_reqed instead.
pass
def http_error_auth_reqed(self, auth_header, host, req, headers):
# Reset the retry counter once for each request.
if req is not self.retried_req:
self.retried_req = req
self.retried = 0
# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
# it doesn't know about the auth type requested. This can happen if
# somebody is using BasicAuth and types a bad password.
try:
return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers)
except ValueError, inst:
arg = inst.args[0]
if arg.startswith("AbstractDigestAuthHandler doesn't know "):
return
raise

View File

@@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.monkeys
"""

View File

@@ -2,7 +2,7 @@
"""
requests.session
~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~
This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
@@ -11,30 +11,78 @@ requests (cookies, auth, proxies).
import cookielib
from . import api
from .utils import add_dict_to_cookiejar
from .defaults import defaults
from .models import Request
from .hooks import dispatch_hook
from .utils import add_dict_to_cookiejar, cookiejar_from_dict, header_expand
def merge_kwargs(local_kwarg, default_kwarg):
"""Merges kwarg dictionaries.
If a local key in the dictionary is set to None, it will be removed.
"""
if default_kwarg is None:
return local_kwarg
if isinstance(local_kwarg, basestring):
return local_kwarg
if local_kwarg is None:
return default_kwarg
# Bypass if not a dictionary (e.g. timeout)
if not hasattr(default_kwarg, 'items'):
return local_kwarg
# Update new values.
kwargs = default_kwarg.copy()
kwargs.update(local_kwarg)
# Remove keys that are set to None.
for (k,v) in local_kwarg.items():
if v is None:
del kwargs[k]
return kwargs
class Session(object):
"""A Requests session."""
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks']
__attrs__ = [
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
'params', 'config']
def __init__(self, **kwargs):
def __init__(self,
headers=None,
cookies=None,
auth=None,
timeout=None,
proxies=None,
hooks=None,
params=None,
config=None,
keep_alive=True):
self.headers = headers or {}
self.cookies = cookies or {}
self.auth = auth
self.timeout = timeout
self.proxies = proxies or {}
self.hooks = hooks or {}
self.params = params or {}
self.config = config or {}
self.keep_alive = keep_alive
for (k, v) in defaults.items():
self.config.setdefault(k, v)
# 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))
@@ -42,43 +90,160 @@ class Session(object):
return self
def __exit__(self, *args):
# print args
pass
def request(self, method, url,
params=None,
data=None,
headers=None,
cookies=None,
files=None,
auth=None,
timeout=None,
allow_redirects=False,
proxies=None,
hooks=None,
return_response=True,
config=None):
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.
"""Constructs and sends a :class:`Request <Request>`.
Returns :class:`Response <Response>` object.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param auth: (optional) Auth typle to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param return_response: (optional) If False, an un-sent Request object will returned.
:param config: (optional) A configuration dictionary.
"""
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())
method = str(method).upper()
# 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 cookies is None:
cookies = {}
if kwargs.get('headers', None) and inst_attrs.get('headers', None):
kwargs['headers'].update(inst_attrs['headers'])
if isinstance(cookies, dict):
cookies = add_dict_to_cookiejar(self.cookies, cookies)
return func(*args, **kwargs)
return wrapper_func
cookies = cookiejar_from_dict(cookies)
# Expand header values
if headers:
for k, v in headers.items() or {}:
headers[k] = header_expand(v)
args = dict(
method=method,
url=url,
data=data,
params=params,
headers=headers,
cookies=cookies,
files=files,
auth=auth,
hooks=hooks,
timeout=timeout,
allow_redirects=allow_redirects,
proxies=proxies,
config=config
)
for attr in self.__attrs__:
session_val = getattr(self, attr, None)
local_val = args.get(attr)
args[attr] = merge_kwargs(local_val, session_val)
# Arguments manipulation hook.
args = dispatch_hook('args', args['hooks'], args)
r = Request(**args)
# Don't send if asked nicely.
if not return_response:
return r
# Send the HTTP Request.
r.send()
return r.response
def get(self, url, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
return self.request('GET', url, **kwargs)
def head(self, url, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
return self.request('HEAD', url, **kwargs)
def post(self, url, data='', **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return self.request('post', url, data=data, **kwargs)
def put(self, url, data='', **kwargs):
"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return self.request('put', url, data=data, **kwargs)
def patch(self, url, data='', **kwargs):
"""Sends a PATCH request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return self.request('patch', url, data='', **kwargs)
def delete(self, url, **kwargs):
"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
"""
return self.request('delete', url, **kwargs)
# 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."""
"""Returns a :class:`Session` for context-management."""
return Session(**kwargs)

View File

@@ -47,15 +47,18 @@ _codes = {
412: ('precondition_failed', 'precondition'),
413: ('request_entity_too_large',),
414: ('request_uri_too_large',),
415: ('unspported_media_type', 'unspported_media', 'media_type'),
415: ('unsupported_media_type', 'unsupported_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'),
424: ('failed_dependency', 'dependency'),
425: ('unordered_collection', 'unordered'),
426: ('upgrade_required', 'upgrade'),
428: ('precondition_required', 'precondition'),
429: ('too_many_requests', 'too_many'),
431: ('header_fields_too_large', 'fields_too_large'),
444: ('no_response', 'none'),
449: ('retry_with', 'retry'),
450: ('blocked_by_windows_parental_controls', 'parental_controls'),

View File

@@ -4,7 +4,7 @@
requests.structures
~~~~~~~~~~~~~~~~~~~
Datastructures that power Requests.
Data structures that power Requests.
"""

View File

@@ -9,15 +9,178 @@ that are also useful for external consumption.
"""
import cgi
import codecs
import cookielib
import os
import random
import re
import zlib
from urllib2 import parse_http_list as _parse_list_header
def dict_from_cookiejar(cookiejar):
"""Returns a key/value dictionary from a CookieJar."""
# From mitsuhiko/werkzeug (used with permission).
def parse_list_header(value):
"""Parse lists as described by RFC 2068 Section 2.
In particular, parse comma-separated lists where the elements of
the list may include quoted-strings. A quoted-string could
contain a comma. A non-quoted string could have quotes in the
middle. Quotes are removed automatically after parsing.
It basically works like :func:`parse_set_header` just that items
may appear multiple times and case sensitivity is preserved.
The return value is a standard :class:`list`:
>>> parse_list_header('token, "quoted value"')
['token', 'quoted value']
To create a header from the :class:`list` again, use the
:func:`dump_header` function.
:param value: a string with a list header.
:return: :class:`list`
"""
result = []
for item in _parse_list_header(value):
if item[:1] == item[-1:] == '"':
item = unquote_header_value(item[1:-1])
result.append(item)
return result
# From mitsuhiko/werkzeug (used with permission).
def parse_dict_header(value):
"""Parse lists of key, value pairs as described by RFC 2068 Section 2 and
convert them into a python dict:
>>> d = parse_dict_header('foo="is a fish", bar="as well"')
>>> type(d) is dict
True
>>> sorted(d.items())
[('bar', 'as well'), ('foo', 'is a fish')]
If there is no value for a key it will be `None`:
>>> parse_dict_header('key_without_value')
{'key_without_value': None}
To create a header from the :class:`dict` again, use the
:func:`dump_header` function.
:param value: a string with a dict header.
:return: :class:`dict`
"""
result = {}
for item in _parse_list_header(value):
if '=' not in item:
result[item] = None
continue
name, value = item.split('=', 1)
if value[:1] == value[-1:] == '"':
value = unquote_header_value(value[1:-1])
result[name] = value
return result
# From mitsuhiko/werkzeug (used with permission).
def unquote_header_value(value, is_filename=False):
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
This does not use the real unquoting but what browsers are actually
using for quoting.
:param value: the header value to unquote.
"""
if value and value[0] == value[-1] == '"':
# this is not the real unquoting, but fixing this so that the
# RFC is met will result in bugs with internet explorer and
# probably some other browsers as well. IE for example is
# uploading files with "C:\foo\bar.txt" as filename
value = value[1:-1]
# if this is a filename and the starting characters look like
# a UNC path, then just return the value without quotes. Using the
# replace sequence below on a UNC path has the effect of turning
# the leading double slash into a single slash and then
# _fix_ie_filename() doesn't work correctly. See #458.
if not is_filename or value[:2] != '\\\\':
return value.replace('\\\\', '\\').replace('\\"', '"')
return value
def header_expand(headers):
"""Returns an HTTP Header value string from a dictionary.
Example expansion::
{'text/x-dvi': {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}, 'text/x-c': {}}
# Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
(('text/x-dvi', {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}), ('text/x-c', {}))
# Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
"""
collector = []
if isinstance(headers, dict):
headers = headers.items()
elif isinstance(headers, basestring):
return headers
for i, (value, params) in enumerate(headers):
_params = []
for (p_k, p_v) in params.items():
_params.append('%s=%s' % (p_k, p_v))
collector.append(value)
collector.append('; ')
if len(params):
collector.append('; '.join(_params))
if not len(headers) == i+1:
collector.append(', ')
# Remove trailing separators.
if collector[-1] in (', ', '; '):
del collector[-1]
return ''.join(collector)
def randombytes(n):
"""Return n random bytes."""
# Use /dev/urandom if it is available. Fall back to random module
# if not. It might be worthwhile to extend this function to use
# other platform-specific mechanisms for getting random bytes.
if os.path.exists("/dev/urandom"):
f = open("/dev/urandom")
s = f.read(n)
f.close()
return s
else:
L = [chr(random.randrange(0, 256)) for i in range(n)]
return "".join(L)
def dict_from_cookiejar(cj):
"""Returns a key/value dictionary from a CookieJar.
:param cj: CookieJar object to extract cookies from.
"""
cookie_dict = {}
for _, cookies in cookiejar._cookies.items():
for _, cookies in cj._cookies.items():
for _, cookies in cookies.items():
for cookie in cookies.values():
# print cookie
@@ -27,7 +190,10 @@ def dict_from_cookiejar(cookiejar):
def cookiejar_from_dict(cookie_dict):
"""Returns a CookieJar from a key/value dictionary."""
"""Returns a CookieJar from a key/value dictionary.
:param cookie_dict: Dict of key/values to insert into CookieJar.
"""
# return cookiejar if one was passed in
if isinstance(cookie_dict, cookielib.CookieJar):
@@ -42,7 +208,11 @@ def cookiejar_from_dict(cookie_dict):
def add_dict_to_cookiejar(cj, cookie_dict):
"""Returns a CookieJar from a key/value dictionary."""
"""Returns a CookieJar from a key/value dictionary.
:param cj: CookieJar to insert cookies into.
:param cookie_dict: Dict of key/values to insert into CookieJar.
"""
for k, v in cookie_dict.items():
@@ -70,3 +240,124 @@ def add_dict_to_cookiejar(cj, cookie_dict):
cj.set_cookie(cookie)
return cj
def get_encodings_from_content(content):
"""Returns encodings from given content string.
:param content: bytestring to extract encodings from.
"""
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
return charset_re.findall(content)
def get_encoding_from_headers(headers):
"""Returns encodings from given HTTP Header Dict.
:param headers: dictionary to extract encoding from.
"""
content_type = headers.get('content-type')
if not content_type:
return None
content_type, params = cgi.parse_header(content_type)
if 'charset' in params:
return params['charset'].strip("'\"")
def unicode_from_html(content):
"""Attempts to decode an HTML string into unicode.
If unsuccessful, the original content is returned.
"""
encodings = get_encodings_from_content(content)
for encoding in encodings:
try:
return unicode(content, encoding)
except (UnicodeError, TypeError):
pass
return content
def stream_decode_response_unicode(iterator, r):
"""Stream decodes a iterator."""
encoding = get_encoding_from_headers(r.headers)
if encoding is None:
for item in iterator:
yield item
return
decoder = codecs.getincrementaldecoder(encoding)(errors='replace')
for chunk in iterator:
rv = decoder.decode(chunk)
if rv:
yield rv
rv = decoder.decode('', final=True)
if rv:
yield rv
def get_unicode_from_response(r):
"""Returns the requested content back in unicode.
:param r: Response object to get unicode content from.
Tried:
1. charset from content-type
2. every encodings from ``<meta ... charset=XXX>``
3. fall back and replace all unicode characters
"""
tried_encodings = []
# Try charset from content-type
encoding = get_encoding_from_headers(r.headers)
if encoding:
try:
return unicode(r.content, encoding)
except UnicodeError:
tried_encodings.append(encoding)
# Fall back:
try:
return unicode(r.content, encoding, errors='replace')
except TypeError:
return r.content
def decode_gzip(content):
"""Return gzip-decoded string.
:param content: bytestring to gzip-decode.
"""
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
def stream_decode_gzip(iterator):
"""Stream decodes a gzip-encoded iterator"""
try:
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
for chunk in iterator:
rv = dec.decompress(chunk)
if rv:
yield rv
buf = dec.decompress('')
rv = buf + dec.flush()
if rv:
yield rv
except zlib.error:
pass

View File

@@ -3,24 +3,26 @@
from __future__ import with_statement
import unittest
import time
import cookielib
import os
import unittest
import requests
import envoy
from urllib2 import HTTPError
try:
import omnijson as json
except ImportError:
import json
import requests
from requests.sessions import Session
# TODO: Detect an open port.
PORT = os.environ.get('HTTPBIN_PORT', '7077')
HTTPBIN_URL = 'http://httpbin.ep.io/'
HTTPSBIN_URL = 'https://httpbin.ep.io/'
# HTTPBIN_URL = 'http://staging.httpbin.org/'
# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/'
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
def httpbin(*suffix):
@@ -29,15 +31,9 @@ def httpbin(*suffix):
return HTTPBIN_URL + '/'.join(suffix)
def httpsbin(*suffix):
"""Returns url for HTTPSBIN resource."""
return HTTPSBIN_URL + '/'.join(suffix)
SERVICES = (httpbin, httpsbin)
SERVICES = (httpbin, )
_httpbin = False
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
@@ -46,27 +42,52 @@ class RequestsTestSuite(unittest.TestCase):
_multiprocess_can_split_ = True
def setUp(self):
pass
global _httpbin
if not _httpbin:
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
self.httpbin = c
_httpbin = True
time.sleep(1)
def tearDown(self):
"""Teardown."""
pass
# self.httpbin.kill()
def test_entry_points(self):
import requests
requests.session
requests.session().get
requests.session().head
requests.get
requests.head
requests.put
requests.patch
requests.post
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
def test_HTTP_200_OK_GET(self):
r = requests.get(httpbin('/'))
r = requests.get(httpbin('/get'))
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_GET(self):
r = requests.get(httpsbin('/'))
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
r = requests.get(httpbin('redirect', '1'))
self.assertEqual(r.status_code, 200)
def test_HTTP_302_GET(self):
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
self.assertEqual(r.status_code, 302)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
@@ -105,12 +126,7 @@ class RequestsTestSuite(unittest.TestCase):
def test_HTTP_200_OK_HEAD(self):
r = requests.head(httpbin('/'))
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_HEAD(self):
r = requests.head(httpsbin('/'))
r = requests.head(httpbin('/get'))
self.assertEqual(r.status_code, 200)
@@ -119,22 +135,12 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_PUT(self):
r = requests.put(httpsbin('put'))
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_PATCH(self):
r = requests.patch(httpbin('patch'))
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_PATCH(self):
r = requests.patch(httpsbin('patch'))
self.assertEqual(r.status_code, 200)
def test_AUTH_HTTP_200_OK_GET(self):
def test_BASICAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
@@ -142,14 +148,35 @@ class RequestsTestSuite(unittest.TestCase):
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, 401)
s = requests.session(auth=auth)
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = ('digest', 'user', 'pass')
url = service('digest-auth', 'auth', 'user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_POSTBIN_GET_POST_FILES(self):
for service in SERVICES:
@@ -199,7 +226,7 @@ class RequestsTestSuite(unittest.TestCase):
r = requests.get(service('status', '500'))
self.assertEqual(bool(r), False)
r = requests.get(service('/'))
r = requests.get(service('/get'))
self.assertEqual(bool(r), True)
@@ -213,7 +240,7 @@ class RequestsTestSuite(unittest.TestCase):
def test_status_raising(self):
r = requests.get(httpbin('status', '404'))
self.assertRaises(requests.HTTPError, r.raise_for_status)
self.assertRaises(HTTPError, r.raise_for_status)
r = requests.get(httpbin('status', '200'))
self.assertFalse(r.error)
@@ -250,7 +277,7 @@ class RequestsTestSuite(unittest.TestCase):
for service in SERVICES:
url = service('/')
url = service('/get')
requests.get(url, params={'foo': u'føø'})
requests.get(url, params={u'føø': u'føø'})
@@ -268,19 +295,6 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEquals(r.status_code, 401)
def test_settings(self):
def test():
r = requests.get(httpbin(''))
r.raise_for_status()
with requests.settings(timeout=0.0000001):
self.assertRaises(requests.Timeout, test)
with requests.settings(timeout=100):
requests.get(httpbin(''))
def test_urlencoded_post_data(self):
for service in SERVICES:
@@ -379,9 +393,9 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEquals(rbody.get('data'), 'foobar')
def test_idna(self):
r = requests.get(u'http://➡.ws/httpbin')
assert 'httpbin' in r.url
# def test_idna(self):
# r = requests.get(u'http://➡.ws/httpbin')
# assert 'httpbin' in r.url
def test_urlencoded_get_query_multivalued_param(self):
@@ -462,15 +476,8 @@ class RequestsTestSuite(unittest.TestCase):
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('/'))
s = requests.session()
r = s.get(httpbin('/get'))
self.assertEqual(r.status_code, 200)
@@ -478,18 +485,55 @@ class RequestsTestSuite(unittest.TestCase):
heads = {'User-agent': 'Mozilla/5.0'}
s = Session()
s = requests.session()
s.headers = heads
# Make 2 requests from Session object, should send header both times
r1 = s.get(httpbin('user-agent'))
assert heads['User-agent'] in r1.content
r2 = s.get(httpbin('user-agent'))
r2 = s.get(httpbin('user-agent'))
assert heads['User-agent'] in r2.content
new_heads = {'User-agent': 'blah'}
r3 = s.get(httpbin('user-agent'), headers=new_heads)
assert new_heads['User-agent'] in r3.content
self.assertEqual(r2.status_code, 200)
def test_session_persistent_params(self):
params = {'a': 'a_test'}
s = requests.session()
s.params = params
# Make 2 requests from Session object, should send header both times
r1 = s.get(httpbin('get'))
assert params['a'] in r1.content
params2 = {'b': 'b_test'}
r2 = s.get(httpbin('get'), params=params2)
assert params['a'] in r2.content
assert params2['b'] in r2.content
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
r3 = s.get(httpbin('get'), params=params3)
assert not params['a'] in r3.content
assert params3['b'] in r3.content
assert params3['c'] in r3.content
def test_invalid_content(self):
r = requests.get('http://somedomainthatclearlydoesntexistg.com')
assert r.content == None
if __name__ == '__main__':
unittest.main()

View File

@@ -1,8 +0,0 @@
[tox]
envlist = py25,py26,py27
[testenv]
commands=py.test --junitxml=junit-{envname}.xml
deps =
pytest
omnijson