Compare commits

...

562 Commits

Author SHA1 Message Date
Kenneth Reitz
10d2398173 fix test for python3 2012-06-07 22:21:06 -04:00
Kenneth Reitz
32941fdd20 Merge remote-tracking branch 'origin/develop' into develop 2012-06-07 22:09:16 -04:00
Kenneth Reitz
7c2306baec v0.13.1 2012-06-07 22:09:01 -04:00
Kenneth Reitz
404585ce8c += AUTHORS #659 2012-06-07 22:07:26 -04:00
Kenneth Reitz
78eecd7d7e Merge pull request #659 from Bluehorn/develop
Allow passing a file or file-like object as data.
2012-06-07 19:06:49 -07:00
Kenneth Reitz
fadcf799de fix unicode literal for python 3 2012-06-07 21:58:52 -04:00
Kenneth Reitz
8bfbfdfc79 Merge remote-tracking branch 'origin/develop' into develop 2012-06-07 21:55:11 -04:00
Kenneth Reitz
843672f4fe make empty body text responses work
#660
2012-06-07 21:55:07 -04:00
Kenneth Reitz
e8f3fc33bb test that empty body text responses work 2012-06-07 21:54:45 -04:00
Kenneth Reitz
89266f9062 Merge pull request #665 from dalanmiller/patch-2
If requests.async has been removed, the docs should point people to the right place.
2012-06-07 16:55:06 -07:00
Daniel Miller
c589d8a251 If this comment is true (https://github.com/kennethreitz/requests/issues/239#issuecomment-6180706), then it would be good to point people in the right direction. Otherwise disregard this message. 2012-06-07 16:47:54 -07:00
Torsten Landschoff
f0e327f979 Allow passing a file or file-like object as data.
The underlying httplib already allows passing an open file object as body
to its HTTPConnection.request method. I think requests should allow that
as well.
2012-06-07 10:13:40 +02:00
Kenneth Reitz
c0ae3dd31e more examples
#657
2012-06-07 03:09:19 +02:00
Kenneth Reitz
dda9689ca4 Revert "In fact, the feature is so awesome that it should go into index.rst"
This reverts commit f968fe0136.
2012-06-07 03:02:32 +02:00
Danilo Bargen
f968fe0136 In fact, the feature is so awesome that it should go into index.rst 2012-06-06 17:07:42 +02:00
Danilo Bargen
1164450dac Added r.json documentation (document ALL the awesome features!) 2012-06-06 16:59:20 +02:00
Kenneth Reitz
aa42318395 v0.13.1 2012-06-05 10:25:08 +02:00
Kenneth Reitz
488a51d619 Revert "Auto-detect CA Cert path instead of requiring users to hack setup.py"
This reverts commit 62a23bfbde.
2012-06-05 00:51:46 +02:00
Kenneth Reitz
12e5f3855c Merge pull request #651 from urbanairship/detect-cacerts
Auto-detect CA Cert path instead of requiring users to hack setup.py
2012-06-04 15:24:14 -07:00
Michael Schurter
62a23bfbde Auto-detect CA Cert path instead of requiring users to hack setup.py
Also fixed nearby comments to wrap at 79 characters as per PEP8
2012-06-04 13:19:10 -07:00
Kenneth Reitz
b026def961 Merge pull request #646 from mkomitee/hooks
Allowing hooks to return responses that indicate errors
2012-06-04 07:44:56 -07:00
Michael Komitee
662c3edacc Allowing hooks to return responses that indicate errors
Since response objects for failures (4xx/5xx responses) evaluate to False
in a boolean context, any hook which returns such a failure response will
evaluate to False.

The way hooks were setup, any failure response resulting from a hook would
be ignored, and the initial response before it got processed by the hook
would be substituted in its place. This commit changes that logic to test
for None so that hooks that return failures can do so.
2012-06-01 16:05:22 -04:00
Kenneth Reitz
355b97165c Merge pull request #643 from gwrtheyrn/docs_proxy_auth
Added information about HTTP proxy authentication to docs
2012-05-30 12:48:33 -07:00
Danilo Bargen
d81dfe266e Added information about HTTP proxy authentication to docs 2012-05-30 17:34:24 +02:00
Kenneth Reitz
77a57c0e7e v0.13.0 2012-05-29 20:53:51 -04:00
Kenneth Reitz
a291d0d0f8 whitespace 2012-05-29 20:53:13 -04:00
Kenneth Reitz
617091e747 v0.13.0 2012-05-29 20:52:58 -04:00
Kenneth Reitz
cb15310d6a by @mwielgoszewski 2012-05-29 19:54:40 -04:00
Kenneth Reitz
1c0abbd74b cleanup #633 2012-05-29 19:53:22 -04:00
Kenneth Reitz
7813bd8ac0 Merge pull request #633 from tzuryby/develop
poor man's patch for issue #630
2012-05-29 16:51:48 -07:00
Kenneth Reitz
ad8205f930 Merge pull request #619 from Lukasa/develop
Rewrite quickstart docs.
2012-05-29 16:49:51 -07:00
Kenneth Reitz
f9dd485582 Merge pull request #640 from JuhaS/patch-1
Small correction on a comment.
2012-05-29 16:45:54 -07:00
Kenneth Reitz
d0f309b947 Merge pull request #638 from mher/develop
no_proxy support
2012-05-29 16:44:56 -07:00
Kenneth Reitz
400b4567f0 Merge pull request #632 from amalakar/develop
Issue #505: Changed the store_cookie configuration to be part of the conig dict, instead of being a new argument
2012-05-29 16:41:39 -07:00
JuhaS
ba4cc326c2 Small correction on a comment. 2012-05-28 16:28:31 +03:00
Mher Movsisyan
cb8f9756e3 Fixes python3 compatibility issue 2012-05-28 14:11:59 +05:00
Mher Movsisyan
b8561cda5e no_proxy support 2012-05-27 22:21:26 +05:00
Tzury Bar Yochay
7125fe5554 this try catch is a poor man's patch for issue #630 see https://github.com/kennethreitz/requests/issues/630 2012-05-24 06:17:21 +03:00
Arup Malakar
4a30979622 Change the None check from self.assertIsNone to python style None Check, as build was failing 2012-05-23 14:25:54 -07:00
Arup Malakar
b28673368b Issue #505: Changed the store_cookie configuration to be part of the config dict, instead of being a new argument 2012-05-23 14:12:18 -07:00
Kenneth Reitz
de1637c3de Merge pull request #626 from joshimhoff/develop
Improvements to RequestsCookieJar
2012-05-21 21:45:44 -07:00
Josh Imhoff
0eb0b89431 Removed print statements left over from debugging. 2012-05-21 20:27:47 -04:00
Josh Imhoff
96cd8e9ca0 Wrote tests for new CookieJar functionality and made them pass. 2012-05-21 20:14:12 -04:00
Josh Imhoff
9f26d0ced3 In cookies.py, CookieConflictError is now thrown if there is more than one cookie with same name. 2012-05-21 18:21:12 -04:00
Josh Imhoff
b7d0925d6b Docstrings and bug fixes in cookies.py 2012-05-21 17:59:38 -04:00
Cory Benfield
2b8e01bff7 First draft of full verbs. 2012-05-19 21:34:36 +01:00
Josh Imhoff
686db5d452 Defined keys(), values(), and items() in order to support dict-like client interface. Now, we throw exceptions if __getitem__() or __setitem__() is used when multiple domains are in jar. 2012-05-18 00:48:59 -04:00
Cory Benfield
36fd8da1c0 Correctly reflect redirection behaviour.
The docs incorrectly listed HEAD as a method that follows redirects by
default: it does not. This commit resolves this issue, and thus resolves
issue #504.
2012-05-17 19:25:35 +01:00
Cory Benfield
45506d1f39 Rewrite quickstart docs.
This should help with issue #503.
2012-05-17 10:16:42 +01:00
Kenneth Reitz
2d9746621a Merge pull request #617 from mwielgoszewski/develop
Expose case-insensitive key access when deleting a key from CaseInsensitveDict
2012-05-16 17:34:02 -07:00
Kenneth Reitz
3c14435109 fix test for 2.6's unittest 2012-05-16 17:13:55 -07:00
Kenneth Reitz
4cf31b0b86 authors 2012-05-16 17:09:43 -07:00
Arup Malakar
c501784541 Issue #505: Allow disabling of cookie persistence/sending 2012-05-16 15:07:15 -07:00
Marcin Wielgoszewski
4b7c5dab0e Expose case-insensitive key access when deleting a key from CaseInsensitiveDict 2012-05-16 13:58:51 -04:00
Kenneth Reitz
c477f6eb6f Merge pull request #562 from slingamn/urandom
replace utils.randombytes with os.urandom
2012-05-15 22:49:27 -07:00
Shivaram Lingamneni
f25827c5b6 replace utils.randombytes with os.urandom 2012-05-15 15:42:03 -07:00
Kenneth Reitz
355003b400 Merge pull request #614 from ib-lundgren/multipart_fix
Better content-type detection and unicode fix
2012-05-15 08:39:20 -07:00
Ib Lundgren
6a9d59ea97 Unicode fix for py3 2012-05-15 17:37:15 +02:00
Kenneth Reitz
2f50cb92b6 Merge pull request #613 from surajbarkale/patch-1
Remove sharing of base_headers config between sessions.
2012-05-15 08:31:43 -07:00
Ib Lundgren
c24665b593 Better content-type detection and unicode fix 2012-05-15 17:20:01 +02:00
Suraj Barkale
973e9cb548 Remove sharing of base_headers config between sessions.
Since base_headers is a dictionary, it's reference was shared with all instances of
Session objects. Using deepcopy here should not incur large performance penalty.
2012-05-15 19:47:02 +05:30
Kenneth Reitz
763393f70a Merge pull request #611 from pnasrat/develop
Improve developer documentation.
2012-05-13 19:47:19 -07:00
Paul Nasrat
2c11473816 Improve developer documentation.
I cloned, followed the todo and had failures as no certifi or chardet.

Fixed up so easy to get started.
2012-05-13 16:09:52 -07:00
Kenneth Reitz
40d325366b Merge pull request #610 from medwards/whitespace
Removed extra whitespace
2012-05-13 11:01:22 -07:00
Michael Edwards
51f7628078 Removed extra whitespace 2012-05-13 11:59:31 +02:00
Kenneth Reitz
4161a7ec6a Merge pull request #607 from ztest/develop
Fix a docstring typo (of -> if).
2012-05-12 14:23:04 -07:00
ztest
ed767cb703 Fix a docstring typo (of -> if). 2012-05-12 13:09:14 -04:00
Shivaram Lingamneni
0442122d34 remove unused import 2012-05-10 15:14:44 -07:00
Kenneth Reitz
327b9eab87 semver 2012-05-10 17:11:35 -05:00
Kenneth Reitz
3ee594140b remove oreos 2012-05-10 17:10:47 -05:00
Kenneth Reitz
15694fa2f7 remove gevent extras_require 2012-05-10 17:09:53 -05:00
Kenneth Reitz
07f6675376 remove requests async tests 2012-05-10 17:02:22 -05:00
Kenneth Reitz
32327f8f7c remove requests.async 2012-05-10 17:01:09 -05:00
Kenneth Reitz
a050faf084 makin' history 2012-05-10 13:10:50 -05:00
Kenneth Reitz
0a37d82444 Merge pull request #603 from benmao/develop
get cookie like dict, such as s.cookies.get('test', 'no cookie')
2012-05-10 11:07:00 -07:00
Kenneth Reitz
3d2d072927 Merge pull request #604 from joshimhoff/develop
New implementation of safe_mode.
2012-05-10 11:06:24 -07:00
ben
44663b5e8e get cookie like dict, such as self.cookies.get('test', 'no cookie') 2012-05-10 22:17:23 +08:00
Josh Imhoff
db43eb4cb9 Now, safe_mode will catch socket.timeout too. 2012-05-09 15:13:12 -04:00
Josh Imhoff
cff70e75b7 New implementation of safe_mode. Now, we throw exceptions in models.py regardless of safe_mode. We catch those exceptions at the API level and return a blank Response. See safe_mode.py for details. 2012-05-09 14:47:29 -04:00
Kenneth Reitz
2d328d8a7f Merge pull request #595 from reclosedev/sess-cookies-no-resp
Session cookies not saved when Session.request is called with return_response=False
2012-05-08 08:06:51 -07:00
Roman Haritonov
c010f5895a Add myself to AUTHORS.rst 2012-05-08 14:30:24 +04:00
Roman Haritonov
33f1087584 Move session cookie saving from Session.request() to Request._build_response() 2012-05-08 13:02:39 +04:00
Roman Haritonov
975430d5e6 Test async requests with Session cookies 2012-05-08 12:55:36 +04:00
Roman Haritonov
56ef079e6f Test if cookies are saving to session when session.request is called with return_response=False 2012-05-08 12:17:58 +04:00
Kenneth Reitz
0308a2806a Merge pull request #594 from slingamn/makefile_again
unbreak the Jenkins build again/hopefully :-/
2012-05-07 22:46:00 -07:00
Shivaram Lingamneni
175945b0bd remove the oauthlib trunk from requirements.txt 2012-05-07 22:40:11 -07:00
Shivaram Lingamneni
2ee67441c8 unbreak the Jenkins build (again/hopefully)
Pyflakes is unavailable in the Jenkins Python 3 / PyPy environments
2012-05-07 22:36:51 -07:00
Kenneth Reitz
6fd03d5f20 thread safe 2012-05-08 01:24:30 -04:00
Kenneth Reitz
a3a928c7f1 gah 2012-05-08 01:21:48 -04:00
Kenneth Reitz
bca920c1c1 v0.12.1 2012-05-08 01:18:05 -04:00
Kenneth Reitz
6b09e6ee34 redirects clear files also
Fixes #575
2012-05-08 01:14:17 -04:00
Kenneth Reitz
2a7811bf2f Merge remote-tracking branch 'origin/develop' into develop 2012-05-08 00:58:32 -04:00
Kenneth Reitz
e627c1f8a8 Merge pull request #559 from barberj/issue_526
Issue #526
2012-05-07 21:58:20 -07:00
Kenneth Reitz
fe7348cc53 yay json
fixes #593
2012-05-08 00:57:46 -04:00
barberj
b066902705 adding test verifying data httpbin sees 2012-05-06 19:24:37 -04:00
barberj
64eb415ec0 synatx error for python3. all strings are unicode 2012-05-06 18:50:42 -04:00
barberj
e912a6f5da sending strings to be received as files 2012-05-06 15:14:57 -07:00
barberj
3288426f2b added unicode strings 2012-05-06 15:14:57 -07:00
barberj
0a7d98871c credit where credits due 2012-05-06 15:14:57 -07:00
barberj
ed7b14899f added mutlipart file=strings 2012-05-06 15:14:40 -07:00
Kenneth Reitz
7b6338c4b6 Merge pull request #590 from berkerpeksag/doc-links
Fixed broken links in TODO docs.
2012-05-06 12:54:06 -07:00
Berker Peksag
a0f991dbad Fixed broken links in TODO docs. 2012-05-06 22:26:28 +03:00
Kenneth Reitz
c84012edec Merge pull request #585 from chazapis/develop
Fix out-of-range issue with iter_lines (#515).
2012-05-04 11:49:49 -07:00
Kenneth Reitz
7f571f5e5a Merge pull request #587 from slingamn/jenkins
unbreak the Jenkins build by excluding ./env/*
2012-05-04 11:48:35 -07:00
Shivaram Lingamneni
d4227051a9 unbreak the Jenkins build by excluding ./env/* 2012-05-04 04:45:30 -07:00
Kenneth Reitz
3d373e2a16 Merge pull request #579 from slingamn/pyflakes.release
Changes for #574
2012-05-03 23:29:04 -07:00
Kenneth Reitz
38e6695627 Merge pull request #581 from crodjer/develop
A utility for using environment proxies
2012-05-03 23:28:07 -07:00
Kenneth Reitz
8bd6515564 Merge pull request #582 from slingamn/cookietests.release
Tests for persistent and secure cookies
2012-05-03 23:26:44 -07:00
Shivaram Lingamneni
8fedec785a Tests for persistent and secure cookies 2012-05-03 23:20:37 -07:00
Rohan Jain
7cf6ca80a6 Add myself to authors
Signed-off-by: Rohan Jain <crodjer@gmail.com>
2012-05-04 10:59:57 +05:30
Rohan Jain
abc38cf17f Use utility for environment proxy as fallback
Signed-off-by: Rohan Jain <crodjer@gmail.com>
2012-05-04 10:44:07 +05:30
Rohan Jain
0ba8c44260 A utility function to fetch environment proxies
This adds support for lowercase environment proxy variables (which are
quite popular too). It returns proxies in a format compatible with
request's proxy parameter.
Moreover, it can be used in the request models for proxy defaults.

Signed-off-by: Rohan Jain <crodjer@gmail.com>
2012-05-04 10:44:01 +05:30
Kenneth Reitz
1adaba3865 Merge pull request #580 from slingamn/keepalive
Fix and test for #458
2012-05-03 21:33:42 -07:00
Shivaram Lingamneni
35ec6bb613 fix for #458 2012-05-03 21:16:56 -07:00
Shivaram Lingamneni
3d4d5ab0df test for #458 2012-05-03 21:16:46 -07:00
Shivaram Lingamneni
bcc4125a18 quiet spurious pyflakes errors 2012-05-03 19:59:15 -07:00
Shivaram Lingamneni
7f9ea04286 Various Makefile updates 2012-05-03 19:59:15 -07:00
Kenneth Reitz
317f64a11f Merge pull request #577 from slingamn/redirects_537
Fix and test for #537
2012-05-03 01:36:12 -07:00
Kenneth Reitz
a3e55634d6 Merge pull request #572 from slingamn/chardet_2
cleanup, redux
2012-05-03 01:35:47 -07:00
Shivaram Lingamneni
9bf53676b8 fix crash in safe mode when response.cookies is None 2012-05-03 01:29:12 -07:00
Shivaram Lingamneni
41d1a427b0 add a test for max_redirects in safe/normal modes 2012-05-03 01:29:12 -07:00
Shivaram Lingamneni
853a07112b consistency: .text shouldn't recover from unicode decoding errors, either 2012-05-02 20:21:02 -07:00
Shivaram Lingamneni
a8555d811d remove fancy test assertions that are unavailable on 2.6 2012-05-02 20:21:02 -07:00
Shivaram Lingamneni
538de55607 raise a ValueError if no encoding was sent and chardet is unavailable 2012-05-02 20:21:02 -07:00
Shivaram Lingamneni
57ccd7e65c Have Travis run all tests except the external and async ones
(async is currently unsupported under Python 3)
2012-05-02 20:20:26 -07:00
Kenneth Reitz
a9033912b3 missed import 2012-05-02 19:18:14 -04:00
Kenneth Reitz
373a34a144 date fix 2012-05-02 19:17:14 -04:00
Kenneth Reitz
9324c2a9b4 oauthlib 2012-05-02 19:15:43 -04:00
Kenneth Reitz
fb2931801e v0.12.0 2012-05-02 19:13:19 -04:00
Kenneth Reitz
8ac6b266c9 pre-send hook
#516
2012-05-02 19:09:18 -04:00
Kenneth Reitz
efe5d13ed6 Merge pull request #516 from EnTeQuAk/move_pre_request
Move pre_request to a more usable place, followup to #382
2012-05-02 15:58:08 -07:00
Kenneth Reitz
a9963e3841 upper case automatically 2012-05-02 18:57:06 -04:00
Kenneth Reitz
f1d104e004 making history 2012-05-02 18:56:51 -04:00
Kenneth Reitz
996f99b267 Merge remote-tracking branch 'origin/develop' into develop 2012-05-02 18:47:53 -04:00
Kenneth Reitz
62d2ea8034 default chunk size for .content much bigger 2012-05-02 18:47:50 -04:00
Kenneth Reitz
ed15c70cd0 Merge pull request #573 from idangazit/develop
Lazily encode data, params, files
2012-05-02 14:19:56 -07:00
Idan Gazit
cf6b20429a Merge remote-tracking branch 'upstream/develop' into develop 2012-05-03 00:08:00 +03:00
Idan Gazit
324336e7f6 Lazily encode data, params, files
Previously, data, params, and files were encoded and stored in
Request.__init__, and subsequently put into service during
Request.send. The problem with this approach is that hooks and auth
callables need to be aware of the eager encoding, and if they touch the
originals, make sure to update the encoded versions.

A better approach is to only encode late in the sending process. This
way, hooks and auth callables can safely make changes without fear of
the old, encoded variant overriding it.
2012-05-03 00:04:13 +03:00
Idan Gazit
d240b1fe15 PEP8 Cleanup 2012-05-02 23:16:38 +03:00
Antony Chazapis
c333bd144e Fix out-of-range issue with iter_lines (#515). 2012-05-02 16:51:19 +03:00
Kenneth Reitz
53d60cac4f Revert "Update Makefile and travis configuration to run all (non-external) tests"
This reverts commit a453a78c7c.
2012-05-02 02:38:20 -04:00
Kenneth Reitz
6271046a27 Revert "Cache the value of the Response.text property"
This reverts commit d232948188.
2012-05-02 02:38:11 -04:00
Kenneth Reitz
8a81a437d2 Revert "Use utf-8 as the default encoding"
This reverts commit 35d5ac6e8f.
2012-05-02 02:37:51 -04:00
Shivaram Lingamneni
a453a78c7c Update Makefile and travis configuration to run all (non-external) tests 2012-05-01 23:21:05 -07:00
Shivaram Lingamneni
bdd7f8f8a2 Change setup.py to reflect required/optional dependencies 2012-05-01 23:21:05 -07:00
Shivaram Lingamneni
d232948188 Cache the value of the Response.text property 2012-05-01 22:28:50 -07:00
Shivaram Lingamneni
35d5ac6e8f Use utf-8 as the default encoding
(fixes crashes on failure to import chardet)
2012-05-01 21:46:53 -07:00
Shivaram Lingamneni
fdd08215ea update documentation to correspond to new cookie API 2012-05-01 21:46:53 -07:00
Shivaram Lingamneni
ff7cfa732f restore docs/_build directory to .gitignore 2012-05-01 19:18:55 -07:00
Kenneth Reitz
45be14ed6f Merge pull request #565 from slingamn/cookiejar
Support CookieJar, references #281
2012-05-01 17:22:37 -07:00
Shivaram Lingamneni
11a3eaec26 add Python 3 compatibility 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
7832f2ae7e add laurentb's test case for cookie handling on redirects 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
6eb349f5d2 remove unused import in test 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
ff169d32fe fix and test for a bug dhagrow reported 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
404ecaf95b clarified documentation from e-mail discussion 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
1de14e6a93 fix usage of compat.urlparse
(this code path appears to have been exercised only by secure cookies)
2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
492bda1343 add a test for redirection 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
4d6871d917 Further changes to move cookies to CookieJar 2012-05-01 17:00:19 -07:00
miguel
c4ab96b638 mostly complete cookie support based on cookiejar 2012-05-01 17:00:19 -07:00
miguel
fed6cfbf4d mostly complete cookie support based on cookiejar 2012-05-01 17:00:19 -07:00
miguel
2a27b123fd catch TooManyRedirects in safe_mode 2012-05-01 17:00:19 -07:00
Shivaram Lingamneni
7c5151e8e7 changes to .gitignore 2012-05-01 17:00:19 -07:00
Kenneth Reitz
a189c238cc LOLJK 2012-05-01 07:22:59 -04:00
Kenneth Reitz
d427bb8603 OAUTH ALL THE THINGS 2012-05-01 07:19:04 -04:00
Kenneth Reitz
7eed89a0df v0.11.3 2012-05-01 07:17:35 -04:00
Kenneth Reitz
5d9699bbdc oauthlib only for 2 2012-05-01 07:07:18 -04:00
Kenneth Reitz
e6cce7a5bd syntax errors 2012-05-01 07:06:55 -04:00
Kenneth Reitz
d79e1bbaad or str 2012-05-01 07:02:13 -04:00
Kenneth Reitz
bf1744eea2 hax 2012-05-01 06:57:25 -04:00
Kenneth Reitz
9427e1efe8 missed an import 2012-05-01 06:55:19 -04:00
Kenneth Reitz
661af1dc1a only import oauthlib in python2 2012-05-01 06:54:09 -04:00
Kenneth Reitz
a5bc99d53e no -e 2012-05-01 06:50:38 -04:00
Kenneth Reitz
14fae695bf uh, sure 2012-05-01 06:47:58 -04:00
Kenneth Reitz
be04142723 Revert "Call auth callable before encoding body data"
This reverts commit 9217a2523d.
2012-05-01 06:42:58 -04:00
Kenneth Reitz
748c65f7a7 git+git://github.com/idangazit/oauthlib.git 2012-05-01 06:27:55 -04:00
Idan Gazit
2fc9f6bbc9 Trim down OAuth1 callable logic
Moved much of the validation out of requests and into oauthlib
2012-05-01 06:20:53 -04:00
Idan Gazit
65bf3ab201 Backing out stupidly incorrect str vs unicode check
It's simply wrong. Since compat.py sets str = unicode for py2, the
original logic is sound.
2012-05-01 06:20:53 -04:00
Idan Gazit
906fcd7328 Fix order of return values from OAuth Client.sign() 2012-05-01 06:20:52 -04:00
Idan Gazit
7b819852b9 Fix Content-Type validation in OAuth1 callable 2012-05-01 06:20:52 -04:00
Idan Gazit
9217a2523d Call auth callable before encoding body data 2012-05-01 06:20:52 -04:00
Idan Gazit
0aedef07c3 Improve _encode_params to handle lists of two-element tuples 2012-05-01 06:20:52 -04:00
Idan Gazit
3e60a9eb9c Proper test for str vs unicode when preparing data
In python 2.x, requests sets str = unicode in requests/compat.py. This causes
isinstance(foo, str) to return True even if foo is a unicode string.
2012-05-01 06:20:52 -04:00
Idan Gazit
798dc4ae1d Initial implementation of OAuth1 request signing with OAuthLib 2012-05-01 06:20:52 -04:00
Idan Gazit
632d9893b0 Improve docstring description of auth parameter 2012-05-01 06:20:52 -04:00
Idan Gazit
809d9863bf Add .egg-info/ dir to gitignore 2012-05-01 06:20:52 -04:00
Kenneth Reitz
742cbeaee1 httpbin weirdness 2012-05-01 05:51:59 -04:00
Kenneth Reitz
2159c80270 v0.11.2 2012-04-22 22:29:26 -04:00
Kenneth Reitz
8e9446d3fb revert 2012-04-22 22:29:00 -04:00
Kenneth Reitz
a7da521a62 merge conflict 2012-04-22 22:26:53 -04:00
Kenneth Reitz
2621fe2bff Revert "Add failing tests for invalid %encodings"
This reverts commit e28c1c9bfa.
2012-04-22 22:26:07 -04:00
Kenneth Reitz
3843a40f7c Revert "Reenable test_session_with_escaped_url test"
This reverts commit 3550ac7473.
2012-04-22 22:25:57 -04:00
Kenneth Reitz
f647352d01 v0.11.2 2012-04-22 22:08:39 -04:00
Kenneth Reitz
1631f0f5ae Merge pull request #552 from slingamn/bundle_2
attempt to use the OS's certificate bundle
2012-04-22 18:53:31 -07:00
Shivaram Lingamneni
b4eb8663af prefer certifi's bundle to the OS bundle 2012-04-22 18:43:59 -07:00
Shivaram Lingamneni
e1528ce3be add the FreeBSD certificate bundle path 2012-04-19 14:27:26 -07:00
Shivaram Lingamneni
1360e77cb2 Add a smoke test for https functionality 2012-04-16 17:50:40 -07:00
Shivaram Lingamneni
9921099546 Try to use the OS's CA certificate bundle for SSL verification 2012-04-16 15:28:31 -07:00
Kenneth Reitz
2f6b5feb1a Merge pull request #550 from reclosedev/develop
Fix issue #549. Add 'prefetch' to Session.__attrs__ and Requests.__init__
2012-04-14 03:19:10 -07:00
Roman Haritonov
d569a2d6bc fix #549 Add 'prefetch' to Session.__attrs__ and Requests.__init__ 2012-04-13 19:25:10 +04:00
Kenneth Reitz
e3ba495ed9 Merge branch 'develop' of https://github.com/catwell/requests into develop 2012-04-13 08:34:18 +02:00
Kenneth Reitz
d4a77cf2af Merge pull request #548 from umbrae/httperror_fix
A get with an invalid port should wrap urllib3's LocationParseError exception with our own.
2012-04-12 12:44:44 -07:00
Chris Dary
3a2eaa67a2 A get with an invalid port should wrap urllib3's LocationParseError exception with our own. 2012-04-12 12:35:34 -04:00
Pierre Chapuis
f3ad56f103 Encode Multipart Post data at Request creation
This fixes #298 (File upload not working with
HTTP digest authentication)
2012-04-12 17:49:48 +02:00
Pierre Chapuis
c3e6c41fc1 Fix infinite loop on wrong Digest Authentication 2012-04-12 16:33:15 +02:00
Kenneth Reitz
8a055c5d42 Merge pull request #535 from Jonty/develop
Fix decoding of invalid %encodings in URLs
2012-04-11 08:02:46 -07:00
Jonty Wareing
06e4971fe7 Rewrite unquote_unreserved based on urllib.unquote
This is almost entirely taken from the unquote implementation in urllib,
slightly modified for the case in hand. It now deals with invalid %encodings
rather than exploding, and the code is somewhat simpler.
2012-04-11 14:56:19 +00:00
Jonty Wareing
e28c1c9bfa Add failing tests for invalid %encodings 2012-04-11 14:55:43 +00:00
Jonty Wareing
3550ac7473 Reenable test_session_with_escaped_url test 2012-04-11 14:55:43 +00:00
Idan Gazit
51800bf36f Trigger setup.py develop when installing requirements 2012-04-11 14:05:33 +03:00
Idan Gazit
c89a3e207e Clarify digest hash method names 2012-04-11 14:02:59 +03:00
Kenneth Reitz
365c64fe3b #539 2012-04-10 23:00:25 -03:00
Kenneth Reitz
6b652f766d Merge pull request #545 from sharat87/patch-1
Add docs on sending lists for multivalued query params.
2012-04-10 08:43:46 -07:00
Shrikant Sharat Kandula
5a8a8f2bd3 Add docs on sending lists for multivalued query params. 2012-04-10 14:42:38 +05:30
Kenneth Reitz
821cbb3e08 moar 2012-04-09 04:22:10 -04:00
Kenneth Reitz
f8798aee28 Merge pull request #542 from newmaniese/develop
Attempting to address issue #377 by making content on the response model a little smarter
2012-04-08 20:13:38 -07:00
Michael Newman
11cd2b8253 Fixing a silly typo in my test 2012-04-08 18:46:26 -04:00
Michael Newman
1677c688cb Shamelessly adding myself to the authors list 2012-04-08 18:42:08 -04:00
Michael Newman
b92feb3f0a If there is no content in a response don't throw an error the second time that content is attempted to be read. This addresses issue #377 <https://github.com/kennethreitz/requests/issues/377> 2012-04-08 18:39:44 -04:00
Kenneth Reitz
7d4d91f013 Merge pull request #540 from jhalcrow/patch-1
Correct path to test_requests.py for setup.py test
2012-04-06 10:01:19 -07:00
Jonathan Halcrow
3279906737 Correct path to test_requests.py for setup.py test 2012-04-06 12:50:16 -03:00
Kenneth Reitz
c7b1bc8197 Merge pull request #534 from ThiefMaster/patch-1
Remove bad tab indent
2012-04-04 01:05:00 -07:00
Adrian
da288ad57a Remove bad tab indent 2012-04-04 11:03:49 +03:00
Andrey Petrov
03ae9bee74 Fixed typoe (s/repsonses/responses/) 2012-04-02 16:59:08 -07:00
Kenneth Reitz
a364395612 Merge pull request #530 from Osmose/sidebar_donate
Fix #506: Change "Support Requests" to "Donate".
2012-04-02 00:44:00 -07:00
Kenneth Reitz
37f3a29d49 Michael Kelly 2012-04-02 03:43:11 -04:00
Michael Kelly
48396f97a9 Fix #506: Change "Support Requests" to "Donate". 2012-04-02 00:40:20 -07:00
Kenneth Reitz
ccd19c53c8 Merge remote-tracking branch 'origin/develop' into develop 2012-03-31 17:39:31 -04:00
Kenneth Reitz
2bae0d295b new urllib3 version
#498
2012-03-31 17:39:26 -04:00
Kenneth Reitz
8755969629 Merge pull request #529 from piotr-dobrogost/develop
Removed unnecessary leading slashes in httpbin's endpoints
2012-03-31 13:21:17 -07:00
Piotr Dobrogost
e12d053ab8 Removed unnecessary leading slashes in httpbin's endpoints 2012-03-31 22:16:55 +02:00
Kenneth Reitz
13b07cb5d8 Merge pull request #528 from floppym/dist-tests
Include tests in tarballs.
2012-03-31 11:36:21 -07:00
Mike Gilbert
d3adc9d334 Include tests in tarballs. 2012-03-31 14:22:49 -04:00
Kenneth Reitz
751e2ea2d4 roadmap 2012-03-31 03:21:31 -04:00
Kenneth Reitz
dab3df7619 fake change for commit hook test 2012-03-31 03:11:43 -04:00
Kenneth Reitz
171b370b0e app engine #498 2012-03-31 03:08:12 -04:00
Kenneth Reitz
bcbc1be1c8 blah 2012-03-31 03:06:28 -04:00
Kenneth Reitz
04c329d703 license=open("LICENSE").read( 2012-03-31 02:53:14 -04:00
Kenneth Reitz
9a62aa6380 .. :changelog: 2012-03-31 02:51:38 -04:00
Kenneth Reitz
11e5c8eab7 HTTP/1.1 2012-03-31 00:08:38 -04:00
Kenneth Reitz
775b6f6f00 v0.11.1 2012-03-30 23:42:56 -04:00
Kenneth Reitz
dacc1d8aaf default to true for redirects now, since safe 2012-03-30 23:42:04 -04:00
Kenneth Reitz
18591920f3 upload data on redirects too 2012-03-30 23:31:19 -04:00
Kenneth Reitz
193eb42891 v0.11.1 2012-03-30 23:30:56 -04:00
Kenneth Reitz
b20d171c1c POST redirect => GET
#269
2012-03-30 23:28:07 -04:00
Kenneth Reitz
590211e13e Merge remote-tracking branch 'origin/develop' into develop 2012-03-30 23:15:47 -04:00
Kenneth Reitz
fff5269d1a Fix app engine error for #498 2012-03-30 23:15:43 -04:00
Kenneth Reitz
e831947be4 strict mode for #269 2012-03-30 23:15:31 -04:00
Kenneth Reitz
c205ce658f Merge pull request #519 from pulpe/doc-fix
little correction of curl in docs
2012-03-30 19:58:38 -07:00
pulpe
13060f89c7 little correction of curl in docs 2012-03-28 10:45:21 +02:00
Christopher Grebs
f25901874a Move pre_request to a more usable place, followup to #382
Having the hook called so late makes it impossible to overwrite the HTTP request
calling behavior and set self.sent to True in case of possible cache matches
for example.

This way we can interfere before we waste any CPU time to calculate
a request.
2012-03-27 15:38:56 +02:00
Kenneth Reitz
3cc6acaca4 Merge remote-tracking branch 'origin/develop' into develop 2012-03-25 15:26:58 -04:00
Kenneth Reitz
21ccf8f8c0 urllib3 v1.3 2012-03-25 15:26:55 -04:00
Kenneth Reitz
b973d08b0e Merge pull request #512 from silent1mezzo/master
Adds MissingSchema and InvalidSchema Exceptions
2012-03-25 12:00:57 -07:00
Adam McKerlie
d24ee0e1dc Created MissingSchema and InvalidSchema which inherits from RequestException and ValueError 2012-03-25 11:03:36 -04:00
Kenneth Reitz
afb1bd103a paypal 2012-03-25 03:56:00 -04:00
Kenneth Reitz
4c1852f928 Merge branch 'develop' of github.com:kennethreitz/requests into develop 2012-03-22 15:00:51 -04:00
Kenneth Reitz
6e26f5754b #508 2012-03-22 15:00:47 -04:00
Daniel Miller
82d1b340dc Suggesting changing "async.map(rs)" to "responses = async.map(rs)" just to show that it returns a list rather than mutating the original? Maybe this is apparent for most, but I was a bit confused =/ http://goo.gl/gE4up 2012-03-22 11:57:19 -07:00
Kenneth Reitz
eab2ab9faa Steve Pulec 2012-03-18 22:19:48 -04:00
Kenneth Reitz
12427e3434 Merge branch 'patch-1' of https://github.com/spulec/requests into develop 2012-03-18 22:18:49 -04:00
Kenneth Reitz
76bf523a1f Merge #491 2012-03-18 22:15:05 -04:00
Kenneth Reitz
613816dd9d Merge pull request #488 from newmaniese/develop
Excluding select from the monkey patching that gevent does to allow sele...
2012-03-18 19:08:51 -07:00
Kenneth Reitz
f8ec2544ce Merge pull request #494 from JDrosdeck/doc
Add quickstart doc and example for passing parameters in a GET request
2012-03-18 19:07:31 -07:00
Steve Pulec
3c2f52d681 Fixes issues #496 by having Response.ok catch all Requests exceptions. 2012-03-18 23:06:42 -03:00
Kenneth Reitz
3baf3ab069 Merge pull request #497 from iandanforth/develop
Develop
2012-03-18 19:04:56 -07:00
Kenneth Reitz
5c575b30c2 Merge remote-tracking branch 'origin/develop' into develop 2012-03-18 22:02:46 -04:00
Kenneth Reitz
e65f52aae4 remove debian packaging warning
it's up to date now
2012-03-18 22:02:43 -04:00
Ian Danforth
44277441d6 Sphinx throws an error if you don't escape ** in **kwargs 2012-03-18 18:11:15 -07:00
Ian Danforth
7b68c124d5 Sphinx throws an error if you don't escape ** in **kwargs 2012-03-18 18:09:58 -07:00
Jonathan Drosdeck
f2172922c5 Add quickstart doc and example for passing parameters in a GET request 2012-03-18 15:37:46 -04:00
Michael Newman
079a825ac3 Merge https://github.com/kennethreitz/requests into develop 2012-03-16 15:32:50 -04:00
Kenneth Reitz
9cf9d76eb5 Merge pull request #477 from maxcountryman/develop
potentially fixes #338
2012-03-15 18:15:27 -07:00
Kenneth Reitz
dc7029ec98 Merge pull request #483 from maxcountryman/cleanup
removing unused import
2012-03-15 18:14:35 -07:00
Kenneth Reitz
f0019c06d7 Merge pull request #489 from maxcountryman/patch-1
fixing link to AUTHORS in README
2012-03-15 18:13:26 -07:00
Max Countryman
a094ad8f31 fixing link to AUTHORS in README 2012-03-15 22:05:08 -03:00
Michael Newman
d6827a37d0 Excluding select from the monkey patching that gevent does to allow select.poll and requests.async in the same project
Fix for issue #487, including a regression test that checks the
existence of select.poll before and after loading requests.async.
2012-03-15 14:41:45 -04:00
Kenneth Reitz
5b7a79b0ff Merge pull request #486 from Pinkerton/patch-1
Fix issue #484
2012-03-14 20:08:17 -07:00
Pinkerton
ee861f4287 Fix issue #484 2012-03-14 19:54:06 -07:00
Kenneth Reitz
0c0bab3ad0 v0.11.0 2012-03-14 20:31:43 -04:00
Kenneth Reitz
1c73ec22e9 Nick Chadwick 2012-03-14 20:30:03 -04:00
Kenneth Reitz
4677ba5cd9 only verify private cert when verity is true 2012-03-14 20:29:30 -04:00
Kenneth Reitz
093709be4d Merge branch 'develop' of https://github.com/chadnickbok/requests into develop 2012-03-14 20:27:07 -04:00
Kenneth Reitz
f6ed16bbbe Merge remote-tracking branch 'origin/develop' into develop 2012-03-14 20:27:02 -04:00
chadnickbok@gmail.com
fc618aa78a Updated Requests api to accept a 'cert' argument.
This argument can be either a string, containing the path to a
pem-formatted key and certificate chain, or a tuple of (cert, key).

When supplied a tuple, the values are paths to an SSL certificate
chain file and key, respectively.
2012-03-14 17:15:29 -07:00
chadnickbok@gmail.com
82e69de44f Exposed key_file and cert_file in requests, to support https client certificates. 2012-03-14 15:57:30 -07:00
Max Countryman
8f8062723a removing unused import 2012-03-13 20:10:32 -04:00
Idan Gazit
cc4381b91e Merge branch 'develop' of github.com:kennethreitz/requests into develop 2012-03-13 17:05:24 -07:00
Idan Gazit
eae641485b Remove unused import 2012-03-13 17:04:26 -07:00
Kenneth Reitz
29309201b6 remove cruft 2012-03-13 16:44:45 -07:00
Kenneth Reitz
0dd6c40205 dict sequence 2012-03-13 16:42:16 -07:00
Kenneth Reitz
84d9a1b577 dict_to_sequence 2012-03-13 16:39:51 -07:00
Idan Gazit
17170a2ca7 Merge branch 'develop', remote-tracking branch 'upstream/develop' into develop 2012-03-13 12:59:33 -07:00
Kenneth Reitz
8d34538f4c further lies. 2012-03-12 20:54:02 -07:00
Kenneth Reitz
8d1a85a8d0 Merge branch 'josh' into develop 2012-03-12 20:51:59 -07:00
Kenneth Reitz
06f048e507 lies. 2012-03-12 20:46:23 -07:00
Max Countryman
83a9f2c740 explicating the cause of LookupError with a better comment 2012-03-11 22:22:29 -04:00
Max Countryman
bc5cc0dc92 potentially fixes #338
This attempts to fix an issue where encoding of a string might fail when the
encoding is set to some unknown format. Here we attempt to catch the
LookupException and subsequently blindly encode the string one final time.
That is we call str() over response.content without specifying an encoding.
This may still fail in certain cases but does properly handle the case of #338
by returning the expected string.
2012-03-09 17:30:35 -05:00
Kenneth Reitz
6562137731 cookie history 2012-03-09 08:59:12 -08:00
Kenneth Reitz
d078b69aef catch cookie errors that are prone to failure 2012-03-09 08:56:41 -08:00
Kenneth Reitz
0a1e527aec Merge of maxcountryman 2012-03-09 08:51:03 -08:00
Kenneth Reitz
9f55f503b1 merge conflict and fixes for python3 2012-03-09 08:43:12 -08:00
Kenneth Reitz
253cba373b _detected_encoding 2012-03-09 08:35:51 -08:00
Kenneth Reitz
9451ae2e98 iter_lines py3 compatibility
#470
2012-03-09 08:26:12 -08:00
Kenneth Reitz
d203349740 Merge remote-tracking branch 'origin/develop' into develop 2012-03-09 08:07:17 -08:00
Kenneth Reitz
4aad69594d chunk_size iter_lines 2012-03-09 08:07:10 -08:00
Kenneth Reitz
c7f165ae6f Merge branch 'iter_lines-fix' of https://github.com/fawek/requests into develop 2012-03-09 08:03:20 -08:00
Kenneth Reitz
98cbde1324 Chris Dary 2012-03-09 07:59:08 -08:00
Kenneth Reitz
77e5443f71 self.config.get('trust_env')
just for @mitsuhiko
2012-03-09 07:58:00 -08:00
Kenneth Reitz
e15ae8f9a7 v0.10.8 2012-03-09 07:55:54 -08:00
Kenneth Reitz
0eb2509449 Merge pull request #475 from umbrae/generate-chunked-valueerror-fix
Generate chunked ValueError fix
2012-03-08 14:57:58 -08:00
Chris Dary
bd0e002804 Move chunked head redirect test to ext 2012-03-08 14:44:07 -08:00
Chris Dary
0e65dff008 Remove unnecessary bit from previous branch.
This reverts commit e2645826be.
2012-03-08 13:22:38 -08:00
Chris Dary
fba77fc65f Potential fix for #436 2012-03-08 13:10:18 -08:00
Chris Dary
1cf5ec872c Merge remote-tracking branch 'upstream/develop' into develop 2012-03-08 08:19:33 -08:00
Johannes Gorset
21c2146be4 Facilitate proxy configuration by environment variables 2012-03-08 13:34:23 +01:00
Kenneth Reitz
2f91d8e773 bunk 2012-03-07 16:54:53 -08:00
Kenneth Reitz
946f7ec579 percent encoded 2012-03-07 16:50:02 -08:00
Kenneth Reitz
658d609e9f default encode_uri to True 2012-03-07 16:46:15 -08:00
Kenneth Reitz
abca7f0ad0 Merge remote-tracking branch 'origin/develop' into develop 2012-03-07 16:30:30 -08:00
Kenneth Reitz
cf5ddecc4c encode_uri 2012-03-07 16:28:04 -08:00
Kenneth Reitz
c1a44259d6 disablable encode_urls 2012-03-07 16:25:46 -08:00
Kenneth Reitz
54b2cb0b27 move config around a bit 2012-03-07 16:25:27 -08:00
Kenneth Reitz
09b6aef835 encode urls setting 2012-03-07 16:13:34 -08:00
Jakub Wieczorek
228c5e68ae Considerably simplify and fix the iter_lines() function
1. Do not use rstrip() as this would also throw away meaningful whitespaces
2. Avoid assuming what striplines() considers a line break terminator,
   the list of those is quite long in Unicode.
3. Add tests for the above edge cases.
2012-03-05 18:39:55 +01:00
Kenneth Reitz
6bb0529bed Merge pull request #466 from maxcountryman/develop
fixing a capitalization error in the send() method doc string
2012-03-01 14:21:45 -08:00
Max Countryman
1532effb68 fixing a capitalization error in the send() method doc string 2012-03-01 17:20:01 -05:00
Kenneth Reitz
65eaebb177 Merge remote-tracking branch 'origin/develop' into develop 2012-02-28 15:21:21 -05:00
Kenneth Reitz
ab0714ce9c develop 2012-02-28 15:21:12 -05:00
Kenneth Reitz
a3a7c504ad Merge pull request #464 from brendanmaguire/session_prefetch
Removed prefetch from args.
2012-02-28 12:19:25 -08:00
Brendan Maguire
ca45da39fa Removed prefetch from args. 2012-02-28 20:16:01 +00:00
Kenneth Reitz
313ff95ee4 Merge pull request #463 from brendanmaguire/session_prefetch
Prefetch for sessions
2012-02-28 12:08:48 -08:00
Brendan Maguire
97815c9930 Prefetch for sessions 2012-02-28 20:02:21 +00:00
Chris Dary
e2645826be Decode URL to utf-8 before joining.
To avoid UnicodeDecodeError's like on http://blip.fm/~1abvfu
2012-02-28 12:08:37 -05:00
Kenneth Reitz
7cc898f9ed master build test 2012-02-26 11:30:44 -05:00
Kenneth Reitz
ec979dc60a nose not found :( 2012-02-26 10:26:45 -05:00
Kenneth Reitz
32a6d07036 simpleci 2012-02-26 10:25:26 -05:00
Kenneth Reitz
d6c2d8f1dc simpleci 2012-02-26 10:24:48 -05:00
Kenneth Reitz
4a5c94f410 rudolf the red-nosed reindeer 2012-02-26 10:22:47 -05:00
Kenneth Reitz
0eb5b633aa v0.10.6 2012-02-25 23:17:45 -05:00
Kenneth Reitz
56c36ac27d history 2012-02-25 23:16:08 -05:00
Kenneth Reitz
508bd26741 Merge remote-tracking branch 'origin/develop' into develop 2012-02-25 23:15:54 -05:00
Kenneth Reitz
835053c55e #452 2012-02-25 23:15:33 -05:00
Kenneth Reitz
f0e0a62bbe Merge branch 'develop' of https://github.com/honzajavorek/requests into develop 2012-02-25 23:14:49 -05:00
Kenneth Reitz
bdcbabb758 Merge pull request #450 from Lukasa/issue_440
Issue #440: Accept '=' in cookie keys.
2012-02-25 20:14:11 -08:00
Kenneth Reitz
7cc2d468c7 urllib3 v1.2.2 2012-02-25 23:11:20 -05:00
Kenneth Reitz
83f4c01564 urllib3 release 2012-02-25 23:11:06 -05:00
Kenneth Reitz
cfeb485434 v0.10.5 2012-02-25 23:05:39 -05:00
Kenneth Reitz
d3b7f98814 hurray for passing tests 2012-02-25 22:44:46 -05:00
Kenneth Reitz
f5b0043c08 #457 2012-02-25 22:40:18 -05:00
Kenneth Reitz
6ce27f7d55 Merge branch 'develop' of https://github.com/asciimoo/requests into develop 2012-02-25 22:39:29 -05:00
asciimoo
531dc35126 [enh] async.imap added -- generator object handling 2012-02-26 04:04:33 +01:00
Kenneth Reitz
d974b46dbe simplify httpbin stuff 2012-02-25 21:57:30 -05:00
Kenneth Reitz
ea1d5f3648 Fix content-length: 0
#454
2012-02-24 18:42:33 -05:00
Kenneth Reitz
83b4081e85 Builds?? 2012-02-24 00:45:17 -05:00
Kenneth Reitz
e18e79d820 python3 fix 2012-02-24 00:44:01 -05:00
Kenneth Reitz
7234c7240a Merge branch 'master' into develop 2012-02-23 23:51:24 -05:00
Kenneth Reitz
7fa6276eb7 i can't believe i'm doing this 2012-02-23 22:43:16 -05:00
Kenneth Reitz
bbdf81dc1c teh spelling 2012-02-23 22:34:17 -05:00
Kenneth Reitz
4b0d53dcb7 no color 2012-02-23 22:27:29 -05:00
Kenneth Reitz
746d2cad5d mirrors 2012-02-23 22:22:43 -05:00
Kenneth Reitz
14f57f6585 Merge remote-tracking branch 'origin/develop' into develop 2012-02-23 22:21:16 -05:00
Kenneth Reitz
e63dff0a3a travis ci 2012-02-23 22:14:31 -05:00
Honza Javorek
09c0365255 str is redefined in compat module, but was not imported 2012-02-23 23:51:59 +01:00
Honza Javorek
1661bf6543 get_unicode_from_response returned str instead of unicode 2012-02-23 17:28:56 +01:00
Cory Benfield
2ef0b1b51d Accept '=' in cookie keys. 2012-02-21 22:51:38 +00:00
Kenneth Reitz
3c58600187 Merge pull request #449 from benweatherman/develop
Don't bonk if .netrc isn't readable
2012-02-21 13:55:50 -08:00
Ben
572c15d049 Don't bonk if .netrc isn't readable.
It's possible in some shared hosting environments that the .netrc file isn't readable by the user for the calling process. In that circumstance, just forgo the netrc authentication.
2012-02-21 15:53:33 -06:00
Kenneth Reitz
99a6074816 Merge pull request #447 from gwrtheyrn/patch-1
Added StringIO/Image example to quickstart documentation.
2012-02-21 00:27:16 -08:00
Danilo Bargen
8b750ee182 Added StringIO/Image example to quickstart documentation. 2012-02-21 09:24:52 +01:00
Kenneth Reitz
c9b4920230 v0.10.4 2012-02-20 16:21:25 -05:00
Kenneth Reitz
793d314c8d Merge branch 'netrc' into develop 2012-02-20 16:20:44 -05:00
Kenneth Reitz
8c873712fe .netrc 2012-02-20 16:20:07 -05:00
Kenneth Reitz
faae6b87b1 v0.10.4 2012-02-20 16:12:08 -05:00
Kenneth Reitz
fd7610dd7a python 3 unicode grr 2012-02-20 16:07:36 -05:00
Kenneth Reitz
b0e1865073 netrc fix 2012-02-20 15:43:45 -05:00
Kenneth Reitz
de0658f7e8 stick .netrc auth in pipeline 2012-02-20 15:43:38 -05:00
Kenneth Reitz
79bb9ee141 netrc parsing 2012-02-20 15:35:19 -05:00
Kenneth Reitz
2f33679cb3 spacing 2012-02-20 15:35:02 -05:00
Kenneth Reitz
6f5735274b history for HEAD 3xx 2012-02-20 14:02:13 -05:00
Kenneth Reitz
709828204a HEADs don't follow redirects by default. 2012-02-20 13:56:11 -05:00
Kenneth Reitz
b355b678a9 danger_mode redirect test 2012-02-20 13:51:46 -05:00
Kenneth Reitz
124c172c72 100k times 2012-02-20 13:49:29 -05:00
Kenneth Reitz
709c8bdcbf v0.10.3 2012-02-20 13:49:06 -05:00
Kenneth Reitz
2c55d269ce allow 3XX in danger_mode 2012-02-20 13:46:54 -05:00
Kenneth Reitz
e0d1b09ffe don't raise for redirects if they're not allowed 2012-02-20 13:40:55 -05:00
Kenneth Reitz
ceb42dd5de no more SOPA 2012-02-20 12:39:25 -05:00
Kenneth Reitz
80fe5dc147 none headers test
#433
2012-02-20 12:31:52 -05:00
Kenneth Reitz
d806ffc1cd test cleanups 2012-02-20 12:30:22 -05:00
Kenneth Reitz
4f75f1f41d Values can be none #433 2012-02-20 12:30:12 -05:00
Kenneth Reitz
fe10eeb5be SCHEMAS 2012-02-20 12:25:53 -05:00
Kenneth Reitz
cf37f63ccb #435 2012-02-20 12:22:11 -05:00
Kenneth Reitz
e7efce91fb models cleanup 2012-02-20 12:21:01 -05:00
Kenneth Reitz
5f834715d0 Merge branch 'issue_380' of https://github.com/Lukasa/requests into develop 2012-02-20 12:11:31 -05:00
Kenneth Reitz
dd89df314d v0.10.3 2012-02-20 12:09:46 -05:00
Kenneth Reitz
fdbb53db19 __file__ 2012-02-20 12:06:59 -05:00
Shrikant Sharat
590ce29743 Implemented #439. Added supporting test. 2012-02-19 15:11:19 +05:30
Kenneth Reitz
7548bc7ccc Update HISTORY.rst 2012-02-16 18:10:44 -05:00
Cory Benfield
544ebf402f Correct unfortunate typo. 2012-02-16 20:42:05 +00:00
Cory Benfield
640538adcb Fail if unsupported schemas are used.
requests only supports http and https. This change enforces that.
2012-02-16 20:20:20 +00:00
Cory Benfield
1ded0ad3d5 Remove a wayward change. 2012-02-16 19:32:20 +00:00
Cory Benfield
d339b0f64f Added failing test for issue #380. 2012-02-16 18:21:32 +00:00
Kenneth Reitz
8e2696b385 history 2012-02-15 03:14:48 -05:00
Kenneth Reitz
b5212ddc3d remove bunk utils for decoding 2012-02-15 03:09:40 -05:00
Kenneth Reitz
dacaae520a bytes data fix for python3 2012-02-15 03:09:26 -05:00
Kenneth Reitz
e663c77e70 fix bytes 2012-02-15 02:54:00 -05:00
Kenneth Reitz
ec109c9ac8 path fixes 2012-02-15 02:53:50 -05:00
Kenneth Reitz
0b9a2a1581 move tests over 2012-02-15 02:52:32 -05:00
Kenneth Reitz
2759060559 it's a start 2012-02-15 02:45:21 -05:00
Kenneth Reitz
00b13673e3 simplify 2012-02-15 02:28:58 -05:00
Kenneth Reitz
91ac5ba110 don't rely on urllib3's decode 2012-02-15 02:07:55 -05:00
Kenneth Reitz
0d8c08cd22 binary data upload bug test 2012-02-15 02:03:29 -05:00
Kenneth Reitz
ab4c03adc3 cleanups 2012-02-15 02:03:13 -05:00
Kenneth Reitz
75b0c4be1b latin1, remove unicode_literals
Fixes #407
2012-02-15 02:01:36 -05:00
Kenneth Reitz
4a8e6415c7 sp 2012-02-15 01:47:08 -05:00
Kenneth Reitz
58bf158840 RuntimeError 2012-02-15 01:46:06 -05:00
Kenneth Reitz
c0f066626e whitespace 2012-02-15 01:42:57 -05:00
Kenneth Reitz
69c7a8638e make all the tests 2012-02-15 01:42:52 -05:00
Kenneth Reitz
1b6f1b091c Merge branch '400-header_expand-unicode' of https://github.com/acdha/requests into develop 2012-02-15 01:25:29 -05:00
Kenneth Reitz
d39cdc45d1 decode response url again 2012-02-15 01:21:38 -05:00
Kenneth Reitz
7f761f3adf history for 0.10.2 2012-02-15 01:18:41 -05:00
Kenneth Reitz
5e76943f02 Merge branch 'develop' of https://github.com/Lukasa/requests into develop 2012-02-15 01:08:10 -05:00
Kenneth Reitz
b887ea3b60 Merge branch 'mgiuca-uri-cleanup-nonpath' into develop 2012-02-15 01:00:52 -05:00
Matt Giuca
690426ac86 Added Matt Giuca to AUTHORS. 2012-02-15 12:06:34 +11:00
Matt Giuca
b8298b0710 Rename utils.requote_path to requote_uri.
It is now applied to a whole URI, not just the path.
2012-02-15 12:03:08 +11:00
Matt Giuca
3bd5406c06 Now requotes the entire URL, not just the path (Issue #429). 2012-02-15 12:00:47 +11:00
Matt Giuca
3ae0ff3685 Added test cases for URI cleaning in the query part (Issue #429). 2012-02-15 12:00:27 +11:00
Cory Benfield
80e29bfcab Change the RFC we reference. 2012-02-14 14:11:25 +00:00
Cory Benfield
d7107c2cdc Make clearer the source of forbidden characters 2012-02-14 12:58:35 +00:00
Cory Benfield
658da765ec Keep non RFC cookie key characters separate.
Moved the characters that we accept in keys but that the RFC forbids
into a separate string to keep track of them.
2012-02-14 10:25:40 +00:00
Kenneth Reitz
9e2f5dd0ed Merge branch 'py3-uri-encoding' of https://github.com/mgiuca/requests into develop 2012-02-14 04:30:10 -05:00
Matt Giuca
1ffce4f7dc Simplify requote_path.
It no longer needs to split on '/' since '/' will not be encoded.
2012-02-14 12:57:49 +11:00
Matt Giuca
fcac1c3746 Fixed URI encoding of reserved characters (Issue #369).
Previously, util.requote_path would unquote and requote all characters,
causing reserved characters to become encoded (changing the semantics of the
URI). Now, it has special code for unquoting just the unreserved characters,
then quotes only illegal characters.
This ensures that illegal characters are fixed, and URIs are normalised, but
reserved characters do not erroneously become quoted.
Test case test_session_with_escaped_url now passes.
2012-02-14 12:51:03 +11:00
Matt Giuca
c0763bb8d5 Changed test case test_path_is_not_double_encoded.
Uses the space character instead of ~ as a test (since ~ is unreserved, it
shouldn't really be encoded at all).
2012-02-14 12:49:31 +11:00
Matt Giuca
75bd9d0e94 Added test case testing URI normalisation.
Tests that percent-encoded unreserved characters get de-encoded.
2012-02-14 12:20:48 +11:00
Matt Giuca
08bc1198d3 Added test case for Issue #369.
Tests that legal reserved and unreserved characters in the path are not
percent-encoded. Currently fails.
2012-02-14 12:15:32 +11:00
Matt Giuca
cf54f60367 Fixed URI re-encoding on Python 3 (Issue #369).
Request.full_url now performs requoting of the path (like it does in Python 2).
Request.path_url no longer quotes the already-quoted path (double quoting).
Fixed utils.requote_path so it works properly in Python 3.
2012-02-14 11:50:02 +11:00
Matt Giuca
329efcae6e Added test case for quoted and illegal characters in the URL.
This is a test case for issue #369.
It passes in Python 2, but fails in Python 3 (though the test suite doesn't
actually work in Python 3, it can be shown to fail with some modification).
2012-02-14 11:47:14 +11:00
Kenneth Reitz
5a2a78f7df Merge pull request #425 from Lukasa/develop
Issue #335: Accept unusual keys in cookies.
2012-02-13 16:14:39 -08:00
Cory Benfield
f72c13ffda Accept unusual keys in cookies.
The Oreos module throws CookieErrors when it catches keys that have
characters that shouldn't be in them, like colons and braces. Other
services are happy to send out such keys, however. This commit makes
requests non-standards compliant, just like everyone else is.
2012-02-14 00:10:26 +00:00
Chris Adams
0a17badb21 header_expand: handle Unicode strings (closes #400) 2012-02-13 11:10:05 -05:00
Kenneth Reitz
12f9aa3669 Merge pull request #410 from eriolv/develop
Fix SyntaxError on Python3 because "except Error, e" is not supported anymore
2012-02-12 02:16:48 -08:00
Kenneth Reitz
afa2e86a4d Merge pull request #413 from bancek/patch-1
Typo fix in oreos/structures.py
2012-02-06 09:08:03 -08:00
Luka Zakrajšek
b7842ab34b Typo fix in oreos/structures.py 2012-02-06 13:42:34 +01:00
Greg Taylor
f8b3141204 Use basestring instead of str to determine when to return headers as-is. This makes sure that unicode instances aren't enumerated. 2012-02-05 13:57:35 -05:00
Daniele Tricoli
c40d6d0509 Since requests 0.10.1 dropped Python5 support, to catch an exception and
store the exception object in a variable the "as" keyword can be used so
Python3 will not raise a SyntaxError
2012-02-05 04:18:22 +01:00
Kenneth Reitz
a74481a1d4 Update docs/community/faq.rst 2012-02-03 13:24:00 -05:00
Kenneth Reitz
b898415d0d :trollface:
because i can.
2012-02-02 17:23:47 -05:00
Kenneth Reitz
b47effe5d8 test #2 2012-02-01 13:35:34 -05:00
Kenneth Reitz
4f10605389 quick test 2012-02-01 13:33:59 -05:00
Kenneth Reitz
120e62900b Native Instruments! 2012-01-31 02:15:55 -05:00
Kenneth Reitz
986704dc62 v0.10.2 2012-01-30 09:41:09 -05:00
Kenneth Reitz
2f13956357 Merge branch 'unicode_missing_self' of https://github.com/Telofy/requests into develop 2012-01-30 09:35:53 -05:00
Kenneth Reitz
8d700dd1e5 Merge branch 'pr-urlfix-1' of https://github.com/brendoncrawford/requests into develop 2012-01-30 09:33:31 -05:00
Kenneth Reitz
5be412db13 Brendon Crawford 2012-01-30 09:33:14 -05:00
Telofy
a61a861cfb The self was missing again, and I think the two blocks were redundant. 2012-01-30 12:23:43 +01:00
Brendon Crawford
c363ca476f Added ability to check if missing trailing slash in path.
Previously this URL would not work:

    http://example.com?foo=bar

But this URL would work:

    http://example.com/?foo=bar

Even though a slash is required for a valid URL, it is a common use case
for the slash to be left out, so it would be a good idea to account for this.
2012-01-29 23:24:54 -08:00
Kenneth Reitz
319b1ed551 Merge branch '239-socket-leak' of https://github.com/acdha/requests into develop 2012-01-29 19:06:41 -05:00
Chris Adams
67f50a0fe5 Less redundant freeing of resources on redirects
With 495646bb6a the double flush is no longer
needed.
2012-01-29 18:45:14 -05:00
Chris Adams
b98cc8b4e9 Revert "Initial stop-the-bleeding fix"
This reverts commit 730c0e2e2b.
2012-01-29 18:43:13 -05:00
Chris Adams
495646bb6a async: force prefetch=True on send
… otherwise we can run out of sockets before any of the responses are ever
processed
2012-01-29 18:42:48 -05:00
Kenneth Reitz
4516bd45b2 Release redirected connections.
#239
2012-01-29 18:00:33 -05:00
Chris Adams
730c0e2e2b Initial stop-the-bleeding fix
This is redundant but it avoids crashing otherwise working code. The code-smell
from duplication suggests that this interface should be refactored slightly.
2012-01-29 17:31:59 -05:00
Chris Adams
3020d0e56a Consistent self.cookies usage
This avoids two variables pointing to the same thing and avoids
a duplicate dict update() call
2012-01-29 16:52:08 -05:00
Chris Adams
49bed18a83 Minor PEP-8 cleanup 2012-01-29 16:51:28 -05:00
Kenneth Reitz
e6959412aa only do oreos for 2 2012-01-28 23:10:03 -05:00
Kenneth Reitz
04c0bcc9b4 wow. 2012-01-27 11:18:56 -06:00
Kenneth Reitz
ec4edae977 lazy tests 2012-01-27 11:16:47 -06:00
David Kemp (Work)
f478c34671 add failing test for unicode headers (python 2.x only) 2012-01-26 18:45:38 +00:00
Kenneth Reitz
a0ef4f2358 Merge pull request #389 from johtso/patch-1
Fix httpbin urls with missing .org in docs
2012-01-25 22:40:30 -08:00
Johannes
ca57e93ebf Fix httpbin urls with missing .org 2012-01-26 02:13:04 +00:00
Kenneth Reitz
5ca5b7ec65 Merge remote-tracking branch 'origin/develop' into develop 2012-01-25 11:26:08 -05:00
Kenneth Reitz
2b202aabd7 David Kemp 2012-01-25 11:26:04 -05:00
Kenneth Reitz
cb64d31171 Merge pull request #387 from davidarkemp/develop
fix problem with path being double escaped
2012-01-25 08:25:26 -08:00
David Kemp (Work)
80eafe3b63 fix problem with path being double escaped 2012-01-25 16:21:07 +00:00
Kenneth Reitz
16aa444261 Merge branch 'develop' of https://github.com/mastahyeti/requests into develop 2012-01-25 09:04:41 -05:00
Kenneth Reitz
0bbdbac17f moar tests 2012-01-25 09:03:20 -05:00
Piotr Dobrogost
c430b60e38 Standard tests from RequestsTestSuite run using async api 2012-01-24 21:56:59 +01:00
Ben
02f6555536 added myself to authors.rst 2012-01-24 09:59:41 -06:00
Mastahyeti
4293e3fdb2 moved call to pre_request hook, allowing for more meaningful use. 2012-01-23 20:56:05 -06:00
Kenneth Reitz
50298bbf97 80k 2012-01-23 02:52:38 -05:00
Kenneth Reitz
ac82fa47a0 @takluyver 2012-01-23 02:49:10 -05:00
Kenneth Reitz
976bfbf687 text 2012-01-23 02:43:38 -05:00
Kenneth Reitz
1bc377b188 r.text 2012-01-23 02:42:34 -05:00
Kenneth Reitz
a89530aef5 explain binary response data 2012-01-23 02:42:05 -05:00
Kenneth Reitz
c32b1cd6d6 better feature list 2012-01-23 02:34:42 -05:00
Kenneth Reitz
90f5c8bd85 r.text 2012-01-23 02:32:09 -05:00
Kenneth Reitz
878bb61d24 readme update 2012-01-23 02:31:16 -05:00
Kenneth Reitz
8493d8329e PYTHON 3 2012-01-23 02:28:01 -05:00
Kenneth Reitz
57aeafa115 pythons 2012-01-23 02:21:35 -05:00
Kenneth Reitz
a29e862adf new pythons! 2012-01-23 02:21:30 -05:00
Kenneth Reitz
5ed62e1137 don't let that happen... 2012-01-23 02:19:56 -05:00
Kenneth Reitz
f99a2534c9 v0.10.1 2012-01-23 02:18:26 -05:00
Kenneth Reitz
a939423006 dammit, i changed again 2012-01-23 02:03:16 -05:00
Kenneth Reitz
4493acc5ee fix 26 2012-01-23 02:01:53 -05:00
Kenneth Reitz
c4eb9301bb assert isinstance 2012-01-23 01:59:13 -05:00
Kenneth Reitz
57ba337e79 assert isinstance 2012-01-23 01:58:32 -05:00
Kenneth Reitz
3d542693bc no more unittest2 2012-01-23 01:56:58 -05:00
Kenneth Reitz
5f2607ffca yikes 2012-01-23 01:54:00 -05:00
Kenneth Reitz
ab916a8a8d hmm 2012-01-23 01:52:33 -05:00
Kenneth Reitz
1fb4bd82f7 cchch changes 2012-01-23 01:48:40 -05:00
Kenneth Reitz
d892e93589 requirements.txt 2012-01-23 01:47:39 -05:00
Kenneth Reitz
38c2532c5f new certifi 2012-01-23 01:46:41 -05:00
Kenneth Reitz
18b5ee7d5b fixes 2012-01-23 01:44:07 -05:00
Kenneth Reitz
9fd2d07c1d reqs fixes 2012-01-23 01:44:01 -05:00
Kenneth Reitz
e7bcc3b332 oh snap 2012-01-23 01:32:09 -05:00
Kenneth Reitz
b001fcf968 SimpleCookie 2012-01-23 01:02:16 -05:00
Kenneth Reitz
125619c783 urllib3 update 2012-01-23 01:01:42 -05:00
Kenneth Reitz
fe6882623c more stuff and things 2012-01-23 00:52:38 -05:00
Kenneth Reitz
11c4315da3 good 2012-01-23 00:43:01 -05:00
Kenneth Reitz
672e66ba5f compat 2012-01-23 00:34:31 -05:00
Kenneth Reitz
bcfddb3d65 cleanups and stuffs 2012-01-23 00:29:50 -05:00
Kenneth Reitz
9cae98c167 blah 2012-01-23 00:25:48 -05:00
Kenneth Reitz
b0e2e4f284 blah 2012-01-23 00:15:25 -05:00
Kenneth Reitz
36842db26f stuff and things 2012-01-22 20:12:49 -05:00
Kenneth Reitz
e84a7c7e6f whatever 2012-01-22 20:12:43 -05:00
Kenneth Reitz
2109afc144 further 2012-01-22 19:42:13 -05:00
Kenneth Reitz
786dfdd7e5 python3 version of urllib3 2012-01-22 15:06:58 -05:00
Kenneth Reitz
62d05f842d dates 2012-01-21 23:19:49 -05:00
Kenneth Reitz
fc3b03a5f5 @gulopine 2012-01-21 23:18:06 -05:00
Kenneth Reitz
f4f2bd9fab Response.url. 2012-01-21 23:10:37 -05:00
Kenneth Reitz
b0555b68e7 history! 2012-01-21 23:06:34 -05:00
Kenneth Reitz
497695864b Beautiful history. 2012-01-21 23:03:07 -05:00
Kenneth Reitz
128b49a1fb whitespace 2012-01-21 23:00:28 -05:00
Kenneth Reitz
0dbda6665d cleanup 2012-01-21 22:59:57 -05:00
Kenneth Reitz
464279f48d typo
Signed-off-by: Kenneth Reitz <me@kennethreitz.com>
2012-01-21 22:58:38 -05:00
Kenneth Reitz
b14a244ab4 simpler 2012-01-21 22:57:24 -05:00
Kenneth Reitz
2c7e920298 don't double decode 2012-01-21 22:56:53 -05:00
Kenneth Reitz
b725bfd3fb Use chardet for detection when no encoding is provided. 2012-01-21 22:55:41 -05:00
Kenneth Reitz
47e96b8040 chardet 2012-01-21 22:35:19 -05:00
Kenneth Reitz
9a1348707c chardet 2012-01-21 22:35:11 -05:00
Kenneth Reitz
ada98899f8 build number 2012-01-21 20:00:12 -05:00
Kenneth Reitz
16a7a26452 v0.10.0 2012-01-21 19:58:51 -05:00
Kenneth Reitz
2c04f9d199 Merge remote-tracking branch 'origin/develop' into develop 2012-01-21 17:48:55 -05:00
Kenneth Reitz
70e98e47cf No encoding? r.text is None. 2012-01-21 17:48:51 -05:00
Kenneth Reitz
b525dc29f7 Merge pull request #370 from piotr-dobrogost/develop
tiny map() refactoring
2012-01-21 05:13:15 -08:00
Kenneth Reitz
a3fb5ac326 history 2012-01-21 07:47:46 -05:00
Kenneth Reitz
8e6f98f44e Merge branch 'redsea' into develop 2012-01-21 07:17:30 -05:00
Kenneth Reitz
9ba93b885c history 2012-01-21 07:17:23 -05:00
Kenneth Reitz
5f44d79729 move digest to new register_hook
#372
2012-01-21 07:14:26 -05:00
Kenneth Reitz
a3b26c7e5b Provide new register_hook system.
#372
2012-01-21 07:13:58 -05:00
Kenneth Reitz
e9be7c1a58 make list of HOOKS available 2012-01-21 07:07:18 -05:00
Kenneth Reitz
6c359b2457 Merge branch 'develop' of https://github.com/jgorset/requests into redsea 2012-01-21 06:36:06 -05:00
Johannes Gorset
7647e52943 Facilitate for multiple hooks 2012-01-21 12:14:55 +01:00
Johannes Gorset
a0ae2e6c7b Default the encoding of "text" media subtypes to "ISO-8859-1"
Ref. RFC2616 (HyperText Transfer Protocol), section 3.7.1 (Canonicalization and Text Defaults).
2012-01-21 11:01:45 +01:00
Kenneth Reitz
efd2b57b92 cleanup docs for request defaults 2012-01-21 01:44:57 -05:00
Kenneth Reitz
47aba0cc20 remove is_error 2012-01-21 01:31:23 -05:00
Kenneth Reitz
f5a2b75924 only try to decode if self.encoding is set 2012-01-21 01:28:26 -05:00
Kenneth Reitz
49a38ac89a New bytes/unicode separation.
Response.content for bytes.
New Response.text for unicode.
2012-01-21 01:24:21 -05:00
Kenneth Reitz
6d071f4331 kill decode_unicode config 2012-01-21 01:23:35 -05:00
Piotr Dobrogost
3873121c01 tiny map() refactoring 2012-01-20 21:42:25 +01:00
Kenneth Reitz
5bff8e362f Update docs/dev/todo.rst 2012-01-19 18:43:43 -05:00
Kenneth Reitz
62733146be Merge pull request #366 from jgorset/how-to-help
Elaborate on how to help
2012-01-19 15:42:55 -08:00
Kenneth Reitz
99f6581770 Merge pull request #368 from jgorset/unicode-response-url
Decode response URL to a unicode string.
2012-01-19 15:42:27 -08:00
Johannes Gorset
b37fbf30a3 Decode response URL to a unicode string.
Fixes #182
2012-01-20 00:34:44 +01:00
Johannes Gorset
e65d0ac9e3 Elaborate on how to help 2012-01-20 00:09:30 +01:00
Kenneth Reitz
61a0e85672 Merge pull request #364 from gthank/8efb6cc870bd23de0d8d87708217d818236a733f
Allow sessions and async requests to mix.
2012-01-19 11:12:23 -08:00
Kenneth Reitz
c18e6459a8 v0.9.3 2012-01-18 21:56:33 -05:00
Kenneth Reitz
6db255d17d Merge branch 'develop' of github.com:kennethreitz/requests into develop 2012-01-18 21:49:25 -05:00
Kenneth Reitz
17f2002839 Merge pull request #363 from gazpachoking/develop
Stop always verifying certs for same host that has verified once.
2012-01-18 17:19:43 -08:00
Chase Sterling
a6ee9757a7 Add myself to AUTHORS 2012-01-18 19:51:10 -05:00
Chase Sterling
d9cb18ad2a Make sure verify=False is respected for session even when there has already been a verified request to the same host.
Update tests to expose the (fixed) problem.
2012-01-18 19:49:22 -05:00
Hank Gay
8efb6cc870 Allow mixing sessions and async. 2012-01-17 11:44:59 -05:00
57 changed files with 4174 additions and 2948 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
workon r

13
.gitignore vendored
View File

@@ -1,9 +1,14 @@
.coverage
MANIFEST
coverage.xml
nosetests.xml
junit-report.xml
pylint.txt
*.pyc
docs/_build
toy.py
.gitignore
junit-report.xml
violations.pyflakes.txt
cover/
docs/_build
requests.egg-info/
*.pyc
*.swp
env/

10
.travis.yml Normal file
View File

@@ -0,0 +1,10 @@
language: python
python:
- 2.6
- 2.7
- 3.2
env: HTTPBIN_URL=http://httpbin.org/
script: make travis
install:
- pip install nose
- pip install . --use-mirrors

1
.workon Normal file
View File

@@ -0,0 +1 @@
/Users/kreitz/.virtualenvs/r26/

View File

@@ -73,3 +73,34 @@ Patches and Suggestions
- Lucian Ursu
- Tom Moertel
- Frank Kumro Jr
- Chase Sterling
- Marty Alchin
- takluyver
- Ben Toews (mastahyeti)
- David Kemp
- Brendon Crawford
- Denis (Telofy)
- Cory Benfield (Lukasa)
- Matt Giuca
- Adam Tauber
- Honza Javorek
- Brendan Maguire <maguire.brendan@gmail.com>
- Chris Dary
- Danver Braganza <danverbraganza@gmail.com>
- Max Countryman
- Nick Chadwick
- Jonathan Drosdeck
- Jiri Machalek
- Steve Pulec
- Michael Kelly
- Michael Newman <newmaniese@gmail.com>
- Jonty Wareing <jonty@jonty.co.uk>
- Shivaram Lingamneni
- Miguel Turner
- Rohan Jain (crodjer)
- Justin Barber <barber.justin@gmail.com>
- Roman Haritonov <@reclosedev>
- Josh Imhoff <joshimhoff13@gmail.com>
- Arup Malakar <amalakar@gmail.com>
- Danilo Bargen (gwrtheyrn)
- Torsten Landschoff

View File

@@ -1,11 +1,153 @@
.. :changelog:
History
-------
0.13.1 (2012-06-07)
+++++++++++++++++++
- Allow passing a file or file-like object as data.
- Allow hooks to return responses that indicate errors.
- Fix Response.text and Response.json for body-less responses.
0.13.0 (2012-05-29)
+++++++++++++++++++
- Removal of Requests.async in favor of `grequests <https://github.com/kennethreitz/grequests>`_
- Allow disabling of cookie persistiance.
- New implimentation of safe_mode
- cookies.get now supports default argument
- Session cookies not saved when Session.request is called with return_response=False
- Env: no_proxy support.
- RequestsCookieJar improvements.
- Various bug fixes.
0.12.1 (2012-05-08)
+++++++++++++++++++
- New ``Response.json`` property.
- Ability to add string file uploads.
- Fix out-of-range issue with iter_lines.
- Fix iter_content default size.
- Fix POST redirects containing files.
0.12.0 (2012-05-02)
+++++++++++++++++++
- EXPERIMENTAL OAUTH SUPPORT!
- Proper CookieJar-backed cookies interface with awesome dict-like interface.
- Speed fix for non-iterated content chunks.
- Move ``pre_request`` to a more usable place.
- New ``pre_send`` hook.
- Lazily encode data, params, files.
- Load system Certificate Bundle if ``certify`` isn't available.
- Cleanups, fixes.
0.11.2 (2012-04-22)
+++++++++++++++++++
- Attempt to use the OS's certificate bundle if ``certifi`` isn't available.
- Infinite digest auth redirect fix.
- Multi-part file upload improvements.
- Fix decoding of invalid %encodings in URLs.
- If there is no content in a response don't throw an error the second time that content is attempted to be read.
- Upload data on redirects.
0.11.1 (2012-03-30)
+++++++++++++++++++
* POST redirects now break RFC to do what browsers do: Follow up with a GET.
* New ``strict_mode`` configuration to disable new redirect behavior.
0.11.0 (2012-03-14)
+++++++++++++++++++
* Private SSL Certificate support
* Remove select.poll from Gevent monkeypatching
* Remove redundant generator for chunked transfer encoding
* Fix: Response.ok raises Timeout Exception in safe_mode
0.10.8 (2012-03-09)
+++++++++++++++++++
* Generate chunked ValueError fix
* Proxy configuration by environment variables
* Simplification of iter_lines.
* New `trust_env` configuration for disabling system/environment hints.
* Suppress cookie errors.
0.10.7 (2012-03-07)
+++++++++++++++++++
* `encode_uri` = False
0.10.6 (2012-02-25)
+++++++++++++++++++
* Allow '=' in cookies.
0.10.5 (2012-02-25)
+++++++++++++++++++
* Response body with 0 content-length fix.
* New async.imap.
* Don't fail on netrc.
0.10.4 (2012-02-20)
+++++++++++++++++++
* Honor netrc.
0.10.3 (2012-02-20)
+++++++++++++++++++
* HEAD requests don't follow redirects anymore.
* raise_for_status() doesn't raise for 3xx anymore.
* Make Session objects picklable.
* ValueError for invalid schema URLs.
0.10.2 (2012-01-15)
+++++++++++++++++++
* Vastly improved URL quoting.
* Additional allowed cookie key values.
* Attempted fix for "Too many open files" Error
* Replace unicode errors on first pass, no need for second pass.
* Append '/' to bare-domain urls before query insertion.
* Exceptions now inherit from RuntimeError.
* Binary uploads + auth fix.
* Bugfixes.
0.10.1 (2012-01-23)
+++++++++++++++++++
* PYTHON 3 SUPPORT!
* Dropped 2.5 Support. (*Backwards Incompatible*)
0.10.0 (2012-01-21)
+++++++++++++++++++
* ``Response.content`` is now bytes-only. (*Backwards Incompatible*)
* New ``Response.text`` is unicode-only.
* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Respoonse.text`` will guess an encoding.
* Default to ISO-8859-1 (Western) encoding for "text" subtypes.
* Removal of `decode_unicode`. (*Backwards Incompatible*)
* New multiple-hooks system.
* New ``Response.register_hook`` for registering hooks within the pipeline.
* ``Response.url`` is now Unicode.
0.9.3 (2012-01-18)
++++++++++++++++++
* SSL verify=False bugfix (apparent on windows machines).
0.9.2 (2012-01-18)
++++++++++++++++++
* Asyncronous async.send method.
* SSL verify=False bugfix (apparent on windows machines).
* Asynchronous async.send method.
* Support for proper chunk streams with boundaries.
* session argument for Session classes.
* Print entire hook tracebacks, not just exception instance.
@@ -19,7 +161,7 @@ History
++++++++++++++++++
* danger_mode for automatic Response.raise_for_status()
* Response.iter_lines refator
* Response.iter_lines refactor
0.9.0 (2011-12-28)
++++++++++++++++++
@@ -76,7 +218,7 @@ History
0.8.2 (2011-11-19)
++++++++++++++++++
* New unicode decoding system, based on overridable `Response.encoding`.
* New Unicode decoding system, based on over-ridable `Response.encoding`.
* Proper URL slash-quote handling.
* Cookies with ``[``, ``]``, and ``_`` allowed.
@@ -116,7 +258,7 @@ History
0.7.4 (2011-10-26)
++++++++++++++++++
* Sesion Hooks fix.
* Session Hooks fix.
0.7.3 (2011-10-23)
++++++++++++++++++

View File

@@ -1 +1 @@
include README.rst LICENSE NOTICE HISTORY.rst test_requests.py
include README.rst LICENSE NOTICE HISTORY.rst tests/*.py

View File

@@ -1,24 +1,67 @@
SHELL := /bin/bash
# these files should pass pyflakes
# exclude ./env/, which may contain virtualenv packages
PYFLAKES_WHITELIST=$(shell find . -name "*.py" ! -path "./docs/*" ! -path "./tests/*" \
! -path "./requests/packages/*" ! -path "./env/*" \
! -path "./requests/__init__.py" ! -path "./requests/compat.py")
# hack: if pyflakes is available, set this to the location of pyflakes
# if it's not, e.g., in the Python 3 or PyPy Jenkins environments, set it to
# the location of the no-op `true` command.
PYFLAKES_IF_AVAILABLE=$(shell if which pyflakes > /dev/null ; \
then which pyflakes; \
else which true; fi )
# test_requests_ext.py depends on external services, and async doesn't work under Python 3
# Travis/Jenkins should be ensuring that all other tests pass on all supported versions
CI_TESTS=$(shell find tests/ -name "*.py" ! -name "test_requests_ext.py" ! -name "test_requests_async.py")
init:
python setup.py develop
pip install -r requirements.txt
test:
nosetests --with-color test_requests.py
nosetests ./tests/*
ci: init
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
lazy:
nosetests --with-color tests/test_requests.py
stats:
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
simple:
nosetests tests/test_requests.py
pyflakes:
pyflakes ${PYFLAKES_WHITELIST}
cipyflakes:
${PYFLAKES_IF_AVAILABLE} ${PYFLAKES_WHITELIST}
citests:
nosetests ${CI_TESTS} --with-xunit --xunit-file=junit-report.xml
ci: citests cipyflakes
travis: citests
server:
gunicorn httpbin:app --bind=0.0.0.0:7077 &
# compute statistics of various kinds
lemonade:
-pyflakes requests > violations.pyflakes.txt
# HTML output will be available in the default location, ./cover/
nosetests --with-coverage --cover-html --cover-package=requests ${CI_TESTS} ./tests/test_requests_async.py
site:
cd docs; make dirhtml
pyc:
find . -name "*.pyc" -exec rm '{}' ';'
clean:
git clean -Xfd
deps:
rm -fr requests/packages/urllib3
git clone https://github.com/shazow/urllib3.git
cd urllib3 && git checkout master && cd ..
mv urllib3/urllib3 requests/packages/
rm -fr urllib3

View File

@@ -1,6 +1,9 @@
Requests: HTTP for Humans
=========================
.. image:: https://secure.travis-ci.org/kennethreitz/requests.png?branch=develop
Requests is an ISC Licensed HTTP library, written in Python, for human
beings.
@@ -19,13 +22,12 @@ Things shouldn't be this way. Not in Python.
204
>>> r.headers['content-type']
'application/json'
>>> r.content
>>> r.text
...
See `the same code, without Requests <https://gist.github.com/973705>`_.
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
Requests allow you to send HTTP/1.1 requests. You can add headers, form data,
multipart files, and parameters with simple Python dictionaries, and access the
response data in the same way. It's powered by httplib and `urllib3
<https://github.com/shazow/urllib3>`_, but it does all the hard work and crazy
@@ -35,46 +37,17 @@ hacks for you.
Features
--------
- Browser standard SSL verification.
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests.
- Gevent support for Asyncronous Requests.
- Sessions with cookie persistence.
- Basic, Digest, and Custom Authentication support.
- Automatic form-encoding of dictionaries
- A simple dictionary interface for request/response cookies.
- Multipart file uploads.
- Automatc decoding of Unicode, gzip, and deflate responses.
- Full support for unicode URLs and domain names.
Usage
-----
It couldn't be simpler::
>>> import requests
>>> r = requests.get('http://google.com')
HTTPS? Basic Authentication? ::
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass')
>>> r.status_code
401
Uh oh, we're not authorized! Let's add authentication. ::
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json'
>>> r.content
'{"authenticated": true, "user": "user"}'
- International Domains and URLs
- Keep-Alive & Connection Pooling
- Sessions with Cookie Persistence
- Browser-style SSL Verification
- Basic/Digest Authentication
- Elegant Key/Value Cookies
- Automatic Decompression
- Unicode Response Bodies
- Multipart File Uploads
- Connection Timeouts
- Thread-safety
Installation
@@ -101,4 +74,4 @@ Contribute
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.
.. _`the repository`: http://github.com/kennethreitz/requests
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS
.. _AUTHORS: https://github.com/kennethreitz/requests/blob/develop/AUTHORS.rst

View File

@@ -16,7 +16,7 @@
</p>
<h3>Support Requests</h3>
<h3>Donate</h3>
<p>
If you love Requests, consider making a small donation <a href="https://flattr.com/thing/442264/Requests">on Flattr</a>:
</p>

View File

@@ -26,8 +26,6 @@
/* ]]> */
</script>
<script type="text/javascript" src="//www.hellobar.com/hellobar.js"></script>
<script type="text/javascript">
new HelloBar(36402,48802);
</script>

View File

@@ -51,23 +51,17 @@ Chris Adams gave an excellent summary on
Python 3 Support?
-----------------
It's on the way. Here's a list of Python platforms that are officially
Yes! Here's a list of Python platforms that are officially
supported:
* cPython 2.5
* cPython 2.5.5
* cPython 2.5.6
* cPython 2.6
* cPython 2.6.6
* cPython 2.6.7
* cPython 2.7
* cPython 2.7.1
* cPython 2.7.2
* cPython 3.1
* cPython 3.2
* PyPy-c 1.4
* PyPy-c 1.5
Support for Python 3.x is coming *very* soon.
* PyPy-c 1.6
* PyPy-c 1.7
Keep-alive Support?

View File

@@ -50,9 +50,6 @@ Requests is available installed as a Debian package! Debian Etch Ubuntu, since O
$ apt-get install python-requests
Unfortunately, the most recent version available is v0.5.0. If you're on the
Debian Python Package team, I'd love an update of that :)
Fedora and RedHat
-----------------

View File

@@ -3,10 +3,29 @@ How to Help
Requests is under active development, and contributions are more than welcome!
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
There is a Contributor Friendly tag for issues that should be ideal for people who are not very
familiar with the codebase yet.
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ 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 <https://github.com/kennethreitz/requests/blob/develop/AUTHORS.rst>`_.
Development dependencies
------------------------
You'll need to install ``gunicorn`` and ``httpbin`` and various other dependencies in
order to run requests' test suite::
$ virtualenv env
$ . env/bin/activate
$ make
$ make test
The ``Makefile`` has various useful targets for testing.
What Needs to be Done
---------------------
- HTTP Cert Checking (`#30 <https://github.com/kennethreitz/requests/issues/30>`_)
- Python 3.x Support (`#70 <https://github.com/kennethreitz/requests/issues/70>`_)
- Kerberos Support (`#47 <https://github.com/kennethreitz/requests/issues/47>`_)
- Documentation needs a roadmap.

View File

@@ -18,27 +18,33 @@ Things shouldnt be this way. Not in Python.
::
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
204
200
>>> r.headers['content-type']
'application/json'
>>> r.content
...
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
u'{"type":"User"...'
>>> r.json
{u'private_gists': 419, u'total_private_repos': 77, ...}
See `the same code, without Requests <https://gist.github.com/973705>`_.
See `similar code, without Requests <https://gist.github.com/973705>`_.
Requests takes all of the work out of Python HTTP — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by `urllib3 <https://github.com/shazow/urllib3>`_, which is embedded within Requests.
Requests takes all of the work out of Python HTTP/1.1 making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by `urllib3 <https://github.com/shazow/urllib3>`_, which is embedded within Requests.
Testimonials
------------
`The Washington Post <http://www.washingtonpost.com/>`_,
`Heroku <http://heroku.com>`_, `PayPal <https://www.paypal.com/>`_,
`Transifex <https://www.transifex.net/>`_,
`Native Instruments <http://www.native-instruments.com/>`_, `The Washington Post <http://www.washingtonpost.com/>`_,
`Twitter, Inc <http://twitter.com>`_,
`Readability <http://readability.com>`_, and
Federal US Institutions
use Requests internally. It has been installed over 60,000 times from PyPI.
use Requests internally. It has been installed over 100,000 times from PyPI.
**Armin Ronacher**
Requests is the perfect example how beautiful an API can be with the
@@ -72,6 +78,8 @@ Requests is ready for today's web.
- Unicode Response Bodies
- Multipart File Uploads
- Connection Timeouts
- ``.netrc`` support
- Thread-safe.
User Guide

View File

@@ -22,7 +22,7 @@ Let's persist some cookies across requests::
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")
print r.content
print r.text
# '{"cookies": {"sessioncookie": "123456789"}}'
@@ -45,6 +45,7 @@ Any dictionaries that you pass to a request method will be merged with the sessi
All values that are contained within a session are directly available to you. See the :ref:`Session API Docs <sessionapi>` to learn more.
SSL Cert Verification
---------------------
@@ -106,42 +107,8 @@ If you'd like to disable keep-alive, you can simply set the ``keep_alive`` confi
Asynchronous Requests
----------------------
Requests has first-class support for concurrent requests, powered by gevent.
This allows you to send a bunch of HTTP requests at the same time.
First, let's import the async module. Heads up — if you don't have
`gevent <http://pypi.python.org/pypi/gevent>`_ this will fail::
from requests import async
The ``async`` module has the exact same api as ``requests``, except it
doesn't send the request immediately. Instead, it returns the ``Request``
object.
We can build a list of ``Request`` objects easily::
urls = [
'http://python-requests.org',
'http://httpbin.org',
'http://python-guide.org',
'http://kennethreitz.com'
]
rs = [async.get(u) for u in urls]
Now we have a list of ``Request`` objects, ready to be sent. We could send them
one at a time with ``Request.send()``, but that would take a while. Instead,
we'll send them all at the same time with ``async.map()``. Using ``async.map()``
will also guarantee execution of the ``response`` hook, described below. ::
>>> async.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
.. admonition:: Throttling
The ``map`` function also takes a ``size`` parameter, that specifies the number of connections to make at a time::
async.map(rs, size=5)
``requests.async`` has been removed from requests and is now its own repository named `GRequests <https://github.com/kennethreitz/grequests>`_.
Event Hooks
@@ -187,8 +154,8 @@ anything, nothing else is effected.
Let's print some request method arguments at runtime::
>>> requests.get('http://httpbin', hooks=dict(args=print_url))
http://httpbin
>>> requests.get('http://httpbin.org', hooks=dict(args=print_url))
http://httpbin.org
<Response [200]>
Let's hijack some arguments this time with a new callback::
@@ -206,7 +173,7 @@ Let's hijack some arguments this time with a new callback::
And give it a try::
>>> requests.get('http://httpbin/headers', hooks=hooks, headers=headers)
>>> requests.get('http://httpbin.org/headers', hooks=hooks, headers=headers)
{
"headers": {
"Content-Length": "",
@@ -275,8 +242,8 @@ To use the Twitter Streaming API to track the keyword "requests":
data={'track': 'requests'}, auth=('username', 'password'))
for line in r.iter_lines():
if line: # filter out keep-alive new lines
print json.loads(line)
if line: # filter out keep-alive new lines
print json.loads(line)
Verbose Logging
@@ -291,3 +258,196 @@ To do so, just configure Requests with a stream to write to::
>>> requests.get('http://httpbin.org/headers', config=my_config)
2011-08-17T03:04:23.380175 GET http://httpbin.org/headers
<Response [200]>
Proxies
-------
If you need to use a proxy, you can configure individual requests with the
``proxies`` argument to any request method:
::
import requests
proxies = {
"http": "10.10.1.10:3128",
"https": "10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)
You can also configure proxies by environment variables ``HTTP_PROXY`` and ``HTTPS_PROXY``.
::
$ export HTTP_PROXY="10.10.1.10:3128"
$ export HTTPS_PROXY="10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get("http://example.org")
To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syntax:
::
proxies = {
"http": "http://user:pass@10.10.1.10:3128/",
}
HTTP Verbs
----------
Requests provides access to almost the full range of HTTP verbs: GET, OPTIONS,
HEAD, POST, PUT, PATCH and DELETE. The following provides detailed examples of
using these various verbs in Requests, using the GitHub API.
We will begin with the verb most commonly used: GET. HTTP GET is an idempotent
method that returns a resource from a given URL. As a result, it is the verb
you ought to use when attempting to retrieve data from a web location. An
example usage would be attempting to get information about a specific commit
from GitHub. Suppose we wanted commit ``a050faf`` on Requests. We would get it
like so::
>>> import requests
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
We should confirm that GitHub responded correctly. If it has, we want to work
out what type of content it is. Do this like so::
>>> if (r.status_code == requests.codes.ok):
... print r.headers['content-type']
...
application/json; charset=utf-8
So, GitHub returns JSON. That's great, we can use the JSON module to turn it
into Python objects. Because GitHub returned UTF-8, we should use the
``r.text`` method, not the ``r.content`` method. ``r.content`` returns a
bytestring, while ``r.text`` returns a Unicode-encoded string. I have no plans
to perform byte-manipulation on this response, so I want any Unicode code
points encoded.::
>>> import json
>>> commit_data = json.loads(r.text)
>>> print commit_data.keys()
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
>>> print commit_data[u'committer']
{u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'}
>>> print commit_data[u'message']
makin' history
So far, so simple. Well, let's investigate the GitHub API a little bit. Now,
we could look at the documentation, but we might have a little more fun if we
use Requests instead. We can take advantage of the Requests OPTIONS verb to
see what kinds of HTTP methods are supported on the url we just used.::
>>> verbs = requests.options(r.url)
>>> verbs.status_code
500
Uh, what? That's unhelpful! Turns out GitHub, like many API providers, don't
actually implement the OPTIONS method. This is an annoying oversight, but it's
OK, we can just use the boring documentation. If GitHub had correctly
implemented OPTIONS, however, they should return the allowed methods in the
headers, e.g.::
>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print verbs.headers['allow']
GET,HEAD,POST,OPTIONS
Turning to the documentation, we see that the only other method allowed for
commits is POST, which creates a new commit. As we're using the Requests repo,
we should probably avoid making ham-handed POSTS to it. Instead, let's play
with the Issues feature of GitHub.
This documentation was added in response to Issue #482. Given that this issue
already exists, we will use it as an example. Let's start by getting it.::
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
>>> r.status_code
200
>>> issue = json.loads(r.text)
>>> print issue[u'title']
Feature any http verb in docs
>>> print issue[u'comments']
3
Cool, we have three comments. Let's take a look at the last of them.::
>>> r = requests.get(r.url + u'/comments')
>>> r.status_code
200
>>> comments = json.loads(r.text)
>>> print comments[0].keys()
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
>>> print comments[2][u'body']
Probably in the "advanced" section
Well, that seems like a silly place. Let's post a comment telling the poster
that he's silly. Who is the poster, anyway?::
>>> print comments[2][u'user'][u'login']
kennethreitz
OK, so let's tell this Kenneth guy that we think this example should go in the
quickstart guide instead. According to the GitHub API doc, the way to do this
is to POST to the thread. Let's do it.::
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
404
Huh, that's weird. We probably need to authenticate. That'll be a pain, right?
Wrong. Requests makes it easy to use many forms of authentication, including
the very common Basic Auth.::
>>> from requests.auth import HTTPBasicAuth
>>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
>>> r = requests.post(url=url, data=body, auth=auth)
>>> r.status_code
201
>>> content = json.loads(r.text)
>>> print content[u'body']
Sounds great! I'll get right on it.
Brilliant. Oh, wait, no! I meant to add that it would take me a while, because
I had to go feed my cat. If only I could edit this comment! Happily, GitHub
allows us to use another HTTP verb, PATCH, to edit this comment. Let's do
that.::
>>> print content[u"id"]
5804413
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200
Excellent. Now, just to torture this Kenneth guy, I've decided to let him
sweat and not tell him that I'm working on this. That means I want to delete
this comment. GitHub lets us delete comments using the incredibly aptly named
DELETE method. Let's get rid of it.::
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
Excellent. All gone. The last thing I want to know is how much of my ratelimit
I've used. Let's find out. GitHub sends that information in the headers, so
rather than download the whole page I'll send a HEAD request to get the
headers.::
>>> r = requests.head(url=url, auth=auth)
>>> print r.headers
// ...snip... //
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
// ...snip... //
Excellent. Time to write a Python program that abuses the GitHub API in all
kinds of exciting ways, 4995 more times.

View File

@@ -43,11 +43,11 @@ You can either clone the public repository::
Download the `tarball <https://github.com/kennethreitz/requests/tarball/master>`_::
$ curl -O https://github.com/kennethreitz/requests/tarball/master
$ curl -OL https://github.com/kennethreitz/requests/tarball/master
Or, download the `zipball <https://github.com/kennethreitz/requests/zipball/master>`_::
$ curl -O https://github.com/kennethreitz/requests/zipball/master
$ curl -OL https://github.com/kennethreitz/requests/zipball/master
Once you have a copy of the source, you can embed it in your Python package,

View File

@@ -15,82 +15,126 @@ First, make sure that:
* Requests is :ref:`up-to-date <updates>`
Lets gets started with some simple use cases and examples.
Let's get started with some simple examples.
Make a GET Request
Make a Request
------------------
Making a standard request with Requests is very simple.
Making a request with Requests is very simple.
Let's get GitHub's public timeline ::
Begin by importing the Requests module::
>>> import requests
r = requests.get('https://github.com/timeline.json')
Now, let's try to get a webpage. For this example, let's get GitHub's public
timeline ::
>>> r = requests.get('https://github.com/timeline.json')
Now, we have a :class:`Response` object called ``r``. We can get all the
information we need from this.
information we need from this object.
Requests' simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request::
>>> r = requests.post("http://httpbin.org/post")
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple::
>>> r = requests.put("http://httpbin.org/put")
>>> r = requests.delete("http://httpbin.org/delete")
>>> r = requests.head("http://httpbin.org/get")
>>> r = requests.options("http://httpbin.org/get")
That's all well and good, but it's also only the start of what Requests can
do.
Passing Parameters In URLs
--------------------------
You often want to send some sort of data in the URL's query string. If
you were constructing the URL by hand, this data would be given as key/value
pairs in the URL after a question mark, e.g. ``httpbin.org/get?key=val``.
Requests allows you to provide these arguments as a dictionary, using the
``params`` keyword argument. As an example, if you wanted to pass
``key1=value1`` and ``key2=value2`` to ``httpbin.org/get``, you would use the
following code::
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.get("http://httpbin.org/get", params=payload)
You can see that the URL has been correctly encoded by printing the URL::
>>> print r.url
u'http://httpbin.org/get?key2=value2&key1=value1'
Response Content
----------------
We can read the content of the server's response::
We can read the content of the server's response. Consider the GitHub timeline
again::
>>> r.content
>>> import requests
>>> r = requests.get('https://github.com/timeline.json')
>>> r.text
'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests does its best to decode content from the server. Most unicode
charsets, ``gzip``, and ``deflate`` encodings are all seamlessly decoded.
Requests will automatically decode content from the server. Most unicode
charsets are seamlessly decoded.
When you make a request, ``r.encoding`` is set, based on the HTTP headers.
Requests will attempt to use that encoding when you access ``r.content``. You
can manually set ``r.encoding`` to any encoding you'd like (including ``None``),
and that charset will be used.
Requests will use that encoding when you access ``r.text``. If ``r.encoding``
is ``None``, Requests will make an extremely educated guess of the encoding
of the response body. You can manually set ``r.encoding`` to any encoding
you'd like, and that charset will be used.
Make a POST Request
-------------------
Binary Response Content
-----------------------
POST requests are equally simple::
You can also access the response body as bytes, for non-text requests::
r = requests.post("http://httpbin.org/post")
>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
For example, to create an image from binary data returned by a request, you can
use the following code:
>>> from PIL import Image
>>> from StringIO import StringIO
>>> i = Image.open(StringIO(r.content))
Typically, you want to send some form-encoded data — much like an HTML form.
To do this, simply pass a dictionary to the `data` argument. Your
dictionary of data will automatically be form-encoded when the request is made::
JSON Response Content
---------------------
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print r.content
{
"origin": "179.13.100.4",
"files": {},
"form": {
"key2": "value2",
"key1": "value1"
},
"url": "http://httpbin.org/post",
"args": {},
"headers": {
"Content-Length": "23",
"Accept-Encoding": "identity, deflate, compress, gzip",
"Accept": "*/*",
"User-Agent": "python-requests/0.8.0",
"Host": "127.0.0.1:7077",
"Content-Type": "application/x-www-form-urlencoded"
},
"data": ""
}
There's also a builtin JSON decoder, in case you're dealing with JSON data::
There are many times that you want to send data that is not form-encoded. If you pass in a ``string`` instead of a ``dict``, that data will be posted directly.
>>> import requests
>>> r = requests.get('https://github.com/timeline.json')
>>> r.json
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
In case the JSON decoding fails, ``r.json`` simply returns ``None``.
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
r = requests.post(url, data=json.dumps(payload))
Raw Response Content
--------------------
In the rare case that you'd like to get the absolute raw socket response from the server,
you can access ``r.raw``::
>>> r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
>>> r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
Custom Headers
@@ -101,11 +145,42 @@ If you'd like to add HTTP headers to a request, simply pass in a ``dict`` to the
For example, we didn't specify our content-type in the previous example::
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
headers = {'content-type': 'application/json'}
>>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> headers = {'content-type': 'application/json'}
r = requests.post(url, data=json.dumps(payload), headers=headers)
>>> r = requests.post(url, data=json.dumps(payload), headers=headers)
More complicated POST requests
------------------------------
Typically, you want to send some form-encoded data — much like an HTML form.
To do this, simply pass a dictionary to the `data` argument. Your
dictionary of data will automatically be form-encoded when the request is made::
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print r.text
{
// ...snip... //
"form": {
"key2": "value2",
"key1": "value1"
},
// ...snip... //
}
There are many times that you want to send data that is not form-encoded. If you pass in a ``string`` instead of a ``dict``, that data will be posted directly.
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
>>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, data=json.dumps(payload))
POST a Multipart-Encoded File
@@ -117,50 +192,43 @@ Requests makes it simple to upload Multipart-encoded files::
>>> files = {'report.xls': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)
>>> r.content
>>> r.text
{
"origin": "179.13.100.4",
// ...snip... //
"files": {
"report.xls": "<censored...binary...data>"
},
"form": {},
"url": "http://httpbin.org/post",
"args": {},
"headers": {
"Content-Length": "3196",
"Accept-Encoding": "identity, deflate, compress, gzip",
"Accept": "*/*",
"User-Agent": "python-requests/0.8.0",
"Host": "httpbin.org:80",
"Content-Type": "multipart/form-data; boundary=127.0.0.1.502.21746.1321131593.786.1"
},
"data": ""
// ...snip... //
}
Setting filename explicitly::
You can set the filename explicitly::
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'))}
>>> r = requests.post(url, files=files)
>>> r.content
>>> r.text
{
"origin": "179.13.100.4",
// ...snip... //
"files": {
"file": "<censored...binary...data>"
},
"form": {},
"url": "http://httpbin.org/post",
"args": {},
"headers": {
"Content-Length": "3196",
"Accept-Encoding": "identity, deflate, compress, gzip",
"Accept": "*/*",
"User-Agent": "python-requests/0.8.0",
"Host": "httpbin.org:80",
"Content-Type": "multipart/form-data; boundary=127.0.0.1.502.21746.1321131593.786.1"
// ...snip... //
}
If you want, you can send strings to be received as files::
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
>>> r = requests.post(url, files=files)
>>> r.text
{
// ...snip... //
"files": {
"file": "some,data,to,send\\nanother,row,to,send\\n"
},
"data": ""
// ...snip... //
}
@@ -169,6 +237,7 @@ Response Status Codes
We can check the response status code::
>>> r = requests.get("http://httpbin.org/get')
>>> r.status_code
200
@@ -191,7 +260,8 @@ If we made a bad request (non-200 response), we can raise it with
raise self.error
urllib2.HTTPError: HTTP Error 404: NOT FOUND
But, since our ``status_code`` was ``200``, when we call it::
But, since our ``status_code`` for ``r`` was ``200``, when we call
``raise_for_status()`` we get::
>>> r.raise_for_status()
None
@@ -202,8 +272,7 @@ All is well.
Response Headers
----------------
We can view the server's response headers with a simple Python dictionary
interface::
We can view the server's response headers using a Python dictionary::
>>> r.headers
{
@@ -243,8 +312,8 @@ If a response contains some Cookies, you can get quick access to them::
>>> url = 'http://httpbin.org/cookies/set/requests-is/awesome'
>>> r = requests.get(url)
>>> print r.cookies
{'requests-is': 'awesome'}
>>> r.cookies['requests-is']
'awesome'
To send your own cookies to the server, you can use the ``cookies``
parameter::
@@ -253,14 +322,14 @@ parameter::
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.content
>>> r.text
'{"cookies": {"cookies_are": "working"}}'
Basic Authentication
--------------------
Most web services require authentication. There many different types of
Many web services require authentication. There many different types of
authentication, but the most common is HTTP Basic Auth.
Making requests with Basic Auth is extremely simple::
@@ -293,16 +362,20 @@ Another popular form of web service protection is Digest Authentication::
OAuth Authentication
--------------------
Miguel Araujo's `requests-oauth <http://pypi.python.org/pypi/requests-oauth>`_ project provides a simple interface for
establishing OAuth connections. Documentation and examples can be found on the requests-oauth `git repository <https://github.com/maraujop/requests-oauth>`_.
Miguel Araujo's `requests-oauth <http://pypi.python.org/pypi/requests-oauth>`_
project provides a simple interface for establishing OAuth connections.
Documentation and examples can be found on the requests-oauth
`git repository <https://github.com/maraujop/requests-oauth>`_.
Redirection and History
-----------------------
Requests will automatically perform location redirection while using idempotent methods.
Requests will automatically perform location redirection while using the GET
and OPTIONS verbs.
GitHub redirects all HTTP requests to HTTPS. Let's see what happens::
GitHub redirects all HTTP requests to HTTPS. We can use the ``history`` method
of the Response object to track redirection. Let's see what Github does::
>>> r = requests.get('http://github.com')
>>> r.url
@@ -315,8 +388,8 @@ GitHub redirects all HTTP requests to HTTPS. Let's see what happens::
The :class:`Response.history` list contains a list of the
:class:`Request` objects that were created in order to complete the request.
If you're using GET, HEAD, or OPTIONS, you can disable redirection
handling with the ``allow_redirects`` parameter::
If you're using GET or OPTIONS, you can disable redirection handling with the
``allow_redirects`` parameter::
>>> r = requests.get('http://github.com', allow_redirects=False)
>>> r.status_code
@@ -324,7 +397,8 @@ handling with the ``allow_redirects`` parameter::
>>> r.history
[]
If you're using POST, PUT, PATCH, *&c*, you can also explicitly enable redirection as well::
If you're using POST, PUT, PATCH, DELETE or HEAD, you can enable
redirection as well::
>>> r = requests.post('http://github.com', allow_redirects=True)
>>> r.url
@@ -336,16 +410,18 @@ If you're using POST, PUT, PATCH, *&c*, you can also explicitly enable redirecti
Timeouts
--------
You can tell requests to stop waiting for a response after a given number of seconds with the ``timeout`` parameter::
You can tell requests to stop waiting for a response after a given number of
seconds with the ``timeout`` parameter::
>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: Request timed out.
.. admonition:: Note
.. admonition:: Note:
``timeout`` only effects the connection process itself, not the downloading of the response body.
``timeout`` only effects the connection process itself, not the
downloading of the response body.
Errors and Exceptions
@@ -359,14 +435,17 @@ an :class:`HTTPError` exception.
If a request times out, a :class:`Timeout` exception is raised.
If a request exceeds the configured number of maximum redirections, a :class:`TooManyRedirects` exception is raised.
If a request exceeds the configured number of maximum redirections, a
:class:`TooManyRedirects` exception is raised.
All exceptions that Requests explicitly raises inherit from
:class:`requests.exceptions.RequestException`.
You can refer to :ref:`Configuration API Docs <configurations>` for immediate raising of :class:`HTTPError` exceptions
via the ``danger_mode`` option or have Requests catch the majority of :class:`requests.exceptions.RequestException` exceptions
with the ``safe_mode`` option.
You can refer to :ref:`Configuration API Docs <configurations>` for immediate
raising of :class:`HTTPError` exceptions via the ``danger_mode`` option or
have Requests catch the majority of
:class:`requests.exceptions.RequestException` exceptions with the ``safe_mode``
option.
-----------------------

View File

@@ -15,14 +15,13 @@ requests
"""
__title__ = 'requests'
__version__ = '0.9.2'
__build__ = 0x000902
__version__ = '0.13.1'
__build__ = 0x001301
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2012 Kenneth Reitz'
from . import utils
from .models import Request, Response
from .api import request, get, head, post, patch, put, delete, options

View File

@@ -12,7 +12,9 @@ This module implements the Requests API.
"""
from . import sessions
from .safe_mode import catch_exceptions_if_in_safe_mode
@catch_exceptions_if_in_safe_mode
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
Returns :class:`Response <Response>` object.
@@ -30,9 +32,10 @@ def request(method, url, **kwargs):
: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 session: (optional) A :class:`Session` object to be used for the request.
:param config: (optional) A configuration dictionary.
:param config: (optional) A configuration dictionary. See ``request.defaults`` for allowed keys and their default values.
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
"""
s = kwargs.pop('session') if 'session' in kwargs else sessions.session()
@@ -44,7 +47,7 @@ def get(url, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
@@ -55,7 +58,7 @@ def options(url, **kwargs):
"""Sends a OPTIONS request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
@@ -66,10 +69,10 @@ def head(url, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
kwargs.setdefault('allow_redirects', False)
return request('head', url, **kwargs)
@@ -78,7 +81,7 @@ def post(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return request('post', url, data=data, **kwargs)
@@ -89,7 +92,7 @@ def put(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return request('put', url, data=data, **kwargs)
@@ -100,7 +103,7 @@ def patch(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return request('patch', url, data=data, **kwargs)
@@ -110,7 +113,7 @@ def delete(url, **kwargs):
"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return request('delete', url, **kwargs)

View File

@@ -1,92 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.async
~~~~~~~~~~~~~~
This module contains an asynchronous replica of ``requests.api``, powered
by gevent. All API methods return a ``Request`` instance (as opposed to
``Response``). A list of requests can be sent with ``map()``.
"""
try:
import gevent
from gevent import monkey as curious_george
from gevent.pool import Pool
except ImportError:
raise RuntimeError('Gevent is required for requests.async.')
# Monkey-patch.
curious_george.patch_all(thread=False)
from . import api
__all__ = (
'map',
'get', 'options', 'head', 'post', 'put', 'patch', 'delete', 'request'
)
def patched(f):
"""Patches a given API function to not send."""
def wrapped(*args, **kwargs):
kwargs['return_response'] = False
kwargs['prefetch'] = True
config = kwargs.get('config', {})
config.update(safe_mode=True)
kwargs['config'] = config
return f(*args, **kwargs)
return wrapped
def send(r, pool=None):
"""Sends the request object using the specified pool. If a pool isn't
specified this method blocks. Pools are useful because you can specify size
and can hence limit concurrency."""
if pool != None:
return pool.spawn(r.send)
return gevent.spawn(r.send)
# Patched requests.api functions.
get = patched(api.get)
options = patched(api.options)
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, size=None):
"""Concurrently converts a list of Requests to Responses.
:param requests: a collection of Request objects.
:param prefetch: If False, the content will not be downloaded immediately.
:param size: Specifies the number of requests to make at a time. If None, no throttling occurs.
"""
requests = list(requests)
if size:
pool = Pool(size)
jobs = [pool.spawn(r.send) for r in requests]
else:
jobs = [gevent.spawn(r.send) for r in requests]
gevent.joinall(jobs)
if prefetch:
[r.response.content for r in requests]
return [r.response for r in requests]

View File

@@ -7,19 +7,30 @@ requests.auth
This module contains the authentication handlers for Requests.
"""
import os
import time
import hashlib
from base64 import b64encode
from urlparse import urlparse
from .utils import randombytes, parse_dict_header
from .compat import urlparse, str
from .utils import parse_dict_header
try:
from oauthlib.oauth1.rfc5849 import (Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER)
from oauthlib.common import extract_params
# hush pyflakes:
SIGNATURE_HMAC; SIGNATURE_TYPE_AUTH_HEADER
except (ImportError, SyntaxError):
SIGNATURE_HMAC = None
SIGNATURE_TYPE_AUTH_HEADER = None
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
return 'Basic %s' % b64encode('%s:%s' % (username, password))
return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1')
class AuthBase(object):
@@ -29,11 +40,79 @@ class AuthBase(object):
raise NotImplementedError('Auth hooks must be callable.')
class OAuth1(AuthBase):
"""Signs the request using OAuth 1 (RFC5849)"""
def __init__(self, client_key,
client_secret=None,
resource_owner_key=None,
resource_owner_secret=None,
callback_uri=None,
signature_method=SIGNATURE_HMAC,
signature_type=SIGNATURE_TYPE_AUTH_HEADER,
rsa_key=None, verifier=None):
try:
signature_type = signature_type.upper()
except AttributeError:
pass
self.client = Client(client_key, client_secret, resource_owner_key,
resource_owner_secret, callback_uri, signature_method,
signature_type, rsa_key, verifier)
def __call__(self, r):
"""Add OAuth parameters to the request.
Parameters may be included from the body if the content-type is
urlencoded, if no content type is set an educated guess is made.
"""
contenttype = r.headers.get('Content-Type', None)
# extract_params will not give params unless the body is a properly
# formatted string, a dictionary or a list of 2-tuples.
decoded_body = extract_params(r.data)
if contenttype == None and decoded_body != None:
# extract_params can only check the present r.data and does not know
# of r.files, thus an extra check is performed. We know that
# if files are present the request will not have
# Content-type: x-www-form-urlencoded. We guess it will have
# a mimetype of multipart/form-encoded and if this is not the case
# we assume the correct header will be set later.
if r.files:
# Omit body data in the signing and since it will always
# be empty (cant add paras to body if multipart) and we wish
# to preserve body.
r.headers['Content-Type'] = 'multipart/form-encoded'
r.url, r.headers, _ = self.client.sign(
unicode(r.url), unicode(r.method), None, r.headers)
else:
# Normal signing
r.headers['Content-Type'] = 'application/x-www-form-urlencoded'
r.url, r.headers, r.data = self.client.sign(
unicode(r.url), unicode(r.method), r.data, r.headers)
# Having the authorization header, key or value, in unicode will
# result in UnicodeDecodeErrors when the request is concatenated
# by httplib. This can easily be seen when attaching files.
# Note that simply encoding the value is not enough since Python
# saves the type of first key set. Thus we remove and re-add.
# >>> d = {u'a':u'foo'}
# >>> d['a'] = 'foo'
# >>> d
# { u'a' : 'foo' }
u_header = unicode('Authorization')
if u_header in r.headers:
auth_header = r.headers[u_header].encode('utf-8')
del r.headers[u_header]
r.headers['Authorization'] = auth_header
return r
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
def __init__(self, username, password):
self.username = str(username)
self.password = str(password)
self.username = username
self.password = password
def __call__(self, r):
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
@@ -56,6 +135,8 @@ class HTTPDigestAuth(AuthBase):
def handle_401(self, r):
"""Takes the given response and tries digest-auth, if needed."""
r.request.deregister_hook('response', self.handle_401)
s_auth = r.headers.get('www-authenticate', '')
if 'digest' in s_auth.lower():
@@ -74,13 +155,21 @@ class HTTPDigestAuth(AuthBase):
algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level
if algorithm == 'MD5':
H = lambda x: hashlib.md5(x).hexdigest()
def md5_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.md5(x).hexdigest()
hash_utf8 = md5_utf8
elif algorithm == 'SHA':
H = lambda x: hashlib.sha1(x).hexdigest()
def sha_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.sha1(x).hexdigest()
hash_utf8 = sha_utf8
# XXX MD5-sess
KD = lambda s, d: H("%s:%s" % (s, d))
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
if H is None:
if hash_utf8 is None:
return None
# XXX not implemented yet
@@ -101,14 +190,16 @@ class HTTPDigestAuth(AuthBase):
last_nonce = nonce
ncvalue = '%08x' % nonce_count
cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
nonce_count, nonce, time.ctime(), randombytes(8)))
.hexdigest()[:16]
)
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
respdig = KD(H(A1), noncebit)
s = str(nonce_count).encode('utf-8')
s += nonce.encode('utf-8')
s += time.ctime().encode('utf-8')
s += os.urandom(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16])
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, hash_utf8(A2))
respdig = KD(hash_utf8(A1), noncebit)
elif qop is None:
respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
else:
# XXX handle auth-int.
return None
@@ -134,5 +225,5 @@ class HTTPDigestAuth(AuthBase):
return r
def __call__(self, r):
r.hooks['response'] = self.handle_401
r.register_hook('response', self.handle_401)
return r

105
requests/compat.py Normal file
View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
"""
pythoncompat
"""
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
#: Python 3.0.x
is_py30 = (is_py3 and _ver[1] == 0)
#: Python 3.1.x
is_py31 = (is_py3 and _ver[1] == 1)
#: Python 3.2.x
is_py32 = (is_py3 and _ver[1] == 2)
#: Python 3.3.x
is_py33 = (is_py3 and _ver[1] == 3)
#: Python 3.4.x
is_py34 = (is_py3 and _ver[1] == 4)
#: Python 2.7.x
is_py27 = (is_py2 and _ver[1] == 7)
#: Python 2.6.x
is_py26 = (is_py2 and _ver[1] == 6)
#: Python 2.5.x
is_py25 = (is_py2 and _ver[1] == 5)
#: Python 2.4.x
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
# ---------
# Platforms
# ---------
# Syntax sugar.
_ver = sys.version.lower()
is_pypy = ('pypy' in _ver)
is_jython = ('jython' in _ver)
is_ironpython = ('iron' in _ver)
# Assume CPython, if nothing else.
is_cpython = not any((is_pypy, is_jython, is_ironpython))
# Windows-based system.
is_windows = 'win32' in str(sys.platform).lower()
# Standard Linux 2+ system.
is_linux = ('linux' in str(sys.platform).lower())
is_osx = ('darwin' in str(sys.platform).lower())
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
# ---------
# Specifics
# ---------
if is_py2:
from urllib import quote, unquote, urlencode
from urlparse import urlparse, urlunparse, urljoin, urlsplit
from urllib2 import parse_http_list
import cookielib
from Cookie import Morsel
from StringIO import StringIO
bytes = str
str = unicode
basestring = basestring
elif is_py3:
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
from urllib.request import parse_http_list
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
str = str
bytes = bytes
basestring = (str,bytes)

363
requests/cookies.py Normal file
View File

@@ -0,0 +1,363 @@
"""
Compatibility code to be able to use `cookielib.CookieJar` with requests.
requests.utils imports from here, so be careful with imports.
"""
import collections
from .compat import cookielib, urlparse, Morsel
try:
import threading
# grr, pyflakes: this fixes "redefinition of unused 'threading'"
threading
except ImportError:
import dummy_threading as threading
class MockRequest(object):
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
The code in `cookielib.CookieJar` expects this interface in order to correctly
manage cookie policies, i.e., determine whether a cookie can be set, given the
domains of the request and the cookie.
The original request object is read-only. The client is responsible for collecting
the new headers via `get_new_headers()` and interpreting them appropriately. You
probably want `get_cookie_header`, defined below.
"""
def __init__(self, request):
self._r = request
self._new_headers = {}
def get_type(self):
return urlparse(self._r.full_url).scheme
def get_host(self):
return urlparse(self._r.full_url).netloc
def get_origin_req_host(self):
if self._r.response.history:
r = self._r.response.history[0]
return urlparse(r).netloc
else:
return self.get_host()
def get_full_url(self):
return self._r.full_url
def is_unverifiable(self):
# unverifiable == redirected
return bool(self._r.response.history)
def has_header(self, name):
return name in self._r.headers or name in self._new_headers
def get_header(self, name, default=None):
return self._r.headers.get(name, self._new_headers.get(name, default))
def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
def add_unredirected_header(self, name, value):
self._new_headers[name] = value
def get_new_headers(self):
return self._new_headers
class MockResponse(object):
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
...what? Basically, expose the parsed HTTP headers from the server response
the way `cookielib` expects to see them.
"""
def __init__(self, headers):
"""Make a MockResponse for `cookielib` to read.
:param headers: a httplib.HTTPMessage or analogous carrying the headers
"""
self._headers = headers
def info(self):
return self._headers
def getheaders(self, name):
self._headers.getheaders(name)
def extract_cookies_to_jar(jar, request, response):
"""Extract the cookies from the response into a CookieJar.
:param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
:param request: our own requests.Request object
:param response: urllib3.HTTPResponse object
"""
# the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request)
# pull out the HTTPMessage with the headers and put it in the mock:
res = MockResponse(response._original_response.msg)
jar.extract_cookies(res, req)
def get_cookie_header(jar, request):
"""Produce an appropriate Cookie header string to be sent with `request`, or None."""
r = MockRequest(request)
jar.add_cookie_header(r)
return r.get_new_headers().get('Cookie')
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
"""Unsets a cookie by name, by default over all domains and paths.
Wraps CookieJar.clear(), is O(n).
"""
clearables = []
for cookie in cookiejar:
if cookie.name == name:
if domain is None or domain == cookie.domain:
if path is None or path == cookie.path:
clearables.append((cookie.domain, cookie.path, cookie.name))
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
class CookieConflictError(RuntimeError):
"""There are two cookies that meet the criteria specified in the cookie jar.
Use .get and .set and include domain and path args in order to be more specific."""
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
This is the CookieJar we create by default for requests and sessions that
don't specify one, since some clients may expect response.cookies and
session.cookies to support dict operations.
Don't use the dict interface internally; it's just for compatibility with
with external client code. All `requests` code should work out of the box
with externally provided instances of CookieJar, e.g., LWPCookieJar and
FileCookieJar.
Caution: dictionary operations that are normally O(1) may be O(n).
Unlike a regular CookieJar, this class is pickleable.
"""
def get(self, name, default=None, domain=None, path=None):
"""Dict-like get() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains. Caution: operation is O(n), not O(1)."""
try:
return self._find_no_duplicates(name, domain, path)
except KeyError:
return default
def set(self, name, value, **kwargs):
"""Dict-like set() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains."""
# support client code that unsets cookies by assignment of a None value:
if value is None:
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
return
if isinstance(value, Morsel):
c = morsel_to_cookie(value)
else:
c = create_cookie(name, value, **kwargs)
self.set_cookie(c)
return c
def keys(self):
"""Dict-like keys() that returns a list of names of cookies from the jar.
See values() and items()."""
keys = []
for cookie in iter(self):
keys.append(cookie.name)
return keys
def values(self):
"""Dict-like values() that returns a list of values of cookies from the jar.
See keys() and items()."""
values = []
for cookie in iter(self):
values.append(cookie.value)
return values
def items(self):
"""Dict-like items() that returns a list of name-value tuples from the jar.
See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
and get a vanilla python dict of key value pairs."""
items = []
for cookie in iter(self):
items.append((cookie.name, cookie.value))
return items
def list_domains(self):
"""Utility method to list all the domains in the jar."""
domains = []
for cookie in iter(self):
if cookie.domain not in domains:
domains.append(cookie.domain)
return domains
def list_paths(self):
"""Utility method to list all the paths in the jar."""
paths = []
for cookie in iter(self):
if cookie.path not in paths:
paths.append(cookie.path)
return paths
def multiple_domains(self):
"""Returns True if there are multiple domains in the jar.
Returns False otherwise."""
domains = []
for cookie in iter(self):
if cookie.domain is not None and cookie.domain in domains:
return True
domains.append(cookie.domain)
return False # there is only one domain in jar
def get_dict(self, domain=None, path=None):
"""Takes as an argument an optional domain and path and returns a plain old
Python dict of name-value pairs of cookies that meet the requirements."""
dictionary = {}
for cookie in iter(self):
if (domain == None or cookie.domain == domain) and (path == None
or cookie.path == path):
dictionary[cookie.name] = cookie.value
return dictionary
def __getitem__(self, name):
"""Dict-like __getitem__() for compatibility with client code. Throws exception
if there are more than one cookie with name. In that case, use the more
explicit get() method instead. Caution: operation is O(n), not O(1)."""
return self._find_no_duplicates(name)
def __setitem__(self, name, value):
"""Dict-like __setitem__ for compatibility with client code. Throws exception
if there is already a cookie of that name in the jar. In that case, use the more
explicit set() method instead."""
self.set(name, value)
def __delitem__(self, name):
"""Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
remove_cookie_by_name(self, name)
def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values. Takes as args name
and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
_find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
if there are conflicting cookies."""
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
return cookie.value
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
def _find_no_duplicates(self, name, domain=None, path=None):
"""__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
Takes as args name and optional domain and path. Returns a cookie.value.
Throws KeyError if cookie is not found and CookieConflictError if there are
multiple cookies that match name and optionally domain and path."""
toReturn = None
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
if toReturn != None: # if there are multiple cookies that meet passed in criteria
raise CookieConflictError('There are multiple cookies with name, %r' % (name))
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
if toReturn:
return toReturn
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
def __getstate__(self):
"""Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy()
# remove the unpickleable RLock object
state.pop('_cookies_lock')
return state
def __setstate__(self, state):
"""Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state)
if '_cookies_lock' not in self.__dict__:
self._cookies_lock = threading.RLock()
def copy(self):
"""This is not implemented. Calling this will throw an exception."""
raise NotImplementedError
def create_cookie(name, value, **kwargs):
"""Make a cookie from underspecified parameters.
By default, the pair of `name` and `value` will be set for the domain ''
and sent on every request (this is sometimes called a "supercookie").
"""
result = dict(
version=0,
name=name,
value=value,
port=None,
domain='',
path='/',
secure=False,
expires=None,
discard=True,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False,
)
badargs = set(kwargs) - set(result)
if badargs:
err = 'create_cookie() got unexpected keyword arguments: %s'
raise TypeError(err % list(badargs))
result.update(kwargs)
result['port_specified'] = bool(result['port'])
result['domain_specified'] = bool(result['domain'])
result['domain_initial_dot'] = result['domain'].startswith('.')
result['path_specified'] = bool(result['path'])
return cookielib.Cookie(**result)
def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
c = create_cookie(
name=morsel.key,
value=morsel.value,
version=morsel['version'] or 0,
port=None,
port_specified=False,
domain=morsel['domain'],
domain_specified=bool(morsel['domain']),
domain_initial_dot=morsel['domain'].startswith('.'),
path=morsel['path'],
path_specified=bool(morsel['path']),
secure=bool(morsel['secure']),
expires=morsel['max-age'] or morsel['expires'],
discard=False,
comment=morsel['comment'],
comment_url=bool(morsel['comment']),
rest={'HttpOnly': morsel['httponly']},
rfc2109=False,
)
return c
def cookiejar_from_dict(cookie_dict, cookiejar=None):
"""Returns a CookieJar from a key/value dictionary.
:param cookie_dict: Dict of key/values to insert into CookieJar.
"""
if cookiejar is None:
cookiejar = RequestsCookieJar()
if cookie_dict is not None:
for name in cookie_dict:
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
return cookiejar

View File

@@ -10,18 +10,22 @@ Configurations:
:base_headers: Default HTTP headers.
:verbose: Stream to write request logging to.
:timeout: Seconds until request timeout.
:max_redirects: Maximum number of redirects allowed within a request.
:decode_unicode: Decode unicode responses automatically?
:max_redirects: Maximum number of redirects allowed within a request.s
:keep_alive: Reuse HTTP Connections?
:max_retries: The number of times a request should be retried in the event of a connection failure.
:danger_mode: If true, Requests will raise errors immediately.
:safe_mode: If true, Requests will catch all errors.
:strict_mode: If true, Requests will do its best to follow RFCs (e.g. POST redirects).
:pool_maxsize: The maximium size of an HTTP connection pool.
:pool_connections: The number of active HTTP connection pools to use.
:encode_uri: If true, URIs will automatically be percent-encoded.
:trust_env: If true, the surrouding environment will be trusted (environ, netrc).
:param store_cookies: If false, the received cookies as part of the HTTP response would be ignored.
"""
SCHEMAS = ['http', 'https']
from . import __version__
defaults = dict()
@@ -35,10 +39,15 @@ defaults['base_headers'] = {
defaults['verbose'] = None
defaults['max_redirects'] = 30
defaults['decode_unicode'] = True
defaults['pool_connections'] = 10
defaults['pool_maxsize'] = 10
defaults['max_retries'] = 0
defaults['danger_mode'] = False
defaults['safe_mode'] = False
defaults['strict_mode'] = False
defaults['keep_alive'] = True
defaults['encode_uri'] = True
defaults['trust_env'] = True
defaults['store_cookies'] = True

View File

@@ -8,12 +8,13 @@ This module contains the set of Requests' exceptions.
"""
class RequestException(Exception):
class RequestException(RuntimeError):
"""There was an ambiguous exception that occurred while handling your
request."""
class HTTPError(RequestException):
"""An HTTP error occurred."""
response = None
class ConnectionError(RequestException):
"""A Connection error occurred."""
@@ -29,3 +30,12 @@ class URLRequired(RequestException):
class TooManyRedirects(RequestException):
"""Too many redirects."""
class MissingSchema(RequestException, ValueError):
"""The URL schema (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
"""See defaults.py for valid schemas."""
class InvalidURL(RequestException, ValueError):
""" The URL provided was somehow invalid. """

View File

@@ -12,6 +12,9 @@ Available hooks:
A dictionary of the arguments being sent to Request().
``pre_request``:
The Request object, directly after being created.
``pre_send``:
The Request object, directly before being sent.
``post_request``:
@@ -25,16 +28,26 @@ Available hooks:
import traceback
HOOKS = ('args', 'pre_request', 'pre_send', 'post_request', 'response')
def dispatch_hook(key, hooks, hook_data):
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or dict()
if key in hooks:
try:
return hooks.get(key).__call__(hook_data) or hook_data
hooks = hooks.get(key)
except Exception:
traceback.print_exc()
if hasattr(hooks, '__call__'):
hooks = [hooks]
for hook in hooks:
try:
_hook_data = hook(hook_data)
if _hook_data is not None:
hook_data = _hook_data
except Exception:
traceback.print_exc()
return hook_data

View File

@@ -7,34 +7,43 @@ requests.models
This module contains the primary objects that power Requests.
"""
import json
import os
import urllib
from urlparse import urlparse, urlunparse, urljoin, urlsplit
from datetime import datetime
from .hooks import dispatch_hook
from .hooks import dispatch_hook, HOOKS
from .structures import CaseInsensitiveDict
from .status_codes import codes
from .packages import oreos
from .auth import HTTPBasicAuth, HTTPProxyAuth
from .packages.urllib3.response import HTTPResponse
from .packages.urllib3.exceptions import MaxRetryError
from .cookies import cookiejar_from_dict, extract_cookies_to_jar, get_cookie_header
from .packages.urllib3.exceptions import MaxRetryError, LocationParseError
from .packages.urllib3.exceptions import SSLError as _SSLError
from .packages.urllib3.exceptions import HTTPError as _HTTPError
from .packages.urllib3 import connectionpool, poolmanager
from .packages.urllib3.filepost import encode_multipart_formdata
from .defaults import SCHEMAS
from .exceptions import (
ConnectionError, HTTPError, RequestException, Timeout, TooManyRedirects,
URLRequired, SSLError)
URLRequired, SSLError, MissingSchema, InvalidSchema, InvalidURL)
from .utils import (
get_encoding_from_headers, stream_decode_response_unicode,
stream_decompress, guess_filename, requote_path)
get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri,
stream_decode_response_unicode, get_netrc_auth, get_environ_proxies,
DEFAULT_CA_BUNDLE_PATH)
from .compat import (
cookielib, urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
StringIO, is_py2)
# Import chardet if it is available.
try:
import chardet
# hush pyflakes
chardet
except ImportError:
chardet = None
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
CONTENT_CHUNK_SIZE = 10 * 1024
class Request(object):
"""The :class:`Request <Request>` object. It carries out all functionality of
@@ -56,8 +65,14 @@ class Request(object):
proxies=None,
hooks=None,
config=None,
prefetch=False,
_poolmanager=None,
verify=None):
verify=None,
session=None,
cert=None):
#: Dictionary of configurations for this request.
self.config = dict(config or [])
#: Float describes the timeout of the request.
# (Use socket.setdefaulttimeout() as fallback)
@@ -70,17 +85,18 @@ class Request(object):
self.headers = dict(headers or [])
#: Dictionary of files to multipart upload (``{filename: content}``).
self.files = files
self.files = None
#: HTTP Method to use.
self.method = method
#: Dictionary or byte of request body data to attach to the
#: Dictionary, bytes or file stream of request body data to attach to the
#: :class:`Request <Request>`.
self.data = None
#: Dictionary or byte of querystring data to attach to the
#: :class:`Request <Request>`.
#: :class:`Request <Request>`. The dictionary values can be lists for representing
#: multivalued query parameters.
self.params = None
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
@@ -93,8 +109,14 @@ class Request(object):
# Dictionary mapping protocol to the URL of the proxy (e.g. {'http': 'foo.bar:3128'})
self.proxies = dict(proxies or [])
self.data, self._enc_data = self._encode_params(data)
self.params, self._enc_params = self._encode_params(params)
# If no proxies are given, allow configuration by environment variables
# HTTP_PROXY and HTTPS_PROXY.
if not self.proxies and self.config.get('trust_env'):
self.proxies = get_environ_proxies()
self.data = data
self.params = params
self.files = files
#: :class:`Response <Response>` instance, containing
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
@@ -104,46 +126,54 @@ class Request(object):
self.auth = auth
#: CookieJar to attach to :class:`Request <Request>`.
self.cookies = dict(cookies or [])
#: Dictionary of configurations for this request.
self.config = dict(config or [])
if isinstance(cookies, cookielib.CookieJar):
self.cookies = cookies
else:
self.cookies = cookiejar_from_dict(cookies)
#: True if Request has been sent.
self.sent = False
#: Event-handling hooks.
self.hooks = hooks
self.hooks = {}
for event in HOOKS:
self.hooks[event] = []
hooks = hooks or {}
for (k, v) in list(hooks.items()):
self.register_hook(event=k, hook=v)
#: Session.
self.session = None
self.session = session
#: SSL Verification.
self.verify = verify
#: SSL Certificate
self.cert = cert
#: Prefetch response content
self.prefetch = prefetch
if headers:
headers = CaseInsensitiveDict(self.headers)
else:
headers = CaseInsensitiveDict()
# Add configured base headers.
for (k, v) in self.config.get('base_headers', {}).items():
for (k, v) in list(self.config.get('base_headers', {}).items()):
if k not in headers:
headers[k] = v
self.headers = headers
self._poolmanager = _poolmanager
# Pre-request hook.
r = dispatch_hook('pre_request', hooks, self)
self.__dict__.update(r.__dict__)
def __repr__(self):
return '<Request [%s]>' % (self.method)
def _build_response(self, resp, is_error=False):
def _build_response(self, resp):
"""Build internal :class:`Response <Response>` object
from given response.
"""
@@ -166,49 +196,51 @@ class Request(object):
# Set encoding.
response.encoding = get_encoding_from_headers(response.headers)
# Start off with our local cookies.
cookies = self.cookies or dict()
# Add new cookies from the server.
if 'set-cookie' in response.headers:
cookie_header = response.headers['set-cookie']
cookies = oreos.dict_from_string(cookie_header)
# Add new cookies from the server. Don't if configured not to
if self.config.get('store_cookies'):
extract_cookies_to_jar(self.cookies, self, resp)
# Save cookies in Response.
response.cookies = cookies
response.cookies = self.cookies
# Save cookies in Session.
for cookie in self.cookies:
self.session.cookies.set_cookie(cookie)
# No exceptions were harmed in the making of this request.
response.error = getattr(resp, 'error', None)
# Save original response for later.
response.raw = resp
if is_error:
response.error = resp
response.url = self.full_url
if isinstance(self.full_url, bytes):
response.url = self.full_url.decode('utf-8')
else:
response.url = self.full_url
return response
history = []
r = build(resp)
cookies = self.cookies
self.cookies.update(r.cookies)
if r.status_code in REDIRECT_STATI and not self.redirect:
while (
('location' in r.headers) and
((r.status_code is codes.see_other) or (self.allow_redirects))
):
while (('location' in r.headers) and
((r.status_code is codes.see_other) or (self.allow_redirects))):
r.content # Consume socket so it can be released
if not len(history) < self.config.get('max_redirects'):
raise TooManyRedirects()
# Release the connection back into the pool.
r.raw.release_conn()
history.append(r)
url = r.headers['location']
data = self.data
files = self.files
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'):
@@ -218,14 +250,32 @@ class Request(object):
# Facilitate non-RFC2616-compliant 'location' headers
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
if not urlparse(url).netloc:
url = urljoin(r.url, url)
url = urljoin(r.url,
# Compliant with RFC3986, we percent
# encode the url.
requote_uri(url))
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
if r.status_code is codes.see_other:
method = 'GET'
data = None
files = None
else:
method = self.method
# Do what the browsers do if strict_mode is off...
if (not self.config.get('strict_mode')):
if r.status_code in (codes.moved, codes.found) and self.method == 'POST':
method = 'GET'
data = None
files = None
if (r.status_code == 303) and self.method != 'HEAD':
method = 'GET'
data = None
files = None
# Remove the cookie headers that were sent.
headers = self.headers
try:
@@ -236,55 +286,86 @@ class Request(object):
request = Request(
url=url,
headers=headers,
files=self.files,
files=files,
method=method,
params=self.session.params,
auth=self.auth,
cookies=cookies,
cookies=self.cookies,
redirect=True,
data=data,
config=self.config,
timeout=self.timeout,
_poolmanager=self._poolmanager,
proxies = self.proxies,
verify = self.verify
proxies=self.proxies,
verify=self.verify,
session=self.session,
cert=self.cert
)
request.send()
cookies.update(request.response.cookies)
r = request.response
self.cookies.update(r.cookies)
r.history = history
self.response = r
self.response.request = self
self.response.cookies.update(self.cookies)
@staticmethod
def _encode_params(data):
"""Encode parameters in a piece of data.
If the data supplied is a dictionary, encodes each parameter in it, and
returns a list of tuples containing the encoded parameters, and a urlencoded
version of that.
Otherwise, assumes the data is already encoded appropriately, and
returns it twice.
Will successfully encode parameters when passed as a dict or a list of
2-tuples. Order is retained if data is a list of 2-tuples but abritrary
if parameters are supplied as a dict.
"""
if hasattr(data, '__iter__'):
data = dict(data)
if isinstance(data, bytes):
return data
if isinstance(data, str):
return data
elif hasattr(data, 'read'):
return data
elif hasattr(data, '__iter__'):
try:
dict(data)
except ValueError:
raise ValueError('Unable to encode lists with elements that are not 2-tuples.')
if hasattr(data, 'items'):
params = list(data.items() if isinstance(data, dict) else data)
result = []
for k, vs in data.items():
for k, vs in params:
for v in isinstance(vs, list) and vs or [vs]:
result.append((k.encode('utf-8') if isinstance(k, unicode) else k,
v.encode('utf-8') if isinstance(v, unicode) else v))
return result, urllib.urlencode(result, doseq=True)
result.append(
(k.encode('utf-8') if isinstance(k, str) else k,
v.encode('utf-8') if isinstance(v, str) else v))
return urlencode(result, doseq=True)
else:
return data, data
return data
def _encode_files(self, files):
if (not files) or isinstance(self.data, str):
return None
try:
fields = self.data.copy()
except AttributeError:
fields = dict(self.data)
for (k, v) in list(files.items()):
# support for explicit filename
if isinstance(v, (tuple, list)):
fn, fp = v
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, (bytes, str)):
fp = StringIO(fp)
fields.update({k: (fn, fp.read())})
(body, content_type) = encode_multipart_formdata(fields)
return (body, content_type)
@property
def full_url(self):
@@ -293,28 +374,49 @@ class Request(object):
if not self.url:
raise URLRequired()
url = self.url
# Support for unicode domain names and paths.
scheme, netloc, path, params, query, fragment = urlparse(self.url)
scheme, netloc, path, params, query, fragment = urlparse(url)
if not scheme:
raise ValueError("Invalid URL %r: No schema supplied" %self.url)
raise MissingSchema("Invalid URL %r: No schema supplied" % url)
netloc = netloc.encode('idna')
if not scheme in SCHEMAS:
raise InvalidSchema("Invalid scheme %r" % scheme)
if isinstance(path, unicode):
path = path.encode('utf-8')
netloc = netloc.encode('idna').decode('utf-8')
path = requote_path(path)
if not path:
path = '/'
url = str(urlunparse([ scheme, netloc, path, params, query, fragment ]))
if is_py2:
if isinstance(scheme, str):
scheme = scheme.encode('utf-8')
if isinstance(netloc, str):
netloc = netloc.encode('utf-8')
if isinstance(path, str):
path = path.encode('utf-8')
if isinstance(params, str):
params = params.encode('utf-8')
if isinstance(query, str):
query = query.encode('utf-8')
if isinstance(fragment, str):
fragment = fragment.encode('utf-8')
if self._enc_params:
url = (urlunparse([scheme, netloc, path, params, query, fragment]))
enc_params = self._encode_params(self.params)
if enc_params:
if urlparse(url).query:
return '%s&%s' % (url, self._enc_params)
url = '%s&%s' % (url, enc_params)
else:
return '%s?%s' % (url, self._enc_params)
else:
return url
url = '%s?%s' % (url, enc_params)
if self.config.get('encode_uri', True):
url = requote_uri(url)
return url
@property
def path_url(self):
@@ -331,6 +433,7 @@ class Request(object):
path = p.path
if not path:
path = '/'
url.append(path)
query = p.query
@@ -340,10 +443,24 @@ class Request(object):
return ''.join(url)
def register_hook(self, event, hook):
"""Properly register a hook."""
self.hooks[event].append(hook)
def deregister_hook(self, event, hook):
"""Deregister a previously registered hook.
Returns True if the hook existed, False if not.
"""
try:
self.hooks[event].remove(hook)
return True
except ValueError:
return False
def send(self, anyway=False, prefetch=False):
"""Sends the request. Returns True of successful, false if not.
"""Sends the request. Returns True if successful, False if not.
If there was an HTTPError during transmission,
self.response.status_code will contain the HTTPError code.
@@ -356,6 +473,10 @@ class Request(object):
# Build the URL
url = self.full_url
# Pre-request hook.
r = dispatch_hook('pre_request', self.hooks, self)
self.__dict__.update(r.__dict__)
# Logging
if self.config.get('verbose'):
self.config.get('verbose').write('%s %s %s\n' % (
@@ -366,40 +487,9 @@ class Request(object):
body = None
content_type = None
# Multi-part file uploads.
if self.files:
if not isinstance(self.data, basestring):
try:
fields = self.data.copy()
except AttributeError:
fields = dict(self.data)
for (k, v) in self.files.items():
# support for explicit filename
if isinstance(v, (tuple, list)):
fn, fp = v
else:
fn = guess_filename(v) or k
fp = v
fields.update({k: (fn, fp.read())})
(body, content_type) = encode_multipart_formdata(fields)
else:
pass
# TODO: Conflict?
else:
if self.data:
body = self._enc_data
if isinstance(self.data, basestring):
content_type = None
else:
content_type = 'application/x-www-form-urlencoded'
# Add content-type if it wasn't explicitly provided.
if (content_type) and (not 'content-type' in self.headers):
self.headers['Content-Type'] = content_type
# Use .netrc auth if none was provided.
if not self.auth and self.config.get('trust_env'):
self.auth = get_netrc_auth(url)
if self.auth:
if isinstance(self.auth, tuple) and len(self.auth) == 2:
@@ -412,10 +502,27 @@ class Request(object):
# Update self to reflect the auth changes.
self.__dict__.update(r.__dict__)
# Multi-part file uploads.
if self.files:
(body, content_type) = self._encode_files(self.files)
else:
if self.data:
body = self._encode_params(self.data)
if isinstance(self.data, str) or hasattr(self.data, 'read'):
content_type = None
else:
content_type = 'application/x-www-form-urlencoded'
# Add content-type if it wasn't explicitly provided.
if (content_type) and (not 'content-type' in self.headers):
self.headers['Content-Type'] = content_type
_p = urlparse(url)
no_proxy = filter(lambda x:x.strip(), self.proxies.get('no', '').split(','))
proxy = self.proxies.get(_p.scheme)
if proxy:
if proxy and not any(map(_p.netloc.endswith, no_proxy)):
conn = poolmanager.proxy_from_url(proxy)
_proxy = urlparse(proxy)
if '@' in _proxy.netloc:
@@ -425,10 +532,14 @@ class Request(object):
self.__dict__.update(r.__dict__)
else:
# Check to see if keep_alive is allowed.
if self.config.get('keep_alive'):
conn = self._poolmanager.connection_from_url(url)
else:
conn = connectionpool.connection_from_url(url)
try:
if self.config.get('keep_alive'):
conn = self._poolmanager.connection_from_url(url)
else:
conn = connectionpool.connection_from_url(url)
self.headers['Connection'] = 'close'
except LocationParseError as e:
raise InvalidURL(e)
if url.startswith('https') and self.verify:
@@ -438,78 +549,72 @@ class Request(object):
if self.verify is not True:
cert_loc = self.verify
# Look for configuration.
if not cert_loc:
if not cert_loc and self.config.get('trust_env'):
cert_loc = os.environ.get('REQUESTS_CA_BUNDLE')
# Curl compatiblity.
if not cert_loc:
# Curl compatibility.
if not cert_loc and self.config.get('trust_env'):
cert_loc = os.environ.get('CURL_CA_BUNDLE')
# Use the awesome certifi list.
if not cert_loc:
cert_loc = __import__('certifi').where()
cert_loc = DEFAULT_CA_BUNDLE_PATH
if not cert_loc:
raise Exception("Could not find a suitable SSL CA certificate bundle.")
conn.cert_reqs = 'CERT_REQUIRED'
conn.ca_certs = cert_loc
else:
conn.cert_reqs = 'CERT_NONE'
conn.ca_certs = None
if self.cert and self.verify:
if len(self.cert) == 2:
conn.cert_file = self.cert[0]
conn.key_file = self.cert[1]
else:
conn.cert_file = self.cert
if not self.sent or anyway:
if self.cookies:
# Skip if 'cookie' header is explicitly set.
if 'cookie' not in self.headers:
# Simple cookie with our dict.
c = oreos.monkeys.SimpleCookie()
for (k, v) in self.cookies.items():
c[k] = v
# Turn it into a header.
cookie_header = c.output(header='', sep='; ').strip()
# Attach Cookie header to request.
# Skip if 'cookie' header is explicitly set.
if 'cookie' not in self.headers:
cookie_header = get_cookie_header(self.cookies, self)
if cookie_header is not None:
self.headers['Cookie'] = cookie_header
# Pre-send hook.
r = dispatch_hook('pre_send', self.hooks, self)
self.__dict__.update(r.__dict__)
# catch urllib3 exceptions and throw Requests exceptions
try:
# The inner try .. except re-raises certain exceptions as
# internal exception types; the outer suppresses exceptions
# when safe mode is set.
try:
# Send the request.
r = conn.urlopen(
method=self.method,
url=self.path_url,
body=body,
headers=self.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=True,
retries=self.config.get('max_retries', 0),
timeout=self.timeout,
)
self.sent = True
# Send the request.
r = conn.urlopen(
method=self.method,
url=self.path_url,
body=body,
headers=self.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.config.get('max_retries', 0),
timeout=self.timeout,
)
self.sent = True
except MaxRetryError, e:
raise ConnectionError(e)
except MaxRetryError as e:
raise ConnectionError(e)
except (_SSLError, _HTTPError), e:
if self.verify and isinstance(e, _SSLError):
raise SSLError(e)
except (_SSLError, _HTTPError) as e:
if self.verify and isinstance(e, _SSLError):
raise SSLError(e)
raise Timeout('Request timed out.')
except RequestException, e:
if self.config.get('safe_mode', False):
# In safe mode, catch the exception and attach it to
# a blank urllib3.HTTPResponse object.
r = HTTPResponse()
r.error = e
else:
raise
raise Timeout('Request timed out.')
# build_response can throw TooManyRedirects
self._build_response(r)
# Response manipulation hook.
@@ -520,10 +625,10 @@ class Request(object):
self.__dict__.update(r.__dict__)
# If prefetch is True, mark content as consumed.
if prefetch:
if prefetch or self.prefetch:
# Save the response.
self.response.content
if self.config.get('danger_mode'):
self.response.raise_for_status()
@@ -539,7 +644,7 @@ class Response(object):
def __init__(self):
self._content = None
self._content = False
self._content_consumed = False
#: Integer Code of responded HTTP Status.
@@ -559,7 +664,7 @@ class Response(object):
#: Resulting :class:`HTTPError` of request, if one occurred.
self.error = None
#: Encoding to decode with when accessing r.content.
#: Encoding to decode with when accessing r.text.
self.encoding = None
#: A list of :class:`Response <Response>` objects from
@@ -570,16 +675,19 @@ class Response(object):
#: The :class:`Request <Request>` that created the Response.
self.request = None
#: A dictionary of Cookies the server sent back.
self.cookies = {}
#: A CookieJar of Cookies the server sent back.
self.cookies = None
#: Dictionary of configurations for this request.
self.config = {}
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
def __bool__(self):
"""Returns true if :attr:`status_code` is 'OK'."""
return self.ok
def __nonzero__(self):
"""Returns true if :attr:`status_code` is 'OK'."""
return self.ok
@@ -588,12 +696,11 @@ class Response(object):
def ok(self):
try:
self.raise_for_status()
except HTTPError:
except RequestException:
return False
return True
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
def iter_content(self, chunk_size=1, decode_unicode=False):
"""Iterates over the response data. This avoids reading the content
at once into memory for large responses. The chunk size is the number
of bytes it should read into memory. This is not necessarily the
@@ -611,133 +718,120 @@ class Response(object):
break
yield chunk
self._content_consumed = True
def generate_chunked():
resp = self.raw._original_response
fp = resp.fp
if resp.chunk_left is not None:
pending_bytes = resp.chunk_left
while pending_bytes:
chunk = fp.read(min(chunk_size, pending_bytes))
pending_bytes-=len(chunk)
yield chunk
fp.read(2) # throw away crlf
while 1:
#XXX correct line size? (httplib has 64kb, seems insane)
pending_bytes = fp.readline(40).strip()
pending_bytes = int(pending_bytes, 16)
if pending_bytes == 0:
break
while pending_bytes:
chunk = fp.read(min(chunk_size, pending_bytes))
pending_bytes-=len(chunk)
yield chunk
fp.read(2) # throw away crlf
self._content_consumed = True
fp.close()
if getattr(getattr(self.raw, '_original_response', None), 'chunked', False):
gen = generate_chunked()
else:
gen = generate()
if 'gzip' in self.headers.get('content-encoding', ''):
gen = stream_decompress(gen, mode='gzip')
elif 'deflate' in self.headers.get('content-encoding', ''):
gen = stream_decompress(gen, mode='deflate')
if decode_unicode is None:
decode_unicode = self.config.get('decode_unicode')
gen = stream_untransfer(generate(), self)
if decode_unicode:
gen = stream_decode_response_unicode(gen, self)
return gen
def iter_lines(self, chunk_size=10 * 1024, decode_unicode=None):
"""Iterates over the response data, one line at a time. This
avoids reading the content at once into memory for large
responses.
"""
#XXX: why rstrip by default
pending = None
for chunk in self.iter_content(chunk_size, decode_unicode=decode_unicode):
for chunk in self.iter_content(
chunk_size=chunk_size,
decode_unicode=decode_unicode):
if pending is not None:
chunk = pending + chunk
lines = chunk.splitlines(True)
for line in lines[:-1]:
yield line.rstrip()
# Save the last part of the chunk for next iteration, to keep full line together
# lines may be empty for the last chunk of a chunked response
if lines:
pending = lines[-1]
#if pending is a complete line, give it baack
if pending[-1] == '\n':
yield pending.rstrip()
pending = None
lines = chunk.splitlines()
if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
pending = lines.pop()
else:
pending = None
# Yield the last line
if pending is not None:
yield pending.rstrip()
for line in lines:
yield line
if pending is not None:
yield pending
@property
def content(self):
"""Content of the response, in bytes or unicode
(if available).
"""
"""Content of the response, in bytes."""
if self._content is None:
if self._content is False:
# Read the contents.
try:
if self._content_consumed:
raise RuntimeError(
'The content for this response was already consumed')
self._content = self.raw.read()
if self.status_code is 0:
self._content = None
else:
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
except AttributeError:
self._content = None
content = self._content
# Decode unicode content.
if self.config.get('decode_unicode'):
# Try charset from content-type
if self.encoding:
try:
content = unicode(content, self.encoding)
except UnicodeError:
pass
# Fall back:
try:
content = unicode(content, self.encoding, errors='replace')
except TypeError:
pass
self._content_consumed = True
return self._content
@property
def text(self):
"""Content of the response, in unicode.
if Response.encoding is None and chardet module is available, encoding
will be guessed.
"""
# Try charset from content-type
content = None
encoding = self.encoding
if not self.content:
return str('')
# Fallback to auto-detected encoding.
if self.encoding is None:
if chardet is not None:
encoding = chardet.detect(self.content)['encoding']
# Decode unicode from given encoding.
try:
content = str(self.content, encoding, errors='replace')
except LookupError:
# A LookupError is raised if the encoding was not found which could
# indicate a misspelling or similar mistake.
#
# So we try blindly encoding.
content = str(self.content, errors='replace')
return content
@property
def json(self):
"""Returns the json-encoded content of a request, if any."""
try:
return json.loads(self.text or self.content)
except ValueError:
return None
def raise_for_status(self):
def raise_for_status(self, allow_redirects=True):
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""
if self.error:
raise self.error
if (self.status_code >= 300) and (self.status_code < 400):
raise HTTPError('%s Redirection' % self.status_code)
if (self.status_code >= 300) and (self.status_code < 400) and not allow_redirects:
http_error = HTTPError('%s Redirection' % self.status_code)
http_error.response = self
raise http_error
elif (self.status_code >= 400) and (self.status_code < 500):
raise HTTPError('%s Client Error' % self.status_code)
http_error = HTTPError('%s Client Error' % self.status_code)
http_error.response = self
raise http_error
elif (self.status_code >= 500) and (self.status_code < 600):
raise HTTPError('%s Server Error' % self.status_code)
http_error = HTTPError('%s Server Error' % self.status_code)
http_error.response = self
raise http_error

View File

@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from .core import dict_from_string

View File

@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
"""
oreos.core
~~~~~~~~~~
The creamy white center.
"""
from .monkeys import SimpleCookie
def dict_from_string(s):
"""Returns a MultiDict with Cookies."""
cookies = dict()
c = SimpleCookie()
c.load(s)
for k,v in c.items():
cookies.update({k: v.value})
return cookies

View File

@@ -1,770 +0,0 @@
# -*- coding: utf-8 -*-
"""
oreos.monkeys
~~~~~~~~~~~~~
Monkeypatches.
"""
#!/usr/bin/env python
#
####
# Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software
# and its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Timothy O'Malley not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
# ANY SPECIAL, 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.
#
####
#
# Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp
# by Timothy O'Malley <timo@alum.mit.edu>
#
# Cookie.py is a Python module for the handling of HTTP
# cookies as a Python dictionary. See RFC 2109 for more
# information on cookies.
#
# The original idea to treat Cookies as a dictionary came from
# Dave Mitchell (davem@magnet.com) in 1995, when he released the
# first version of nscookie.py.
#
####
r"""
Here's a sample session to show how to use this module.
At the moment, this is the only documentation.
The Basics
----------
Importing is easy..
>>> import Cookie
Most of the time you start by creating a cookie. Cookies come in
three flavors, each with slightly different encoding semantics, but
more on that later.
>>> C = Cookie.SimpleCookie()
>>> C = Cookie.SerialCookie()
>>> C = Cookie.SmartCookie()
[Note: Long-time users of Cookie.py will remember using
Cookie.Cookie() to create an Cookie object. Although deprecated, it
is still supported by the code. See the Backward Compatibility notes
for more information.]
Once you've created your Cookie, you can add values just as if it were
a dictionary.
>>> C = Cookie.SmartCookie()
>>> C["fig"] = "newton"
>>> C["sugar"] = "wafer"
>>> C.output()
'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer'
Notice that the printable representation of a Cookie is the
appropriate format for a Set-Cookie: header. This is the
default behavior. You can change the header and printed
attributes by using the .output() function
>>> C = Cookie.SmartCookie()
>>> C["rocky"] = "road"
>>> C["rocky"]["path"] = "/cookie"
>>> print C.output(header="Cookie:")
Cookie: rocky=road; Path=/cookie
>>> print C.output(attrs=[], header="Cookie:")
Cookie: rocky=road
The load() method of a Cookie extracts cookies from a string. In a
CGI script, you would use this method to extract the cookies from the
HTTP_COOKIE environment variable.
>>> C = Cookie.SmartCookie()
>>> C.load("chips=ahoy; vienna=finger")
>>> C.output()
'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger'
The load() method is darn-tootin smart about identifying cookies
within a string. Escaped quotation marks, nested semicolons, and other
such trickeries do not confuse it.
>>> C = Cookie.SmartCookie()
>>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
>>> print C
Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
Each element of the Cookie also supports all of the RFC 2109
Cookie attributes. Here's an example which sets the Path
attribute.
>>> C = Cookie.SmartCookie()
>>> C["oreo"] = "doublestuff"
>>> C["oreo"]["path"] = "/"
>>> print C
Set-Cookie: oreo=doublestuff; Path=/
Each dictionary element has a 'value' attribute, which gives you
back the value associated with the key.
>>> C = Cookie.SmartCookie()
>>> C["twix"] = "none for you"
>>> C["twix"].value
'none for you'
A Bit More Advanced
-------------------
As mentioned before, there are three different flavors of Cookie
objects, each with different encoding/decoding semantics. This
section briefly discusses the differences.
SimpleCookie
The SimpleCookie expects that all values should be standard strings.
Just to be sure, SimpleCookie invokes the str() builtin to convert
the value to a string, when the values are set dictionary-style.
>>> C = Cookie.SimpleCookie()
>>> C["number"] = 7
>>> C["string"] = "seven"
>>> C["number"].value
'7'
>>> C["string"].value
'seven'
>>> C.output()
'Set-Cookie: number=7\r\nSet-Cookie: string=seven'
SerialCookie
The SerialCookie expects that all values should be serialized using
cPickle (or pickle, if cPickle isn't available). As a result of
serializing, SerialCookie can save almost any Python object to a
value, and recover the exact same object when the cookie has been
returned. (SerialCookie can yield some strange-looking cookie
values, however.)
>>> C = Cookie.SerialCookie()
>>> C["number"] = 7
>>> C["string"] = "seven"
>>> C["number"].value
7
>>> C["string"].value
'seven'
>>> C.output()
'Set-Cookie: number="I7\\012."\r\nSet-Cookie: string="S\'seven\'\\012p1\\012."'
Be warned, however, if SerialCookie cannot de-serialize a value (because
it isn't a valid pickle'd object), IT WILL RAISE AN EXCEPTION.
SmartCookie
The SmartCookie combines aspects of each of the other two flavors.
When setting a value in a dictionary-fashion, the SmartCookie will
serialize (ala cPickle) the value *if and only if* it isn't a
Python string. String objects are *not* serialized. Similarly,
when the load() method parses out values, it attempts to de-serialize
the value. If it fails, then it fallsback to treating the value
as a string.
>>> C = Cookie.SmartCookie()
>>> C["number"] = 7
>>> C["string"] = "seven"
>>> C["number"].value
7
>>> C["string"].value
'seven'
>>> C.output()
'Set-Cookie: number="I7\\012."\r\nSet-Cookie: string=seven'
Backwards Compatibility
-----------------------
In order to keep compatibilty with earlier versions of Cookie.py,
it is still possible to use Cookie.Cookie() to create a Cookie. In
fact, this simply returns a SmartCookie.
>>> C = Cookie.Cookie()
>>> print C.__class__.__name__
SmartCookie
Finis.
""" #"
# ^
# |----helps out font-lock
#
# Import our required modules
#
import string
try:
from cPickle import dumps, loads
except ImportError:
from pickle import dumps, loads
import re, warnings
__all__ = ["CookieError","BaseCookie","SimpleCookie","SerialCookie",
"SmartCookie","Cookie"]
_nulljoin = ''.join
_semispacejoin = '; '.join
_spacejoin = ' '.join
#
# Define an exception visible to External modules
#
class CookieError(Exception):
pass
# These quoting routines conform to the RFC2109 specification, which in
# turn references the character definitions from RFC2068. They provide
# a two-way quoting algorithm. Any non-text character is translated
# into a 4 character sequence: a forward-slash followed by the
# three-digit octal equivalent of the character. Any '\' or '"' is
# quoted with a preceeding '\' slash.
#
# These are taken from RFC2068 and RFC2109.
# _LegalChars is the list of chars which don't require "'s
# _Translator hash-table for fast quoting
#
_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~[]_"
_Translator = {
'\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
'\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
'\006' : '\\006', '\007' : '\\007', '\010' : '\\010',
'\011' : '\\011', '\012' : '\\012', '\013' : '\\013',
'\014' : '\\014', '\015' : '\\015', '\016' : '\\016',
'\017' : '\\017', '\020' : '\\020', '\021' : '\\021',
'\022' : '\\022', '\023' : '\\023', '\024' : '\\024',
'\025' : '\\025', '\026' : '\\026', '\027' : '\\027',
'\030' : '\\030', '\031' : '\\031', '\032' : '\\032',
'\033' : '\\033', '\034' : '\\034', '\035' : '\\035',
'\036' : '\\036', '\037' : '\\037',
# Because of the way browsers really handle cookies (as opposed
# to what the RFC says) we also encode , and ;
',' : '\\054', ';' : '\\073',
'"' : '\\"', '\\' : '\\\\',
'\177' : '\\177', '\200' : '\\200', '\201' : '\\201',
'\202' : '\\202', '\203' : '\\203', '\204' : '\\204',
'\205' : '\\205', '\206' : '\\206', '\207' : '\\207',
'\210' : '\\210', '\211' : '\\211', '\212' : '\\212',
'\213' : '\\213', '\214' : '\\214', '\215' : '\\215',
'\216' : '\\216', '\217' : '\\217', '\220' : '\\220',
'\221' : '\\221', '\222' : '\\222', '\223' : '\\223',
'\224' : '\\224', '\225' : '\\225', '\226' : '\\226',
'\227' : '\\227', '\230' : '\\230', '\231' : '\\231',
'\232' : '\\232', '\233' : '\\233', '\234' : '\\234',
'\235' : '\\235', '\236' : '\\236', '\237' : '\\237',
'\240' : '\\240', '\241' : '\\241', '\242' : '\\242',
'\243' : '\\243', '\244' : '\\244', '\245' : '\\245',
'\246' : '\\246', '\247' : '\\247', '\250' : '\\250',
'\251' : '\\251', '\252' : '\\252', '\253' : '\\253',
'\254' : '\\254', '\255' : '\\255', '\256' : '\\256',
'\257' : '\\257', '\260' : '\\260', '\261' : '\\261',
'\262' : '\\262', '\263' : '\\263', '\264' : '\\264',
'\265' : '\\265', '\266' : '\\266', '\267' : '\\267',
'\270' : '\\270', '\271' : '\\271', '\272' : '\\272',
'\273' : '\\273', '\274' : '\\274', '\275' : '\\275',
'\276' : '\\276', '\277' : '\\277', '\300' : '\\300',
'\301' : '\\301', '\302' : '\\302', '\303' : '\\303',
'\304' : '\\304', '\305' : '\\305', '\306' : '\\306',
'\307' : '\\307', '\310' : '\\310', '\311' : '\\311',
'\312' : '\\312', '\313' : '\\313', '\314' : '\\314',
'\315' : '\\315', '\316' : '\\316', '\317' : '\\317',
'\320' : '\\320', '\321' : '\\321', '\322' : '\\322',
'\323' : '\\323', '\324' : '\\324', '\325' : '\\325',
'\326' : '\\326', '\327' : '\\327', '\330' : '\\330',
'\331' : '\\331', '\332' : '\\332', '\333' : '\\333',
'\334' : '\\334', '\335' : '\\335', '\336' : '\\336',
'\337' : '\\337', '\340' : '\\340', '\341' : '\\341',
'\342' : '\\342', '\343' : '\\343', '\344' : '\\344',
'\345' : '\\345', '\346' : '\\346', '\347' : '\\347',
'\350' : '\\350', '\351' : '\\351', '\352' : '\\352',
'\353' : '\\353', '\354' : '\\354', '\355' : '\\355',
'\356' : '\\356', '\357' : '\\357', '\360' : '\\360',
'\361' : '\\361', '\362' : '\\362', '\363' : '\\363',
'\364' : '\\364', '\365' : '\\365', '\366' : '\\366',
'\367' : '\\367', '\370' : '\\370', '\371' : '\\371',
'\372' : '\\372', '\373' : '\\373', '\374' : '\\374',
'\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
}
_idmap = ''.join(chr(x) for x in xrange(256))
def _quote(str, LegalChars=_LegalChars,
idmap=_idmap, translate=string.translate):
#
# If the string does not need to be double-quoted,
# then just return the string. Otherwise, surround
# the string in doublequotes and precede quote (with a \)
# special characters.
#
if "" == translate(str, idmap, LegalChars):
return str
else:
return '"' + _nulljoin( map(_Translator.get, str, str) ) + '"'
# end _quote
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
_QuotePatt = re.compile(r"[\\].")
def _unquote(str):
# If there aren't any doublequotes,
# then there can't be any special characters. See RFC 2109.
if len(str) < 2:
return str
if str[0] != '"' or str[-1] != '"':
return str
# We have to assume that we must decode this string.
# Down to work.
# Remove the "s
str = str[1:-1]
# Check for special sequences. Examples:
# \012 --> \n
# \" --> "
#
i = 0
n = len(str)
res = []
while 0 <= i < n:
Omatch = _OctalPatt.search(str, i)
Qmatch = _QuotePatt.search(str, i)
if not Omatch and not Qmatch: # Neither matched
res.append(str[i:])
break
# else:
j = k = -1
if Omatch: j = Omatch.start(0)
if Qmatch: k = Qmatch.start(0)
if Qmatch and ( not Omatch or k < j ): # QuotePatt matched
res.append(str[i:k])
res.append(str[k+1])
i = k+2
else: # OctalPatt matched
res.append(str[i:j])
res.append( chr( int(str[j+1:j+4], 8) ) )
i = j+4
return _nulljoin(res)
# end _unquote
# The _getdate() routine is used to set the expiration time in
# the cookie's HTTP header. By default, _getdate() returns the
# current time in the appropriate "expires" format for a
# Set-Cookie header. The one optional argument is an offset from
# now, in seconds. For example, an offset of -3600 means "one hour ago".
# The offset may be a floating point number.
#
_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
_monthname = [None,
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
from time import gmtime, time
now = time()
year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future)
return "%s, %02d-%3s-%4d %02d:%02d:%02d GMT" % \
(weekdayname[wd], day, monthname[month], year, hh, mm, ss)
#
# A class to hold ONE key,value pair.
# In a cookie, each such pair may have several attributes.
# so this class is used to keep the attributes associated
# with the appropriate key,value pair.
# This class also includes a coded_value attribute, which
# is used to hold the network representation of the
# value. This is most useful when Python objects are
# pickled for network transit.
#
class Morsel(dict):
# RFC 2109 lists these attributes as reserved:
# path comment domain
# max-age secure version
#
# For historical reasons, these attributes are also reserved:
# expires
#
# This is an extension from Microsoft:
# httponly
#
# This dictionary provides a mapping from the lowercase
# variant on the left to the appropriate traditional
# formatting on the right.
_reserved = { "expires" : "expires",
"path" : "Path",
"comment" : "Comment",
"domain" : "Domain",
"max-age" : "Max-Age",
"secure" : "secure",
"httponly" : "httponly",
"version" : "Version",
}
def __init__(self):
# Set defaults
self.key = self.value = self.coded_value = None
# Set default attributes
for K in self._reserved:
dict.__setitem__(self, K, "")
# end __init__
def __setitem__(self, K, V):
K = K.lower()
if not K in self._reserved:
raise CookieError("Invalid Attribute %s" % K)
dict.__setitem__(self, K, V)
# end __setitem__
def isReservedKey(self, K):
return K.lower() in self._reserved
# end isReservedKey
def set(self, key, val, coded_val,
LegalChars=_LegalChars,
idmap=_idmap, translate=string.translate):
# First we verify that the key isn't a reserved word
# Second we make sure it only contains legal characters
if key.lower() in self._reserved:
raise CookieError("Attempt to set a reserved key: %s" % key)
if "" != translate(key, idmap, LegalChars):
raise CookieError("Illegal key value: %s" % key)
# It's a good key, so save it.
self.key = key
self.value = val
self.coded_value = coded_val
# end set
def output(self, attrs=None, header = "Set-Cookie:"):
return "%s %s" % ( header, self.OutputString(attrs) )
__str__ = output
def __repr__(self):
return '<%s: %s=%s>' % (self.__class__.__name__,
self.key, repr(self.value) )
def js_output(self, attrs=None):
# Print javascript
return """
<script type="text/javascript">
<!-- begin hiding
document.cookie = \"%s\";
// end hiding -->
</script>
""" % ( self.OutputString(attrs).replace('"',r'\"'), )
# end js_output()
def OutputString(self, attrs=None):
# Build up our result
#
result = []
RA = result.append
# First, the key=value pair
RA("%s=%s" % (self.key, self.coded_value))
# Now add any defined attributes
if attrs is None:
attrs = self._reserved
items = self.items()
items.sort()
for K,V in items:
if V == "": continue
if K not in attrs: continue
if K == "expires" and type(V) == type(1):
RA("%s=%s" % (self._reserved[K], _getdate(V)))
elif K == "max-age" and type(V) == type(1):
RA("%s=%d" % (self._reserved[K], V))
elif K == "secure":
RA(str(self._reserved[K]))
elif K == "httponly":
RA(str(self._reserved[K]))
else:
RA("%s=%s" % (self._reserved[K], V))
# Return the result
return _semispacejoin(result)
# end OutputString
# end Morsel class
#
# Pattern for finding cookie
#
# This used to be strict parsing based on the RFC2109 and RFC2068
# specifications. I have since discovered that MSIE 3.0x doesn't
# follow the character rules outlined in those specs. As a
# result, the parsing rules here are less strict.
#
_LegalCharsPatt = r"[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]\_]"
_CookiePattern = re.compile(
r"(?x)" # This is a Verbose pattern
r"(?P<key>" # Start of group 'key'
""+ _LegalCharsPatt +"+?" # Any word of at least one letter, nongreedy
r")" # End of group 'key'
r"\s*=\s*" # Equal Sign
r"(?P<val>" # Start of group 'val'
r'"(?:[^\\"]|\\.)*"' # Any doublequoted string
r"|" # or
r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr
r"|" # or
""+ _LegalCharsPatt +"*" # Any word or empty string
r")" # End of group 'val'
r"\s*;?" # Probably ending in a semi-colon
)
# At long last, here is the cookie class.
# Using this class is almost just like using a dictionary.
# See this module's docstring for example usage.
#
class BaseCookie(dict):
# A container class for a set of Morsels
#
def value_decode(self, val):
"""real_value, coded_value = value_decode(STRING)
Called prior to setting a cookie's value from the network
representation. The VALUE is the value read from HTTP
header.
Override this function to modify the behavior of cookies.
"""
return val, val
# end value_encode
def value_encode(self, val):
"""real_value, coded_value = value_encode(VALUE)
Called prior to setting a cookie's value from the dictionary
representation. The VALUE is the value being assigned.
Override this function to modify the behavior of cookies.
"""
strval = str(val)
return strval, strval
# end value_encode
def __init__(self, input=None):
if input: self.load(input)
# end __init__
def __set(self, key, real_value, coded_value):
"""Private method for setting a cookie's value"""
M = self.get(key, Morsel())
M.set(key, real_value, coded_value)
dict.__setitem__(self, key, M)
# end __set
def __setitem__(self, key, value):
"""Dictionary style assignment."""
rval, cval = self.value_encode(value)
self.__set(key, rval, cval)
# end __setitem__
def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
"""Return a string suitable for HTTP."""
result = []
items = self.items()
items.sort()
for K,V in items:
result.append( V.output(attrs, header) )
return sep.join(result)
# end output
__str__ = output
def __repr__(self):
L = []
items = self.items()
items.sort()
for K,V in items:
L.append( '%s=%s' % (K,repr(V.value) ) )
return '<%s: %s>' % (self.__class__.__name__, _spacejoin(L))
def js_output(self, attrs=None):
"""Return a string suitable for JavaScript."""
result = []
items = self.items()
items.sort()
for K,V in items:
result.append( V.js_output(attrs) )
return _nulljoin(result)
# end js_output
def load(self, rawdata):
"""Load cookies from a string (presumably HTTP_COOKIE) or
from a dictionary. Loading cookies from a dictionary 'd'
is equivalent to calling:
map(Cookie.__setitem__, d.keys(), d.values())
"""
if type(rawdata) == type(""):
self.__ParseString(rawdata)
else:
# self.update() wouldn't call our custom __setitem__
for k, v in rawdata.items():
self[k] = v
return
# end load()
def __ParseString(self, str, patt=_CookiePattern):
i = 0 # Our starting point
n = len(str) # Length of string
M = None # current morsel
while 0 <= i < n:
# Start looking for a cookie
match = patt.search(str, i)
if not match: break # No more cookies
K,V = match.group("key"), match.group("val")
i = match.end(0)
# Parse the key, value in case it's metainfo
if K[0] == "$":
# We ignore attributes which pertain to the cookie
# mechanism as a whole. See RFC 2109.
# (Does anyone care?)
if M:
M[ K[1:] ] = V
elif K.lower() in Morsel._reserved:
if M:
M[ K ] = _unquote(V)
else:
rval, cval = self.value_decode(V)
self.__set(K, rval, cval)
M = self[K]
# end __ParseString
# end BaseCookie class
class SimpleCookie(BaseCookie):
"""SimpleCookie
SimpleCookie supports strings as cookie values. When setting
the value using the dictionary assignment notation, SimpleCookie
calls the builtin str() to convert the value to a string. Values
received from HTTP are kept as strings.
"""
def value_decode(self, val):
return _unquote( val ), val
def value_encode(self, val):
strval = str(val)
return strval, _quote( strval )
# end SimpleCookie
class SerialCookie(BaseCookie):
"""SerialCookie
SerialCookie supports arbitrary objects as cookie values. All
values are serialized (using cPickle) before being sent to the
client. All incoming values are assumed to be valid Pickle
representations. IF AN INCOMING VALUE IS NOT IN A VALID PICKLE
FORMAT, THEN AN EXCEPTION WILL BE RAISED.
Note: Large cookie values add overhead because they must be
retransmitted on every HTTP transaction.
Note: HTTP has a 2k limit on the size of a cookie. This class
does not check for this limit, so be careful!!!
"""
def __init__(self, input=None):
warnings.warn("SerialCookie class is insecure; do not use it",
DeprecationWarning)
BaseCookie.__init__(self, input)
# end __init__
def value_decode(self, val):
# This could raise an exception!
return loads( _unquote(val) ), val
def value_encode(self, val):
return val, _quote( dumps(val) )
# end SerialCookie
class SmartCookie(BaseCookie):
"""SmartCookie
SmartCookie supports arbitrary objects as cookie values. If the
object is a string, then it is quoted. If the object is not a
string, however, then SmartCookie will use cPickle to serialize
the object into a string representation.
Note: Large cookie values add overhead because they must be
retransmitted on every HTTP transaction.
Note: HTTP has a 2k limit on the size of a cookie. This class
does not check for this limit, so be careful!!!
"""
def __init__(self, input=None):
warnings.warn("Cookie/SmartCookie class is insecure; do not use it",
DeprecationWarning)
BaseCookie.__init__(self, input)
# end __init__
def value_decode(self, val):
strval = _unquote(val)
try:
return loads(strval), val
except:
return strval, val
def value_encode(self, val):
if type(val) == type(""):
return val, _quote(val)
else:
return val, _quote( dumps(val) )
# end SmartCookie
###########################################################
# Backwards Compatibility: Don't break any existing code!
# We provide Cookie() as an alias for SmartCookie()
Cookie = SmartCookie
#
###########################################################
def _test():
import doctest, Cookie
return doctest.testmod(Cookie)
if __name__ == "__main__":
_test()
#Local Variables:
#tab-width: 4
#end:

View File

@@ -1,399 +0,0 @@
# -*- coding: utf-8 -*-
"""
oreos.sructures
~~~~~~~~~~~~~~~
The plastic blue packaging.
This is mostly directly stolen from mitsuhiko/werkzeug.
"""
__all__ = ('MultiDict',)
class _Missing(object):
def __repr__(self):
return 'no value'
def __reduce__(self):
return '_missing'
_missing = _Missing()
def iter_multi_items(mapping):
"""Iterates over the items of a mapping yielding keys and values
without dropping any from more complex structures.
"""
if isinstance(mapping, MultiDict):
for item in mapping.iteritems(multi=True):
yield item
elif isinstance(mapping, dict):
for key, value in mapping.iteritems():
if isinstance(value, (tuple, list)):
for value in value:
yield key, value
else:
yield key, value
else:
for item in mapping:
yield item
class TypeConversionDict(dict):
"""Works like a regular dict but the :meth:`get` method can perform
type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
are subclasses of this class and provide the same feature.
.. versionadded:: 0.5
"""
def get(self, key, default=None, type=None):
"""Return the default value if the requested data doesn't exist.
If `type` is provided and is a callable it should convert the value,
return it or raise a :exc:`ValueError` if that is not possible. In
this case the function will return the default as if the value was not
found:
>>> d = TypeConversionDict(foo='42', bar='blub')
>>> d.get('foo', type=int)
42
>>> d.get('bar', -1, type=int)
-1
:param key: The key to be looked up.
:param default: The default value to be returned if the key can't
be looked up. If not further specified `None` is
returned.
:param type: A callable that is used to cast the value in the
:class:`MultiDict`. If a :exc:`ValueError` is raised
by this callable the default value is returned.
"""
try:
rv = self[key]
if type is not None:
rv = type(rv)
except (KeyError, ValueError):
rv = default
return rv
class MultiDict(TypeConversionDict):
"""A :class:`MultiDict` is a dictionary subclass customized to deal with
multiple values for the same key which is for example used by the parsing
functions in the wrappers. This is necessary because some HTML form
elements pass multiple values for the same key.
:class:`MultiDict` implements all standard dictionary methods.
Internally, it saves all values for a key as a list, but the standard dict
access methods will only return the first value for a key. If you want to
gain access to the other values, too, you have to use the `list` methods as
explained below.
Basic Usage:
>>> d = MultiDict([('a', 'b'), ('a', 'c')])
>>> d
MultiDict([('a', 'b'), ('a', 'c')])
>>> d['a']
'b'
>>> d.getlist('a')
['b', 'c']
>>> 'a' in d
True
It behaves like a normal dict thus all dict functions will only return the
first value when multiple values for one key are found.
From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
exceptions.
A :class:`MultiDict` can be constructed from an iterable of
``(key, value)`` tuples, a dict, a :class:`MultiDict` or from Werkzeug 0.2
onwards some keyword parameters.
:param mapping: the initial value for the :class:`MultiDict`. Either a
regular dict, an iterable of ``(key, value)`` tuples
or `None`.
"""
def __init__(self, mapping=None):
if isinstance(mapping, MultiDict):
dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
elif isinstance(mapping, dict):
tmp = {}
for key, value in mapping.iteritems():
if isinstance(value, (tuple, list)):
value = list(value)
else:
value = [value]
tmp[key] = value
dict.__init__(self, tmp)
else:
tmp = {}
for key, value in mapping or ():
tmp.setdefault(key, []).append(value)
dict.__init__(self, tmp)
def __getstate__(self):
return dict(self.lists())
def __setstate__(self, value):
dict.clear(self)
dict.update(self, value)
def __iter__(self):
return self.iterkeys()
def __getitem__(self, key):
"""Return the first data value for this key;
raises KeyError if not found.
:param key: The key to be looked up.
:raise KeyError: if the key does not exist.
"""
if key in self:
return dict.__getitem__(self, key)[0]
raise KeyError(key)
def __setitem__(self, key, value):
"""Like :meth:`add` but removes an existing key first.
:param key: the key for the value.
:param value: the value to set.
"""
dict.__setitem__(self, key, [value])
def add(self, key, value):
"""Adds a new value for the key.
.. versionadded:: 0.6
:param key: the key for the value.
:param value: the value to add.
"""
dict.setdefault(self, key, []).append(value)
def getlist(self, key, type=None):
"""Return the list of items for a given key. If that key is not in the
`MultiDict`, the return value will be an empty list. Just as `get`
`getlist` accepts a `type` parameter. All items will be converted
with the callable defined there.
:param key: The key to be looked up.
:param type: A callable that is used to cast the value in the
:class:`MultiDict`. If a :exc:`ValueError` is raised
by this callable the value will be removed from the list.
:return: a :class:`list` of all the values for the key.
"""
try:
rv = dict.__getitem__(self, key)
except KeyError:
return []
if type is None:
return list(rv)
result = []
for item in rv:
try:
result.append(type(item))
except ValueError:
pass
return result
def setlist(self, key, new_list):
"""Remove the old values for a key and add new ones. Note that the list
you pass the values in will be shallow-copied before it is inserted in
the dictionary.
>>> d = MultiDict()
>>> d.setlist('foo', ['1', '2'])
>>> d['foo']
'1'
>>> d.getlist('foo')
['1', '2']
:param key: The key for which the values are set.
:param new_list: An iterable with the new values for the key. Old values
are removed first.
"""
dict.__setitem__(self, key, list(new_list))
def setdefault(self, key, default=None):
"""Returns the value for the key if it is in the dict, otherwise it
returns `default` and sets that value for `key`.
:param key: The key to be looked up.
:param default: The default value to be returned if the key is not
in the dict. If not further specified it's `None`.
"""
if key not in self:
self[key] = default
else:
default = self[key]
return default
def setlistdefault(self, key, default_list=None):
"""Like `setdefault` but sets multiple values. The list returned
is not a copy, but the list that is actually used internally. This
means that you can put new values into the dict by appending items
to the list:
>>> d = MultiDict({"foo": 1})
>>> d.setlistdefault("foo").extend([2, 3])
>>> d.getlist("foo")
[1, 2, 3]
:param key: The key to be looked up.
:param default: An iterable of default values. It is either copied
(in case it was a list) or converted into a list
before returned.
:return: a :class:`list`
"""
if key not in self:
default_list = list(default_list or ())
dict.__setitem__(self, key, default_list)
else:
default_list = dict.__getitem__(self, key)
return default_list
def items(self, multi=False):
"""Return a list of ``(key, value)`` pairs.
:param multi: If set to `True` the list returned will have a
pair for each value of each key. Otherwise it
will only contain pairs for the first value of
each key.
:return: a :class:`list`
"""
return list(self.iteritems(multi))
def lists(self):
"""Return a list of ``(key, values)`` pairs, where values is the list of
all values associated with the key.
:return: a :class:`list`
"""
return list(self.iterlists())
def values(self):
"""Returns a list of the first value on every key's value list.
:return: a :class:`list`.
"""
return [self[key] for key in self.iterkeys()]
def listvalues(self):
"""Return a list of all values associated with a key. Zipping
:meth:`keys` and this is the same as calling :meth:`lists`:
>>> d = MultiDict({"foo": [1, 2, 3]})
>>> zip(d.keys(), d.listvalues()) == d.lists()
True
:return: a :class:`list`
"""
return list(self.iterlistvalues())
def iteritems(self, multi=False):
"""Like :meth:`items` but returns an iterator."""
for key, values in dict.iteritems(self):
if multi:
for value in values:
yield key, value
else:
yield key, values[0]
def iterlists(self):
"""Like :meth:`items` but returns an iterator."""
for key, values in dict.iteritems(self):
yield key, list(values)
def itervalues(self):
"""Like :meth:`values` but returns an iterator."""
for values in dict.itervalues(self):
yield values[0]
def iterlistvalues(self):
"""Like :meth:`listvalues` but returns an iterator."""
return dict.itervalues(self)
def copy(self):
"""Return a shallow copy of this object."""
return self.__class__(self)
def to_dict(self, flat=True):
"""Return the contents as regular dict. If `flat` is `True` the
returned dict will only have the first item present, if `flat` is
`False` all values will be returned as lists.
:param flat: If set to `False` the dict returned will have lists
with all the values in it. Otherwise it will only
contain the first value for each key.
:return: a :class:`dict`
"""
if flat:
return dict(self.iteritems())
return dict(self.lists())
def update(self, other_dict):
"""update() extends rather than replaces existing key lists."""
for key, value in iter_multi_items(other_dict):
MultiDict.add(self, key, value)
def pop(self, key, default=_missing):
"""Pop the first item for a list on the dict. Afterwards the
key is removed from the dict, so additional values are discarded:
>>> d = MultiDict({"foo": [1, 2, 3]})
>>> d.pop("foo")
1
>>> "foo" in d
False
:param key: the key to pop.
:param default: if provided the value to return if the key was
not in the dictionary.
"""
try:
return dict.pop(self, key)[0]
except KeyError, e:
if default is not _missing:
return default
raise KeyError(str(e))
def popitem(self):
"""Pop an item from the dict."""
try:
item = dict.popitem(self)
return (item[0], item[1][0])
except KeyError, e:
raise KeyError(str(e))
def poplist(self, key):
"""Pop the list for a key from the dict. If the key is not in the dict
an empty list is returned.
.. versionchanged:: 0.5
If the key does no longer exist a list is returned instead of
raising an error.
"""
return dict.pop(self, key, [])
def popitemlist(self):
"""Pop a ``(key, list)`` tuple from the dict."""
try:
return dict.popitem(self)
except KeyError, e:
raise KeyError(str(e))
def __copy__(self):
return self.copy()
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.items(multi=True))

View File

@@ -1,5 +1,5 @@
# urllib3/__init__.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -10,26 +10,20 @@ urllib3 - Thread-safe connection pooling and re-using.
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.0.2'
__version__ = 'dev'
from .connectionpool import (
HTTPConnectionPool,
HTTPSConnectionPool,
connection_from_url,
get_host,
make_headers)
from .exceptions import (
HTTPError,
MaxRetryError,
SSLError,
TimeoutError)
connection_from_url
)
from . import exceptions
from .filepost import encode_multipart_formdata
from .poolmanager import PoolManager, ProxyManager, proxy_from_url
from .response import HTTPResponse
from .filepost import encode_multipart_formdata
from .util import make_headers, get_host
# Set default logging handler to avoid "No handler found" warnings.

View File

@@ -1,5 +1,5 @@
# urllib3/_collections.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

View File

@@ -1,5 +1,5 @@
# urllib3/connectionpool.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -7,37 +7,64 @@
import logging
import socket
from httplib import HTTPConnection, HTTPSConnection, HTTPException
from Queue import Queue, Empty, Full
from select import select
from socket import error as SocketError, timeout as SocketTimeout
from .packages.ssl_match_hostname import match_hostname, CertificateError
try: # Python 3
from http.client import HTTPConnection, HTTPException
from http.client import HTTP_PORT, HTTPS_PORT
except ImportError:
from httplib import HTTPConnection, HTTPException
from httplib import HTTP_PORT, HTTPS_PORT
try: # Python 3
from queue import LifoQueue, Empty, Full
except ImportError:
from Queue import LifoQueue, Empty, Full
try: # Compiled with SSL?
HTTPSConnection = object
BaseSSLError = None
ssl = None
try: # Python 3
from http.client import HTTPSConnection
except ImportError:
from httplib import HTTPSConnection
try:
import ssl
BaseSSLError = ssl.SSLError
except ImportError:
ssl = None
BaseSSLError = None
except (ImportError, AttributeError):
pass
from .request import RequestMethods
from .response import HTTPResponse
from .util import get_host, is_connection_dropped
from .exceptions import (
SSLError,
MaxRetryError,
TimeoutError,
HostChangedError,
EmptyPoolError,
HostChangedError,
MaxRetryError,
SSLError,
TimeoutError,
)
from .packages.ssl_match_hostname import match_hostname, CertificateError
from .packages import six
xrange = six.moves.xrange
log = logging.getLogger(__name__)
_Default = object()
port_by_scheme = {
'http': HTTP_PORT,
'https': HTTPS_PORT,
}
## Connection objects (extension of httplib)
@@ -74,6 +101,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
if self.ca_certs:
match_hostname(self.sock.getpeercert(), self.host)
## Pool objects
class ConnectionPool(object):
@@ -81,7 +109,17 @@ class ConnectionPool(object):
Base class for all connection pools, such as
:class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.
"""
pass
scheme = None
QueueCls = LifoQueue
def __init__(self, host, port=None):
self.host = host
self.port = port
def __str__(self):
return '%s(host=%r, port=%r)' % (type(self).__name__,
self.host, self.port)
class HTTPConnectionPool(ConnectionPool, RequestMethods):
@@ -127,11 +165,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
def __init__(self, host, port=None, strict=False, timeout=None, maxsize=1,
block=False, headers=None):
self.host = host
self.port = port
super(HTTPConnectionPool, self).__init__(host, port)
self.strict = strict
self.timeout = timeout
self.pool = Queue(maxsize)
self.pool = self.QueueCls(maxsize)
self.block = block
self.headers = headers or {}
@@ -169,14 +207,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
conn = self.pool.get(block=self.block, timeout=timeout)
# If this is a persistent connection, check if it got disconnected
if conn and conn.sock and select([conn.sock], [], [], 0.0)[0]:
# Either data is buffered (bad), or the connection is dropped.
if conn and is_connection_dropped(conn):
log.info("Resetting dropped connection: %s" % self.host)
conn.close()
except Empty:
if self.block:
raise EmptyPoolError("Pool reached maximum size and no more "
raise EmptyPoolError(self,
"Pool reached maximum size and no more "
"connections are allowed.")
pass # Oh well, we'll create a new connection then
@@ -213,15 +251,20 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
timeout = self.timeout
conn.timeout = timeout # This only does anything in Py26+
conn.request(method, url, **httplib_request_kw)
conn.sock.settimeout(timeout)
# Set timeout
sock = getattr(conn, 'sock', False) # AppEngine doesn't have sock attr.
if sock:
sock.settimeout(timeout)
httplib_response = conn.getresponse()
log.debug("\"%s %s %s\" %s %s" %
(method, url,
conn._http_vsn_str, # pylint: disable-msg=W0212
httplib_response.status, httplib_response.length))
# AppEngine doesn't have a version attr.
http_version = getattr(conn, '_http_vsn_str', 'HTTP/?'),
log.debug("\"%s %s %s\" %s %s" % (method, url, http_version,
httplib_response.status,
httplib_response.length))
return httplib_response
@@ -229,11 +272,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
def is_same_host(self, url):
"""
Check if the given ``url`` is a member of the same host as this
conncetion pool.
connection pool.
"""
# TODO: Add optional support for socket.gethostbyname checking.
scheme, host, port = get_host(url)
if self.port and not port:
# Use explicit default port for comparison when none is given.
port = port_by_scheme.get(scheme)
return (url.startswith('/') or
get_host(url) == (self.scheme, self.host, self.port))
(scheme, host, port) == (self.scheme, self.host, self.port))
def urlopen(self, method, url, body=None, headers=None, retries=3,
redirect=True, assert_same_host=True, timeout=_Default,
@@ -246,7 +295,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
.. note::
More commonly, it's appropriate to use a convenience method provided
by :class:`.RequestMethods`, such as :meth:`.request`.
by :class:`.RequestMethods`, such as :meth:`request`.
.. note::
@@ -306,7 +355,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
headers = self.headers
if retries < 0:
raise MaxRetryError(url)
raise MaxRetryError(self, url)
if timeout is _Default:
timeout = self.timeout
@@ -320,7 +369,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
if self.port:
host = "%s:%d" % (host, self.port)
raise HostChangedError(host, url, retries - 1)
raise HostChangedError(self, url, retries - 1)
conn = None
@@ -351,27 +400,29 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# ``response.release_conn()`` is called (implicitly by
# ``response.read()``)
except (Empty), e:
except Empty as e:
# Timed out by queue
raise TimeoutError("Request timed out. (pool_timeout=%s)" %
raise TimeoutError(self, "Request timed out. (pool_timeout=%s)" %
pool_timeout)
except (SocketTimeout), e:
except SocketTimeout as e:
# Timed out by socket
raise TimeoutError("Request timed out. (timeout=%s)" %
raise TimeoutError(self, "Request timed out. (timeout=%s)" %
timeout)
except (BaseSSLError), e:
except BaseSSLError as e:
# SSL certificate error
raise SSLError(e)
except (CertificateError), e:
except CertificateError as e:
# Name mismatch
raise SSLError(e)
except (HTTPException, SocketError), e:
except (HTTPException, SocketError) as e:
# Connection broken, discard. It will be replaced next _get_conn().
conn = None
# This is necessary so we can access e below
err = e
finally:
if conn and release_conn:
@@ -380,7 +431,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
if not conn:
log.warn("Retrying (%d attempts remain) after connection "
"broken by '%r': %s" % (retries, e, url))
"broken by '%r': %s" % (retries, err, url))
return self.urlopen(method, url, body, headers, retries - 1,
redirect, assert_same_host) # Try again
@@ -431,7 +482,11 @@ class HTTPSConnectionPool(HTTPConnectionPool):
log.info("Starting new HTTPS connection (%d): %s"
% (self.num_connections, self.host))
if not ssl:
if not ssl: # Platform-specific: Python compiled without +ssl
if not HTTPSConnection or HTTPSConnection is object:
raise SSLError("Can't connect to HTTPS URL because the SSL "
"module is not available.")
return HTTPSConnection(host=self.host, port=self.port)
connection = VerifiedHTTPSConnection(host=self.host, port=self.port)
@@ -440,87 +495,6 @@ class HTTPSConnectionPool(HTTPConnectionPool):
return connection
## Helpers
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
basic_auth=None):
"""
Shortcuts for generating request headers.
:param keep_alive:
If ``True``, adds 'connection: keep-alive' header.
:param accept_encoding:
Can be a boolean, list, or string.
``True`` translates to 'gzip,deflate'.
List will get joined by comma.
String will be used as provided.
:param user_agent:
String representing the user-agent you want, such as
"python-urllib3/0.6"
:param basic_auth:
Colon-separated username:password string for 'authorization: basic ...'
auth header.
Example: ::
>>> make_headers(keep_alive=True, user_agent="Batman/1.0")
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
>>> make_headers(accept_encoding=True)
{'accept-encoding': 'gzip,deflate'}
"""
headers = {}
if accept_encoding:
if isinstance(accept_encoding, str):
pass
elif isinstance(accept_encoding, list):
accept_encoding = ','.join(accept_encoding)
else:
accept_encoding = 'gzip,deflate'
headers['accept-encoding'] = accept_encoding
if user_agent:
headers['user-agent'] = user_agent
if keep_alive:
headers['connection'] = 'keep-alive'
if basic_auth:
headers['authorization'] = 'Basic ' + \
basic_auth.encode('base64').strip()
return headers
def get_host(url):
"""
Given a url, return its scheme, host and port (None if it's not there).
For example: ::
>>> get_host('http://google.com/mail/')
('http', 'google.com', None)
>>> get_host('google.com:80')
('http', 'google.com', 80)
"""
# This code is actually similar to urlparse.urlsplit, but much
# simplified for our needs.
port = None
scheme = 'http'
if '://' in url:
scheme, url = url.split('://', 1)
if '/' in url:
url, _path = url.split('/', 1)
if '@' in url:
_auth, url = url.split('@', 1)
if ':' in url:
url, port = url.split(':', 1)
port = int(port)
return scheme, url, port
def connection_from_url(url, **kw):
"""
Given a url, return an :class:`.ConnectionPool` instance of its host.

View File

@@ -1,5 +1,5 @@
# urllib3/contrib/ntlmpool.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -10,7 +10,10 @@ NTLM authenticating pool, contributed by erikcederstran
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
"""
import httplib
try:
from http.client import HTTPSConnection
except ImportError:
from httplib import HTTPSConnection
from logging import getLogger
from ntlm import ntlm
@@ -53,7 +56,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
req_header = 'Authorization'
resp_header = 'www-authenticate'
conn = httplib.HTTPSConnection(host=self.host, port=self.port)
conn = HTTPSConnection(host=self.host, port=self.port)
# Send negotiation message
headers[req_header] = (

View File

@@ -1,45 +1,67 @@
# urllib3/exceptions.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
## Exceptions
## Base Exceptions
class HTTPError(Exception):
"Base exception used by this module."
pass
class SSLError(Exception):
class PoolError(HTTPError):
"Base exception for errors caused within a pool."
def __init__(self, pool, message):
self.pool = pool
HTTPError.__init__(self, "%s: %s" % (pool, message))
class SSLError(HTTPError):
"Raised when SSL certificate fails in an HTTPS connection."
pass
class MaxRetryError(HTTPError):
## Leaf Exceptions
class MaxRetryError(PoolError):
"Raised when the maximum number of retries is exceeded."
def __init__(self, url):
HTTPError.__init__(self, "Max retries exceeded for url: %s" % url)
def __init__(self, pool, url):
message = "Max retries exceeded with url: %s" % url
PoolError.__init__(self, pool, message)
self.url = url
class TimeoutError(HTTPError):
class HostChangedError(PoolError):
"Raised when an existing pool gets a request for a foreign host."
def __init__(self, pool, url, retries=3):
message = "Tried to open a foreign host with url: %s" % url
PoolError.__init__(self, pool, message)
self.url = url
self.retries = retries
class TimeoutError(PoolError):
"Raised when a socket timeout occurs."
pass
class HostChangedError(HTTPError):
"Raised when an existing pool gets a request for a foreign host."
def __init__(self, original_host, new_url, retries=3):
HTTPError.__init__(self,
"Connection pool with host '%s' tried to open a foreign host: %s" %
(original_host, new_url))
self.original_host = original_host
self.new_url = new_url
self.retries = retries
class EmptyPoolError(HTTPError):
class EmptyPoolError(PoolError):
"Raised when a pool runs out of connections and no more are allowed."
pass
class LocationParseError(ValueError, HTTPError):
"Raised when get_host or similar fails to parse the URL input."
def __init__(self, location):
message = "Failed to parse: %s" % location
super(LocationParseError, self).__init__(self, message)
self.location = location

View File

@@ -1,18 +1,21 @@
# urllib3/filepost.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import codecs
import mimetools
import mimetypes
try:
from cStringIO import StringIO
from mimetools import choose_boundary
except ImportError:
from StringIO import StringIO # pylint: disable-msg=W0404
from .packages.mimetools_choose_boundary import choose_boundary
from io import BytesIO
from .packages import six
from .packages.six import b
writer = codecs.lookup('utf-8')[3]
@@ -21,51 +24,65 @@ def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
def iter_fields(fields):
"""
Iterate over fields.
Supports list of (k, v) tuples and dicts.
"""
if isinstance(fields, dict):
return ((k, v) for k, v in six.iteritems(fields))
return ((k, v) for k, v in fields)
def encode_multipart_formdata(fields, boundary=None):
"""
Encode a dictionary of ``fields`` using the multipart/form-data mime format.
:param fields:
Dictionary of fields. The key is treated as the field name, and the
value as the body of the form-data. If the value is a tuple of two
elements, then the first element is treated as the filename of the
form-data section.
Dictionary of fields or list of (key, value) field tuples. The key is
treated as the field name, and the value as the body of the form-data
bytes. If the value is a tuple of two elements, then the first element
is treated as the filename of the form-data section.
Field names and filenames must be unicode.
:param boundary:
If not specified, then a random boundary will be generated using
:func:`mimetools.choose_boundary`.
"""
body = StringIO()
body = BytesIO()
if boundary is None:
boundary = mimetools.choose_boundary()
boundary = choose_boundary()
for fieldname, value in fields.iteritems():
body.write('--%s\r\n' % (boundary))
for fieldname, value in iter_fields(fields):
body.write(b('--%s\r\n' % (boundary)))
if isinstance(value, tuple):
filename, data = value
writer(body).write('Content-Disposition: form-data; name="%s"; '
'filename="%s"\r\n' % (fieldname, filename))
body.write('Content-Type: %s\r\n\r\n' %
(get_content_type(filename)))
body.write(b('Content-Type: %s\r\n\r\n' %
(get_content_type(filename))))
else:
data = value
writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
% (fieldname))
body.write('Content-Type: text/plain\r\n\r\n')
body.write(b'Content-Type: text/plain\r\n\r\n')
if isinstance(data, int):
data = str(data) # Backwards compatibility
if isinstance(data, unicode):
if isinstance(data, six.text_type):
writer(body).write(data)
else:
body.write(data)
body.write('\r\n')
body.write(b'\r\n')
body.write('--%s--\r\n' % (boundary))
body.write(b('--%s--\r\n' % (boundary)))
content_type = 'multipart/form-data; boundary=%s' % boundary
content_type = b('multipart/form-data; boundary=%s' % boundary)
return body.getvalue(), content_type

View File

@@ -0,0 +1,47 @@
"""The function mimetools.choose_boundary() from Python 2.7, which seems to
have disappeared in Python 3 (although email.generator._make_boundary() might
work as a replacement?).
Tweaked to use lock from threading rather than thread.
"""
import os
from threading import Lock
_counter_lock = Lock()
_counter = 0
def _get_next_counter():
global _counter
with _counter_lock:
_counter += 1
return _counter
_prefix = None
def choose_boundary():
"""Return a string usable as a multipart boundary.
The string chosen is unique within a single program run, and
incorporates the user id (if available), process id (if available),
and current time. So it's very unlikely the returned string appears
in message text, but there's no guarantee.
The boundary contains dots so you have to quote it in the header."""
global _prefix
import time
if _prefix is None:
import socket
try:
hostid = socket.gethostbyname(socket.gethostname())
except socket.gaierror:
hostid = '127.0.0.1'
try:
uid = repr(os.getuid())
except AttributeError:
uid = '1'
try:
pid = repr(os.getpid())
except AttributeError:
pid = '1'
_prefix = hostid + '.' + uid + '.' + pid
return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())

View File

@@ -0,0 +1,372 @@
"""Utilities for writing code that runs on Python 2 and 3"""
#Copyright (c) 2010-2011 Benjamin Peterson
#Permission is hereby granted, free of charge, to any person obtaining a copy of
#this software and associated documentation files (the "Software"), to deal in
#the Software without restriction, including without limitation the rights to
#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
#the Software, and to permit persons to whom the Software is furnished to do so,
#subject to the following conditions:
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.1.0"
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules["six.moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_code = "func_code"
_func_defaults = "func_defaults"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
if PY3:
def get_unbound_function(unbound):
return unbound
advance_iterator = next
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
else:
def get_unbound_function(unbound):
return unbound.im_func
def advance_iterator(it):
return it.next()
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
def iterkeys(d):
"""Return an iterator over the keys of a dictionary."""
return getattr(d, _iterkeys)()
def itervalues(d):
"""Return an iterator over the values of a dictionary."""
return getattr(d, _itervalues)()
def iteritems(d):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return getattr(d, _iteritems)()
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
print_ = getattr(builtins, "print")
del builtins
else:
def exec_(code, globs=None, locs=None):
"""Execute code in a namespace."""
if globs is None:
frame = sys._getframe(1)
globs = frame.f_globals
if locs is None:
locs = frame.f_locals
del frame
elif locs is None:
locs = globs
exec("""exec code in globs, locs""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})

View File

@@ -1,5 +1,5 @@
# urllib3/poolmanager.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -8,7 +8,7 @@ import logging
from ._collections import RecentlyUsedContainer
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from .connectionpool import get_host, connection_from_url
from .connectionpool import get_host, connection_from_url, port_by_scheme
from .exceptions import HostChangedError
from .request import RequestMethods
@@ -21,11 +21,6 @@ pool_classes_by_scheme = {
'https': HTTPSConnectionPool,
}
port_by_scheme = {
'http': 80,
'https': 443,
}
log = logging.getLogger(__name__)
@@ -44,11 +39,11 @@ class PoolManager(RequestMethods):
Example: ::
>>> manager = PoolManager()
>>> manager = PoolManager(num_pools=2)
>>> r = manager.urlopen("http://google.com/")
>>> r = manager.urlopen("http://google.com/mail")
>>> r = manager.urlopen("http://yahoo.com/")
>>> len(r.pools)
>>> len(manager.pools)
2
"""
@@ -108,9 +103,9 @@ class PoolManager(RequestMethods):
try:
return conn.urlopen(method, url, **kw)
except HostChangedError, e:
except HostChangedError as e:
kw['retries'] = e.retries # Persist retries countdown
return self.urlopen(method, e.new_url, **kw)
return self.urlopen(method, e.url, **kw)
class ProxyManager(RequestMethods):
@@ -122,9 +117,19 @@ class ProxyManager(RequestMethods):
def __init__(self, proxy_pool):
self.proxy_pool = proxy_pool
def _set_proxy_headers(self, headers=None):
headers = headers or {}
# Same headers are curl passes for --proxy1.0
headers['Accept'] = '*/*'
headers['Proxy-Connection'] = 'Keep-Alive'
return headers
def urlopen(self, method, url, **kw):
"Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
kw['assert_same_host'] = False
kw['headers'] = self._set_proxy_headers(kw.get('headers'))
return self.proxy_pool.urlopen(method, url, **kw)

View File

@@ -1,11 +1,13 @@
# urllib3/request.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from urllib import urlencode
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
from .filepost import encode_multipart_formdata
@@ -42,7 +44,7 @@ class RequestMethods(object):
def urlopen(self, method, url, body=None, headers=None,
encode_multipart=True, multipart_boundary=None,
**kw):
**kw): # Abstract
raise NotImplemented("Classes extending RequestMethods must implement "
"their own ``urlopen`` method.")
@@ -124,22 +126,3 @@ class RequestMethods(object):
return self.urlopen(method, url, body=body, headers=headers,
**urlopen_kw)
# Deprecated:
def get_url(self, url, fields=None, **urlopen_kw):
"""
.. deprecated:: 1.0
Use :meth:`request` instead.
"""
return self.request_encode_url('GET', url, fields=fields,
**urlopen_kw)
def post_url(self, url, fields=None, headers=None, **urlopen_kw):
"""
.. deprecated:: 1.0
Use :meth:`request` instead.
"""
return self.request_encode_body('POST', url, fields=fields,
headers=headers,
**urlopen_kw)

View File

@@ -1,5 +1,5 @@
# urllib3/response.py
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -8,21 +8,17 @@ import gzip
import logging
import zlib
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO # pylint: disable-msg=W0404
from io import BytesIO
from .exceptions import HTTPError
from .packages.six import string_types as basestring
log = logging.getLogger(__name__)
def decode_gzip(data):
gzipper = gzip.GzipFile(fileobj=StringIO(data))
gzipper = gzip.GzipFile(fileobj=BytesIO(data))
return gzipper.read()
@@ -71,7 +67,7 @@ class HTTPResponse(object):
self.strict = strict
self._decode_content = decode_content
self._body = None
self._body = body if body and isinstance(body, basestring) else None
self._fp = None
self._original_response = original_response
@@ -81,7 +77,7 @@ class HTTPResponse(object):
if hasattr(body, 'read'):
self._fp = body
if preload_content:
if preload_content and not self._body:
self._body = self.read(decode_content=decode_content)
def get_redirect_location(self):
@@ -139,21 +135,19 @@ class HTTPResponse(object):
if decode_content is None:
decode_content = self._decode_content
data = self._fp and self._fp.read(amt)
if self._fp is None:
return
try:
if amt:
return data
if not decode_content or not decoder:
if cache_content:
self._body = data
return data
if amt is None:
# cStringIO doesn't like amt=None
data = self._fp.read()
else:
return self._fp.read(amt)
try:
data = decoder(data)
if decode_content and decoder:
data = decoder(data)
except IOError:
raise HTTPError("Received response with content-encoding: %s, but "
"failed to decode it." % content_encoding)
@@ -164,7 +158,6 @@ class HTTPResponse(object):
return data
finally:
if self._original_response and self._original_response.isclosed():
self.release_conn()
@@ -178,12 +171,26 @@ class HTTPResponse(object):
with ``original_response=r``.
"""
# Normalize headers between different versions of Python
headers = {}
for k, v in r.getheaders():
# Python 3: Header keys are returned capitalised
k = k.lower()
has_value = headers.get(k)
if has_value: # Python 3: Repeating header keys are unmerged.
v = ', '.join([has_value, v])
headers[k] = v
# HTTPResponse objects in Python 3 don't have a .strict attribute
strict = getattr(r, 'strict', 0)
return ResponseCls(body=r,
headers=dict(r.getheaders()),
headers=headers,
status=r.status,
version=r.version,
reason=r.reason,
strict=r.strict,
strict=strict,
original_response=r,
**response_kw)

View File

@@ -0,0 +1,136 @@
# urllib3/util.py
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from base64 import b64encode
try:
from select import poll, POLLIN
except ImportError: # `poll` doesn't exist on OSX and other platforms
poll = False
try:
from select import select
except ImportError: # `select` doesn't exist on AppEngine.
select = False
from .packages import six
from .exceptions import LocationParseError
def make_headers(keep_alive=None, accept_encoding=None, user_agent=None,
basic_auth=None):
"""
Shortcuts for generating request headers.
:param keep_alive:
If ``True``, adds 'connection: keep-alive' header.
:param accept_encoding:
Can be a boolean, list, or string.
``True`` translates to 'gzip,deflate'.
List will get joined by comma.
String will be used as provided.
:param user_agent:
String representing the user-agent you want, such as
"python-urllib3/0.6"
:param basic_auth:
Colon-separated username:password string for 'authorization: basic ...'
auth header.
Example: ::
>>> make_headers(keep_alive=True, user_agent="Batman/1.0")
{'connection': 'keep-alive', 'user-agent': 'Batman/1.0'}
>>> make_headers(accept_encoding=True)
{'accept-encoding': 'gzip,deflate'}
"""
headers = {}
if accept_encoding:
if isinstance(accept_encoding, str):
pass
elif isinstance(accept_encoding, list):
accept_encoding = ','.join(accept_encoding)
else:
accept_encoding = 'gzip,deflate'
headers['accept-encoding'] = accept_encoding
if user_agent:
headers['user-agent'] = user_agent
if keep_alive:
headers['connection'] = 'keep-alive'
if basic_auth:
headers['authorization'] = 'Basic ' + \
b64encode(six.b(basic_auth)).decode('utf-8')
return headers
def get_host(url):
"""
Given a url, return its scheme, host and port (None if it's not there).
For example: ::
>>> get_host('http://google.com/mail/')
('http', 'google.com', None)
>>> get_host('google.com:80')
('http', 'google.com', 80)
"""
# This code is actually similar to urlparse.urlsplit, but much
# simplified for our needs.
port = None
scheme = 'http'
if '://' in url:
scheme, url = url.split('://', 1)
if '/' in url:
url, _path = url.split('/', 1)
if '@' in url:
_auth, url = url.split('@', 1)
if ':' in url:
url, port = url.split(':', 1)
if not port.isdigit():
raise LocationParseError("Failed to parse: %s" % url)
port = int(port)
return scheme, url, port
def is_connection_dropped(conn):
"""
Returns True if the connection is dropped and should be closed.
:param conn:
``HTTPConnection`` object.
Note: For platforms like AppEngine, this will always return ``False`` to
let the platform handle connection recycling transparently for us.
"""
sock = getattr(conn, 'sock', False)
if not sock: #Platform-specific: AppEngine
return False
if not poll: # Platform-specific
if not select: #Platform-specific: AppEngine
return False
return select([sock], [], [], 0.0)[0]
# This version is better on platforms that support it.
p = poll()
p.register(sock, POLLIN)
for (fno, ev) in p.poll(0.0):
if fno == sock.fileno():
# Either data is buffered (bad), or the connection is dropped.
return True

38
requests/safe_mode.py Normal file
View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""
requests.safe_mode
~~~~~~~~~~~~
This module contains a decorator that implements safe_mode.
:copyright: (c) 2012 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
from .models import Response
from .packages.urllib3.response import HTTPResponse
from .exceptions import RequestException, ConnectionError, HTTPError
import socket
def catch_exceptions_if_in_safe_mode(function):
"""New implementation of safe_mode. We catch all exceptions at the API level
and then return a blank Response object with the error field filled. This decorator
wraps request() in api.py.
"""
def wrapped(method, url, **kwargs):
# if save_mode, we catch exceptions and fill error field
if (kwargs.get('config') and kwargs.get('config').get('safe_mode')) or (kwargs.get('session')
and kwargs.get('session').config.get('safe_mode')):
try:
return function(method, url, **kwargs)
except (RequestException, ConnectionError, HTTPError, socket.timeout) as e:
r = Response()
r.error = e
r.raw = HTTPResponse() # otherwise, tests fail
r.status_code = 0 # with this status_code, content returns None
return r
return function(method, url, **kwargs)
return wrapped

View File

@@ -9,13 +9,15 @@ requests (cookies, auth, proxies).
"""
from copy import deepcopy
from .compat import cookielib
from .cookies import cookiejar_from_dict, remove_cookie_by_name
from .defaults import defaults
from .models import Request
from .hooks import dispatch_hook
from .utils import header_expand
from .packages.urllib3.poolmanager import PoolManager
def merge_kwargs(local_kwarg, default_kwarg):
"""Merges kwarg dictionaries.
@@ -25,7 +27,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
if default_kwarg is None:
return local_kwarg
if isinstance(local_kwarg, basestring):
if isinstance(local_kwarg, str):
return local_kwarg
if local_kwarg is None:
@@ -40,7 +42,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
kwargs.update(local_kwarg)
# Remove keys that are set to None.
for (k,v) in local_kwarg.items():
for (k, v) in list(local_kwarg.items()):
if v is None:
del kwargs[k]
@@ -52,7 +54,7 @@ class Session(object):
__attrs__ = [
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
'params', 'config']
'params', 'config', 'verify', 'cert', 'prefetch']
def __init__(self,
@@ -64,33 +66,38 @@ class Session(object):
hooks=None,
params=None,
config=None,
verify=True):
prefetch=False,
verify=True,
cert=None):
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.prefetch = prefetch
self.verify = verify
self.cert = cert
for (k, v) in defaults.items():
self.config.setdefault(k, v)
for (k, v) in list(defaults.items()):
self.config.setdefault(k, deepcopy(v))
self.init_poolmanager()
# Set up a CookieJar to be used by default
if isinstance(cookies, cookielib.CookieJar):
self.cookies = cookies
else:
self.cookies = cookiejar_from_dict(cookies)
def init_poolmanager(self):
self.poolmanager = PoolManager(
num_pools=self.config.get('pool_connections'),
maxsize=self.config.get('pool_maxsize')
)
# Set up a CookieJar to be used by default
self.cookies = {}
# Add passed cookies in.
if cookies is not None:
self.cookies.update(cookies)
def __repr__(self):
return '<requests-client at 0x%x>' % (id(self))
@@ -108,13 +115,14 @@ class Session(object):
files=None,
auth=None,
timeout=None,
allow_redirects=False,
allow_redirects=True,
proxies=None,
hooks=None,
return_response=True,
config=None,
prefetch=False,
verify=None):
verify=None,
cert=None):
"""Constructs and sends a :class:`Request <Request>`.
Returns :class:`Response <Response>` object.
@@ -126,36 +134,34 @@ class Session(object):
: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 tuple to enable Basic/Digest/Custom HTTP Auth.
:param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) Float describing the timeout of the request.
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
:param allow_redirects: (optional) Boolean. Set to True by default.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param return_response: (optional) If False, an un-sent Request object will returned.
:param config: (optional) A configuration dictionary.
:param config: (optional) A configuration dictionary. See ``request.defaults`` for allowed keys and their default values.
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
"""
method = str(method).upper()
# Default empty dicts for dict params.
cookies = {} if cookies is None else cookies
data = {} if data is None else data
files = {} if files is None else files
headers = {} if headers is None else headers
params = {} if params is None else params
hooks = {} if hooks is None else hooks
if verify is None:
verify = self.verify
prefetch = self.prefetch or prefetch
# use session's hooks as defaults
for key, cb in self.hooks.iteritems():
for key, cb in list(self.hooks.items()):
hooks.setdefault(key, cb)
# Expand header values.
if headers:
for k, v in headers.items() or {}:
for k, v in list(headers.items()) or {}:
headers[k] = header_expand(v)
args = dict(
@@ -172,15 +178,39 @@ class Session(object):
allow_redirects=allow_redirects,
proxies=proxies,
config=config,
prefetch=prefetch,
verify=verify,
cert=cert,
_poolmanager=self.poolmanager
)
# merge session cookies into passed-in ones
dead_cookies = None
# passed-in cookies must become a CookieJar:
if not isinstance(cookies, cookielib.CookieJar):
args['cookies'] = cookiejar_from_dict(cookies)
# support unsetting cookies that have been passed in with None values
# this is only meaningful when `cookies` is a dict ---
# for a real CookieJar, the client should use session.cookies.clear()
if cookies is not None:
dead_cookies = [name for name in cookies if cookies[name] is None]
# merge the session's cookies into the passed-in cookies:
for cookie in self.cookies:
args['cookies'].set_cookie(cookie)
# remove the unset cookies from the jar we'll be using with the current request
# (but not from the session's own store of cookies):
if dead_cookies is not None:
for name in dead_cookies:
remove_cookie_by_name(args['cookies'], name)
# Merge local kwargs with session kwargs.
for attr in self.__attrs__:
# we already merged cookies:
if attr == 'cookies':
continue
session_val = getattr(self, attr, None)
local_val = args.get(attr)
args[attr] = merge_kwargs(local_val, session_val)
# Arguments manipulation hook.
@@ -199,9 +229,6 @@ class Session(object):
# Send the HTTP Request.
r.send(prefetch=prefetch)
# Send any cookies back up the to the session.
self.cookies.update(r.response.cookies)
# Return the response.
return r.response
@@ -210,7 +237,7 @@ class Session(object):
"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
@@ -221,7 +248,7 @@ class Session(object):
"""Sends a OPTIONS request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
@@ -232,10 +259,10 @@ class Session(object):
"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
kwargs.setdefault('allow_redirects', False)
return self.request('head', url, **kwargs)
@@ -244,7 +271,7 @@ class Session(object):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return self.request('post', url, data=data, **kwargs)
@@ -255,7 +282,7 @@ class Session(object):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return self.request('put', url, data=data, **kwargs)
@@ -266,7 +293,7 @@ class Session(object):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return self.request('patch', url, data=data, **kwargs)
@@ -276,11 +303,20 @@ class Session(object):
"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param **kwargs: Optional arguments that ``request`` takes.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return self.request('delete', url, **kwargs)
def __getstate__(self):
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
def __setstate__(self, state):
for attr, value in state.items():
setattr(self, attr, value)
self.init_poolmanager()
def session(**kwargs):
"""Returns a :class:`Session` for context-management."""

View File

@@ -79,7 +79,7 @@ _codes = {
codes = LookupDict(name='status_codes')
for (code, titles) in _codes.items():
for (code, titles) in list(_codes.items()):
for title in titles:
setattr(codes, title, code)
if not title.startswith('\\'):

View File

@@ -18,7 +18,7 @@ class CaseInsensitiveDict(dict):
@property
def lower_keys(self):
if not hasattr(self, '_lower_keys') or not self._lower_keys:
self._lower_keys = dict((k.lower(), k) for k in self.iterkeys())
self._lower_keys = dict((k.lower(), k) for k in list(self.keys()))
return self._lower_keys
def _clear_lower_keys(self):
@@ -30,7 +30,7 @@ class CaseInsensitiveDict(dict):
self._clear_lower_keys()
def __delitem__(self, key):
dict.__delitem__(self, key)
dict.__delitem__(self, self.lower_keys.get(key.lower(), key))
self._lower_keys.clear()
def __contains__(self, key):
@@ -63,4 +63,4 @@ class LookupDict(dict):
return self.__dict__.get(key, None)
def get(self, key, default=None):
return self.__dict__.get(key, default)
return self.__dict__.get(key, default)

View File

@@ -11,14 +11,91 @@ that are also useful for external consumption.
import cgi
import codecs
import cookielib
import os
import random
import re
import zlib
import urllib
from netrc import netrc, NetrcParseError
from urllib2 import parse_http_list as _parse_list_header
from .compat import parse_http_list as _parse_list_header
from .compat import quote, urlparse, basestring, bytes, str
from .cookies import RequestsCookieJar, cookiejar_from_dict
_hush_pyflakes = (RequestsCookieJar,)
CERTIFI_BUNDLE_PATH = None
try:
# see if requests's own CA certificate bundle is installed
import certifi
CERTIFI_BUNDLE_PATH = certifi.where()
except ImportError:
pass
NETRC_FILES = ('.netrc', '_netrc')
# common paths for the OS's CA certificate bundle
POSSIBLE_CA_BUNDLE_PATHS = [
# Red Hat, CentOS, Fedora and friends (provided by the ca-certificates package):
'/etc/pki/tls/certs/ca-bundle.crt',
# Ubuntu, Debian, and friends (provided by the ca-certificates package):
'/etc/ssl/certs/ca-certificates.crt',
# FreeBSD (provided by the ca_root_nss package):
'/usr/local/share/certs/ca-root-nss.crt',
]
def get_os_ca_bundle_path():
"""Try to pick an available CA certificate bundle provided by the OS."""
for path in POSSIBLE_CA_BUNDLE_PATHS:
if os.path.exists(path):
return path
return None
# if certifi is installed, use its CA bundle;
# otherwise, try and use the OS bundle
DEFAULT_CA_BUNDLE_PATH = CERTIFI_BUNDLE_PATH or get_os_ca_bundle_path()
def dict_to_sequence(d):
"""Returns an internal sequence dictionary update."""
if hasattr(d, 'items'):
d = d.items()
return d
def get_netrc_auth(url):
"""Returns the Requests tuple auth for a given url from netrc."""
try:
locations = (os.path.expanduser('~/{0}'.format(f)) for f in NETRC_FILES)
netrc_path = None
for loc in locations:
if os.path.exists(loc) and not netrc_path:
netrc_path = loc
# Abort early if there isn't one.
if netrc_path is None:
return netrc_path
ri = urlparse(url)
# Strip port numbers from netloc
host = ri.netloc.split(':')[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
if _netrc:
# Return with login / password
login_i = (0 if _netrc[0] else 1)
return (_netrc[login_i], _netrc[2])
except (NetrcParseError, IOError):
# If there was a parsing error or a permissions issue reading the file,
# we'll just skip netrc auth
pass
# AppEngine hackiness.
except AttributeError:
pass
def guess_filename(obj):
@@ -27,6 +104,7 @@ def guess_filename(obj):
if name and name[0] != '<' and name[-1] != '>':
return name
# From mitsuhiko/werkzeug (used with permission).
def parse_list_header(value):
"""Parse lists as described by RFC 2068 Section 2.
@@ -132,16 +210,22 @@ def header_expand(headers):
collector = []
if isinstance(headers, dict):
headers = headers.items()
headers = list(headers.items())
elif isinstance(headers, basestring):
return headers
elif isinstance(headers, str):
# As discussed in https://github.com/kennethreitz/requests/issues/400
# latin-1 is the most conservative encoding used on the web. Anyone
# who needs more can encode to a byte-string before calling
return headers.encode("latin-1")
elif headers is None:
return headers
for i, (value, params) in enumerate(headers):
_params = []
for (p_k, p_v) in params.items():
for (p_k, p_v) in list(params.items()):
_params.append('%s=%s' % (p_k, p_v))
@@ -152,10 +236,9 @@ def header_expand(headers):
collector.append('; '.join(_params))
if not len(headers) == i+1:
if not len(headers) == i + 1:
collector.append(', ')
# Remove trailing separators.
if collector[-1] in (', ', '; '):
del collector[-1]
@@ -163,22 +246,6 @@ def header_expand(headers):
return ''.join(collector)
def randombytes(n):
"""Return n random bytes."""
# Use /dev/urandom if it is available. Fall back to random module
# if not. It might be worthwhile to extend this function to use
# other platform-specific mechanisms for getting random bytes.
if os.path.exists("/dev/urandom"):
f = open("/dev/urandom")
s = f.read(n)
f.close()
return s
else:
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.
@@ -187,33 +254,15 @@ def dict_from_cookiejar(cj):
cookie_dict = {}
for _, cookies in cj._cookies.items():
for _, cookies in cookies.items():
for cookie in cookies.values():
for _, cookies in list(cj._cookies.items()):
for _, cookies in list(cookies.items()):
for cookie in list(cookies.values()):
# print cookie
cookie_dict[cookie.name] = cookie.value
return cookie_dict
def cookiejar_from_dict(cookie_dict):
"""Returns a CookieJar from a key/value dictionary.
:param cookie_dict: Dict of key/values to insert into CookieJar.
"""
# return cookiejar if one was passed in
if isinstance(cookie_dict, cookielib.CookieJar):
return cookie_dict
# create cookiejar
cj = cookielib.CookieJar()
cj = add_dict_to_cookiejar(cj, cookie_dict)
return cj
def add_dict_to_cookiejar(cj, cookie_dict):
"""Returns a CookieJar from a key/value dictionary.
@@ -221,31 +270,9 @@ def add_dict_to_cookiejar(cj, cookie_dict):
:param cookie_dict: Dict of key/values to insert into CookieJar.
"""
for k, v in cookie_dict.items():
cookie = cookielib.Cookie(
version=0,
name=k,
value=v,
port=None,
port_specified=False,
domain='',
domain_specified=False,
domain_initial_dot=False,
path='/',
path_specified=True,
secure=False,
expires=None,
discard=True,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False
)
# add cookie to cookiejar
cj2 = cookiejar_from_dict(cookie_dict)
for cookie in cj2:
cj.set_cookie(cookie)
return cj
@@ -276,22 +303,8 @@ def get_encoding_from_headers(headers):
if 'charset' in params:
return params['charset'].strip("'\"")
def unicode_from_html(content):
"""Attempts to decode an HTML string into unicode.
If unsuccessful, the original content is returned.
"""
encodings = get_encodings_from_content(content)
for encoding in encodings:
try:
return unicode(content, encoding)
except (UnicodeError, TypeError):
pass
return content
if 'text' in content_type:
return 'ISO-8859-1'
def stream_decode_response_unicode(iterator, r):
@@ -334,26 +347,17 @@ def get_unicode_from_response(r):
if encoding:
try:
return unicode(r.content, encoding)
return str(r.content, encoding)
except UnicodeError:
tried_encodings.append(encoding)
# Fall back:
try:
return unicode(r.content, encoding, errors='replace')
return str(r.content, encoding, errors='replace')
except TypeError:
return r.content
def decode_gzip(content):
"""Return gzip-decoded string.
:param content: bytestring to gzip-decode.
"""
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
def stream_decompress(iterator, mode='gzip'):
"""
Stream decodes an iterator over compressed data
@@ -381,18 +385,71 @@ def stream_decompress(iterator, mode='gzip'):
yield chunk
else:
# Make sure everything has been returned from the decompression object
buf = dec.decompress('')
buf = dec.decompress(bytes())
rv = buf + dec.flush()
if rv:
yield rv
def requote_path(path):
"""Re-quote the given URL path component.
def stream_untransfer(gen, resp):
if 'gzip' in resp.headers.get('content-encoding', ''):
gen = stream_decompress(gen, mode='gzip')
elif 'deflate' in resp.headers.get('content-encoding', ''):
gen = stream_decompress(gen, mode='deflate')
This function passes the given path through an unquote/quote cycle to
return gen
# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789-._~")
def unquote_unreserved(uri):
"""Un-escape any percent-escape sequences in a URI that are unreserved
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
"""
try:
parts = uri.split('%')
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2 and h.isalnum():
c = chr(int(h, 16))
if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:]
else:
parts[i] = '%' + parts[i]
else:
parts[i] = '%' + parts[i]
return ''.join(parts)
except ValueError:
return uri
def requote_uri(uri):
"""Re-quote the given URI.
This function passes the given URI through an unquote/quote cycle to
ensure that it is fully and consistently quoted.
"""
parts = path.split("/")
parts = (urllib.quote(urllib.unquote(part), safe="") for part in parts)
return "/".join(parts)
# Unquote only the unreserved characters
# Then quote only illegal characters (do not quote reserved, unreserved,
# or '%')
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
def get_environ_proxies():
"""Return a dict of environment proxies."""
proxy_keys = [
'all',
'http',
'https',
'ftp',
'socks',
'no'
]
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
proxies = [(key, get_proxy(key + '_proxy')) for key in proxy_keys]
return dict([(key, val) for (key, val) in proxies if val])

View File

@@ -1,7 +1,2 @@
envoy==0.0.2
httpbin==0.0.6
gunicorn
nose
pyflakes
omnijson
rudolf2
rudolf2

View File

@@ -1,31 +1,51 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
distutils/setuptools install script. See inline comments for packaging documentation.
"""
import os
import sys
import requests
from requests.compat import is_py3
try:
from setuptools import setup
# hush pyflakes
setup
except ImportError:
from distutils.core import setup
if sys.argv[-1] == 'publish':
os.system('python setup.py sdist upload')
sys.exit()
if sys.argv[-1] == 'test':
os.system('python test_requests.py')
sys.exit()
required = [
'certifi>=0.0.4'
packages = [
'requests',
'requests.packages',
'requests.packages.urllib3',
'requests.packages.urllib3.packages',
'requests.packages.urllib3.packages.ssl_match_hostname',
'requests.packages.urllib3.packages.mimetools_choose_boundary',
]
if sys.version_info[:2] < (2,6):
required.append('simplejson')
# certifi is a Python package containing a CA certificate bundle for SSL verification.
# On certain supported platforms (e.g., Red Hat / Debian / FreeBSD), Requests can
# use the system CA bundle instead; see `requests.utils` for details.
# If your platform is supported, set `requires` to [] instead:
requires = ['certifi>=0.0.7']
# chardet is used to optimally guess the encodings of pages that don't declare one.
# At this time, chardet is not a required dependency. However, it's sufficiently
# important that pip/setuptools should install it when it's unavailable.
if is_py3:
chardet_package = 'chardet2'
else:
chardet_package = 'chardet>=1.0.0'
requires.append('oauthlib>=0.1.0,<0.2.0')
requires.append(chardet_package)
setup(
name='requests',
@@ -36,28 +56,21 @@ setup(
author='Kenneth Reitz',
author_email='me@kennethreitz.com',
url='http://python-requests.org',
packages=[
'requests',
'requests.packages',
'requests.packages.urllib3',
'requests.packages.urllib3.packages',
'requests.packages.urllib3.packages.ssl_match_hostname',
'requests.packages.oreos'
],
packages=packages,
package_data={'': ['LICENSE', 'NOTICE']},
include_package_data=True,
install_requires=required,
license='ISC',
install_requires=requires,
license=open("LICENSE").read(),
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 :: 3',
'Programming Language :: Python :: 3.0',
'Programming Language :: Python :: 3.1',
),
)

View File

@@ -1,658 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
import StringIO
import time
import os
import unittest
import requests
import envoy
from requests import HTTPError
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
try:
import omnijson as json
except ImportError:
import json
# TODO: Detect an open port.
PORT = os.environ.get('HTTPBIN_PORT', '7077')
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
def httpbin(*suffix):
"""Returns url for HTTPBIN resource."""
return HTTPBIN_URL + '/'.join(suffix)
SERVICES = (httpbin, )
_httpbin = False
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
# It goes to eleven.
_multiprocess_can_split_ = True
def setUp(self):
global _httpbin
if not _httpbin:
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
self.httpbin = c
_httpbin = True
time.sleep(1)
def tearDown(self):
"""Teardown."""
# self.httpbin.kill()
pass
def test_entry_points(self):
requests.session
requests.session().get
requests.session().head
requests.get
requests.head
requests.put
requests.patch
requests.post
def test_invalid_url(self):
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
def test_HTTP_200_OK_GET(self):
r = requests.get(httpbin('/get'))
self.assertEqual(r.status_code, 200)
def test_response_sent(self):
r = requests.get(httpbin('/get'))
self.assertTrue(r.request.sent)
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
r = requests.get(httpbin('redirect', '1'))
self.assertEqual(r.status_code, 200)
def test_HTTP_302_GET(self):
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
self.assertEqual(r.status_code, 302)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
r = requests.get(httpbin('user-agent'), headers=heads)
assert heads['User-agent'] in r.content
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
self.assertEqual(r.status_code, 200)
def test_user_agent_transfers(self):
"""Issue XX"""
heads = {
'User-agent':
'Mozilla/5.0 (github.com/kennethreitz/requests)'
}
r = requests.get(httpbin('user-agent'), headers=heads);
self.assertTrue(heads['User-agent'] in r.content)
heads = {
'user-agent':
'Mozilla/5.0 (github.com/kennethreitz/requests)'
}
r = requests.get(httpbin('user-agent'), headers=heads);
self.assertTrue(heads['user-agent'] in r.content)
def test_HTTP_200_OK_HEAD(self):
r = requests.head(httpbin('/get'))
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_PUT(self):
r = requests.put(httpbin('put'))
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_PATCH(self):
r = requests.patch(httpbin('patch'))
self.assertEqual(r.status_code, 200)
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = ('user', 'pass')
url = service('basic-auth', 'user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_BASICAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = HTTPBasicAuth('user', 'pass')
url = service('basic-auth', 'user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
auth = ('user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = HTTPDigestAuth('user', 'pass')
url = service('digest-auth', 'auth', 'user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_POSTBIN_GET_POST_FILES(self):
for service in SERVICES:
url = service('post')
post = requests.post(url).raise_for_status()
post = requests.post(url, data={'some': 'data'})
self.assertEqual(post.status_code, 200)
post2 = requests.post(url, files={'some': open('test_requests.py')})
self.assertEqual(post2.status_code, 200)
post3 = requests.post(url, data='[{"some": "json"}]')
self.assertEqual(post3.status_code, 200)
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
for service in SERVICES:
url = service('post')
post = requests.post(url,
files={'some': open('test_requests.py')},
data={'some': 'data'})
self.assertEqual(post.status_code, 200)
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
for service in SERVICES:
url = service('post')
post2 = requests.post(url,
files={'some': open('test_requests.py')},
headers = {'User-Agent': 'requests-tests'})
self.assertEqual(post2.status_code, 200)
def test_nonzero_evaluation(self):
for service in SERVICES:
r = requests.get(service('status', '500'))
self.assertEqual(bool(r), False)
r = requests.get(service('/get'))
self.assertEqual(bool(r), True)
def test_request_ok_set(self):
for service in SERVICES:
r = requests.get(service('status', '404'))
# print r.status_code
# r.raise_for_status()
self.assertEqual(r.ok, False)
def test_status_raising(self):
r = requests.get(httpbin('status', '404'))
self.assertRaises(HTTPError, r.raise_for_status)
r = requests.get(httpbin('status', '200'))
self.assertFalse(r.error)
r.raise_for_status()
def test_default_status_raising(self):
config = {'danger_mode': True}
args = [httpbin('status', '404')]
kwargs = dict(config=config)
self.assertRaises(HTTPError, requests.get, *args, **kwargs)
r = requests.get(httpbin('status', '200'))
self.assertEqual(r.status_code, 200)
def test_decompress_gzip(self):
r = requests.get(httpbin('gzip'))
r.content.decode('ascii')
def test_unicode_get(self):
for service in SERVICES:
url = service('/get')
requests.get(url, params={'foo': u'føø'})
requests.get(url, params={u'føø': u'føø'})
requests.get(url, params={'føø': 'føø'})
requests.get(url, params={'foo': u'foo'})
requests.get(service('ø'), params={'foo': u'foo'})
def test_httpauth_recursion(self):
http_auth = HTTPBasicAuth('user', 'BADpass')
for service in SERVICES:
r = requests.get(service('basic-auth', 'user', 'pass'), auth=http_auth)
self.assertEquals(r.status_code, 401)
def test_urlencoded_post_data(self):
for service in SERVICES:
r = requests.post(service('post'), data=dict(test='fooaowpeuf'))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, service('post'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
self.assertEquals(rbody.get('data'), '')
def test_nonurlencoded_post_data(self):
for service in SERVICES:
r = requests.post(service('post'), data='fooaowpeuf')
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, service('post'))
rbody = json.loads(r.content)
# Body wasn't valid url encoded data, so the server returns None as
# "form" and the raw body as "data".
self.assertEquals(rbody.get('form'), {})
self.assertEquals(rbody.get('data'), 'fooaowpeuf')
def test_urlencoded_post_querystring(self):
for service in SERVICES:
r = requests.post(service('post'), params=dict(test='fooaowpeuf'))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), {}) # No form supplied
self.assertEquals(rbody.get('data'), '')
def test_urlencoded_post_query_and_data(self):
for service in SERVICES:
r = requests.post(
service('post'),
params=dict(test='fooaowpeuf'),
data=dict(test2="foobar"))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), dict(test2='foobar'))
self.assertEquals(rbody.get('data'), '')
def test_nonurlencoded_postdata(self):
for service in SERVICES:
r = requests.post(service('post'), data="foobar")
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), {})
self.assertEquals(rbody.get('data'), 'foobar')
# def test_idna(self):
# r = requests.get(u'http://➡.ws/httpbin')
# assert 'httpbin' in r.url
def test_urlencoded_get_query_multivalued_param(self):
for service in SERVICES:
r = requests.get(service('get'), params=dict(test=['foo','baz']))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.url, service('get?test=foo&test=baz'))
def test_urlencoded_post_querystring_multivalued(self):
for service in SERVICES:
r = requests.post(service('post'), params=dict(test=['foo','baz']))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, service('post?test=foo&test=baz'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), {}) # No form supplied
self.assertEquals(rbody.get('data'), '')
def test_urlencoded_post_query_multivalued_and_data(self):
for service in SERVICES:
r = requests.post(
service('post'),
params=dict(test=['foo','baz']),
data=dict(test2="foobar",test3=['foo','baz']))
self.assertEquals(r.status_code, 200)
self.assertEquals(r.headers['content-type'], 'application/json')
self.assertEquals(r.url, service('post?test=foo&test=baz'))
rbody = json.loads(r.content)
self.assertEquals(rbody.get('form'), dict(test2='foobar',test3=['foo','baz']))
self.assertEquals(rbody.get('data'), '')
def test_GET_no_redirect(self):
for service in SERVICES:
r = requests.get(service('redirect', '3'), allow_redirects=False)
self.assertEquals(r.status_code, 302)
self.assertEquals(len(r.history), 0)
def test_HEAD_no_redirect(self):
for service in SERVICES:
r = requests.head(service('redirect', '3'), allow_redirects=False)
self.assertEquals(r.status_code, 302)
self.assertEquals(len(r.history), 0)
def test_redirect_history(self):
for service in SERVICES:
r = requests.get(service('redirect', '3'))
self.assertEquals(r.status_code, 200)
self.assertEquals(len(r.history), 3)
def test_relative_redirect_history(self):
for service in SERVICES:
r = requests.get(service('relative-redirect', '3'))
self.assertEquals(r.status_code, 200)
self.assertEquals(len(r.history), 3)
def test_session_HTTP_200_OK_GET(self):
s = requests.session()
r = s.get(httpbin('/get'))
self.assertEqual(r.status_code, 200)
def test_session_persistent_headers(self):
heads = {'User-agent': 'Mozilla/5.0'}
s = requests.session()
s.headers = heads
# Make 2 requests from Session object, should send header both times
r1 = s.get(httpbin('user-agent'))
assert heads['User-agent'] in r1.content
r2 = s.get(httpbin('user-agent'))
assert heads['User-agent'] in r2.content
new_heads = {'User-agent': 'blah'}
r3 = s.get(httpbin('user-agent'), headers=new_heads)
assert new_heads['User-agent'] in r3.content
self.assertEqual(r2.status_code, 200)
def test_session_persistent_cookies(self):
s = requests.session()
# Internally dispatched cookies are sent.
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
r = s.get(httpbin('cookies'), cookies=_c)
r = s.get(httpbin('cookies'))
# Those cookies persist transparently.
c = json.loads(r.content).get('cookies')
assert c == _c
# Double check.
r = s.get(httpbin('cookies'), cookies={})
c = json.loads(r.content).get('cookies')
assert c == _c
# Remove a cookie by setting it's value to None.
r = s.get(httpbin('cookies'), cookies={'bessie': None})
c = json.loads(r.content).get('cookies')
del _c['bessie']
assert c == _c
# Test session-level cookies.
s = requests.session(cookies=_c)
r = s.get(httpbin('cookies'))
c = json.loads(r.content).get('cookies')
assert c == _c
# Have the server set a cookie.
r = s.get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True)
c = json.loads(r.content).get('cookies')
assert 'k' in c
# And server-set cookie persistience.
r = s.get(httpbin('cookies'))
c = json.loads(r.content).get('cookies')
assert 'k' in c
def test_session_persistent_params(self):
params = {'a': 'a_test'}
s = requests.session()
s.params = params
# Make 2 requests from Session object, should send header both times
r1 = s.get(httpbin('get'))
assert params['a'] in r1.content
params2 = {'b': 'b_test'}
r2 = s.get(httpbin('get'), params=params2)
assert params['a'] in r2.content
assert params2['b'] in r2.content
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
r3 = s.get(httpbin('get'), params=params3)
assert not params['a'] in r3.content
assert params3['b'] in r3.content
assert params3['c'] in r3.content
def test_invalid_content(self):
# WARNING: if you're using a terrible DNS provider (comcast),
# this will fail.
try:
hah = 'http://somedomainthatclearlydoesntexistg.com'
r = requests.get(hah, allow_redirects=False)
except requests.ConnectionError:
pass # \o/
else:
assert False
config = {'safe_mode': True}
r = requests.get(hah, allow_redirects=False, config=config)
assert r.content == None
def test_cached_response(self):
r1 = requests.get(httpbin('get'), prefetch=False)
assert r1.content
assert r1.content
r2 = requests.get(httpbin('get'), prefetch=True)
assert r2._content
assert r2.content
def test_iter_lines(self):
lines = (0, 2, 10, 100)
for i in lines:
r = requests.get(httpbin('stream', str(i)), prefetch=False)
lines = list(r.iter_lines())
len_lines = len(lines)
self.assertEqual(i, len_lines)
# Test 'dangling' fragment in responses that do not terminate in
# a newline.
quote = (
'''Why will he not upon our fair request\n'''
'''Untent his person and share the air with us?'''
)
# Make a request and monkey-patch its contents
r = requests.get(httpbin('get'))
r.raw = StringIO.StringIO(quote)
# Make sure iter_lines doesn't chop the trailing bit
lines = '\n'.join(r.iter_lines())
self.assertEqual(lines, quote)
def test_safe_mode(self):
safe = requests.session(config=dict(safe_mode=True))
# Safe mode creates empty responses for failed requests.
# Iterating on these responses should produce empty sequences
r = safe.get('http://_/')
self.assertEquals(list(r.iter_lines()), [])
self.assertIsInstance(r.error, requests.exceptions.ConnectionError)
r = safe.get('http://_/')
self.assertEquals(list(r.iter_content()), [])
self.assertIsInstance(r.error, requests.exceptions.ConnectionError)
# When not in safe mode, should raise Timeout exception
with self.assertRaises(requests.exceptions.Timeout):
r = requests.get(httpbin('stream', '1000'), timeout=0.0001)
# In safe mode, should return a blank response
r = requests.get(httpbin('stream', '1000'), timeout=0.0001,
config=dict(safe_mode=True))
self.assertIsNone(r.content)
self.assertIsInstance(r.error, requests.exceptions.Timeout)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
import unittest
import requests
try:
import omnijson as json
except ImportError:
import json
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
# It goes to eleven.
_multiprocess_can_split_ = True
def test_addition(self):
assert (1 + 1) == 2
def test_ssl_hostname_ok(self):
requests.get('https://github.com', verify=True)
def test_ssl_hostname_not_ok(self):
requests.get('https://kennethreitz.com', verify=False)
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com')
def test_ssl_hostname_session_not_ok(self):
s = requests.session()
s.get('https://kennethreitz.com', verify=False)
self.assertRaises(requests.exceptions.SSLError, s.get, 'https://kennethreitz.com')
if __name__ == '__main__':
unittest.main()

263
tests/test_cookies.py Executable file
View File

@@ -0,0 +1,263 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import json
import os
import tempfile
import unittest
# Path hack.
sys.path.insert(0, os.path.abspath('..'))
import requests
from requests.compat import cookielib
# More hacks
sys.path.append('.')
from test_requests import httpbin, TestBaseMixin
class CookieTests(TestBaseMixin, unittest.TestCase):
def test_cookies_from_response(self):
"""Basic test that we correctly parse received cookies in the Response object."""
r = requests.get(httpbin('cookies', 'set', 'myname', 'myvalue'))
# test deprecated dictionary interface
self.assertEqual(r.cookies['myname'], 'myvalue')
self.assertEqual(r.cookies.get('myname'), 'myvalue')
# test CookieJar interface
jar = r.cookies
self.assertEqual(len(jar), 1)
cookie_from_jar = list(jar)[0]
self.assertCookieHas(cookie_from_jar, name='myname', value='myvalue')
q = requests.get(httpbin('cookies'), cookies=jar)
self.assertEqual(json.loads(q.text)['cookies'], {'myname': 'myvalue'})
def test_crossdomain_cookies(self):
"""Cookies should not be sent to domains they didn't originate from."""
r = requests.get("http://github.com")
c = r.cookies
# github should send us cookies
self.assertTrue(len(c) >= 1)
# github cookies should not be sent to httpbin.org:
r2 = requests.get(httpbin('cookies'), cookies=c)
self.assertEqual(json.loads(r2.text)['cookies'], {})
# let's do this again using the session object
s = requests.session()
s.get("http://github.com")
self.assertTrue(len(s.cookies) >= 1)
r = s.get(httpbin('cookies'))
self.assertEqual(json.loads(r.text)['cookies'], {})
# we can set a cookie and get exactly that same-domain cookie back:
r = s.get(httpbin('cookies', 'set', 'myname', 'myvalue'))
self.assertEqual(json.loads(r.text)['cookies'], {'myname': 'myvalue'})
def test_overwrite(self):
"""Cookies should get overwritten when appropriate."""
r = requests.get(httpbin('cookies', 'set', 'shimon', 'yochai'))
cookies = r.cookies
requests.get(httpbin('cookies', 'set', 'elazar', 'shimon'), cookies=cookies)
r = requests.get(httpbin('cookies'), cookies=cookies)
self.assertEqual(json.loads(r.text)['cookies'],
{'shimon': 'yochai', 'elazar': 'shimon'})
# overwrite the value of 'shimon'
r = requests.get(httpbin('cookies', 'set', 'shimon', 'gamaliel'), cookies=cookies)
self.assertEqual(len(cookies), 2)
r = requests.get(httpbin('cookies'), cookies=cookies)
self.assertEqual(json.loads(r.text)['cookies'],
{'shimon': 'gamaliel', 'elazar': 'shimon'})
def test_redirects(self):
"""Test that cookies set by a 302 page are correctly processed."""
r = requests.get(httpbin('cookies', 'set', 'redirects', 'work'))
self.assertEqual(r.history[0].status_code, 302)
expected_cookies = {'redirects': 'work'}
self.assertEqual(json.loads(r.text)['cookies'], expected_cookies)
r2 = requests.get(httpbin('cookies', 'set', 'very', 'well'), cookies=r.cookies)
expected_cookies = {'redirects': 'work', 'very': 'well'}
self.assertEqual(json.loads(r2.text)['cookies'], expected_cookies)
self.assertTrue(r.cookies is r2.cookies)
def test_none_cookie(self):
"""Regression test: don't send a Cookie header with a string value of 'None'!"""
page = json.loads(requests.get(httpbin('headers')).text)
self.assertTrue('Cookie' not in page['headers'])
def test_secure_cookies(self):
"""Test that secure cookies can only be sent via https."""
header = "Set-Cookie: ThisIsA=SecureCookie; Path=/; Secure; HttpOnly"
url = 'https://httpbin.org/response-headers?%s' % (requests.utils.quote(header),)
cookies = requests.get(url, verify=False).cookies
self.assertEqual(len(cookies), 1)
self.assertEqual(list(cookies)[0].secure, True)
secure_resp = requests.get('https://httpbin.org/cookies', cookies=cookies, verify=False)
secure_cookies_sent = json.loads(secure_resp.text)['cookies']
self.assertEqual(secure_cookies_sent, {'ThisIsA': 'SecureCookie'})
insecure_resp = requests.get('http://httpbin.org/cookies', cookies=cookies)
insecure_cookies_sent = json.loads(insecure_resp.text)['cookies']
self.assertEqual(insecure_cookies_sent, {})
def test_disabled_cookie_persistence(self):
"""Test that cookies are not persisted when configured accordingly."""
config = {'store_cookies' : False}
# Check the case when no cookie is passed as part of the request and the one in response is ignored
cookies = requests.get(httpbin('cookies', 'set', 'key', 'value'), config = config).cookies
self.assertTrue(cookies.get("key") is None)
# Test that the cookies passed while making the request still gets used and is available in response object.
# only the ones received from server is not saved
cookies_2 = requests.get(httpbin('cookies', 'set', 'key', 'value'), config = config,\
cookies = {"key_2" : "value_2"}).cookies
self.assertEqual(len(cookies_2), 1)
self.assertEqual(cookies_2.get("key_2"), "value_2")
# Use the session and make sure that the received cookie is not used in subsequent calls
s = requests.session()
s.get(httpbin('cookies', 'set', 'key', 'value'), config = config)
r = s.get(httpbin('cookies'))
self.assertEqual(json.loads(r.text)['cookies'], {})
def test_jar_utility_functions(self):
"""Test utility functions such as list_domains, list_paths, multiple_domains."""
r = requests.get("http://github.com")
c = r.cookies
# github should send us cookies
self.assertTrue(len(c) >= 1)
self.assertEqual(len(c), len(r.cookies.keys()))
self.assertEqual(len(c), len(r.cookies.values()))
self.assertEqual(len(c), len(r.cookies.items()))
# domain and path utility functions
domain = r.cookies.list_domains()[0]
path = r.cookies.list_paths()[0]
self.assertEqual(dict(r.cookies), r.cookies.get_dict(domain=domain, path=path))
self.assertEqual(len(r.cookies.list_domains()), 1)
self.assertEqual(len(r.cookies.list_paths()), 1)
self.assertFalse(r.cookies.multiple_domains())
def test_convert_jar_to_dict(self):
"""Test that keys, values, and items are defined and that we can convert
cookie jars to plain old Python dicts."""
r = requests.get(httpbin('cookies', 'set', 'myname', 'myvalue'))
# test keys, values, and items
self.assertEqual(r.cookies.keys(), ['myname'])
self.assertEqual(r.cookies.values(), ['myvalue'])
self.assertEqual(r.cookies.items(), [('myname','myvalue')])
# test if we can convert jar to dict
dictOfCookies = dict(r.cookies)
self.assertEqual(dictOfCookies, {'myname':'myvalue'})
self.assertEqual(dictOfCookies, r.cookies.get_dict())
class LWPCookieJarTest(TestBaseMixin, unittest.TestCase):
"""Check store/load of cookies to FileCookieJar's, specifically LWPCookieJar's."""
COOKIEJAR_CLASS = cookielib.LWPCookieJar
def setUp(self):
# blank the file
self.cookiejar_file = tempfile.NamedTemporaryFile()
self.cookiejar_filename = self.cookiejar_file.name
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar.save()
def tearDown(self):
try:
self.cookiejar_file.close()
except OSError:
pass
def test_cookiejar_persistence(self):
"""Test that we can save cookies to a FileCookieJar."""
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar.load()
# initially should be blank
self.assertEqual(len(cookiejar), 0)
response = requests.get(httpbin('cookies', 'set', 'key', 'value'), cookies=cookiejar)
self.assertEqual(len(cookiejar), 1)
cookie = list(cookiejar)[0]
self.assertEqual(json.loads(response.text)['cookies'], {'key': 'value'})
self.assertCookieHas(cookie, name='key', value='value')
# save and reload the cookies from the file:
cookiejar.save(ignore_discard=True)
cookiejar_2 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar_2.load(ignore_discard=True)
self.assertEqual(len(cookiejar_2), 1)
cookie_2 = list(cookiejar_2)[0]
# this cookie should have been saved with the correct domain restriction:
self.assertCookieHas(cookie_2, name='key', value='value',
domain='httpbin.org', path='/')
# httpbin sets session cookies, so if we don't ignore the discard attribute,
# there should be no cookie:
cookiejar_3 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar_3.load()
self.assertEqual(len(cookiejar_3), 0)
def test_crossdomain(self):
"""Test persistence of the domains associated with the cookies."""
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar.load()
self.assertEqual(len(cookiejar), 0)
# github sets a cookie
requests.get("http://github.com", cookies=cookiejar)
num_github_cookies = len(cookiejar)
self.assertTrue(num_github_cookies >= 1)
# httpbin sets another
requests.get(httpbin('cookies', 'set', 'key', 'value'), cookies=cookiejar)
num_total_cookies = len(cookiejar)
self.assertTrue(num_total_cookies >= 2)
self.assertTrue(num_total_cookies > num_github_cookies)
# save and load
cookiejar.save(ignore_discard=True)
cookiejar_2 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar_2.load(ignore_discard=True)
self.assertEqual(len(cookiejar_2), num_total_cookies)
r = requests.get(httpbin('cookies'), cookies=cookiejar_2)
self.assertEqual(json.loads(r.text)['cookies'], {'key': 'value'})
def test_persistent_cookies(self):
"""Test that we correctly interpret persistent cookies."""
# httpbin's normal cookie methods don't send persistent cookies,
# so cook up the appropriate header and force it to send
header = "Set-Cookie: Persistent=CookiesAreScary; expires=Sun, 04-May-2032 04:56:50 GMT; path=/"
url = httpbin('response-headers?%s' % (requests.utils.quote(header),))
cookiejar = self.COOKIEJAR_CLASS(self.cookiejar_filename)
requests.get(url, cookies=cookiejar)
self.assertEqual(len(cookiejar), 1)
self.assertCookieHas(list(cookiejar)[0], name='Persistent', value='CookiesAreScary')
requests.get(httpbin('cookies', 'set', 'ThisCookieIs', 'SessionOnly'), cookies=cookiejar)
self.assertEqual(len(cookiejar), 2)
self.assertEqual(len([c for c in cookiejar if c.name == 'Persistent']), 1)
self.assertEqual(len([c for c in cookiejar if c.name == 'ThisCookieIs']), 1)
# save and load
cookiejar.save()
cookiejar_2 = self.COOKIEJAR_CLASS(self.cookiejar_filename)
cookiejar_2.load()
# we should only load the persistent cookie
self.assertEqual(len(cookiejar_2), 1)
self.assertCookieHas(list(cookiejar_2)[0], name='Persistent', value='CookiesAreScary')
class MozCookieJarTest(LWPCookieJarTest):
"""Same test, but substitute MozillaCookieJar."""
COOKIEJAR_CLASS = cookielib.MozillaCookieJar
if __name__ == '__main__':
unittest.main()

945
tests/test_requests.py Executable file
View File

@@ -0,0 +1,945 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# from __future__ import unicode_literals
# Path hack.
import sys
import os
sys.path.insert(0, os.path.abspath('..'))
import json
import os
import unittest
import pickle
import tempfile
import requests
from requests.compat import str, StringIO
# import envoy
from requests import HTTPError
from requests import get, post, head, put
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
if 'HTTPBIN_URL' not in os.environ:
os.environ['HTTPBIN_URL'] = 'http://httpbin.org/'
HTTPBIN_URL = os.environ.get('HTTPBIN_URL')
def httpbin(*suffix):
"""Returns url for HTTPBIN resource."""
return HTTPBIN_URL + '/'.join(suffix)
SERVICES = (httpbin, )
_httpbin = False
class TestSetup(object):
"""Requests test cases."""
# It goes to eleven.
_multiprocess_can_split_ = True
def setUp(self):
global _httpbin
if (not 'HTTPBIN_URL' in os.environ) and not _httpbin:
# c = envoy.connect('httpbin %s' % (PORT))
# time.sleep(1)
_httpbin = True
class TestBaseMixin(object):
def assertCookieHas(self, cookie, **kwargs):
"""Assert that a cookie has various specified properties."""
for attr, expected_value in kwargs.items():
cookie_attr = getattr(cookie, attr)
message = 'Failed comparison for %s: %s != %s' % (attr, cookie_attr, expected_value)
self.assertEqual(cookie_attr, expected_value, message)
class RequestsTestSuite(TestSetup, TestBaseMixin, unittest.TestCase):
"""Requests test cases."""
def test_entry_points(self):
requests.session
requests.session().get
requests.session().head
requests.get
requests.head
requests.put
requests.patch
requests.post
def test_invalid_url(self):
self.assertRaises(ValueError, get, 'hiwpefhipowhefopw')
def test_path_is_not_double_encoded(self):
request = requests.Request("http://0.0.0.0/get/test case")
self.assertEqual(request.path_url, "/get/test%20case")
def test_HTTP_200_OK_GET(self):
r = get(httpbin('get'))
self.assertEqual(r.status_code, 200)
def test_response_sent(self):
r = get(httpbin('get'))
self.assertTrue(r.request.sent)
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
r = get(httpbin('redirect', '1'))
self.assertEqual(r.status_code, 200)
def test_HTTP_302_GET(self):
r = get(httpbin('redirect', '1'), allow_redirects=False)
self.assertEqual(r.status_code, 302)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
r = get(httpbin('user-agent'), headers=heads)
assert heads['User-agent'] in r.text
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
heads = {'User-agent': 'Mozilla/5.0'}
r = get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
self.assertEqual(r.status_code, 200)
# def test_unicode_headers(self):
# # Simply calling requests with a unicode instance should simply work
# # when the characters are all representable using latin-1:
# heads = { u'User-Agent': u'Requests Test Suite' }
# requests.get(url=httpbin('get'), headers=heads)
# # Characters outside latin-1 should raise an exception:
# heads = { u'User-Agent': u'\u30cd\u30c3\u30c8\u30ef\u30fc\u30af' }
# self.assertRaises(UnicodeEncodeError, requests.get,
# url=httpbin('get'), headers=heads)
# def test_session_with_escaped_url(self):
# # Test a URL that contains percent-escaped characters
# # This URL should not be modified (double-escaped)
# # Tests:
# # - Quoted illegal characters ("%20" (' '), "%3C" ('<'), "%3E" ('>'))
# # - Quoted reserved characters ("%25" ('%'), "%23" ('#'), "%2F" ('/'))
# # - Quoted non-ASCII characters ("%C3%98", "%C3%A5")
# path_fully_escaped = '%3Ca%25b%23c%2Fd%3E/%C3%98%20%C3%A5'
# url = httpbin('get/' + path_fully_escaped)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
# # Test that illegal characters in a path get properly percent-escaped
# # Tests:
# # - Bare illegal characters (space, '<')
# # - Bare non-ASCII characters ('\u00d8')
# path = u'<a%25b%23c%2Fd%3E/\u00d8 %C3%A5'
# url = httpbin('get/' + path)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
# # Test that reserved characters in a path do not get percent-escaped
# # Tests:
# # - All reserved characters (RFC 3986), except '?', '#', '[' and ']',
# # which are not allowed in the path, and ';' which delimits
# # parameters.
# # All such characters must be allowed bare in path, and must not be
# # encoded.
# # - Special unreserved characters (RFC 3986), which should not be
# # encoded (even though it wouldn't hurt).
# path_reserved = '!$&\'()*+,/:=@-._~'
# url = httpbin('get/' + path_reserved)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/' + path_reserved))
# # Test that percent-encoded unreserved characters in a path get
# # normalised to their un-encoded forms.
# path_unreserved = 'ABCDwxyz1234-._~'
# path_unreserved_escaped = '%41%42%43%44%77%78%79%7A%31%32%33%34%2D%2E%5F%7E'
# url = httpbin('get/' + path_unreserved_escaped)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/' + path_unreserved))
# # Re-run all of the same tests on the query part of the URI
# query_fully_escaped = '%3Ca%25b%23c%2Fd%3E=%C3%98%20%C3%A5'
# url = httpbin('get/?' + query_fully_escaped)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
# query = u'<a%25b%23c%2Fd%3E=\u00d8 %C3%A5'
# url = httpbin('get/?' + query)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
# # The legal characters in query happens to be the same as in path
# query_reserved = '!$&\'()*+,/:=@-._~'
# url = httpbin('get/?' + query_reserved)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/?' + query_reserved))
# query_unreserved = 'ABCDwxyz=1234-._~'
# query_unreserved_escaped = '%41%42%43%44%77%78%79%7A=%31%32%33%34%2D%2E%5F%7E'
# url = httpbin('get/?' + query_unreserved_escaped)
# response = get(url)
# self.assertEqual(response.url, httpbin('get/?' + query_unreserved))
def test_user_agent_transfers(self):
"""Issue XX"""
heads = {
'User-agent':
'Mozilla/5.0 (github.com/kennethreitz/requests)'
}
r = get(httpbin('user-agent'), headers=heads)
self.assertTrue(heads['User-agent'] in r.text)
heads = {
'user-agent':
'Mozilla/5.0 (github.com/kennethreitz/requests)'
}
r = get(httpbin('user-agent'), headers=heads)
self.assertTrue(heads['user-agent'] in r.text)
def test_HTTP_200_OK_HEAD(self):
r = head(httpbin('get'))
self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_PUT(self):
r = put(httpbin('put'))
self.assertEqual(r.status_code, 200)
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = ('user', 'pass')
url = service('basic-auth', 'user', 'pass')
r = get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = get(url, session=s)
self.assertEqual(r.status_code, 200)
def test_BASICAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = HTTPBasicAuth('user', 'pass')
url = service('basic-auth', 'user', 'pass')
r = get(url, auth=auth)
self.assertEqual(r.status_code, 200)
auth = ('user', 'pass')
r = get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = get(url, session=s)
self.assertEqual(r.status_code, 200)
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = HTTPDigestAuth('user', 'pass')
url = service('digest-auth', 'auth', 'user', 'pass')
r = get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = get(url, session=s)
self.assertEqual(r.status_code, 200)
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self):
for service in SERVICES:
auth = HTTPDigestAuth('user', 'wrongpass')
url = service('digest-auth', 'auth', 'user', 'pass')
r = get(url, auth=auth)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = get(url, session=s)
self.assertEqual(r.status_code, 401)
def test_POSTBIN_GET_POST_FILES(self):
for service in SERVICES:
url = service('post')
post1 = post(url).raise_for_status()
post1 = post(url, data={'some': 'data'})
self.assertEqual(post1.status_code, 200)
with open(__file__) as f:
post2 = post(url, files={'some': f})
self.assertEqual(post2.status_code, 200)
post3 = post(url, data='[{"some": "json"}]')
self.assertEqual(post3.status_code, 200)
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
for service in SERVICES:
with open(__file__) as f:
url = service('post')
post1 = post(url,
files={'some': f},
data={'some': 'data'})
self.assertEqual(post1.status_code, 200)
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
for service in SERVICES:
url = service('post')
with open(__file__) as f:
post2 = post(url,
files={'some': f},
headers={'User-Agent': 'requests-tests'})
self.assertEqual(post2.status_code, 200)
def test_POSTBIN_GET_POST_FILES_STRINGS(self):
for service in SERVICES:
url = service('post')
post1 = post(url, files={'fname.txt': 'fdata'})
self.assertEqual(post1.status_code, 200)
post2 = post(url, files={'fname.txt': 'fdata', 'fname2.txt':'more fdata'})
self.assertEqual(post2.status_code, 200)
post3 = post(url, files={'fname.txt': 'fdata', 'fname2.txt':open(__file__,'rb')})
self.assertEqual(post3.status_code, 200)
post4 = post(url, files={'fname.txt': 'fdata'})
self.assertEqual(post4.status_code, 200)
post5 = post(url, files={'file': ('file.txt', 'more fdata')})
self.assertEqual(post5.status_code, 200)
post6 = post(url, files={'fname.txt': '\xe9'})
self.assertEqual(post6.status_code, 200)
post7 = post(url, files={'fname.txt': 'fdata to verify'})
rbody = json.loads(post7.text)
self.assertTrue(rbody.get('files', None))
self.assertTrue(rbody['files'].get('fname.txt'), None)
self.assertEqual(rbody['files']['fname.txt'], 'fdata to verify')
def test_nonzero_evaluation(self):
for service in SERVICES:
r = get(service('status', '500'))
self.assertEqual(bool(r), False)
r = get(service('/get'))
self.assertEqual(bool(r), True)
def test_request_ok_set(self):
for service in SERVICES:
r = get(service('status', '404'))
# print r.status_code
# r.raise_for_status()
self.assertEqual(r.ok, False)
def test_status_raising(self):
r = get(httpbin('status', '404'))
self.assertRaises(HTTPError, r.raise_for_status)
r = get(httpbin('status', '200'))
self.assertFalse(r.error)
r.raise_for_status()
def test_default_status_raising(self):
config = {'danger_mode': True}
args = [httpbin('status', '404')]
kwargs = dict(config=config)
self.assertRaises(HTTPError, get, *args, **kwargs)
r = get(httpbin('status', '200'))
self.assertEqual(r.status_code, 200)
def test_decompress_gzip(self):
r = get(httpbin('gzip'))
r.content.decode('ascii')
def test_response_has_unicode_url(self):
for service in SERVICES:
url = service('get')
response = get(url)
assert isinstance(response.url, str)
def test_unicode_get(self):
for service in SERVICES:
url = service('/get')
get(url, params={'foo': 'føø'})
get(url, params={'føø': 'føø'})
get(url, params={'føø': 'føø'})
get(url, params={'foo': 'foo'})
get(service('ø'), params={'foo': 'foo'})
def test_httpauth_recursion(self):
http_auth = HTTPBasicAuth('user', 'BADpass')
for service in SERVICES:
r = get(service('basic-auth', 'user', 'pass'), auth=http_auth)
self.assertEqual(r.status_code, 401)
def test_urlencoded_post_data(self):
for service in SERVICES:
r = post(service('post'), data=dict(test='fooaowpeuf'))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post'))
rbody = json.loads(r.text)
self.assertEqual(rbody.get('form'), dict(test='fooaowpeuf'))
self.assertEqual(rbody.get('data'), '')
def test_nonurlencoded_post_data(self):
for service in SERVICES:
r = post(service('post'), data='fooaowpeuf')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post'))
rbody = json.loads(r.text)
# Body wasn't valid url encoded data, so the server returns None as
# "form" and the raw body as "data".
assert rbody.get('form') in (None, {})
self.assertEqual(rbody.get('data'), 'fooaowpeuf')
def test_file_post_data(self):
filecontent = b"fooaowpeufbarasjhf"
testfile = tempfile.NamedTemporaryFile()
testfile.write(filecontent)
testfile.flush()
for service in SERVICES:
r = post(service('post'), data=open(testfile.name, "rb"),
headers={"content-type": "application/octet-stream"})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post'))
rbody = json.loads(r.text)
assert rbody.get('form') in (None, {})
self.assertEqual(rbody.get('data'), filecontent.decode('ascii'))
def test_urlencoded_post_querystring(self):
for service in SERVICES:
r = post(service('post'), params=dict(test='fooaowpeuf'))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
rbody = json.loads(r.text)
self.assertEqual(rbody.get('form'), {}) # No form supplied
self.assertEqual(rbody.get('data'), '')
def test_urlencoded_post_query_and_data(self):
for service in SERVICES:
r = post(
service('post'),
params=dict(test='fooaowpeuf'),
data=dict(test2="foobar"))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
rbody = json.loads(r.text)
self.assertEqual(rbody.get('form'), dict(test2='foobar'))
self.assertEqual(rbody.get('data'), '')
def test_nonurlencoded_postdata(self):
for service in SERVICES:
r = post(service('post'), data="foobar")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
rbody = json.loads(r.text)
assert rbody.get('form') in (None, {})
self.assertEqual(rbody.get('data'), 'foobar')
def test_urlencoded_get_query_multivalued_param(self):
for service in SERVICES:
r = get(service('get'), params=dict(test=['foo', 'baz']))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.url, service('get?test=foo&test=baz'))
def test_urlencoded_post_querystring_multivalued(self):
for service in SERVICES:
r = post(service('post'), params=dict(test=['foo', 'baz']))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post?test=foo&test=baz'))
rbody = json.loads(r.text)
self.assertEqual(rbody.get('form'), {}) # No form supplied
self.assertEqual(rbody.get('data'), '')
def test_urlencoded_post_query_multivalued_and_data(self):
for service in SERVICES:
r = post(
service('post'),
params=dict(test=['foo', 'baz']),
data=dict(test2="foobar", test3=['foo', 'baz']))
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/json')
self.assertEqual(r.url, service('post?test=foo&test=baz'))
# print(r.text)
# print('-----------------------')
rbody = json.loads(r.text)
self.assertEqual(rbody.get('form'), dict(test2='foobar', test3=['foo', 'baz']))
self.assertEqual(rbody.get('data'), '')
def test_GET_no_redirect(self):
for service in SERVICES:
r = get(service('redirect', '3'), allow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(r.history), 0)
def test_HEAD_no_redirect(self):
for service in SERVICES:
r = head(service('redirect', '3'), allow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(r.history), 0)
def test_redirect_history(self):
for service in SERVICES:
r = get(service('redirect', '3'))
self.assertEqual(r.status_code, 200)
self.assertEqual(len(r.history), 3)
def test_relative_redirect_history(self):
for service in SERVICES:
r = get(service('relative-redirect', '3'))
self.assertEqual(r.status_code, 200)
self.assertEqual(len(r.history), 3)
def test_session_HTTP_200_OK_GET(self):
s = requests.session()
r = get(httpbin('get'), session=s)
self.assertEqual(r.status_code, 200)
def test_session_persistent_headers(self):
heads = {'User-agent': 'Mozilla/5.0'}
s = requests.session()
s.headers = heads
# Make 2 requests from Session object, should send header both times
r1 = get(httpbin('user-agent'), session=s)
assert heads['User-agent'] in r1.text
r2 = get(httpbin('user-agent'), session=s)
assert heads['User-agent'] in r2.text
new_heads = {'User-agent': 'blah'}
r3 = get(httpbin('user-agent'), headers=new_heads, session=s)
assert new_heads['User-agent'] in r3.text
self.assertEqual(r2.status_code, 200)
def test_single_hook(self):
def add_foo_header(args):
if not args.get('headers'):
args['headers'] = {}
args['headers'].update({
'X-Foo': 'foo'
})
return args
for service in SERVICES:
url = service('headers')
response = get(url=url, hooks={'args': add_foo_header})
assert 'foo' in response.text
def test_multiple_hooks(self):
def add_foo_header(args):
if not args.get('headers'):
args['headers'] = {}
args['headers'].update({
'X-Foo': 'foo'
})
return args
def add_bar_header(args):
if not args.get('headers'):
args['headers'] = {}
args['headers'].update({
'X-Bar': 'bar'
})
return args
for service in SERVICES:
url = service('headers')
response = get(url=url,
hooks={
'args': [add_foo_header, add_bar_header]
}
)
assert 'foo' in response.text
assert 'bar' in response.text
def test_session_persistent_cookies(self):
s = requests.session()
# Internally dispatched cookies are sent.
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
r = get(httpbin('cookies'), cookies=_c, session=s)
r = get(httpbin('cookies'), session=s)
# Those cookies persist transparently.
c = json.loads(r.text).get('cookies')
self.assertEqual(c, _c)
# Double check.
r = get(httpbin('cookies'), cookies={}, session=s)
c = json.loads(r.text).get('cookies')
self.assertEqual(c, _c)
# Remove a cookie by setting it's value to None.
r = get(httpbin('cookies'), cookies={'bessie': None}, session=s)
c = json.loads(r.text).get('cookies')
del _c['bessie']
self.assertEqual(c, _c)
# Test session-level cookies.
s = requests.session(cookies=_c)
r = get(httpbin('cookies'), session=s)
c = json.loads(r.text).get('cookies')
self.assertEqual(c, _c)
# Have the server set a cookie.
r = get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True, session=s)
c = json.loads(r.text).get('cookies')
assert 'k' in c
# And server-set cookie persistience.
r = get(httpbin('cookies'), session=s)
c = json.loads(r.text).get('cookies')
assert 'k' in c
def test_session_persistent_params(self):
params = {'a': 'a_test'}
s = requests.session()
s.params = params
# Make 2 requests from Session object, should send header both times
r1 = get(httpbin('get'), session=s)
assert params['a'] in r1.text
params2 = {'b': 'b_test'}
r2 = get(httpbin('get'), params=params2, session=s)
assert params['a'] in r2.text
assert params2['b'] in r2.text
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
r3 = get(httpbin('get'), params=params3, session=s)
assert not params['a'] in r3.text
assert params3['b'] in r3.text
assert params3['c'] in r3.text
def test_session_cookies_with_return_response_false(self):
s = requests.session()
# return_response=False as it does requests.async.get
rq = get(httpbin('cookies', 'set', 'k', 'v'), return_response=False,
allow_redirects=True, session=s)
rq.send(prefetch=True)
c = rq.response.json.get('cookies')
assert 'k' in c
assert 'k' in s.cookies
def test_session_pickling(self):
s = requests.session(
headers={'header': 'value'},
cookies={'a-cookie': 'cookie-value'},
auth=('username', 'password'))
ds = pickle.loads(pickle.dumps(s))
self.assertEqual(s.headers, ds.headers)
self.assertEqual(s.auth, ds.auth)
# Cookie doesn't have a good __eq__, so verify manually:
self.assertEqual(len(ds.cookies), 1)
for cookie in ds.cookies:
self.assertCookieHas(cookie, name='a-cookie', value='cookie-value')
def test_unpickled_session_requests(self):
s = requests.session()
r = get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True, session=s)
c = json.loads(r.text).get('cookies')
assert 'k' in c
ds = pickle.loads(pickle.dumps(s))
r = get(httpbin('cookies'), session=ds)
c = json.loads(r.text).get('cookies')
assert 'k' in c
ds1 = pickle.loads(pickle.dumps(requests.session()))
ds2 = pickle.loads(pickle.dumps(requests.session(prefetch=True)))
assert not ds1.prefetch
assert ds2.prefetch
def test_invalid_content(self):
# WARNING: if you're using a terrible DNS provider (comcast),
# this will fail.
try:
hah = 'http://somedomainthatclearlydoesntexistg.com'
r = get(hah, allow_redirects=False)
except requests.ConnectionError:
pass # \o/
else:
assert False
config = {'safe_mode': True}
r = get(hah, allow_redirects=False, config=config)
assert r.content == None
def test_cached_response(self):
r1 = get(httpbin('get'), prefetch=False)
assert not r1._content
assert r1.content
assert r1.text
r2 = get(httpbin('get'), prefetch=True)
assert r2._content
assert r2.content
assert r2.text
def test_iter_lines(self):
lines = (0, 2, 10, 100)
for i in lines:
r = get(httpbin('stream', str(i)), prefetch=False)
lines = list(r.iter_lines())
len_lines = len(lines)
self.assertEqual(i, len_lines)
# Tests that trailing whitespaces within lines do not get stripped.
# Tests that a trailing non-terminated line does not get stripped.
quote = (
'''Agamemnon \n'''
'''\tWhy will he not upon our fair request\r\n'''
'''\tUntent his person and share the air with us?'''
)
# Make a request and monkey-patch its contents
r = get(httpbin('get'))
r.raw = StringIO(quote)
lines = list(r.iter_lines())
len_lines = len(lines)
self.assertEqual(len_lines, 3)
joined = lines[0] + '\n' + lines[1] + '\r\n' + lines[2]
self.assertEqual(joined, quote)
def test_safe_mode(self):
safe = requests.session(config=dict(safe_mode=True))
# Safe mode creates empty responses for failed requests.
# Iterating on these responses should produce empty sequences
r = get('http://_/', session=safe)
self.assertEqual(list(r.iter_lines()), [])
assert isinstance(r.error, requests.exceptions.ConnectionError)
r = get('http://_/', session=safe)
self.assertEqual(list(r.iter_content()), [])
assert isinstance(r.error, requests.exceptions.ConnectionError)
# When not in safe mode, should raise Timeout exception
self.assertRaises(
requests.exceptions.Timeout,
get,
httpbin('stream', '1000'), timeout=0.0001)
# In safe mode, should return a blank response
r = get(httpbin('stream', '1000'), timeout=0.0001,
config=dict(safe_mode=True))
assert r.content is None
assert isinstance(r.error, requests.exceptions.Timeout)
def test_upload_binary_data(self):
requests.get(httpbin('post'), auth=('a', 'b'), data='\xff')
def test_useful_exception_for_invalid_port(self):
# If we pass a legitimate URL with an invalid port, we should fail.
self.assertRaises(
ValueError,
get,
'http://google.com:banana')
def test_useful_exception_for_invalid_scheme(self):
# If we pass a legitimate URL with a scheme not supported
# by requests, we should fail.
self.assertRaises(
ValueError,
get,
'ftp://ftp.kernel.org/pub/')
def test_can_have_none_in_header_values(self):
try:
# Don't choke on headers with none in the value.
requests.get(httpbin('headers'), headers={'Foo': None})
except TypeError:
self.fail()
def test_danger_mode_redirects(self):
s = requests.session()
s.config['danger_mode'] = True
s.get(httpbin('redirect', '4'))
def test_empty_response(self):
r = requests.get(httpbin('status', '404'))
r.text
def test_max_redirects(self):
"""Test the max_redirects config variable, normally and under safe_mode."""
def unsafe_callable():
requests.get(httpbin('redirect', '3'), config=dict(max_redirects=2))
self.assertRaises(requests.exceptions.TooManyRedirects, unsafe_callable)
# add safe mode
response = requests.get(httpbin('redirect', '3'), config=dict(safe_mode=True, max_redirects=2))
self.assertTrue(response.content is None)
self.assertTrue(isinstance(response.error, requests.exceptions.TooManyRedirects))
def test_connection_keepalive_and_close(self):
"""Test that we send 'Connection: close' when keep_alive is disabled."""
# keep-alive should be on by default
r1 = requests.get(httpbin('get'))
# XXX due to proxying issues, test the header sent back by httpbin, rather than
# the header reported in its message body. See kennethreitz/httpbin#46
self.assertEqual(r1.headers['Connection'].lower(), 'keep-alive')
# but when we disable it, we should send a 'Connection: close'
# and get the same back:
r2 = requests.get(httpbin('get'), config=dict(keep_alive=False))
self.assertEqual(r2.headers['Connection'].lower(), 'close')
def test_head_content(self):
"""Test that empty bodies are properly supported."""
r = requests.head(httpbin('headers'))
r.content
r.text
if __name__ == '__main__':
unittest.main()

131
tests/test_requests_ext.py Normal file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
import unittest
import requests
from requests.compat import is_py2, is_py3
try:
import omnijson as json
except ImportError:
import json
class RequestsTestSuite(unittest.TestCase):
"""Requests test cases."""
# It goes to eleven.
_multiprocess_can_split_ = True
def test_addition(self):
assert (1 + 1) == 2
def test_ssl_hostname_ok(self):
requests.get('https://github.com', verify=True)
def test_ssl_hostname_not_ok(self):
requests.get('https://kennethreitz.com', verify=False)
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com')
def test_ssl_hostname_session_not_ok(self):
s = requests.session()
self.assertRaises(requests.exceptions.SSLError, s.get, 'https://kennethreitz.com')
s.get('https://kennethreitz.com', verify=False)
def test_binary_post(self):
'''We need to be careful how we build the utf-8 string since
unicode literals are a syntax error in python3
'''
if is_py2:
# Blasphemy!
utf8_string = eval("u'Smörgås'.encode('utf-8')")
elif is_py3:
utf8_string = 'Smörgås'.encode('utf-8')
else:
raise EnvironmentError('Flesh out this test for your environment.')
requests.post('http://www.google.com/', data=utf8_string)
def test_unicode_error(self):
url = 'http://blip.fm/~1abvfu'
requests.get(url)
def test_chunked_head_redirect(self):
url = "http://t.co/NFrx0zLG"
r = requests.head(url, allow_redirects=True)
self.assertEqual(r.status_code, 200)
def test_unicode_redirect(self):
'''This url redirects to a location that has a nonstandard
character in it, that breaks requests in python2.7
After some research, the cause was identified as an unintended
sideeffect of overriding of str with unicode.
In the case that the redirected url is actually a malformed
"bytes" object, i.e. a string with character c where
ord(c) > 127,
then unicode(url) breaks.
'''
r = requests.get('http://www.marketwire.com/mw/release.' +
'do?id=1628202&sourceType=3')
assert r.ok
def test_unicode_url_outright(self):
'''This url visits in my browser'''
r = requests.get('http://www.marketwire.com/press-release/' +
'jp-morgan-behauptet-sich-der-spitze-euro' +
'p%C3%A4ischer-anleihe-analysten-laut-umf' +
'rageergebnissen-1628202.htm')
assert r.ok
def test_redirect_encoding(self):
'''This url redirects to
http://www.dealipedia.com/deal_view_investment.php?r=20012'''
r = requests.get('http://feedproxy.google.com/~r/Dealipedia' +
'News/~3/BQtUJRJeZlo/deal_view_investment.' +
'php')
assert r.ok
def test_cookies_on_redirects(self):
"""Test interaction between cookie handling and redirection."""
# get a cookie for tinyurl.com ONLY
s = requests.session()
s.get(url='http://tinyurl.com/preview.php?disable=1')
# we should have set a cookie for tinyurl: preview=0
self.assertIn('preview', s.cookies)
self.assertEqual(s.cookies['preview'], '0')
self.assertEqual(list(s.cookies)[0].name, 'preview')
self.assertEqual(list(s.cookies)[0].domain, 'tinyurl.com')
# get cookies on another domain
r2 = s.get(url='http://httpbin.org/cookies')
# the cookie is not there
self.assertNotIn('preview', json.loads(r2.text)['cookies'])
# this redirects to another domain, httpbin.org
# cookies of the first domain should NOT be sent to the next one
r3 = s.get(url='http://tinyurl.com/7zp3jnr')
assert r3.url == 'http://httpbin.org/cookies'
self.assertNotIn('preview', json.loads(r2.text)['cookies'])
if __name__ == '__main__':
unittest.main()

31
tests/test_requests_https.py Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os
import json
import unittest
# Path hack.
sys.path.insert(0, os.path.abspath('..'))
import requests
class HTTPSTest(unittest.TestCase):
"""Smoke test for https functionality."""
smoke_url = "https://github.com"
def perform_smoke_test(self, verify=False):
result = requests.get(self.smoke_url, verify=verify)
self.assertEqual(result.status_code, 200)
def test_smoke(self):
"""Smoke test without verification."""
self.perform_smoke_test(verify=False)
def test_smoke_verified(self):
"""Smoke test with SSL verification."""
self.perform_smoke_test(verify=True)
if __name__ == '__main__':
unittest.main()