Compare commits

...

247 Commits

Author SHA1 Message Date
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
49d79546e8 v0.9.2 2012-01-18 21:25:03 -05:00
Kenneth Reitz
f3ba022f46 Merge branch 'streaming' of https://github.com/RonnyPfannschmidt/requests into develop 2012-01-18 21:05:56 -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
Kenneth Reitz
557480cfe8 SOPA 2012-01-18 11:14:32 -05:00
Kenneth Reitz
5dae0bb9bf Merge pull request #360 from mastahyeti/develop
Make requests.async.send asynchronous
2012-01-17 13:32:04 -08:00
Ben
e38046e6c6 The async.send method wasn't really doing anything useful. It was just calling the r.send() method of the Request object 'r'. It took an optional kwarg pools, and assigned it to r._pools which isn't used by anything. This situation was eaither broken or not fully implemented yet. I modified async.map to call r.send rather than send(r) and then modified async.send to actually make use of the pool that it is passed. 2012-01-17 15:21:09 -06:00
Ben
97be9ee4cd The async.send method wasn't really doing anything useful. It was just calling the r.send() method of the Request object 'r'. It took an optional kwarg pools, and assigned it to r._pools which isn't used by anything. This situation was eaither broken or not fully implemented yet. I modified async.map to call r.send rather than send(r) and then modified async.send to actually make use of the pool that it is passed. 2012-01-17 15:20:52 -06:00
Ben
c658b363e3 The async.send method wasn't really doing anything useful. It was just calling the r.send() method of the Request object 'r'. It took an optional kwarg pools, and assigned it to r._pools which isn't used by anything. This situation was eaither broken or not fully implemented yet. I modified async.map to call r.send rather than send(r) and then modified async.send to actually make use of the pool that it is passed. 2012-01-17 15:17:38 -06:00
Ronny Pfannschmidt
7f9cef0114 close the chunked fd at the end and honor content chunksizes for a potential first chunk 2012-01-17 18:14:47 +01:00
Hank Gay
8efb6cc870 Allow mixing sessions and async. 2012-01-17 11:44:59 -05:00
Ronny Pfannschmidt
1c8a141711 remove debug print 2012-01-17 17:39:37 +01:00
Ronny Pfannschmidt
4d38790b5b even more evil, fixes my misstakes with chunked reading 2012-01-17 17:38:52 +01:00
Ronny Pfannschmidt
5cfbb0aaf5 insanity for chunked iteration 2012-01-17 13:16:09 +01:00
Ronny Pfannschmidt
29271a4100 make Response.iter_lines yield the pending buffer if its actually a complete line 2012-01-17 12:47:22 +01:00
Kenneth Reitz
df0dc2b67a +1 2012-01-15 16:39:46 -05:00
Kenneth Reitz
ff286f351c Update AUTHORS.rst 2012-01-13 17:38:28 -05:00
Kenneth Reitz
430125b396 Merge pull request #355 from fkumro/develop
Added prefetch documentation for api
2012-01-13 14:38:09 -08:00
Frank Kumro
0536e5b708 Added prefetch documentation for api 2012-01-12 22:16:31 -05:00
Kenneth Reitz
e6990162e5 Tom Moertel 2012-01-12 12:53:45 -08:00
Kenneth Reitz
d9b304b906 Merge pull request #352 from tmoertel/develop
Fix bug in HTTP-digest auth w/ URI having query string
2012-01-12 12:52:02 -08:00
Tom Moertel
79f5d532c6 Fix bug in HTTP-digest auth w/ URI having query string 2012-01-10 18:18:54 -05:00
Kenneth Reitz
6af6a08b7a Merge pull request #344 from sharat87/patch-1
Do not pass over the `session` argument to `Session.request` method
2012-01-10 11:11:01 -08:00
Kenneth Reitz
96d5044722 Merge pull request #348 from LucianU/develop
Fixed code in 'Event Hooks' section
2012-01-10 11:10:02 -08:00
Kenneth Reitz
4258201900 Update AUTHORS.rst 2012-01-10 11:09:09 -08:00
Kenneth Reitz
11e5126454 Merge pull request #347 from LucianU/patch-1
Printing the entire traceback, instead of just a warning, when an except...
2012-01-10 11:08:53 -08:00
Kenneth Reitz
9e953f3320 Merge pull request #350 from piotr-dobrogost/develop
fixes issue #349
2012-01-10 11:07:45 -08:00
Piotr Dobrogost
bbbeedb0a2 fixes issue #349 2012-01-10 16:47:21 +01:00
Lucian Ursu
de17f4e5a4 Fixed code in 'Event Hooks' section 2012-01-10 12:17:57 +02:00
Lucian Ursu
86eba21b8a Printing the entire traceback, instead of just a warning, when an exception is encountered 2012-01-10 12:10:58 +02:00
Shrikant Sharat Kandula
53d679edba The request method of Session class does not take a session argument. But api.request does. So, it has to be popped before you can send the whole kwargs dict to Session.request method. 2012-01-09 11:16:54 +05:30
Kenneth Reitz
89c90add2c Update docs/index.rst 2012-01-07 18:32:56 -05:00
Kenneth Reitz
a869be1e5a Merge pull request #343 from shazow/urllib3-attribution
urllib3 attribution in docs
2012-01-07 15:28:17 -08:00
Andrey Petrov
68619b8088 Merge branch 'develop' of https://github.com/kennethreitz/requests into urllib3-attribution 2012-01-07 15:19:02 -08:00
Andrey Petrov
534cdd7587 Rewriting history for correctness. 2012-01-07 15:17:14 -08:00
Andrey Petrov
e185aec920 Adding a humble mention of urllib3 to the Requests intros. 2012-01-07 15:17:06 -08:00
Kenneth Reitz
14e8eea0ee Merge remote-tracking branch 'origin/develop' into develop 2012-01-07 17:18:53 -05:00
Kenneth Reitz
2b849545ea urllib3 update 2012-01-07 17:18:49 -05:00
Kenneth Reitz
15396fb2f9 Update docs/user/advanced.rst 2012-01-07 13:44:09 -05:00
Kenneth Reitz
1acef81ed3 Merge pull request #342 from bjedwards/patch-1
Fix syntax error in last commit.
2012-01-06 12:53:56 -08:00
Ben Edwards
a25141f9f4 Fix syntax error in last commit. 2012-01-06 13:52:49 -07:00
Kenneth Reitz
d339ffe9d9 @ojii 2012-01-06 11:36:04 -05:00
Kenneth Reitz
c5ba5888b5 Merge pull request #341 from ojii/patch-1
Give a more useful error message when the URL supplied does not contain ...
2012-01-06 08:20:49 -08:00
Jonas Obrist
3415e6de0b Give a more useful error message when the URL supplied does not contain a schema. 2012-01-06 16:20:33 +01:00
Kenneth Reitz
638532dd9a v0.9.1 2012-01-06 01:10:53 -05:00
Kenneth Reitz
c6fc7d99b5 Merge branch 'develop' of https://github.com/gazpachoking/requests into develop 2012-01-06 01:08:06 -05:00
Kenneth Reitz
281e0598bc Merge pull request #333 from bryanhelmig/develop
Add eager mode for raising errors immediately.
2012-01-05 22:04:11 -08:00
Bryan Helmig
aa39fb0c80 mention of error modes in quickstart 2012-01-05 14:11:19 -06:00
Bryan Helmig
459a91c54f highway to the danger_mode 2012-01-05 12:50:44 -06:00
Bryan Helmig
115b51e68c add eager mode for raising errors immediately 2012-01-04 22:08:01 -06:00
Kenneth Reitz
4441697e49 Update README.rst 2012-01-03 10:21:13 -05:00
Chase Sterling
5174ab4325 Make sure newlines at chunk borders do not get dropped using iter_lines. 2012-01-03 00:38:59 -05:00
Chase Sterling
95911f059a Refactor r.iter_lines to retrieve more than one byte at a time.
Fixes universal line ending handling, but takes away ability to specyfy
custom line endings.
2012-01-02 16:19:35 -05:00
Kenneth Reitz
e3288d5e33 Update docs/user/install.rst 2012-01-02 02:50:09 -05:00
Kenneth Reitz
49140d6880 2012 2012-01-01 00:44:20 -05:00
Chase Sterling
9c6ffc5303 Refactor stream_decode_gzip and _deflate into one function, stream_decompress.
stream_decompress will now iterate over the raw data if there is a problem with decompression
Remove gzip decoding from Response.content, as urllib3 was doing it anyway.
2011-12-30 22:18:08 -05:00
Chase Sterling
f80984f377 Implement automatic deflating when using response.iter_content. refs #327
Refactor response.iter_lines to use responset.iter_content.
2011-12-30 18:55:56 -05:00
45 changed files with 2258 additions and 1157 deletions

View File

@@ -68,3 +68,17 @@ Patches and Suggestions
- Christopher Davis
- Ori Livneh
- Jason Emerick
- Bryan Helmig
- Jonas Obrist
- 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

View File

@@ -1,6 +1,73 @@
History
-------
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)
++++++++++++++++++
* Asynchronous async.send method.
* Support for proper chunk streams with boundaries.
* session argument for Session classes.
* Print entire hook tracebacks, not just exception instance.
* Fix response.iter_lines from pending next line.
* Fix but in HTTP-digest auth w/ URI having query strings.
* Fix in Event Hooks section.
* Urllib3 update.
0.9.1 (2012-01-06)
++++++++++++++++++
* danger_mode for automatic Response.raise_for_status()
* Response.iter_lines refactor
0.9.0 (2011-12-28)
++++++++++++++++++
@@ -57,7 +124,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.
@@ -81,6 +148,7 @@ History
* OPTION method
* Async pool size throttling
* File uploads send real names
* Vendored in urllib3
0.7.6 (2011-11-07)
++++++++++++++++++
@@ -96,7 +164,7 @@ History
0.7.4 (2011-10-26)
++++++++++++++++++
* Sesion Hooks fix.
* Session Hooks fix.
0.7.3 (2011-10-23)
++++++++++++++++++

View File

@@ -1,4 +1,4 @@
Copyright (c) 2011 Kenneth Reitz.
Copyright (c) 2012 Kenneth Reitz.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@@ -1,11 +1,20 @@
SHELL := /bin/bash
init:
python setup.py develop
pip install -r requirements.txt
test:
nosetests --with-color test_requests.py
nosetests --with-color ./tests/*
lazy:
nosetests --with-color tests/test_requests.py
server:
gunicorn httpbin:app --bind=0.0.0.0:7077 &
ci: init
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
nosetests --with-xunit --xunit-file=junit-report.xml
stats:
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
@@ -19,6 +28,7 @@ pyc:
deps:
rm -fr requests/packages/urllib3
git clone https://github.com/shazow/urllib3.git
cd urllib3 && git checkout python3 && cd ..
mv urllib3/urllib3 requests/packages/
rm -fr urllib3

View File

@@ -19,7 +19,7 @@ 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>`_.
@@ -27,53 +27,24 @@ See `the same code, without Requests <https://gist.github.com/973705>`_.
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
multipart files, and parameters with simple Python dictionaries, and access the
response data in the same way. It's powered by httplib, but it does
all the hard work and crazy hacks for you.
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
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
Installation

View File

@@ -25,6 +25,12 @@
})();
/* ]]> */
</script>
<script type="text/javascript">
new HelloBar(36402,48802);
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
@@ -54,4 +60,6 @@
s.parentNode.insertBefore(t, s);
})();
</script>
{%- endblock %}

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

@@ -43,7 +43,7 @@ master_doc = 'index'
# General information about the project.
project = u'Requests'
copyright = u'2011. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
copyright = u'2012. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View File

@@ -3,6 +3,24 @@ 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`_ 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_.
Development dependencies
------------------------
You'll need to install ``gunicorn`` and ``httpbin`` in order to run requests' test suite::
$ pip install -r requirements.txt
What Needs to be Done
---------------------

View File

@@ -23,22 +23,22 @@ Things shouldnt 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 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.
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.
Testimonials
------------
`The Washington Post <http://www.washingtonpost.com/>`_,
`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 +72,7 @@ Requests is ready for today's web.
- Unicode Response Bodies
- Multipart File Uploads
- Connection Timeouts
- ``.netrc`` support
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"}}'
@@ -53,9 +53,9 @@ Requests can verify SSL certificates for HTTPS requests, just like a web browser
>>> requests.get('https://kennethreitz.com', verify=True)
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
I don't have SSL setup on this domain, so it fails. Excellent. I do have certs for httpbin.org though::
I don't have SSL setup on this domain, so it fails. Excellent. Github does though::
>>> requests.get('https://httpbin.org', verify=True)
>>> requests.get('https://github.com', verify=True)
<Response [200]>
You can also pass ``verify`` the path to a CA_BUNDLE file for private certs. You can also set the ``REQUESTS_CA_BUNDLE`` environment variable.
@@ -187,14 +187,14 @@ 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::
def hack_headers(args):
if not args[headers]:
if args.get('headers') is None:
args['headers'] = dict()
args['headers'].update({'X-Testing': 'True'})
@@ -206,7 +206,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": "",

View File

@@ -26,9 +26,9 @@ Cheeseshop Mirror
-----------------
If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's
personal `Cheeseshop mirror <http://pip.kreitz.co/>`_::
personal `Cheeseshop mirror <http://pip.kennethreitz.com/>`_::
$ pip install -i http://pip.kreitz.co/simple requests
$ pip install -i http://pip.kennethreitz.com/simple requests
Get the Code

View File

@@ -36,16 +36,42 @@ Response Content
We can read the content of the server's response::
>>> r.content
>>> 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.
Binary Response Content
-----------------------
You can also access the response body as bytes, for non-text requests::
>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
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'
Make a POST Request
@@ -62,7 +88,7 @@ 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.content
>>> print r.text
{
"origin": "179.13.100.4",
"files": {},
@@ -117,7 +143,7 @@ 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",
"files": {
@@ -143,7 +169,7 @@ Setting filename explicitly::
>>> files = {'file': ('report.xls', open('report.xls', 'rb'))}
>>> r = requests.post(url, files=files)
>>> r.content
>>> r.text
{
"origin": "179.13.100.4",
"files": {
@@ -253,7 +279,7 @@ parameter::
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.content
>>> r.text
'{"cookies": {"cookies_are": "working"}}'
@@ -364,6 +390,10 @@ If a request exceeds the configured number of maximum redirections, a :class:`To
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.
-----------------------
Ready for more? Check out the :ref:`advanced <advanced>` section.

View File

@@ -9,18 +9,17 @@
requests
~~~~~~~~
:copyright: (c) 2011 by Kenneth Reitz.
:copyright: (c) 2012 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
__title__ = 'requests'
__version__ = '0.9.0'
__build__ = 0x000900
__version__ = '0.10.4'
__build__ = 0x001004
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
__copyright__ = 'Copyright 2012 Kenneth Reitz'
from . import utils

View File

@@ -6,7 +6,7 @@ requests.api
This module implements the Requests API.
:copyright: (c) 2011 by Kenneth Reitz.
:copyright: (c) 2012 by Kenneth Reitz.
:license: ISC, see LICENSE for more details.
"""
@@ -32,9 +32,10 @@ def request(method, url, **kwargs):
:param session: (optional) A :class:`Session` object to be used for the request.
:param config: (optional) A configuration dictionary.
: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.
"""
s = kwargs.get('session') or sessions.session()
s = kwargs.pop('session') if 'session' in kwargs else sessions.session()
return s.request(method=method, url=url, **kwargs)
@@ -68,7 +69,7 @@ def head(url, **kwargs):
:param **kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
kwargs.setdefault('allow_redirects', False)
return request('head', url, **kwargs)

View File

@@ -46,15 +46,15 @@ def patched(f):
return wrapped
def send(r, pools=None):
"""Sends a given Request object."""
def send(r, pool=None, prefetch=False):
"""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 pools:
r._pools = pools
if pool != None:
return pool.spawn(r.send, prefetch=prefetch)
r.send()
return r.response
return gevent.spawn(r.send, prefetch=prefetch)
# Patched requests.api functions.
@@ -78,19 +78,8 @@ def map(requests, prefetch=True, size=None):
requests = list(requests)
if size:
pool = Pool(size)
pool.map(send, requests)
pool.join()
else:
jobs = [gevent.spawn(send, r) for r in requests]
gevent.joinall(jobs)
if prefetch:
[r.response.content for r in requests]
return [r.response for r in requests]
pool = Pool(size) if size else None
jobs = [send(r, pool, prefetch=prefetch) for r in requests]
gevent.joinall(jobs)
return [r.response for r in requests]

View File

@@ -11,15 +11,15 @@ import time
import hashlib
from base64 import b64encode
from urlparse import urlparse
from .compat import urlparse, str, bytes
from .utils import randombytes, parse_dict_header
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):
@@ -32,8 +32,8 @@ class AuthBase(object):
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)
@@ -74,9 +74,17 @@ 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 h(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.md5(x).hexdigest()
H = h
elif algorithm == 'SHA':
H = lambda x: hashlib.sha1(x).hexdigest()
def h(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.sha1(x).hexdigest()
H = h
# XXX MD5-sess
KD = lambda s, d: H("%s:%s" % (s, d))
@@ -86,7 +94,9 @@ class HTTPDigestAuth(AuthBase):
# XXX not implemented yet
entdig = None
p_parsed = urlparse(r.request.url)
path = p_parsed.path + p_parsed.query
path = p_parsed.path
if p_parsed.query:
path += '?' + p_parsed.query
A1 = '%s:%s:%s' % (self.username, realm, self.password)
A2 = '%s:%s' % (r.request.method, path)
@@ -99,10 +109,12 @@ 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]
)
s = str(nonce_count).encode('utf-8')
s += nonce.encode('utf-8')
s += time.ctime().encode('utf-8')
s += randombytes(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16])
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
respdig = KD(H(A1), noncebit)
elif qop is None:
@@ -132,5 +144,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 .packages.oreos.monkeys import SimpleCookie
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 SimpleCookie
from io import StringIO
str = str
bytes = bytes
basestring = (str,bytes)

View File

@@ -10,17 +10,17 @@ 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.
:pool_maxsize: The maximium size of an HTTP connection pool.
:pool_connections: The number of active HTTP connection pools to use.
"""
SCHEMAS = ['http', 'https']
from . import __version__
defaults = dict()
@@ -34,9 +34,11 @@ 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['keep_alive'] = True

View File

@@ -8,7 +8,7 @@ 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."""

View File

@@ -22,7 +22,10 @@ Available hooks:
"""
import warnings
import traceback
HOOKS = ('args', 'pre_request', 'post_request', 'response')
def dispatch_hook(key, hooks, hook_data):
@@ -31,10 +34,15 @@ def dispatch_hook(key, hooks, hook_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, why:
warnings.warn(str(why))
if hasattr(hooks, '__call__'):
hooks = [hooks]
for hook in hooks:
try:
hook_data = hook(hook_data) or hook_data
except Exception:
traceback.print_exc()
return hook_data

View File

@@ -8,16 +8,12 @@ This module contains the primary objects that power Requests.
"""
import os
import urllib
import zlib
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
@@ -25,18 +21,26 @@ 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)
from .utils import (
get_encoding_from_headers, stream_decode_response_unicode,
decode_gzip, stream_decode_gzip, guess_filename, requote_path)
get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri,
dict_from_string, stream_decode_response_unicode, get_netrc_auth)
from .compat import (
urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
SimpleCookie, is_py2)
# Import chardet if it is available.
try:
import chardet
except ImportError:
pass
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
class Request(object):
"""The :class:`Request <Request>` object. It carries out all functionality of
Requests. Recommended interface is with the Requests functions.
@@ -58,7 +62,8 @@ class Request(object):
hooks=None,
config=None,
_poolmanager=None,
verify=None):
verify=None,
session=None):
#: Float describes the timeout of the request.
# (Use socket.setdefaulttimeout() as fallback)
@@ -83,7 +88,6 @@ class Request(object):
#: Dictionary or byte of querystring data to attach to the
#: :class:`Request <Request>`.
self.params = None
self.params = dict(params or [])
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
#: and HTTPError storage).
@@ -115,10 +119,18 @@ class Request(object):
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
@@ -129,23 +141,17 @@ class Request(object):
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.
"""
@@ -174,7 +180,7 @@ class Request(object):
# 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)
cookies = dict_from_string(cookie_header)
# Save cookies in Response.
response.cookies = cookies
@@ -184,30 +190,31 @@ class Request(object):
# 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']
@@ -242,17 +249,17 @@ class Request(object):
method=method,
params=self.session.params,
auth=self.auth,
cookies=cookies,
cookies=self.cookies,
redirect=True,
config=self.config,
timeout=self.timeout,
_poolmanager=self._poolmanager,
proxies = self.proxies,
verify = self.verify
proxies=self.proxies,
verify=self.verify,
session=self.session
)
request.send()
cookies.update(request.response.cookies)
r = request.response
self.cookies.update(r.cookies)
@@ -262,7 +269,6 @@ class Request(object):
self.response.request = self
self.response.cookies.update(self.cookies)
@staticmethod
def _encode_params(data):
"""Encode parameters in a piece of data.
@@ -275,16 +281,19 @@ class Request(object):
returns it twice.
"""
if hasattr(data, '__iter__'):
if isinstance(data, bytes):
return data, data
if hasattr(data, '__iter__') and not isinstance(data, str):
data = dict(data)
if hasattr(data, 'items'):
result = []
for k, vs in data.items():
for k, vs in list(data.items()):
for v in isinstance(vs, list) and vs or [vs]:
result.append((k.encode('utf-8') if isinstance(k, unicode) else k,
v.encode('utf-8') if isinstance(v, unicode) else v))
return result, urllib.urlencode(result, doseq=True)
result.append((k.encode('utf-8') if isinstance(k, str) else k,
v.encode('utf-8') if isinstance(v, str) else v))
return result, urlencode(result, doseq=True)
else:
return data, data
@@ -295,28 +304,47 @@ 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()
raise ValueError("Invalid URL %r: No schema supplied" % url)
netloc = netloc.encode('idna')
if not scheme in SCHEMAS:
raise ValueError("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')
url = (urlunparse([scheme, netloc, path, params, query, fragment]))
if self._enc_params:
if urlparse(url).query:
return '%s&%s' % (url, self._enc_params)
url = '%s&%s' % (url, self._enc_params)
else:
return '%s?%s' % (url, self._enc_params)
else:
return url
url = '%s?%s' % (url, self._enc_params)
url = requote_uri(url)
return url
@property
def path_url(self):
@@ -333,6 +361,7 @@ class Request(object):
path = p.path
if not path:
path = '/'
url.append(path)
query = p.query
@@ -342,7 +371,10 @@ class Request(object):
return ''.join(url)
def register_hook(self, event, hook):
"""Properly register a hook."""
return self.hooks[event].append(hook)
def send(self, anyway=False, prefetch=False):
"""Sends the request. Returns True of successful, false if not.
@@ -370,14 +402,14 @@ class Request(object):
# Multi-part file uploads.
if self.files:
if not isinstance(self.data, basestring):
if not isinstance(self.data, str):
try:
fields = self.data.copy()
except AttributeError:
fields = dict(self.data)
for (k, v) in self.files.items():
for (k, v) in list(self.files.items()):
# support for explicit filename
if isinstance(v, (tuple, list)):
fn, fp = v
@@ -394,7 +426,7 @@ class Request(object):
if self.data:
body = self._enc_data
if isinstance(self.data, basestring):
if isinstance(self.data, str):
content_type = None
else:
content_type = 'application/x-www-form-urlencoded'
@@ -403,6 +435,10 @@ class Request(object):
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:
self.auth = get_netrc_auth(url)
if self.auth:
if isinstance(self.auth, tuple) and len(self.auth) == 2:
# special-case basic HTTP auth
@@ -440,7 +476,6 @@ class Request(object):
if self.verify is not True:
cert_loc = self.verify
# Look for configuration.
if not cert_loc:
cert_loc = os.environ.get('REQUESTS_CA_BUNDLE')
@@ -455,6 +490,9 @@ class Request(object):
conn.cert_reqs = 'CERT_REQUIRED'
conn.ca_certs = cert_loc
else:
conn.cert_reqs = 'CERT_NONE'
conn.ca_certs = None
if not self.sent or anyway:
@@ -464,8 +502,8 @@ class Request(object):
if 'cookie' not in self.headers:
# Simple cookie with our dict.
c = oreos.monkeys.SimpleCookie()
for (k, v) in self.cookies.items():
c = SimpleCookie()
for (k, v) in list(self.cookies.items()):
c[k] = v
# Turn it into a header.
@@ -474,6 +512,10 @@ class Request(object):
# Attach Cookie header to request.
self.headers['Cookie'] = cookie_header
# Pre-request hook.
r = dispatch_hook('pre_request', self.hooks, self)
self.__dict__.update(r.__dict__)
try:
# The inner try .. except re-raises certain exceptions as
# internal exception types; the outer suppresses exceptions
@@ -494,16 +536,16 @@ class Request(object):
)
self.sent = True
except MaxRetryError, e:
except MaxRetryError as e:
raise ConnectionError(e)
except (_SSLError, _HTTPError), e:
except (_SSLError, _HTTPError) as e:
if self.verify and isinstance(e, _SSLError):
raise SSLError(e)
raise Timeout('Request timed out.')
except RequestException, e:
except RequestException as e:
if self.config.get('safe_mode', False):
# In safe mode, catch the exception and attach it to
# a blank urllib3.HTTPResponse object.
@@ -526,6 +568,9 @@ class Request(object):
# Save the response.
self.response.content
if self.config.get('danger_mode'):
self.response.raise_for_status()
return self.sent
@@ -575,10 +620,13 @@ class Response(object):
#: 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
@@ -591,8 +639,7 @@ class Response(object):
return False
return True
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
def iter_content(self, chunk_size=10 * 1024, 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,77 +658,79 @@ class Response(object):
yield chunk
self._content_consumed = True
gen = generate()
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 'gzip' in self.headers.get('content-encoding', ''):
gen = stream_decode_gzip(gen)
if getattr(getattr(self.raw, '_original_response', None), 'chunked', False):
gen = generate_chunked()
else:
gen = generate()
if decode_unicode is None:
decode_unicode = self.config.get('decode_unicode')
gen = stream_untransfer(gen, self)
if decode_unicode:
gen = stream_decode_response_unicode(gen, self)
return gen
def iter_lines(self, newlines=None, decode_unicode=None):
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.
:param newlines: a collection of bytes to seperate lines with.
"""
if newlines is None:
newlines = ('\r', '\n', '\r\n')
#TODO: why rstrip by default
pending = None
if self._content_consumed:
raise RuntimeError(
'The content for this response was already consumed'
)
for chunk in self.iter_content(chunk_size, decode_unicode=decode_unicode):
def generate():
if self.raw is not None:
chunk = []
if pending is not None:
chunk = pending + chunk
lines = chunk.splitlines(True)
while 1:
c = self.raw.read(1)
if not c:
break
for line in lines[:-1]:
yield line.rstrip()
if c in newlines:
yield ''.join(chunk)
chunk = []
else:
chunk.append(c)
# 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
# Yield the remainder, in case the response
# did not terminate with a newline
if chunk:
yield ''.join(chunk)
self._content_consumed = True
gen = generate()
if 'gzip' in self.headers.get('content-encoding', ''):
gen = stream_decode_gzip(gen)
if decode_unicode is None:
decode_unicode = self.config.get('decode_unicode')
if decode_unicode:
gen = stream_decode_response_unicode(gen, self)
return gen
if lines:
pending = lines[-1]
#if pending is a complete line, give it baack
if pending[-1] == '\n':
yield pending.rstrip()
pending = None
else:
pending = None
# Yield the last line
if pending is not None:
yield pending.rstrip()
@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:
# Read the contents.
@@ -690,47 +739,51 @@ class Response(object):
raise RuntimeError(
'The content for this response was already consumed')
self._content = self.raw.read()
self._content = bytes().join(self.iter_content()) or None
# print repr(self._content)
except AttributeError:
self._content = None
content = self._content
# Decode GZip'd content.
if 'gzip' in self.headers.get('content-encoding', ''):
try:
content = decode_gzip(self._content)
except zlib.error:
pass
# 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
# Fallback to auto-detected encoding if chardet is available.
if self.encoding is None:
try:
detected = chardet.detect(self.content) or {}
encoding = detected.get('encoding')
# Trust that chardet isn't available or something went terribly wrong.
except Exception:
pass
# Decode unicode from given encoding.
try:
content = str(self.content, encoding, errors='replace')
except (UnicodeError, TypeError):
pass
return content
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):
if (self.status_code >= 300) and (self.status_code < 400) and not allow_redirects:
raise HTTPError('%s Redirection' % self.status_code)
elif (self.status_code >= 400) and (self.status_code < 500):
@@ -738,5 +791,3 @@ class Response(object):
elif (self.status_code >= 500) and (self.status_code < 600):
raise HTTPError('%s Server Error' % self.status_code)

View File

@@ -249,10 +249,13 @@ class CookieError(Exception):
# quoted with a preceeding '\' slash.
#
# These are taken from RFC2068 and RFC2109.
# _RFC2965Forbidden is the list of forbidden chars we accept anyway
# _LegalChars is the list of chars which don't require "'s
# _Translator hash-table for fast quoting
#
_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~[]_"
_RFC2965Forbidden = "[]:{}"
_LegalChars = ( string.ascii_letters + string.digits +
"!#$%&'*+-.^_`|~_" + _RFC2965Forbidden )
_Translator = {
'\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
'\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
@@ -318,7 +321,7 @@ _Translator = {
'\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
}
_idmap = ''.join(chr(x) for x in xrange(256))
_idmap = ''.join(chr(x) for x in range(256))
def _quote(str, LegalChars=_LegalChars,
idmap=_idmap, translate=string.translate):

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
"""
oreos.sructures
~~~~~~~~~~~~~~~
oreos.structures
~~~~~~~~~~~~~~~~
The plastic blue packaging.
@@ -362,7 +362,7 @@ class MultiDict(TypeConversionDict):
"""
try:
return dict.pop(self, key)[0]
except KeyError, e:
except KeyError as e:
if default is not _missing:
return default
raise KeyError(str(e))
@@ -372,7 +372,7 @@ class MultiDict(TypeConversionDict):
try:
item = dict.popitem(self)
return (item[0], item[1][0])
except KeyError, e:
except KeyError as e:
raise KeyError(str(e))
def poplist(self, key):
@@ -389,7 +389,7 @@ class MultiDict(TypeConversionDict):
"""Pop a ``(key, list)`` tuple from the dict."""
try:
return dict.popitem(self)
except KeyError, e:
except KeyError as e:
raise KeyError(str(e))
def __copy__(self):

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,7 +10,7 @@ urllib3 - Thread-safe connection pooling and re-using.
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.0.2'
__version__ = '1.1'
from .connectionpool import (

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,15 +7,27 @@
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:
from select import poll, POLLIN
except ImportError: # Doesn't exist on OSX and other platforms
from select import select
poll = False
try: # Python 3
from http.client import HTTPConnection, HTTPSConnection, HTTPException
from http.client import HTTP_PORT, HTTPS_PORT
except ImportError:
from httplib import HTTPConnection, HTTPSConnection, HTTPException
from httplib import HTTP_PORT, HTTPS_PORT
try: # Python 3
from queue import Queue, Empty, Full
except ImportError:
from Queue import Queue, Empty, Full
try: # Compiled with SSL?
import ssl
BaseSSLError = ssl.SSLError
except ImportError:
@@ -23,21 +35,29 @@ except ImportError:
BaseSSLError = None
from .packages.ssl_match_hostname import match_hostname, CertificateError
from .request import RequestMethods
from .response import HTTPResponse
from .exceptions import (
SSLError,
from .exceptions import (SSLError,
MaxRetryError,
TimeoutError,
HostChangedError,
EmptyPoolError,
)
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)
@@ -81,7 +101,16 @@ class ConnectionPool(object):
Base class for all connection pools, such as
:class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.
"""
pass
scheme = None
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):
@@ -169,14 +198,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 conn.sock 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
@@ -229,11 +258,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,
@@ -306,7 +341,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
headers = self.headers
if retries < 0:
raise MaxRetryError("Max retries exceeded for url: %s" % url)
raise MaxRetryError(self, url)
if timeout is _Default:
timeout = self.timeout
@@ -320,8 +355,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
if self.port:
host = "%s:%d" % (host, self.port)
raise HostChangedError("Connection pool with host '%s' tried to "
"open a foreign host: %s" % (host, url))
raise HostChangedError(self, url, retries - 1)
conn = None
@@ -352,27 +386,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:
@@ -381,19 +417,16 @@ 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
# Handle redirection
if (redirect and
response.status in [301, 302, 303, 307] and
'location' in response.headers): # Redirect, retry
log.info("Redirecting %s -> %s" %
(url, response.headers.get('location')))
return self.urlopen(method, response.headers.get('location'), body,
headers, retries - 1, redirect,
assert_same_host)
# Handle redirect?
redirect_location = redirect and response.get_redirect_location()
if redirect_location:
log.info("Redirecting %s -> %s" % (url, redirect_location))
return self.urlopen(method, redirect_location, body, headers,
retries - 1, redirect, assert_same_host)
return response
@@ -550,3 +583,22 @@ def connection_from_url(url, **kw):
return HTTPSConnectionPool(host, port=port, **kw)
else:
return HTTPConnectionPool(host, port=port, **kw)
def is_connection_dropped(conn):
"""
Returns True if the connection is dropped and should be closed.
:param conn:
``HTTPConnection`` object.
"""
if not poll:
return select([conn.sock], [], [], 0.0)[0]
# This version is better on platforms that support it.
p = poll()
p.register(conn.sock, POLLIN)
for (fno, ev) in p.poll(0.0):
if fno == conn.sock.fileno():
# Either data is buffered (bad), or the connection is dropped.
return True

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,35 +1,54 @@
# 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."
pass
def __init__(self, pool, url):
PoolError.__init__(self, pool,
"Max retries exceeded with url: %s" % url)
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):
PoolError.__init__(self, pool,
"Tried to open a foreign host with url: %s" % url)
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."
pass
class EmptyPoolError(HTTPError):
class EmptyPoolError(PoolError):
"Raised when a pool runs out of connections and no more are allowed."
pass

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]
@@ -35,37 +38,37 @@ def encode_multipart_formdata(fields, boundary=None):
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 six.iteritems(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,32 +1,27 @@
# 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
import logging
from ._collections import RecentlyUsedContainer
from .connectionpool import (
HTTPConnectionPool, HTTPSConnectionPool,
get_host, connection_from_url,
)
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from .connectionpool import get_host, connection_from_url, port_by_scheme
from .exceptions import HostChangedError
from .request import RequestMethods
__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url']
from .request import RequestMethods
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
pool_classes_by_scheme = {
'http': HTTPConnectionPool,
'https': HTTPSConnectionPool,
}
port_by_scheme = {
'http': 80,
'https': 443,
}
log = logging.getLogger(__name__)
class PoolManager(RequestMethods):
@@ -105,7 +100,12 @@ class PoolManager(RequestMethods):
:class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
"""
conn = self.connection_from_url(url)
return conn.urlopen(method, url, assert_same_host=False, **kw)
try:
return conn.urlopen(method, url, **kw)
except HostChangedError as e:
kw['retries'] = e.retries # Persist retries countdown
return self.urlopen(method, e.url, **kw)
class ProxyManager(RequestMethods):

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

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,22 @@ 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
try:
basestring = basestring
except NameError: # Python 3
basestring = (str, bytes)
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 +72,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,9 +82,22 @@ 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):
"""
Should we redirect and where to?
:returns: Truthy redirect location string if we got a redirect status
code and valid location. ``None`` if redirect status and no
location. ``False`` if not a redirect status code.
"""
if self.status in [301, 302, 303, 307]:
return self.headers.get('location')
return False
def release_conn(self):
if not self._pool or not self._connection:
return
@@ -98,10 +112,9 @@ class HTTPResponse(object):
return self._body
if self._fp:
return self.read(decode_content=self._decode_content,
cache_content=True)
return self.read(cache_content=True)
def read(self, amt=None, decode_content=True, cache_content=False):
def read(self, amt=None, decode_content=None, cache_content=False):
"""
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
parameters: ``decode_content`` and ``cache_content``.
@@ -124,22 +137,22 @@ class HTTPResponse(object):
"""
content_encoding = self.headers.get('content-encoding')
decoder = self.CONTENT_DECODERS.get(content_encoding)
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)
@@ -150,12 +163,11 @@ class HTTPResponse(object):
return data
finally:
if self._original_response and self._original_response.isclosed():
self.release_conn()
@staticmethod
def from_httplib(r, **response_kw):
@classmethod
def from_httplib(ResponseCls, r, **response_kw):
"""
Given an :class:`httplib.HTTPResponse` instance ``r``, return a
corresponding :class:`urllib3.response.HTTPResponse` object.
@@ -164,14 +176,17 @@ class HTTPResponse(object):
with ``original_response=r``.
"""
return HTTPResponse(body=r,
headers=dict(r.getheaders()),
status=r.status,
version=r.version,
reason=r.reason,
strict=r.strict,
original_response=r,
**response_kw)
# HTTPResponse objects in Python 3 don't have a .strict attribute
strict = getattr(r, 'strict', 0)
return ResponseCls(body=r,
# In Python 3, the header keys are returned capitalised
headers=dict((k.lower(), v) for k,v in r.getheaders()),
status=r.status,
version=r.version,
reason=r.reason,
strict=strict,
original_response=r,
**response_kw)
# Backwards-compatibility methods for httplib.HTTPResponse
def getheaders(self):

View File

@@ -25,7 +25,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 +40,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 +52,7 @@ class Session(object):
__attrs__ = [
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
'params', 'config']
'params', 'config', 'verify']
def __init__(self,
@@ -76,13 +76,10 @@ class Session(object):
self.config = config or {}
self.verify = verify
for (k, v) in defaults.items():
for (k, v) in list(defaults.items()):
self.config.setdefault(k, v)
self.poolmanager = PoolManager(
num_pools=self.config.get('pool_connections'),
maxsize=self.config.get('pool_maxsize')
)
self.init_poolmanager()
# Set up a CookieJar to be used by default
self.cookies = {}
@@ -91,6 +88,12 @@ class Session(object):
if cookies is not None:
self.cookies.update(cookies)
def init_poolmanager(self):
self.poolmanager = PoolManager(
num_pools=self.config.get('pool_connections'),
maxsize=self.config.get('pool_maxsize')
)
def __repr__(self):
return '<requests-client at 0x%x>' % (id(self))
@@ -146,16 +149,13 @@ class Session(object):
params = {} if params is None else params
hooks = {} if hooks is None else hooks
if verify is None:
verify = self.verify
# 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(
@@ -235,7 +235,7 @@ class Session(object):
:param **kwargs: Optional arguments that ``request`` takes.
"""
kwargs.setdefault('allow_redirects', True)
kwargs.setdefault('allow_redirects', False)
return self.request('head', url, **kwargs)
@@ -281,6 +281,15 @@ class Session(object):
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):
@@ -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,61 @@ 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, cookielib, SimpleCookie, is_py2, urlparse
from .compat import basestring, bytes
NETRC_FILES = ('.netrc', '_netrc')
def get_netrc_auth(url):
"""Returns the Requests tuple auth for a given url from netrc."""
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:
pass
def dict_from_string(s):
"""Returns a MultiDict with Cookies."""
cookies = dict()
c = SimpleCookie()
c.load(s)
for k, v in list(c.items()):
cookies.update({k: v.value})
return cookies
def guess_filename(obj):
@@ -27,6 +74,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 +180,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 +206,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,20 +216,13 @@ 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:
if is_py2:
L = [chr(random.randrange(0, 256)) for i in range(n)]
return "".join(L)
else:
L = [chr(random.randrange(0, 256)).encode('utf-8') for i in range(n)]
return b"".join(L)
def dict_from_cookiejar(cj):
@@ -187,9 +233,9 @@ 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
@@ -221,7 +267,7 @@ 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():
for k, v in list(cookie_dict.items()):
cookie = cookielib.Cookie(
version=0,
@@ -276,22 +322,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,48 +366,91 @@ 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.
def stream_decompress(iterator, mode='gzip'):
"""
Stream decodes an iterator over compressed data
:param content: bytestring to gzip-decode.
:param iterator: An iterator over compressed data
:param mode: 'gzip' or 'deflate'
:return: An iterator over decompressed data
"""
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
if mode not in ['gzip', 'deflate']:
raise ValueError('stream_decompress mode must be gzip or deflate')
def stream_decode_gzip(iterator):
"""Stream decodes a gzip-encoded iterator"""
zlib_mode = 16 + zlib.MAX_WBITS if mode == 'gzip' else -zlib.MAX_WBITS
dec = zlib.decompressobj(zlib_mode)
try:
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
for chunk in iterator:
rv = dec.decompress(chunk)
if rv:
yield rv
buf = dec.decompress('')
except zlib.error:
# If there was an error decompressing, just return the raw chunk
yield chunk
# Continue to return the rest of the raw data
for chunk in iterator:
yield chunk
else:
# Make sure everything has been returned from the decompression object
buf = dec.decompress(bytes())
rv = buf + dec.flush()
if rv:
yield rv
except zlib.error:
pass
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.
"""
parts = uri.split('%')
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2:
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)
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="!#$%&'()*+,/:;=?@[]~")

View File

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

View File

@@ -4,6 +4,7 @@
import os
import sys
import requests
from requests.compat import is_py3, is_py2
try:
from setuptools import setup
@@ -20,12 +21,22 @@ if sys.argv[-1] == 'test':
os.system('python test_requests.py')
sys.exit()
required = [
'certifi>=0.0.4'
required = ['certifi>=0.0.7',]
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')
if is_py3:
required.append('chardet2')
else:
required.append('chardet>=1.0.0')
packages.append('requests.packages.oreos')
setup(
name='requests',
@@ -36,14 +47,7 @@ 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,
@@ -54,10 +58,10 @@ setup(
'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,648 +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_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)
len_lines = len([l for l in r.iter_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()

799
tests/test_requests.py Executable file
View File

@@ -0,0 +1,799 @@
#!/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 sys
import unittest
import pickle
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 (sys.platform == 'win32') and ('HTTPBIN_URL' not in os.environ):
os.environ['HTTPBIN_URL'] = 'http://httpbin.org/'
# TODO: Detect an open port.
PORT = os.environ.get('HTTPBIN_PORT', '7077')
HTTPBIN_URL = os.environ.get('HTTPBIN_URL', 'http://0.0.0.0:%s/' % (PORT))
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 RequestsTestSuite(TestSetup, 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")
assert 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_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_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_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')
assert c == _c
# Double check.
r = get(httpbin('cookies'), cookies={}, session=s)
c = json.loads(r.text).get('cookies')
assert 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']
assert c == _c
# Test session-level cookies.
s = requests.session(cookies=_c)
r = get(httpbin('cookies'), session=s)
c = json.loads(r.text).get('cookies')
assert 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_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.cookies, ds.cookies)
self.assertEqual(s.auth, ds.auth)
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)
# 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 = get(httpbin('get'))
r.raw = 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 = 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_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'))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
import sys
import unittest
from requests import async
import envoy
sys.path.append('.')
from test_requests import httpbin, RequestsTestSuite, SERVICES
class RequestsTestSuiteUsingAsyncApi(RequestsTestSuite):
"""Requests async test cases."""
def patched(f):
"""Automatically send request after creation."""
def wrapped(*args, **kwargs):
request = f(*args, **kwargs)
return async.map([request])[0]
return wrapped
# Patched requests.api functions.
global request
request = patched(async.request)
global delete, get, head, options, patch, post, put
delete = patched(async.delete)
get = patched(async.get)
head = patched(async.head)
options = patched(async.options)
patch = patched(async.patch)
post = patched(async.post)
put = patched(async.put)
def test_entry_points(self):
async.request
async.delete
async.get
async.head
async.options
async.patch
async.post
async.put
async.map
async.send
if __name__ == '__main__':
unittest.main()

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))
import unittest
@@ -22,22 +24,29 @@ class RequestsTestSuite(unittest.TestCase):
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')
s.get('https://kennethreitz.com', verify=False)
def test_binary_post(self):
utf8_string = (u'Smörgås').encode('utf-8')
requests.post('http://www.google.com/', data=utf8_string)
if __name__ == '__main__':