Compare commits

...

173 Commits

Author SHA1 Message Date
Kenneth Reitz
aa33194606 Merge branch 'release/0.4.0' 2011-05-14 23:58:00 -04:00
Kenneth Reitz
51360e1ede version bump 2011-05-14 23:57:53 -04:00
Kenneth Reitz
af0febe8ad history update 2011-05-14 23:57:19 -04:00
Kenneth Reitz
155863bf71 readme update 2011-05-14 23:55:15 -04:00
Kenneth Reitz
3cec61481c ignore http errors in redirect loops 2011-05-14 23:22:26 -04:00
Kenneth Reitz
5708fe9565 python 2.5 fix 2011-05-14 23:21:58 -04:00
Kenneth Reitz
c40deefb8a cleanup mess 2011-05-14 23:03:31 -04:00
Kenneth Reitz
cd8ce63a3b Custom RedirectHandler and .history support.
Closes #23
2011-05-14 23:01:13 -04:00
Kenneth Reitz
35beda21b8 pass things into the dict 2011-05-14 22:02:14 -04:00
Kenneth Reitz
85b8b2bd44 CaseInsensitiveDict for Headers 2011-05-14 22:02:07 -04:00
Kenneth Reitz
29aa7736ca Headers: Default to None 2011-05-14 21:52:33 -04:00
Kenneth Reitz
e8ff405fce remote async __all__ 2011-05-14 21:50:41 -04:00
Kenneth Reitz
5172d24e66 New CaseInsensitiveDict 2011-05-14 21:50:25 -04:00
Kenneth Reitz
dfe7d877eb date header on history 2011-05-14 14:46:47 -04:00
Kenneth Reitz
0c00a17372 Merge branch 'release/0.3.4' 2011-05-14 14:31:24 -04:00
Kenneth Reitz
fa1db95814 Version bump: v0.3.4 2011-05-14 14:30:36 -04:00
Kenneth Reitz
d77c0ec138 history update 2011-05-14 14:29:41 -04:00
Kenneth Reitz
8ba12ff4f3 cleaner patch names 2011-05-14 14:24:12 -04:00
Kenneth Reitz
14ef462263 package refactor 2011-05-14 14:21:42 -04:00
Kenneth Reitz
1421ffa32a HTTP Basic recursion. Fixes #31 2011-05-14 14:02:36 -04:00
Kenneth Reitz
62931b60f8 history for v0.3.4 2011-05-14 13:11:08 -04:00
Kenneth Reitz
b1e28c7fd8 Added Megan Emurayama to authors 2011-05-14 13:04:49 -04:00
Kenneth Reitz
9b0bb871d2 Merge pull request #29 from meganemura/requests
---

raised Attribute Error when post str data something like json because of not having items attribute.
move encoding data to utf-8 inside hasattr(data, items)
2011-05-12 13:36:45 -04:00
megane murayama
1971bdbe67 fix for post non-dict data 2011-05-12 12:50:09 -04:00
Kenneth Reitz
912f48eece slight refactor 2011-05-12 10:12:04 -04:00
Kenneth Reitz
86fb0cb235 Merge https://github.com/jgorset/requests into develop 2011-05-12 10:09:00 -04:00
Johannes Gorset
23ee58d6ec Encode both keys and values for incoming unicode data as UTF-8, coincidentally fixing a bug that caused UTF-8 encoded byte strings to be encoded twice and causing an UnicodeDecodeError. 2011-05-12 10:27:41 +02:00
Kenneth Reitz
9e0dcae0aa Merge branch 'release/0.3.3' into develop 2011-05-12 04:02:18 -04:00
Kenneth Reitz
7215452da5 Merge branch 'release/0.3.3' 2011-05-12 04:02:16 -04:00
Kenneth Reitz
01b22a7e12 version bump 2011-05-12 04:02:06 -04:00
Kenneth Reitz
d28804cc48 update history 2011-05-12 03:57:17 -04:00
Kenneth Reitz
a046a04aa2 Merge branch 'feature/timeout' into develop 2011-05-12 03:56:13 -04:00
Kenneth Reitz
59dd3ed459 unicode urls 2011-05-12 03:20:27 -04:00
Kenneth Reitz
b6f6048cff Encode incoming data.
Closes #27.
2011-05-12 03:14:09 -04:00
Kenneth Reitz
8a4a05aac0 test for unicode url issues 2011-05-12 03:12:30 -04:00
Kenneth Reitz
64d6c3074f sphinx extension fix 2011-05-12 00:29:31 -04:00
Kenneth Reitz
45ca6d8a78 Merge pull request #26 from jgorset/requests
---

Ive implemented a `settings` context manager as described in [pull request #25](https://github.com/kennethreitz/requests/pull/25#issuecomment-1039541).

    >>> import requests
    >>> with requests.settings(timeout=0.5):
    ...     requests.get(http://example.org) # Times out after 0.5 seconds
    ...     requests.get(http://example.org, timeout=10) # Times out after 10 seconds

Settings may also be manipulated separately:

    >>> import requests
    >>> requests.timeout = 0.5
    >>> requests.get(http://example.org) # Times out after 0.5 seconds
2011-04-21 17:24:43 -04:00
Johannes
77f4b8a75e Implement settings context manager 2011-04-21 18:38:33 +02:00
Kenneth Reitz
122c4f0514 updated history w/ latest changes 2011-04-21 10:37:22 -04:00
Kenneth Reitz
83494152fe Merge branch 'timeout' of https://github.com/jgorset/requests into develop 2011-04-21 10:28:26 -04:00
Kenneth Reitz
f6f636f74c Merge pull request #24 from jgorset/requests
---

I made some insignificant corrections to the README.
2011-04-21 10:16:54 -04:00
Johannes
39e9424f01 Update AUTHORS 2011-04-21 14:55:50 +02:00
Johannes
d93731213a Implement optional timeout for request functions 2011-04-21 14:54:30 +02:00
Johannes
f31dc8a9ef Substitute 'request' for 'requests' 2011-04-21 14:17:02 +02:00
Johannes
4687951a04 Substitute <request object> for the actual return value of the request functions 2011-04-21 14:01:43 +02:00
Kenneth Reitz
576cba81d9 Merge branch 'master' into develop 2011-04-15 18:00:37 -04:00
Kenneth Reitz
d3a46c822f preliminary multidicts 2011-04-15 18:00:10 -04:00
Kenneth Reitz
ea86602765 Merge branch 'release/0.3.2' into develop 2011-04-15 17:29:38 -04:00
Kenneth Reitz
fec0d1a9a0 history changes 2011-04-15 17:29:26 -04:00
Kenneth Reitz
642c667566 Merge branch 'release/0.3.2' 2011-04-15 17:27:17 -04:00
Kenneth Reitz
b8b87d416e install simplejson if python < 2.6 2011-04-15 17:27:02 -04:00
Kenneth Reitz
6e14d4704d version bump 2011-04-15 17:26:51 -04:00
Kenneth Reitz
f494cd9c72 add autoauth tuple http basic test 2011-04-15 17:24:03 -04:00
Kenneth Reitz
2814664e91 Fixes #20. 2011-04-15 17:21:38 -04:00
Kenneth Reitz
1359094cc8 test for gzip decompress 2011-04-14 19:46:35 -04:00
Kenneth Reitz
7a62b10ff2 Automatically decompress gzipped responses if content-type is set. fixes #19 2011-04-14 19:44:29 -04:00
Kenneth Reitz
23d5761bd4 accept any extra args to r.read() 2011-04-02 18:22:05 -04:00
Kenneth Reitz
cc16073c36 Merge branch 'develop' 2011-04-01 14:55:24 -04:00
Kenneth Reitz
ed8ff63048 Version bump to 0.3.1 2011-04-01 14:53:53 -04:00
Kenneth Reitz
267a852ba6 Update history 2011-04-01 14:52:33 -04:00
Kenneth Reitz
f2b04f94ca Response.read() [file like object] 2011-03-31 04:47:02 -04:00
Kenneth Reitz
e350bea167 added Request.read 2011-03-31 04:46:19 -04:00
robmadole
2401f14975 Fixing User-agent header problem introduced with Python 2.7.1 2011-03-30 00:07:24 -05:00
Kenneth Reitz
be228043a1 whitespace fix 2011-03-21 20:45:37 -04:00
Kenneth Reitz
48ffb627fe added Aram Dulyan to authors 2011-03-09 13:22:47 -05:00
Aram Dulyan
89192c64f0 An empty cookiejar evaluates to False, so it needs to be compared to None for the cookie functionality to work. 2011-03-09 22:31:13 +11:00
Kenneth Reitz
a67cc5c5a9 someone stole the cookie jar 2011-03-06 19:19:22 -05:00
Kenneth Reitz
fa2f1c5f60 Merge branch 'develop' of https://github.com/robmadole/requests into develop 2011-03-06 19:08:32 -05:00
Kenneth Reitz
1d021c5cc1 Added Rob Madole to AUTHORS 2011-03-06 19:07:29 -05:00
Kenneth Reitz
e17111a5dd testing requests with poster and adding headers 2011-03-06 18:50:28 -05:00
Kenneth Reitz
9fdee250de Better POSTER header compatibility (Fixes #13) 2011-03-06 18:48:44 -05:00
Kenneth Reitz
c6acfef3d0 Added Justin Murphy to AUTHORS 2011-03-06 18:48:14 -05:00
robmadole
e4c690e7bc Initial support for cookies 2011-03-01 09:21:00 -06:00
robmadole
ee43ad2497 Ignore those compiled files 2011-03-01 09:15:14 -06:00
Kenneth Reitz
044252ea7b Merge branch 'release/0.3.0' 2011-02-25 09:00:23 -05:00
Kenneth Reitz
f8032e08ae tests cleanups 2011-02-25 08:58:24 -05:00
Kenneth Reitz
f0d2ac3dd5 Readme update 2011-02-25 08:58:17 -05:00
Kenneth Reitz
270898e521 Readme Updates 2011-02-25 08:48:09 -05:00
Kenneth Reitz
897dd3aaa8 whitespace 2011-02-25 08:11:17 -05:00
Kenneth Reitz
dd01f8661e hacking whitespace fixes 2011-02-25 08:11:10 -05:00
Kenneth Reitz
99c3970fd3 history 2011-02-25 08:04:03 -05:00
Kenneth Reitz
839c8cf150 postbin tests were broken 2011-02-25 08:00:23 -05:00
Kenneth Reitz
b252cd8403 allow files to be upload along with form data 2011-02-25 08:00:11 -05:00
Kenneth Reitz
b3b2b19212 hmmm 2011-02-25 07:39:19 -05:00
Kenneth Reitz
a74e954788 test improvements 2011-02-25 07:21:11 -05:00
Kenneth Reitz
32f75585ad url builder, cleanups 2011-02-25 07:20:59 -05:00
Kenneth Reitz
9a8b25d5fa makin' history 2011-02-25 07:20:37 -05:00
Kenneth Reitz
14ba99f975 prettier imports 2011-02-25 05:03:12 -05:00
Kenneth Reitz
ccb7fdd8c2 Version bump 2011-02-25 04:48:10 -05:00
Kenneth Reitz
9a1d90d184 Proper async module docs 2011-02-25 04:43:18 -05:00
Kenneth Reitz
22798ca32b Prepping for v0.3.0 release. 2011-02-25 04:40:33 -05:00
Kenneth Reitz
3fe4753d5d Cleanups 2011-02-24 02:06:48 -05:00
Kenneth Reitz
d91157c567 Merge branch 'develop' into merge/xero 2011-02-24 01:53:55 -05:00
Kenneth Reitz
e7b8e9371a merge changes 2011-02-24 01:51:53 -05:00
Kenneth Reitz
1f60e1004c Merge https://github.com/Digitalxero/requests into merge/xero 2011-02-24 01:45:44 -05:00
Kenneth Reitz
9e45d201f3 Added Dj Gilcrease to AUTHORS. 2011-02-24 01:38:58 -05:00
Kenneth Reitz
47ecb3662d !+= 2011-02-24 01:30:00 -05:00
Kenneth Reitz
7369e47f18 Added Flavio to AUTHORS 2011-02-24 01:29:51 -05:00
Flavio Percoco Premoli
6f5b64bca6 small workaround for issue #7 2011-02-23 08:49:08 +01:00
Kenneth Reitz
e788ca7ee6 Safer error response handling. 2011-02-21 14:36:41 -05:00
Kenneth Reitz
68dea921e7 Added HACKING file. 2011-02-21 02:25:01 -05:00
digitalxero
f96157d259 Fix a syntax error on py2.5 and pypy 2011-02-19 15:33:24 -05:00
digitalxero
718f45698d Reset copyright on the async module 2011-02-19 15:21:47 -05:00
digitalxero
15761fc126 Made it so people can add their own auth handlers and not just the 4 provided by Python 2011-02-19 15:07:58 -05:00
digitalxero
edfdef2396 Add in a proper AuthManager instead of the list version that was being used.
Added support for all Auth types that python supports
2011-02-19 15:04:56 -05:00
digitalxero
a9e18e9106 Update the README 2011-02-19 11:18:29 -05:00
digitalxero
3b82b06adc Added an async module to make it explicit to the user that they are using async calls. 2011-02-19 11:13:10 -05:00
digitalxero
d1c89333d0 Remove the eventles code, this should live in requests.async or something so the user explicitly requests it
Clean up the code to get rid of the unnecessary AuthObject class
Cleanup the send method
Cleanup the convenience methods by adding a request method that they all use.
2011-02-19 10:51:54 -05:00
Kenneth Reitz
e26570d0d8 Merge branch 'hotfix/date' into develop 2011-02-19 01:30:22 -05:00
Kenneth Reitz
8b3c4da2c7 Merge branch 'hotfix/date' 2011-02-19 01:30:10 -05:00
Kenneth Reitz
5c4aa1f4b7 history date fix 2011-02-19 01:30:03 -05:00
Kenneth Reitz
d2cdefa7df Merge branch 'release/0.2.4' 2011-02-19 01:03:39 -05:00
Kenneth Reitz
720d0b2c38 Version needs to be available externally. 2011-02-19 01:02:13 -05:00
Kenneth Reitz
d2584ff853 Production Ready! Added Python 2.5 to Support list. 2011-02-19 01:02:01 -05:00
Kenneth Reitz
433452f9f1 Simpler setup.py versioning. 2011-02-19 01:01:51 -05:00
Kenneth Reitz
a34e0bea8b Version Bump (v0.2.4) 2011-02-19 01:00:03 -05:00
Kenneth Reitz
f66b35454f v0.2.4 Release information 2011-02-19 00:59:56 -05:00
Kenneth Reitz
b5c2806e8c Roadmap update 2011-02-19 00:43:27 -05:00
Kenneth Reitz
5072399f7a Import fix for python2.5 2011-02-19 00:41:28 -05:00
Kenneth Reitz
736cb70e9a Merge branch 'develop' of github.com:kennethreitz/requests into develop 2011-02-19 00:38:09 -05:00
Kenneth Reitz
8eafb8d3df Added autoauth tests 2011-02-19 00:37:33 -05:00
Kenneth Reitz
e8234a0137 import fix 2011-02-18 16:13:49 -05:00
Kenneth Reitz
fbe0393eb3 cstring io fix for py3 2011-02-18 16:08:26 -05:00
Kenneth Reitz
b82e72eb04 cStringIO fix. 2011-02-18 16:00:34 -05:00
Kenneth Reitz
a7c87edebe screw 2.4 2011-02-18 15:57:50 -05:00
Kenneth Reitz
7e20f32b02 pypy configuration 2011-02-18 04:41:42 -05:00
Kenneth Reitz
4419e7183f no more test suite 2011-02-18 04:41:34 -05:00
Kenneth Reitz
6bde955170 Python 2.5 Fixes. 2011-02-18 04:39:53 -05:00
Kenneth Reitz
952970f403 Merge branch 'release/0.9.4' 2011-02-18 03:35:18 -05:00
Kenneth Reitz
d3911da653 tox update (usable now?) 2011-02-18 03:32:10 -05:00
Kenneth Reitz
7b6ed48a86 tox setup for 2.4-2.7 2011-02-17 19:38:40 -05:00
Kenneth Reitz
7229dbe033 Making everyone else happy. 2011-02-17 16:22:03 -05:00
Kenneth Reitz
015d6b78d5 cleaning up line lengths 2011-02-17 16:20:53 -05:00
Kenneth Reitz
f4c4bf3d0b Much nicer helper functions and constructors 2011-02-17 16:17:46 -05:00
Kenneth Reitz
efedbd9775 added test_suite.sh for CI 2011-02-17 13:14:03 -05:00
Kenneth Reitz
f4e03a5d32 gitignore update for jenkins 2011-02-17 13:13:52 -05:00
Kenneth Reitz
34ca692c9c from __future__ import absolute_imports 2011-02-16 12:47:58 -05:00
Kenneth Reitz
a5517960ea history indent fix for rest/pypi 2011-02-15 10:02:39 -05:00
Kenneth Reitz
fa42c12e5b I need autodoc. 2011-02-15 09:58:02 -05:00
Kenneth Reitz
8ac29b40e9 new documentation on Request changes. 2011-02-15 09:51:46 -05:00
Kenneth Reitz
adb53c2b4f Merge branch 'release/0.2.3' into develop 2011-02-15 09:46:58 -05:00
Kenneth Reitz
53e785f656 Merge branch 'release/0.2.3' 2011-02-15 09:46:55 -05:00
Kenneth Reitz
d5256802d4 v0.2.3 2011-02-15 09:46:46 -05:00
Kenneth Reitz
933cf49daa v0.2.3 2011-02-15 09:45:54 -05:00
Kenneth Reitz
83a9bcf1a1 Updating AUTHORS 2011-02-15 09:44:52 -05:00
Kenneth Reitz
4ad73c3655 leave room for _ 2011-02-15 09:42:05 -05:00
Kenneth Reitz
7ad08c5eda New tests for new HTTP STATUS Handling 2011-02-15 09:37:21 -05:00
Kenneth Reitz
4f1301f6ad Now Requests has it's own HTTPError 2011-02-15 09:36:34 -05:00
Kenneth Reitz
6d8a531e87 HTTPError changes are going down in history 2011-02-15 09:26:44 -05:00
Kenneth Reitz
748633d616 raise_for_status is better 2011-02-15 09:26:07 -05:00
Kenneth Reitz
ce122cc811 None, not False 2011-02-15 09:22:11 -05:00
Kenneth Reitz
c900b4379b Added new HTTPError handing mechanisms. 2011-02-15 09:18:43 -05:00
Kenneth Reitz
d6fa43fba7 Merge branch 'release/0.2.2' into develop 2011-02-14 12:59:22 -05:00
Kenneth Reitz
ed909eaac7 Merge branch 'release/0.2.2' 2011-02-14 12:59:06 -05:00
Kenneth Reitz
d511e6f148 Version Bump v0.2.2 2011-02-14 12:58:20 -05:00
Kenneth Reitz
8ff8e02ace Merge branch 'feature/cookies' into develop 2011-02-14 12:52:50 -05:00
Kenneth Reitz
98c8a78c3e Added Cookies documentation. 2011-02-14 12:52:15 -05:00
Kenneth Reitz
397bd4b676 spelling error 2011-02-14 12:45:03 -05:00
Kenneth Reitz
f023c81dad cookies system in place. 2011-02-14 12:43:13 -05:00
Kenneth Reitz
5fe24abfb9 Gevent / Eventlet is back! 2011-02-14 12:40:41 -05:00
Kenneth Reitz
16e5119b2a fixed gevent problem 2011-02-14 12:39:13 -05:00
Kenneth Reitz
b09218d5ac temporary 2011-02-14 12:33:12 -05:00
Kenneth Reitz
4452934929 cleanups 2011-02-14 12:25:44 -05:00
Kenneth Reitz
c9b8750597 Upated history 2011-02-14 12:24:03 -05:00
Kenneth Reitz
2203c3bccd Added CookieJar 2011-02-14 12:23:55 -05:00
Kenneth Reitz
cde3b88f3e eventlet > gevent (i think) 2011-02-14 12:07:34 -05:00
Kenneth Reitz
bfe91296cb monkey patchin' (will this suffice?) 2011-02-14 11:57:12 -05:00
Kenneth Reitz
d87bcce6ad Added release 0.2.2 info. 2011-02-14 11:46:04 -05:00
Kenneth Reitz
f70e89a163 eventlet support. 2011-02-14 11:45:50 -05:00
Kenneth Reitz
09740c35e1 typo 2011-02-14 11:31:44 -05:00
Kenneth Reitz
10280c697d Handles request in event of HTTPError. Fixes Issue #2
Building of internal request object refactor
2011-02-14 11:31:06 -05:00
Kenneth Reitz
a82f4de38d Added AUTHORS file. 2011-02-14 10:50:36 -05:00
Kenneth Reitz
44d402c1e6 who stole dem cookies 2011-02-14 10:50:18 -05:00
20 changed files with 1198 additions and 533 deletions

6
.gitignore vendored
View File

@@ -1,2 +1,6 @@
.idea
MANIFEST
MANIFEST
coverage.xml
nosetests.xml
pylint.txt
*.pyc

21
AUTHORS Normal file
View File

@@ -0,0 +1,21 @@
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
- 村山めがね (Megan Emurayama)

15
HACKING Normal file
View File

@@ -0,0 +1,15 @@
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,84 @@
History
-------
0.4.0 (2011-05-15)
++++++++++++++++++
* Response.history: list of redirected responses
* Case-Insensitive Header Dictionaries!
* Unicode URLs
0.3.4 (2011-05-14)
++++++++++++++++++
* Urllib2 HTTPAuthentication Recursion fix (Basic/Digest)
* Internal Refactor
* Bytes data upload Bugfix
0.3.3 (2011-05-12)
++++++++++++++++++
* Request timeouts
* Unicode url-encoded data
* Settings context manager and module
0.3.2 (2011-04-15)
++++++++++++++++++
* Automatic Decompression of GZip Encoded Content
* AutoAuth Support for Tupled HTTP Auth
0.3.1 (2011-04-01)
++++++++++++++++++
* Cookie Changes
* Response.read()
* Poster fix
0.3.0 (2011-02-25)
++++++++++++++++++
* Automatic Authentication API Change
* Smarter Query URL Parameterization
* Allow file uploads and POST data together
* New Authentication Manager System
- Simpler Basic HTTP System
- Supports all build-in urllib2 Auths
- Allows for custom Auth Handlers
0.2.4 (2011-02-19)
++++++++++++++++++
* Python 2.5 Support
* PyPy-c v1.4 Support
* Auto-Authentication tests
* Improved Request object constructor
0.2.3 (2011-02-15)
++++++++++++++++++
* New HTTPHandling Methods
- Reponse.__nonzero__ (false if bad HTTP Status)
- Response.ok (True if expected HTTP Status)
- Response.error (Logged HTTPError if bad HTTP Status)
- Reponse.raise_for_status() (Raises stored HTTPError)
0.2.2 (2011-02-14)
++++++++++++++++++
* Still handles request in the event of an HTTPError. (Issue #2)
* Eventlet and Gevent Monkeypatch support.
* Cookie Support (Issue #1)
0.2.1 (2011-02-14)
++++++++++++++++++

View File

@@ -1,11 +1,11 @@
Requests: The Simple (e.g. usable) HTTP Module
==============================================
Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging).
Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging).
But this one's different. This one's going to be awesome. And simple.
Really simple.
Really simple.
Features
--------
@@ -14,7 +14,13 @@ Features
+ Simple HTTP Header Request Attachment
+ Simple Data/Params Request Attachment
+ Simple Multipart File Uploads
- Simple Basic HTTP Authentication
+ CookieJar Support
+ Redirection History
+ Redirection Recursion Urllib Fix
+ Auto Decompression of GZipped Content
+ Unicode URL Support
- Simple Authentication
+ Simple URL + HTTP Auth Registry
@@ -28,23 +34,23 @@ It couldn't be simpler. ::
HTTPS? Basic Authentication? ::
>>> r = requests.get('https://convore.com/api/account/verify.json')
>>> r.status_code
401
Uh oh, we're not authorized! Let's add authentication. ::
>>> conv_auth = requests.AuthObject('requeststest', 'requeststest')
>>> conv_auth = ('requeststest', 'requeststest')
>>> r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth)
>>> r.status_code
200
200
>>> r.headers['content-type']
'application/json'
>>> r.content
'{"username": "requeststest", "url": "/users/requeststest/", "id": "9408", "img": "censored-long-url"}'
@@ -52,54 +58,67 @@ Uh oh, we're not authorized! Let's add authentication. ::
API
---
**Requests:**
All request functions return a Response object (see below).
If a {filename: fileobject} dictionary is passed in (files=...), a multipart_encode upload will be performed.
If CookieJar object is is passed in (cookies=...), the cookies will be sent with the request.
GET Requests
>>> request.get(url, params={}, headers={} auth=None)
<request object>
>>> requests.get(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
HEAD Requests
>>> request.head(url, params={}, headers={} auth=None)
<request object>
>>> requests.head(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
PUT Requests
>>> request.put(url, data='', headers={}, files={}, auth=None)
<request object>
# If files dictionary ( {filename: fileobject, ...} ) is given, a multi-part upload is performed.
>>> requests.put(url, data='', headers={}, files={}, cookies=None, auth=None)
<Response [200]>
POST Requests
>>> request.post(url, data={}, headers={}, files={}, auth=None)
<request object>
# If files dictionary ( {filename: fileobject, ...} ) is given, a multi-part upload is performed.
>>> requests.post(url, data={}, headers={}, files={}, cookies=None, auth=None)
<Response [200]>
DELETE Requests
>>> request.delete(url, params={}, headers={}, auth=None)
<request object>
>>> requests.delete(url, params={}, headers={}, cookies=None, auth=None)
<Response [200]>
**Responses:**
Request.status_code:
Response.status_code
(Integer) Received HTTP Status Code Response
Request.headers:
Response.headers
(Dictionary) Received HTTP Response Headers
Request.content:
Response.content
(Bytes) Received Content
Request.url
(String) URL of response. Useful for detecting redirects.
Response.url
(String) URL of response. Useful for detecting redirects.
Response.ok
(Bool) True if no errors occurred during the request, and the status_code is kosher.
Response.cached
(Bool) True if Response.content is stored within the object.
Response.error
(HTTPError) If an HTTPError occurred (e.g. status of 404), Otherwise this is None.
Response.raise_for_status()
Raises HTTPError if a request is not kosher.
**HTTP Authentication Registry:**
You can register AuthObjects to automatically enable HTTP Authentication on requests that contain a registered base URL string.
>>> requests.add_autoauth(url, authobject)
>>> requests.auth_manager.add_auth(url, authobject)
@@ -109,13 +128,13 @@ Installation
To install requests, simply: ::
$ pip install requests
Or, if you absolutely must: ::
$ easy_install requests
But, you really shouldn't do that.
Contribute
@@ -129,7 +148,6 @@ Roadmap
-------
- Sphinx Documentation
- Exhaustive Unittests
.. _`the repository`: http://github.com/kennethreitz/requests
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS

View File

@@ -25,7 +25,7 @@ import sys, os
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.jsmath', 'sphinx.ext.viewcode']
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -48,9 +48,9 @@ copyright = u'2011, Kenneth Reitz'
# built documents.
#
# The short X.Y version.
version = '0.2.0'
version = '0.3.3'
# The full version, including alpha/beta/rc tags.
release = '0.2.0'
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -1,4 +1,31 @@
# -*- coding: utf-8 -*-
from . import packages
from .core import *
import inspect
import packages
from core import *
from core import __version__
timeout = None
class settings:
"""Context manager for settings."""
cache = {}
def __init__(self, timeout):
self.module = inspect.getmodule(self)
# Cache settings
self.cache['timeout'] = self.module.timeout
self.module.timeout = timeout
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
# Restore settings
for key in self.cache:
setattr(self.module, key, self.cache[key])

115
requests/api.py Normal file
View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""
requests.api
~~~~~~~~~~~~
This module impliments the Requests API.
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
import requests
from .models import Request, Response, AuthManager, AuthObject, auth_manager
__all__ = ('request', 'get', 'head', 'post', 'put', 'delete')
def request(method, url, **kwargs):
"""Sends a `method` request. Returns :class:`Response` object.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET/HEAD/DELETE Parameters to send with the :class:`Request`.
:param data: (optional) Bytes/Dictionary of PUT/POST Data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
data = kwargs.pop('data', dict()) or kwargs.pop('params', dict())
r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', {}),
cookiejar=kwargs.pop('cookies', None), files=kwargs.pop('files', None),
auth=kwargs.pop('auth', auth_manager.get_auth(url)),
timeout=kwargs.pop('timeout', requests.timeout))
r.send()
return r.response
def get(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
return request('GET', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
def head(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
return request('HEAD', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)
def post(url, data={}, headers={}, files=None, cookies=None, auth=None, **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary of POST data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
return request('POST', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs)
def put(url, data='', headers={}, files={}, cookies=None, auth=None, **kwargs):
"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Bytes of PUT Data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
return request('PUT', url, data=data, headers=headers, files=files, cookies=cookies, auth=auth, **kwargs)
def delete(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of DELETE Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param cookies: (optional) CookieJar object to send with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
"""
return request('DELETE', url, params=params, headers=headers, cookies=cookies, auth=auth, **kwargs)

41
requests/async.py Normal file
View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""
requests.async
~~~~~~~~~~~~~~
This module implements the main Requests system, after monkey-patching
the urllib2 module with eventlet or gevent..
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
from __future__ import absolute_import
import urllib
import urllib2
from urllib2 import HTTPError
try:
import eventlet
eventlet.monkey_patch()
except ImportError:
pass
if not 'eventlet' in locals():
try:
from gevent import monkey
monkey.patch_all()
except ImportError:
pass
if not 'eventlet' in locals():
raise ImportError('No Async adaptations of urllib2 found!')
from .core import *

View File

@@ -1,426 +1,24 @@
# -*- coding: utf-8 -*-
"""
requests.core
~~~~~~~~~~~~~
requests.core
~~~~~~~~~~~~~
This module implements the main Requests system.
This module implements the main Requests system.
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
:copyright: (c) 2011 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
import urllib
import urllib2
from .packages.poster.encode import multipart_encode
from .packages.poster.streaminghttp import register_openers
__title__ = 'requests'
__version__ = '0.2.1'
__build__ = 0x000201
__version__ = '0.4.0'
__build__ = 0x000400
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
AUTOAUTHS = []
class _Request(urllib2.Request):
"""Hidden wrapper around the urllib2.Request object. Allows for manual
setting of HTTP methods.
"""
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
urllib2.Request.__init__( self, url, data, headers, origin_req_host, unverifiable)
self.method = method
def get_method(self):
if self.method:
return self.method
return urllib2.Request.get_method(self)
class Request(object):
"""The :class:`Request` object. It carries out all functionality of
Requests. Recommended interface is with the Requests functions.
"""
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
def __init__(self):
self.url = None
self.headers = dict()
self.files = None
self.method = None
self.params = {}
self.data = {}
self.response = Response()
self.auth = None
self.sent = False
def __repr__(self):
return '<Request [%s]>' % (self.method)
def __setattr__(self, name, value):
if (name == 'method') and (value):
if not value in self._METHODS:
raise InvalidMethod()
object.__setattr__(self, name, value)
def _checks(self):
"""Deterministic checks for consistiency."""
if not self.url:
raise URLRequired
def _get_opener(self):
"""Creates appropriate opener object for urllib2."""
if self.auth:
# create a password manager
authr = urllib2.HTTPPasswordMgrWithDefaultRealm()
authr.add_password(None, self.url, self.auth.username, self.auth.password)
handler = urllib2.HTTPBasicAuthHandler(authr)
opener = urllib2.build_opener(handler)
# use the opener to fetch a URL
return opener.open
else:
return urllib2.urlopen
def send(self, anyway=False):
"""Sends the request. Returns True of successfull, false if not.
If there was an HTTPError during transmission,
self.response.status_code will contain the HTTPError code.
Once a request is successfully sent, `sent` will equal True.
:param anyway: If True, request will be sent, even if it has
already been sent.
"""
self._checks()
success = False
if self.method in ('GET', 'HEAD', 'DELETE'):
if (not self.sent) or anyway:
# url encode GET params if it's a dict
if isinstance(self.params, dict):
params = urllib.urlencode(self.params)
else:
params = self.params
req = _Request(("%s?%s" % (self.url, params)), method=self.method)
if self.headers:
req.headers = self.headers
opener = self._get_opener()
try:
resp = opener(req)
self.response.status_code = resp.code
self.response.headers = resp.info().dict
if self.method == 'GET':
self.response.content = resp.read()
self.response.url = resp.url
success = True
except urllib2.HTTPError as why:
self.response.status_code = why.code
elif self.method == 'PUT':
if (not self.sent) or anyway:
if self.files:
register_openers()
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method='PUT')
if self.headers:
req.headers.update(self.headers)
else:
req = _Request(self.url, method='PUT')
if self.headers:
req.headers = self.headers
req.data = self.data
try:
opener = self._get_opener()
resp = opener(req)
self.response.status_code = resp.code
self.response.headers = resp.info().dict
self.response.content = resp.read()
self.response.url = resp.url
success = True
except urllib2.HTTPError as why:
self.response.status_code = why.code
elif self.method == 'POST':
if (not self.sent) or anyway:
if self.files:
register_openers()
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method='POST')
if self.headers:
req.headers.update(self.headers)
else:
req = _Request(self.url, method='POST')
req.headers = self.headers
# url encode form data if it's a dict
if isinstance(self.data, dict):
req.data = urllib.urlencode(self.data)
else:
req.data = self.data
try:
opener = self._get_opener()
resp = opener(req)
self.response.status_code = resp.code
self.response.headers = resp.info().dict
self.response.content = resp.read()
self.response.url = resp.url
success = True
except urllib2.HTTPError as why:
self.response.status_code = why.code
self.sent = True if success else False
return success
class Response(object):
"""The :class:`Request` object. All :class:`Request` objects contain a
:class:`Request.response <response>` attribute, which is an instance of
this class.
"""
def __init__(self):
self.content = None
self.status_code = None
self.headers = dict()
self.url = None
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
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.
"""
def __init__(self, username, password):
self.username = username
self.password = password
def get(url, params={}, headers={}, auth=None):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
"""
r = Request()
r.method = 'GET'
r.url = url
r.params = params
r.headers = headers
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def head(url, params={}, headers={}, auth=None):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
"""
r = Request()
r.method = 'HEAD'
r.url = url
# return response object
r.params = params
r.headers = headers
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def post(url, data={}, headers={}, files=None, auth=None):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary of POST Data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
"""
r = Request()
r.url = url
r.method = 'POST'
r.data = data
if files:
r.files = files
r.headers = headers
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def put(url, data='', headers={}, files={}, auth=None):
"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Bytes of PUT Data to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
"""
r = Request()
r.url = url
r.method = 'PUT'
r.data = data
r.files = files
r.headers = headers
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def delete(url, params={}, headers={}, auth=None):
"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
"""
r = Request()
r.url = url
r.method = 'DELETE'
r.headers = headers
r.auth = _detect_auth(url, auth)
r.send()
return r.response
def add_autoauth(url, authobject):
"""Registers given AuthObject to given URL domain. for auto-activation.
Once a URL is registered with an AuthObject, the configured HTTP
Authentication will be used for all requests with URLS containing the given
URL string.
Example: ::
>>> c_auth = requests.AuthObject('kennethreitz', 'xxxxxxx')
>>> requests.add_autoauth('https://convore.com/api/', c_auth)
>>> r = requests.get('https://convore.com/api/account/verify.json')
# Automatically HTTP Authenticated! Wh00t!
:param url: Base URL for given AuthObject to auto-activate for.
:param authobject: AuthObject to auto-activate.
"""
global AUTOAUTHS
AUTOAUTHS.append((url, authobject))
def _detect_auth(url, auth):
"""Returns registered AuthObject for given url if available, defaulting to
given AuthObject.
"""
return _get_autoauth(url) if not auth else auth
def _get_autoauth(url):
"""Returns registered AuthObject for given url if available."""
for (autoauth_url, auth) in AUTOAUTHS:
if autoauth_url in url:
return auth
return None
class RequestException(Exception):
"""There was an ambiguous exception that occured while handling your request."""
class AuthenticationError(RequestException):
"""The authentication credentials provided were invalid."""
class URLRequired(RequestException):
"""A valid URL is required to make a request."""
class InvalidMethod(RequestException):
"""An inappropriate method was attempted."""
from models import HTTPError, auth_manager
from api import *

472
requests/models.py Normal file
View File

@@ -0,0 +1,472 @@
# -*- coding: utf-8 -*-
"""
requests.system
~~~~~~~~~~~~~~~
"""
import requests
import urllib
import urllib2
import socket
import zlib
from urllib2 import HTTPError
from urlparse import urlparse
from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler
from .structures import CaseInsensitiveDict
from .packages.poster.encode import multipart_encode
from .packages.poster.streaminghttp import register_openers, get_handlers
class Request(object):
"""The :class:`Request` object. It carries out all functionality of
Requests. Recommended interface is with the Requests functions.
"""
_METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
def __init__(self, url=None, headers=dict(), files=None, method=None,
data=dict(), auth=None, cookiejar=None, timeout=None, redirect=True):
socket.setdefaulttimeout(timeout)
self.url = url
self.headers = headers
self.files = files
self.method = method
self.data = dict()
self.redirect = redirect
# self.data = {}
if hasattr(data, 'items'):
for (k, v) in data.items():
self.data.update({
k.encode('utf-8') if isinstance(k, unicode) else k:
v.encode('utf-8') if isinstance(v, unicode) else v
})
# url encode data if it's a dict
if hasattr(data, 'items'):
self._enc_data = urllib.urlencode(self.data)
else:
self._enc_data = data
self.response = Response()
if isinstance(auth, (list, tuple)):
auth = AuthObject(*auth)
if not auth:
auth = auth_manager.get_auth(self.url)
self.auth = auth
self.cookiejar = cookiejar
self.sent = False
def __repr__(self):
return '<Request [%s]>' % (self.method)
def __setattr__(self, name, value):
if (name == 'method') and (value):
if not value in self._METHODS:
raise InvalidMethod()
object.__setattr__(self, name, value)
def _checks(self):
"""Deterministic checks for consistency."""
if not self.url:
raise URLRequired
def _get_opener(self):
"""Creates appropriate opener object for urllib2."""
_handlers = []
if self.cookiejar is not None:
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
if self.auth:
if not isinstance(self.auth.handler, (urllib2.AbstractBasicAuthHandler, urllib2.AbstractDigestAuthHandler)):
auth_manager.add_password(self.auth.realm, self.url, self.auth.username, self.auth.password)
self.auth.handler = self.auth.handler(auth_manager)
auth_manager.add_auth(self.url, self.auth)
_handlers.append(self.auth.handler)
_handlers.append(HTTPRedirectHandler)
# print _handlers
# print '^^'
# print '!'
if not _handlers:
return urllib2.urlopen
if self.data or self.files:
_handlers.extend(get_handlers())
opener = urllib2.build_opener(*_handlers)
if self.headers:
# Allow default headers in the opener to be overloaded
normal_keys = [k.capitalize() for k in self.headers]
for key, val in opener.addheaders[:]:
if key not in normal_keys:
continue
# Remove it, we have a value to take its place
opener.addheaders.remove((key, val))
return opener.open
def _build_response(self, resp):
"""Build internal Response object from given response."""
def build(resp):
response = Response()
response.status_code = getattr(resp, 'code', None)
try:
response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None))
response.content = resp.read()
except AttributeError:
pass
if response.headers['content-encoding'] == 'gzip':
try:
response.content = zlib.decompress(response.content, 16+zlib.MAX_WBITS)
except zlib.error:
pass
response.url = getattr(resp, 'url', None)
return response
history = []
r = build(resp)
if self.redirect:
while 'location' in r.headers:
history.append(r)
url = r.headers['location']
request = Request(
url, self.headers, self.files, self.method,
self.data, self.auth, self.cookiejar, redirect=False
)
request.send()
r = request.response
r.history = history
self.response = r
@staticmethod
def _build_url(url, data=None):
"""Build URLs."""
if urlparse(url).query:
return '%s&%s' % (url, data)
else:
if data:
return '%s?%s' % (url, data)
else:
return url
def send(self, anyway=False):
"""Sends the request. Returns True of successful, false if not.
If there was an HTTPError during transmission,
self.response.status_code will contain the HTTPError code.
Once a request is successfully sent, `sent` will equal True.
:param anyway: If True, request will be sent, even if it has
already been sent.
"""
self._checks()
success = False
if self.method in ('GET', 'HEAD', 'DELETE'):
req = _Request(self._build_url(self.url, self._enc_data), method=self.method)
else:
if self.files:
register_openers()
if self.data:
self.files.update(self.data)
datagen, headers = multipart_encode(self.files)
req = _Request(self.url, data=datagen, headers=headers, method=self.method)
else:
req = _Request(self.url, data=self._enc_data, method=self.method)
if self.headers:
req.headers.update(self.headers)
if not self.sent or anyway:
try:
opener = self._get_opener()
resp = opener(req)
if self.cookiejar is not None:
self.cookiejar.extract_cookies(resp, req)
except urllib2.HTTPError, why:
self._build_response(why)
if not self.redirect:
self.response.error = why
else:
self._build_response(resp)
self.response.ok = True
self.response.cached = False
else:
self.response.cached = True
self.sent = self.response.ok
return self.sent
def read(self, *args):
return self.response.read()
class Response(object):
"""The :class:`Request` object. All :class:`Request` objects contain a
:class:`Request.response <response>` attribute, which is an instance of
this class.
"""
def __init__(self):
self.content = None
self.status_code = None
self.headers = CaseInsensitiveDict()
self.url = None
self.ok = False
self.error = None
self.cached = False
self.history = []
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
def __nonzero__(self):
"""Returns true if status_code is 'OK'."""
return not self.error
def raise_for_status(self):
"""Raises stored HTTPError if one exists."""
if self.error:
raise self.error
def read(self, *args):
return self.content
class AuthManager(object):
"""Authentication Manager."""
def __new__(cls):
singleton = cls.__dict__.get('__singleton__')
if singleton is not None:
return singleton
cls.__singleton__ = singleton = object.__new__(cls)
return singleton
def __init__(self):
self.passwd = {}
self._auth = {}
def __repr__(self):
return '<AuthManager [%s]>' % (self.method)
def add_auth(self, uri, auth):
"""Registers AuthObject to AuthManager."""
uri = self.reduce_uri(uri, False)
# try to make it an AuthObject
if not isinstance(auth, AuthObject):
try:
auth = AuthObject(*auth)
except TypeError:
pass
self._auth[uri] = auth
def add_password(self, realm, uri, user, passwd):
"""Adds password to AuthManager."""
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
reduced_uri = tuple([self.reduce_uri(u, False) for u in uri])
if reduced_uri not in self.passwd:
self.passwd[reduced_uri] = {}
self.passwd[reduced_uri] = (user, passwd)
def find_user_password(self, realm, authuri):
for uris, authinfo in self.passwd.iteritems():
reduced_authuri = self.reduce_uri(authuri, False)
for uri in uris:
if self.is_suburi(uri, reduced_authuri):
return authinfo
return (None, None)
def get_auth(self, uri):
(in_domain, in_path) = self.reduce_uri(uri, False)
for domain, path, authority in (
(i[0][0], i[0][1], i[1]) for i in self._auth.iteritems()
):
if in_domain == domain:
if path in in_path:
return authority
def reduce_uri(self, uri, default_port=True):
"""Accept authority or URI and extract only the authority and path."""
# note HTTP URLs do not have a userinfo component
parts = urllib2.urlparse.urlsplit(uri)
if parts[1]:
# URI
scheme = parts[0]
authority = parts[1]
path = parts[2] or '/'
else:
# host or host:port
scheme = None
authority = uri
path = '/'
host, port = urllib2.splitport(authority)
if default_port and port is None and scheme is not None:
dport = {"http": 80,
"https": 443,
}.get(scheme)
if dport is not None:
authority = "%s:%d" % (host, dport)
return authority, path
def is_suburi(self, base, test):
"""Check if test is below base in a URI tree
Both args must be URIs in reduced form.
"""
if base == test:
return True
if base[0] != test[0]:
return False
common = urllib2.posixpath.commonprefix((base[1], test[1]))
if len(common) == len(base[1]):
return True
return False
def empty(self):
self.passwd = {}
def remove(self, uri, realm=None):
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
for default_port in True, False:
reduced_uri = tuple([self.reduce_uri(u, default_port) for u in uri])
del self.passwd[reduced_uri][realm]
def __contains__(self, uri):
# uri could be a single URI or a sequence
if isinstance(uri, basestring):
uri = [uri]
uri = tuple([self.reduce_uri(u, False) for u in uri])
if uri in self.passwd:
return True
return False
auth_manager = AuthManager()
class AuthObject(object):
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
given to a Requests function, it enables Basic HTTP Authentication for that
Request. You can also enable Authorization for domain realms with AutoAuth.
See AutoAuth for more details.
:param username: Username to authenticate with.
:param password: Password for given username.
:param realm: (optional) the realm this auth applies to
:param handler: (optional) basic || digest || proxy_basic || proxy_digest
"""
_handlers = {
'basic': HTTPBasicAuthHandler,
'digest': HTTPDigestAuthHandler,
'proxy_basic': urllib2.ProxyBasicAuthHandler,
'proxy_digest': urllib2.ProxyDigestAuthHandler
}
def __init__(self, username, password, handler='basic', realm=None):
self.username = username
self.password = password
self.realm = realm
if isinstance(handler, basestring):
self.handler = self._handlers.get(handler.lower(), urllib2.HTTPBasicAuthHandler)
else:
self.handler = handler
class RequestException(Exception):
"""There was an ambiguous exception that occured while handling your
request."""
class AuthenticationError(RequestException):
"""The authentication credentials provided were invalid."""
class URLRequired(RequestException):
"""A valid URL is required to make a request."""
class InvalidMethod(RequestException):
"""An inappropriate method was attempted."""

91
requests/monkeys.py Normal file
View File

@@ -0,0 +1,91 @@
#-*- coding: utf-8 -*-
"""
requests.monkeys
~~~~~~~~~~~~~~~~
Urllib2 Monkey patches.
"""
import urllib2
class Request(urllib2.Request):
"""Hidden wrapper around the urllib2.Request object. Allows for manual
setting of HTTP methods.
"""
def __init__(self, url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None):
urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
self.method = method
def get_method(self):
if self.method:
return self.method
return urllib2.Request.get_method(self)
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
def http_error_301(self, req, fp, code, msg, headers):
pass
http_error_302 = http_error_303 = http_error_307 = http_error_301
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
def __init__(self, *args, **kwargs):
urllib2.HTTPBasicAuthHandler.__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
return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
self, auth_header, host, req, headers
)
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 +1,3 @@
from __future__ import absolute_import
from . import poster

View File

@@ -26,6 +26,8 @@ New releases of poster will always have a version number that compares greater
than an older version of poster.
New in version 0.6."""
from __future__ import absolute_import
from . import streaminghttp
from . import encode

View File

@@ -181,16 +181,18 @@ if hasattr(httplib, 'HTTPS'):
return urllib2.HTTPSHandler.do_request_(self, req)
def get_handlers():
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
if hasattr(httplib, "HTTPS"):
handlers.append(StreamingHTTPSHandler)
return handlers
def register_openers():
"""Register the streaming http handlers in the global urllib2 default
opener object.
Returns the created OpenerDirector object."""
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
if hasattr(httplib, "HTTPS"):
handlers.append(StreamingHTTPSHandler)
opener = urllib2.build_opener(*handlers)
opener = urllib2.build_opener(*get_handlers())
urllib2.install_opener(opener)

5
requests/patches.py Normal file
View File

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

61
requests/structures.py Normal file
View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""
requests.structures
~~~~~~~~~~~~~~~~~~~
Datastructures that power Requests.
"""
from UserDict import DictMixin
class CaseInsensitiveDict(DictMixin):
"""docstring for CaseInsensitiveDict"""
def __init__(self, *args, **kwargs):
# super(CaseInsensitiveDict, self).__init__()
self.data = dict(*args, **kwargs)
def __repr__(self):
return self.data.__repr__()
def __getstate__(self):
return self.data.copy()
def __setstate__(self, d):
self.data = d
def _lower_keys(self):
return map(str.lower, self.data.keys())
def __contains__(self, key):
return key.lower() in self._lower_keys()
def __getitem__(self, key):
if key.lower() in self:
return self.items()[self._lower_keys().index(key.lower())][1]
def __setitem__(self, key, value):
return self.data.__setitem__(key, value)
def __delitem__(self, key):
return self.data.__delitem__(key)
def __keys__(self):
return self.data.__keys__()
def __iter__(self):
return self.data.__iter__()
def iteritems(self):
return self.data.iteritems()

View File

@@ -3,49 +3,51 @@
import os
import sys
import requests
from distutils.core import setup
if sys.argv[-1] == "publish":
os.system("python setup.py sdist upload")
sys.exit()
os.system("python setup.py sdist upload")
sys.exit()
if sys.argv[-1] == "test":
os.system("python test_requests.py")
sys.exit()
os.system("python test_requests.py")
sys.exit()
required = []
# if python > 2.6, require simplejson
if sys.version_info[:2] < (2,6):
required.append('simplejson')
setup(
name='requests',
version='0.2.1',
description='Awesome Python HTTP Library that\'s actually usable.',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='https://github.com/kennethreitz/requests',
packages= [
'requests',
'requests.packages',
'requests.packages.poster'
],
install_requires=required,
license='ISC',
classifiers=(
# 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python',
# 'Programming Language :: Python :: 2.5',
name='requests',
version=requests.__version__,
description='Awesome Python HTTP Library that\'s actually usable.',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='https://github.com/kennethreitz/requests',
packages= [
'requests',
'requests.packages',
'requests.packages.poster'
],
install_requires=required,
license='ISC',
classifiers=(
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
# 'Programming Language :: Python :: 3.0',
# 'Programming Language :: Python :: 3.1',
),
'Programming Language :: Python :: 2.7',
# 'Programming Language :: Python :: 3.0',
# 'Programming Language :: Python :: 3.1',
),
)

174
test_requests.py Normal file → Executable file
View File

@@ -1,61 +1,161 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
from cStringIO import StringIO
import cookielib
import requests
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
def setUp(self):
pass
"""Requests test cases."""
def tearDown(self):
"""Teardown."""
pass
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
def setUp(self):
pass
def test_HTTP_200_OK_GET(self):
r = requests.get('http://google.com')
self.assertEqual(r.status_code, 200)
def tearDown(self):
"""Teardown."""
pass
def test_HTTPS_200_OK_GET(self):
r = requests.get('https://google.com')
self.assertEqual(r.status_code, 200)
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
def test_HTTP_200_OK_HEAD(self):
r = requests.head('http://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET(self):
r = requests.get('http://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_HEAD(self):
r = requests.head('https://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_GET(self):
r = requests.get('https://google.com')
self.assertEqual(r.status_code, 200)
def test_AUTH_HTTPS_200_OK_GET(self):
auth = requests.AuthObject('requeststest', 'requeststest')
url = 'https://convore.com/api/account/verify.json'
r = requests.get(url, auth=auth)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
self.assertEqual(r.status_code, 200)
r = requests.get('http://www.google.com/search', params={'q': 'test'}, headers=heads)
self.assertEqual(r.status_code, 200)
def test_POSTBIN_GET_POST_FILES(self):
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
r = requests.get('http://google.com/search?test=true', params={'q': 'test'}, headers=heads)
self.assertEqual(r.status_code, 200)
post = requests.post(bin.url, data={'some': 'data'})
self.assertEqual(post.status_code, 201)
def test_user_agent_transfers(self):
"""Issue XX"""
heads = {'User-agent':
'Mozilla/5.0 (github.com/kennethreitz/requests)'}
post2 = requests.post(bin.url, files={'some': StringIO('data')})
self.assertEqual(post2.status_code, 201)
r = requests.get('http://whatsmyua.com', headers=heads);
self.assertTrue(heads['User-agent'] in r.content)
def test_HTTP_200_OK_HEAD(self):
r = requests.head('http://google.com')
self.assertEqual(r.status_code, 200)
def test_HTTPS_200_OK_HEAD(self):
r = requests.head('https://google.com')
self.assertEqual(r.status_code, 200)
def test_AUTH_HTTPS_200_OK_GET(self):
auth = ('requeststest', 'requeststest')
url = 'https://convore.com/api/account/verify.json'
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 200)
# reset auto authentication
requests.auth_manager.empty()
def test_POSTBIN_GET_POST_FILES(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
post = requests.post(bin.url, data={'some': 'data'})
self.assertEqual(post.status_code, 201)
post2 = requests.post(bin.url, files={'some': open('test_requests.py')})
self.assertEqual(post2.status_code, 201)
post3 = requests.post(bin.url, data='[{"some": "json"}]')
self.assertEqual(post.status_code, 201)
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
post2 = requests.post(bin.url, files={'some': open('test_requests.py')}, data={'some': 'data'})
self.assertEqual(post2.status_code, 201)
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
bin = requests.post('http://www.postbin.org/')
self.assertEqual(bin.status_code, 200)
post2 = requests.post(bin.url, files={'some': open('test_requests.py')},
headers={'User-Agent': 'requests-tests'})
self.assertEqual(post2.status_code, 201)
def test_nonzero_evaluation(self):
r = requests.get('http://google.com/some-404-url')
self.assertEqual(bool(r), False)
r = requests.get('http://google.com/')
self.assertEqual(bool(r), True)
def test_request_ok_set(self):
r = requests.get('http://google.com/some-404-url')
self.assertEqual(r.ok, False)
def test_status_raising(self):
r = requests.get('http://google.com/some-404-url')
self.assertRaises(requests.HTTPError, r.raise_for_status)
r = requests.get('http://google.com/')
self.assertFalse(r.error)
r.raise_for_status()
def test_cookie_jar(self):
"""
.. todo:: This really doesn't test to make sure the cookie is working
"""
jar = cookielib.CookieJar()
self.assertFalse(jar)
requests.get('http://google.com', cookies=jar)
self.assertTrue(jar)
def test_decompress_gzip(self):
r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags')
r.content.decode('ascii')
def test_autoauth(self):
conv_auth = ('requeststest', 'requeststest')
requests.auth_manager.add_auth('convore.com', conv_auth)
r = requests.get('https://convore.com/api/account/verify.json')
self.assertEquals(r.status_code, 200)
def test_unicode_get(self):
requests.get('http://google.com', params={'foo': u'føø'})
requests.get('http://google.com', params={u'føø': u'føø'})
requests.get('http://google.com', params={'føø': 'føø'})
requests.get('http://google.com', params={'foo': u'foo'})
requests.get('http://google.com/ø', params={'foo': u'foo'})
def test_httpauth_recursion(self):
conv_auth = ('requeststest', 'bad_password')
r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth)
self.assertEquals(r.status_code, 401)
if __name__ == '__main__':
unittest.main()
unittest.main()

11
tox.ini Normal file
View File

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