Compare commits

..

247 Commits

Author SHA1 Message Date
Cory Benfield
1108058626 v2.9.1 2015-12-21 14:53:36 +00:00
Ian Cordasco
06a411c289 Merge pull request #2937 from Lukasa/release/2.9.1
Release 2.9.1
2015-12-21 08:51:21 -06:00
Ian Cordasco
728c318080 Merge pull request #2936 from Lukasa/netrc_with_bytes_url
Handle bytes and unicode URLs for netloc
2015-12-19 11:34:34 -06:00
Cory Benfield
6e0649d9f8 Push code review advice from @sigmavirus24 2015-12-19 08:44:21 +00:00
Cory Benfield
46b7f19857 Changelog for urllib3 update 2015-12-19 08:41:51 +00:00
Cory Benfield
834a9601c3 Update urllib3 to 1.13.1 2015-12-19 08:40:39 +00:00
Cory Benfield
b444f21b1f Update changelog for 2.9.1 2015-12-18 09:54:42 +00:00
Cory Benfield
589f13ca9d Handle bytes and unicode URLs for netloc 2015-12-18 09:22:23 +00:00
Cory Benfield
f7cb796241 Merge branch 'fix-1859' 2015-12-18 09:18:10 +00:00
Cory Benfield
96a068b58e Merge branch 'master' into fix-1859 2015-12-18 09:14:13 +00:00
Ian Cordasco
b32c3bc10f Merge pull request #2931 from Lukasa/uploading_bytes
Fix regression from #2844 regarding binary bodies.
2015-12-16 09:20:26 -06:00
Cory Benfield
fc8fa1aa26 Fix regression from #2844 regarding binary bodies. 2015-12-16 14:56:13 +00:00
Cory Benfield
5f7a3a74aa v2.9.0 2015-12-15 15:29:27 +00:00
Ian Cordasco
046a0f215d Merge pull request #2927 from Lukasa/release/2.9.0
Prepare 2.9.0 release.
2015-12-15 09:26:56 -06:00
Cory Benfield
424fe263d8 Suggested wording from @sigmavirus24 2015-12-15 15:20:26 +00:00
Cory Benfield
2f744b5010 Update 2.9.0 release log. 2015-12-15 14:59:14 +00:00
Cory Benfield
3c0f0b9ab5 Update urllib3 to 1.13 2015-12-15 14:47:20 +00:00
Ian Cordasco
3d33f30436 Merge pull request #2926 from bsamek/master
Refactor default params
2015-12-11 16:13:53 -06:00
Brian Samek
9aca57d4bf Revert changes to params
This reverts commit 9a20afd6b5.

Docstring changes were desirable, but changes to params were not.
2015-12-11 16:47:47 -05:00
Brian Samek
9a20afd6b5 Refactor default params
Specified the default argument for params that have a default in the docstring
so that the default is easier to see from the code. Modified the docstring in
api.py to match the docstring in sessions.py.
2015-12-11 13:32:09 -05:00
Cory Benfield
2d91365cba Merge pull request #2923 from bsamek/verifyparam
Add hint to :param verify.
2015-12-09 16:33:09 +00:00
Brian Samek
3ad66349ea Add hint to :param verify.
It is not clear that :param verify defaults to True. The way the verify
portion of the docstring is written it looks like it defaults to False, and
you have to pass in True if you'd like the SSL cert to be verified, but the
opposite is the case.
2015-12-09 11:21:21 -05:00
Ian Cordasco
e48fea7fb3 Merge pull request #2917 from matt-jordan/qop
requests/auth: Handle an empty 'qop' attribute in an Authenticate challenge
2015-12-05 22:48:52 -06:00
Matt Jordan
5c19d3e07b requests/auth: Handle an empty 'qop' attribute in a Authenticate challenge
Some malfunctioning HTTP servers may return a qop directive with no token, as
opposed to correctly omitting the qop directive completely. For example:

header: WWW-Authenticate: Digest realm="foobar_api_auth", qop="",
        nonce="a12059eaaad0b86ece8f62f04cbafed6", algorithm="MD5",
        stale="false"

Prior to this patch, requests would respond with a 'None' Authorization header.
While the server is certainly incorrect, this patch updates requests to be
more tolerant to this kind of shenaniganry. If we receive an empty string for
the value of the qop attribute, we instead treat that as if the qop attribute
was simply not provided.

Closes #2916
2015-12-05 16:14:52 -06:00
Ian Cordasco
40ce36671c Merge pull request #2896 from BraulioVM/master
[WIP] Set 'Transfer-Encoding: chunked' if data is a file with length 0
2015-12-02 08:35:41 -06:00
Cory Benfield
dfa97addcc Merge pull request #2909 from jwilk/spelling
Fix typos
2015-12-01 11:28:06 +00:00
Jakub Wilk
23d9b077d8 Fix typos 2015-12-01 12:24:56 +01:00
Cory Benfield
6cfe6e775f Merge pull request #2902 from jparise/jparise-authors
Include a link to my GitHub profile.
2015-11-26 14:57:10 +00:00
Jon Parise
27f5a15364 Include a link to my GitHub profile. 2015-11-26 09:34:01 -05:00
Ian Cordasco
c1f51fd100 Merge pull request #2898 from lndbrg/patch-1
Update adapters.py
2015-11-24 20:44:51 -06:00
Olle Lundberg
68db4132e3 Update adapters.py
Remove duplicate word.
2015-11-25 02:02:41 +01:00
Braulio Valdivielso Martínez
4c82dbab6f Fix 'Transfer-Encoding: chunked' change 2015-11-24 13:42:13 +01:00
Braulio Valdivielso Martínez
6f6b2fd1d5 Set 'Transfer-Encoding: chunked' if data file length equals 0 2015-11-23 20:52:06 +01:00
Ian Cordasco
6506dc8f66 Merge pull request #2895 from Lukasa/contributing
Add basic CONTRIBUTING.md file
2015-11-20 08:14:59 -06:00
Cory Benfield
085a8768a2 Where did you get Requests? 2015-11-20 13:59:59 +00:00
Cory Benfield
e5d6630888 Add basic CONTRIBUTING.md file 2015-11-20 11:29:35 +00:00
Cory Benfield
6c47a0c5e9 Merge pull request #2891 from lorenzph/pyopenssl-min-version
Specify minimum pyOpenSSL version
2015-11-19 19:11:34 +00:00
Philip Lorenz
6597b53834 Specify minimum pyOpenSSL version
urllib3 requires "set_tlsext_host_name" which was only added in
pyOpenSSL 0.13. As some distributions (e.g. Ubuntu 12.04) still ship an
older version enforce the correct minimum version during installation.
2015-11-19 20:05:03 +01:00
Cory Benfield
2128321b85 Merge pull request #2878 from kiddick/master
Remove redundant json import.
2015-11-12 13:56:55 +00:00
unknown
de2a5f4332 Remove redundant json import. 2015-11-12 16:54:47 +03:00
Cory Benfield
6d0017ee6a Merge pull request #2873 from sigmavirus24/bug/2872
Fix super_len for partially read files
2015-11-11 07:52:47 +00:00
Ian Cordasco
5397ef71b3 Fix super_len for partially read files
Closes #2872
2015-11-10 21:22:24 -06:00
Ian Cordasco
0a1aaca024 Add failing test for #2872 2015-11-10 21:15:29 -06:00
Cory Benfield
4e89ba7077 Merge pull request #2869 from vienno/unnest-cookie-loop
unnest deep loop
2015-11-10 13:21:01 +00:00
vienno
f835843f87 error in github bio link 2015-11-10 14:07:55 +01:00
vienno
cf9da5b52e add myself to authors 2015-11-10 14:05:24 +01:00
vienno
d849d6e907 Replace 'if x' with 'if x is not None' in cookie loop 2015-11-10 13:58:09 +01:00
vienno
89178ebcdc Complete unnesting of if statements in cookie loop 2015-11-10 13:47:40 +01:00
vienno
a25a641fb4 unnest deep loop 2015-11-10 12:36:36 +01:00
Ian Cordasco
35170e180e Merge pull request #2858 from Lukasa/support_ca_cert_directories
Add support for a directory of CAs
2015-11-07 15:39:41 -06:00
Ian Cordasco
7d5d067e9c Merge pull request #2861 from Lukasa/broken_chunked_encoding
Fix breakage introduced by 8f591682
2015-11-07 11:50:50 -06:00
Cory Benfield
c1c164ba80 Removed test of chunked encoding 2015-11-07 14:08:15 +00:00
Cory Benfield
02576ae7e7 Merge pull request #2864 from aartur/master
Assure session is closed on exception.
2015-11-06 21:02:30 +00:00
Artur Siekielski
1ced0040bd Assure session is closed on exception. 2015-11-06 21:54:43 +01:00
Cory Benfield
e67cd15bc8 Fix breakage introduced by 8f591682 2015-11-05 18:54:40 +00:00
Ian Cordasco
f7e0494130 Merge pull request #2859 from kennethreitz/pytest-httpbin
Move test suite offline using pytest-httpbin
2015-11-05 09:37:22 -06:00
Cory Benfield
43b90d7f72 Remove remaining test that contacts external server. 2015-11-05 13:49:35 +00:00
Cory Benfield
7c91e27d01 Remove threaded test 2015-11-05 13:49:35 +00:00
Cory Benfield
15cf19431a Pin httpbin version. 2015-11-05 13:49:35 +00:00
Cory Benfield
be564e500c Fixup assertion broken in rewrite to py.test
Broke it in 0364a8ca7fc67b89f3e54d50fa3936d65e979b96
2015-11-05 13:49:35 +00:00
Cory Benfield
70d7b134c8 Stop being so specific when testing error strings. 2015-11-05 13:49:35 +00:00
Cory Benfield
a8fdba15b1 Use py.test style assertions 2015-11-05 13:49:35 +00:00
Cory Benfield
3bc189b0af Inject and use httpbin fixture everywhere. 2015-11-05 13:49:35 +00:00
Cory Benfield
9b63f9cd37 Make sure we unapply this patch. 2015-11-05 13:48:42 +00:00
Cory Benfield
f52287d999 Stop using HTTP and HTTPS schemes in one test. 2015-11-05 13:48:42 +00:00
Cory Benfield
a56db9ecc4 Stop inheriting from unittest.TestCase 2015-11-05 13:48:42 +00:00
Cory Benfield
e5053cd322 Define some httpbin fixtures. 2015-11-05 13:48:42 +00:00
Cory Benfield
de53523bef Add pytest-httpbin requirement. 2015-11-05 13:48:42 +00:00
Cory Benfield
0352ec0bd3 Add support for a directory of CAs 2015-11-05 13:21:21 +00:00
Cory Benfield
4dffe5efaa Update trove classifiers. 2015-11-05 10:38:25 +00:00
Ian Cordasco
8f460faa31 Merge pull request #2849 from annp89/master
Add 511 Network Authentication Required status code
2015-10-26 10:58:01 -05:00
Ann Paul
7dd94941c8 Add 511 Network Authentication Required status code to dict of status codes 2015-10-26 08:42:37 -07:00
Ian Cordasco
d290a9b4c6 Merge pull request #2825 from Lukasa/universal_newlines
Add warnings about text/binary mode files.
2015-10-24 09:37:16 -05:00
Cory Benfield
f1fd11e54f Emit warnings when using text mode files. 2015-10-24 09:29:23 +01:00
Cory Benfield
1dff6c9153 Merge pull request #2845 from gagoman/fix-2844
Fix issue #2844
2015-10-23 13:24:36 +01:00
Alex Khomchenko
edc68a0ac8 fix issue #2844 2015-10-23 15:22:36 +03:00
Cory Benfield
13ea7394cc Merge pull request #2831 from shagunsodhani/proposed-fix-for-#2826
added fix for #2826
2015-10-16 13:21:45 +01:00
shagun Sodhani
b675377dc2 added fix for #2826 2015-10-16 17:01:36 +05:30
Cory Benfield
58b20d671c Merge pull request #2829 from dblia/getresponse-buffering
Use buffering for HTTP responses on chunked requests
2015-10-15 15:37:06 +01:00
Dimitris Bliablias
8f591682e6 Use buffering for HTTP responses on chunked requests
For non-chunked requests, the request is performed using the 'urlopen'
method that underlying uses buffering for the HTTP responses (for Python
2.7+ versions). For chunked requests though, the request is made via a
different code path and so the 'getresponse' method is called without
using buffering. So, the response is consumed per single byte via a
recv() call on the underlying socket.

This patch, fixes that issue to mimic the non-chucked request behavior.
2015-10-15 16:58:07 +03:00
Cory Benfield
6c2d09dc2f Merge pull request #2827 from daniel-mueller/master
Fixed usage example in sessions.py
2015-10-15 14:01:13 +01:00
Daniel Müller
d8f28f4bca Fixed usage example in sessions.py 2015-10-15 14:32:35 +02:00
Cory Benfield
7c55446b7e Add warnings about text/binary mode files. 2015-10-14 13:23:19 +01:00
Cory Benfield
18a6b60110 v2.8.1 2015-10-13 13:55:09 +01:00
Cory Benfield
14a555ac71 Merge pull request #2821 from Lukasa/issue/2818
Allow non-native-string method names.
2015-10-12 15:50:53 +01:00
Cory Benfield
2df3c7c75d Move to_native_string for methods to prepare() 2015-10-12 10:50:37 +01:00
Cory Benfield
b071c49556 Failing test for issue #2818 2015-10-12 10:48:57 +01:00
Ian Cordasco
978a89d5a3 Merge pull request #2810 from Lukasa/certupdate
Update requests certificate bundle.
2015-10-08 11:12:09 -05:00
Ian Cordasco
931fd0eece Merge pull request #2814 from Lukasa/issue/2813
Use general null check for JSON
2015-10-08 10:13:08 -05:00
Cory Benfield
3948a9562d Use general null check for JSON 2015-10-08 14:53:35 +01:00
Ian Cordasco
2da8a604d3 Merge pull request #2812 from Lukasa/issue/2811
Don't treat NewConnectionErrors as timeouts.
2015-10-08 08:10:01 -05:00
Cory Benfield
f7be4e5f28 NewConnectionErrors aren't timeouts.
This is a compatibility change, and should be removed in 2.8.0
2015-10-08 14:05:17 +01:00
Cory Benfield
23051979f4 Update requests certificate bundle.
This brings it up-to-date with the certifi 2015.9.6.2 release,
specifically its `weak.pem` bundle.
2015-10-08 08:09:53 +01:00
Ian Cordasco
e499b354c4 Merge pull request #2804 from Lukasa/final-2.8.0
Final 2.8.0
2015-10-05 11:21:40 -05:00
Cory Benfield
ba7d09354a Changelog for #2785. 2015-10-05 15:57:05 +01:00
Ian Cordasco
96e9490100 Merge pull request #2803 from asieira/master
Update default user-agent (#2785)
2015-10-05 09:54:17 -05:00
Cory Benfield
397f10bac7 Increment version numbers. 2015-10-05 15:41:04 +01:00
Cory Benfield
8dcd9f12de Prepare changelog for 2.8.0 2015-10-05 15:38:31 +01:00
Cory Benfield
09bd3232dd Update urllib3 to 1.12 2015-10-05 15:38:24 +01:00
Alexandre Sieira
4e44c2ab64 Removing change to .gitignore (#2785) 2015-10-05 11:36:21 -03:00
Cory Benfield
d996a95cf7 Merge pull request #2721 from Lukasa/no_proxy_fix
Ignore empty fields in no_proxy env var
2015-10-05 15:28:49 +01:00
Cory Benfield
f6afa71281 Merge pull request #2763 from ravipra/json_supersedes
Fixed issue #2756
2015-10-05 15:27:53 +01:00
Alexandre Sieira
2a635ba87d Update default user-agent (#2785)
* Updated default user agent so that it includes only the requests
version;
* Added PyCharm / IDEA project directory to `.gitignore`.
2015-10-05 11:18:23 -03:00
Cory Benfield
8963e1567b Merge pull request #2678 from sigmavirus24/proposed/2.8.0
Proposed 2.8.0
2015-10-05 15:09:41 +01:00
Cory Benfield
f7a43e2664 Merge branch 'hosamaly-master' 2015-10-03 18:33:38 +01:00
Cory Benfield
139418475c Remove reference to ok. 2015-10-03 18:33:25 +01:00
Cory Benfield
a89d5c003a Revert "Clarify the contract of Response#ok, and avoid exceptions in it"
This reverts commit 086e19d8e1.
2015-10-03 18:32:28 +01:00
Hosam Aly
086e19d8e1 Clarify the contract of Response#ok, and avoid exceptions in it 2015-10-03 16:34:36 +01:00
Hosam Aly
443d07e0d3 Fix #2799: Update Quickstart documentation of Response.json() 2015-10-03 00:01:58 +01:00
Cory Benfield
3c6f89b8f4 Merge pull request #2798 from Doctor-love/patch-1
Clarified description for option "trust_env"
2015-10-02 13:48:56 +01:00
Doctor-love
63b3b9be55 Clarified description for option "trust_env" 2015-10-02 14:47:50 +02:00
Cory Benfield
610fbd062f Merge pull request #2797 from sumitbinnani/DocumentationCorrection
Updated Documentation for Issue #2756
2015-10-02 10:08:23 +01:00
sumitbinnani
bf38d5bf52 Update models.py
Updated Documentation:
```data``` as well as ```files``` supersedes json
2015-10-02 13:50:52 +05:30
Cory Benfield
aead8c347c Merge pull request #2792 from Lukasa/update-test-dependencies
Update test dependencies
2015-10-01 10:27:25 +01:00
Cory Benfield
79675a0638 Update test dependencies 2015-10-01 10:26:14 +01:00
Cory Benfield
21bf8cd9bf Merge pull request #2789 from sigmavirus24/remove-self
Remove offer to pair with others from README
2015-09-30 14:29:27 +01:00
Ian Cordasco
76a0d3fcb9 Remove offer to pair with others from README 2015-09-27 21:48:42 -05:00
Kenneth Reitz
b8df8911c3 60m times 2015-09-24 13:40:49 -04:00
Cory Benfield
46ff1a9a54 Merge pull request #2775 from ueg1990/remove_unused_module
Removed unused module from hooks.py
2015-09-13 13:26:05 +01:00
ueg1990
649aeaa489 Removed unused module from hooks.py 2015-09-13 06:36:39 -04:00
Kenneth Reitz
9d69c7845d Merge pull request #2774 from ueg1990/pythonic_hooks
Some pythonic suggestions for requests/hooks.py
2015-09-13 01:05:34 -04:00
ueg1990
c5a41269d9 Some pythonic suggestions for requests/hooks.py 2015-09-12 20:43:25 -04:00
Cory Benfield
7c74050d66 Merge pull request #2772 from jimbrowne/master
Move CA Certificates documentation next to SSL Cert Verification
2015-09-11 20:22:13 +01:00
Jim Browne
2cee3bb57a docs: move CA Certificates next to SSL Cert Verification 2015-09-11 10:50:23 -07:00
Cory Benfield
17a9e6da9e Merge pull request #2769 from jwilk/spelling
docs: Fix typos
2015-09-10 22:14:11 +01:00
Jakub Wilk
e9c94e0f06 docs: Fix typos 2015-09-10 22:45:59 +02:00
Ian Cordasco
ebc38f7825 Merge pull request #2767 from ueg1990/master
Remove redundant function call list when iterating over status codes
2015-09-10 12:38:25 -05:00
Usman Ehtesham Gul
96e8e8b92f Remove redundant function call list when iterating over status codes 2015-09-10 12:59:39 -04:00
Ravi Prakash Putchala
1f48a4c9ea Removed the other 'encode' as well, in the test case. 2015-09-09 14:43:22 +05:30
Ravi Prakash Putchala
7a0cd16c1a changed the null check for 'data' parameter to be more explicit.
removed the superfluous encode from the testcase.
2015-09-09 13:04:24 +05:30
Ravi Prakash Putchala
37037607b5 Fixed issue #2756
Now 'json' parameter will be used to prepare body only if the 'data'
parameter is not present
2015-09-08 14:45:46 +05:30
Cory Benfield
56ecdebcc5 Merge pull request #2741 from jasongrout/per-host-proxy
Implement per-host proxies
2015-09-06 15:02:40 +12:00
Cory Benfield
14ee84b5fd Merge pull request #2758 from mhils/patch-1
Fix RFC Quotation
2015-09-06 09:01:39 +12:00
Maximilian Hils
f4eaefa63e fix rfc quotation 2015-09-05 12:07:49 +02:00
Jason Grout
704a922fc2 Clarify documentation for per-host proxies
Make sure it's clear that a per-host proxy matches a scheme and exact
hostname.
2015-09-04 22:55:38 +00:00
Ian Cordasco
719c76cd9d Merge pull request #2753 from Lukasa/ssldocs
Document the string form of the verify parameter.
2015-08-31 20:34:11 -05:00
Cory Benfield
7c958ee28e Document the string form of the verify parameter. 2015-08-31 10:26:06 +01:00
Ian Cordasco
bf9e66364f Merge pull request #2751 from carlosvargas/master
Add link to ES translation.
2015-08-29 07:21:07 -05:00
Carlos Vargas
00d8c24fe1 Add link to ES translation. 2015-08-28 19:12:10 -07:00
Jason Grout
4c6540898c don't lowercase a url before urlparsing it
urlparse automatically lowercases the scheme and hostname
2015-08-28 20:20:07 +00:00
Jason Grout
f3d4480a33 Use url parsing to check the scheme 2015-08-28 20:06:45 +00:00
Jason Grout
fb496e4e86 Add test for per-host proxy selection 2015-08-27 18:21:04 +00:00
Jason Grout
c80121df91 Fix documentation for the proxies dictionary 2015-08-27 18:21:04 +00:00
Jason Grout
500f15da1f Factor out the proxy selection code 2015-08-27 18:21:04 +00:00
Jason Grout
2c8721540a Fix documentation for proxies in the Session class 2015-08-27 17:26:45 +00:00
Jason Grout
d7a3aa6188 Fix another place the proxy-selecting logic is used. 2015-08-27 17:26:14 +00:00
Ian Cordasco
bf436ea0a4 Merge pull request #2746 from csparpa/master
Add link to IT translation
2015-08-25 19:38:04 -05:00
csparpa
a2e828d4d7 Add link to IT translation 2015-08-25 22:09:10 +02:00
Jason Grout
22075f02d0 Clarify the per-host proxy code 2015-08-25 18:21:20 +00:00
Jason Grout
618514231b Reword documentation for per-host proxies 2015-08-25 15:11:27 +00:00
Jason Grout
56844d1287 Add documentation for host-specific proxies 2015-08-25 14:53:52 +00:00
Cory Benfield
e57925223f Merge pull request #2743 from sigmavirus24/fix-py26-tests
Fix tests on python 2.6
2015-08-25 08:27:20 +01:00
Ian Cordasco
66567e037b Fix tests on python 2.6 2015-08-24 20:47:22 -05:00
Ian Cordasco
767cc9d2dc Merge pull request #2742 from everett-toews/data-arg
Include file-like object in data arg description
2015-08-24 20:44:45 -05:00
Everett Toews
8b03cd96cb Include file-like object in data arg description 2015-08-24 18:28:41 -05:00
Jason Grout
48fe34447d Implement per-host proxies
This change allows the proxy dict to be have entries of the form
{'<scheme>://<hostname>': '<proxy>'}.  Host-specific proxies will be used in
preference to the scheme-specific proxies (i.e., proxy dict entries with keys
like 'http' or 'https').

Fixes #2722
2015-08-24 21:16:35 +00:00
Cory Benfield
cef2589436 Merge pull request #2740 from qingyunha/master
params of proxy_headers are inconsistent with its docstrings
2015-08-24 13:43:01 +01:00
qingyunha
9d88810d52 modify proxy_headers docstrings 2015-08-24 19:48:17 +08:00
Cory Benfield
5d6d1bc284 Merge pull request #2737 from mjpieters/fix_dict_iteration_issue_2706
Avoid altering dictionary size during iteration
2015-08-21 09:08:19 +01:00
Martijn Pieters
4947cd01ae Avoid altering dictionary size during iteration, by gathering keys that need to be deleted first.
Fixes `RuntimeError` on Python 3 introduced in 53ea23128e
2015-08-21 08:44:09 +01:00
Ian Cordasco
c7c26fb8eb Merge pull request #2730 from bmispelon/doc-codeblock-fix
Fixed minor rendering issue in documentation code blocks
2015-08-15 15:02:06 -05:00
Baptiste Mispelon
7197afa887 Fixed minor rendering issue in documentation code blocks 2015-08-15 21:56:24 +02:00
Cory Benfield
3cf4ec41b9 Merge pull request #2729 from smiley/patch-1
Add own name to "AUTHORS" after PR #2724
2015-08-15 14:42:21 -04:00
Smiley Barry
2411b1f56a Added name to AUTHORS after PR #2724 2015-08-15 21:00:13 +03:00
Ian Cordasco
53ea23128e Merge pull request #2706 from ak1r4/fix-merge-setting
Fix merge setting for not preserving original order of dict parameters
2015-08-15 10:39:46 -05:00
Cory Benfield
fdc76f12e5 Merge pull request #2728 from lukasgraf/docs-fix-timeout-links
Docs: Fix links to `timeouts` section by using :ref:
2015-08-15 09:24:20 -04:00
Lukas Graf
f40bd0f707 Docs: Fix links to timeouts section by using :ref: instead of
linking to .html files.
2015-08-15 12:03:05 +02:00
Cory Benfield
98a59e4894 Merge pull request #2724 from smiley/master
Add documentation in "More complicated POST requests" (Quickstart) to resolve #2686
2015-08-14 15:30:00 -04:00
Smiley Barry
b1de7e7287 Changed phrasing to be a little clearer. 2015-08-14 17:31:54 +03:00
Smiley Barry
5a9097dc4b Update quickstart.rst
Added note about the `json` parameter in `requests.post`.
2015-08-14 17:15:51 +03:00
Cory Benfield
b6127ca9ca Ignore empty fields in no_proxy 2015-08-13 15:04:49 -04:00
Ian Cordasco
408d75d47a Merge pull request #2713 from lukasgraf/master
Docs: Clarify that method-level parameters are not persisted in sessions.
2015-08-07 20:18:21 -05:00
Lukas Graf
1bb91653cc Docs: Clarify that method-level params are not persisted in sessions. 2015-08-08 01:57:06 +02:00
Kenneth Reitz
313143959e Merge pull request #2712 from lukasgraf/master
Document use of sessions as context managers
2015-08-07 16:41:21 -04:00
Lukas Graf
8f3ddbeefb Mention connection pooling in session docs. 2015-08-07 20:48:07 +02:00
Lukas Graf
75c1776bde Document use of sessions as context managers. 2015-08-07 20:48:03 +02:00
Cory Benfield
5d7392f140 Merge branch 'fix-merge-setting' of https://github.com/ak1r4/requests into proposed/2.8.0 2015-08-06 19:06:23 -04:00
Akira J
276202f51e Fix merge setting for not preserving original order of dict parameters 2015-08-04 22:47:12 +01:00
Cory Benfield
9b067db19e Merge pull request #2704 from sYnfo/patch-1
Mention SNI being backported to Python2.7.9 in FAQ
2015-08-04 09:54:30 -04:00
Matej Stuchlik
e8a4784c8f Mention SNI being backported to Python2.7.9 in FAQ
That happened as part of PEP 466 - "Network Security Enhancements for Python 2.7"
2015-08-04 15:05:35 +02:00
Cory Benfield
9648301e1e Merge pull request #2680 from peplin/patch-1
Fix docs for passing a list of values for a query string
2015-07-19 10:57:01 +01:00
Christopher Peplin
6648defcdd Fix docs for passing a list of values for a query string
The special `[]` notation at the end of the field name is not necessary to get a field to appear with multiple values in the query string.
2015-07-18 18:50:29 -04:00
Ian Cordasco
873f834fc9 Merge remote-tracking branch 'upstream/master' into proposed/2.8.0 2015-07-18 10:57:34 -05:00
Ian Cordasco
31817b66c6 Fix pool connection closed test 2015-07-18 10:57:28 -05:00
Cory Benfield
b4c1feac43 Merge pull request #2679 from sigmavirus24/bug/2668
Remove broken CaseInsensitiveDict repr test
2015-07-18 16:48:26 +01:00
Ian Cordasco
b8a297baab Remove broken CaseInsensitiveDict repr test
Fixes #2668
2015-07-18 10:47:26 -05:00
Ian Cordasco
d0aecb271f Merge pull request #2506 from Lukasa/chunkeddocs
Expand on chunked handling.
2015-07-18 10:44:31 -05:00
Ian Cordasco
d7026bfb2a Upgrade urllib3 to 7fcfcd08a6a2150f93144d111904d1e4fb65c909 2015-07-18 10:43:26 -05:00
Cory Benfield
2b5cdc56f3 Expand on chunk length 2015-07-18 16:41:29 +01:00
Ian Cordasco
77d7e53c12 Merge branch 'auth-digest-multi-thread' of https://github.com/exvito/requests into proposed/2.8.0 2015-07-18 10:38:58 -05:00
Ian Cordasco
1e640c8456 Merge branch 'debian-imports' of https://github.com/untitaker/requests into proposed/2.8.0 2015-07-18 10:38:30 -05:00
Susan Tan
5ec7576f23 Catch and wrap ClosedPoolError
Partially resolves #1572: "urllib3 exceptions passing through requests
API".

Inspired from Lukasa's 2605be11d82d42438ac7c3993810c955bde74cef.
2015-07-17 01:30:51 -07:00
Ian Cordasco
0be38a0c37 Merge pull request #2672 from petedmarsh/fix-custom-headers-example
Fix quickstart "Custom Headers" example intro
2015-07-15 20:51:06 -05:00
Peter Marsh
beaa7e4180 Fix quickstart "Custom Headers" example intro
Previously this section prefaced an example with:

    For example, we didn't specify our content-type

But, the actual example set a custom user-agent header on the request. This
changes it to say "user-agent" instead which matches the given example.
2015-07-15 23:31:21 +01:00
Cory Benfield
2b26489f74 Merge pull request #2666 from gvangool/extra_tests
Adds extra tests for CaseInsensitiveDict
2015-07-14 07:57:58 +01:00
Gert Van Gool
3b325f1bc5 assert that the copy() is not the original 2015-07-14 08:10:13 +02:00
Gert Van Gool
47e71d1774 Don't test the NotImplemented, just make sure it's False 2015-07-13 15:07:38 +02:00
Gert Van Gool
147a305da8 Adds extra tests for CaseInsensitiveDict
- Test for NotImplemented in __eq__
- Adds test for copy()
- Adds test for __repr__()
2015-07-13 14:33:50 +02:00
Ian Cordasco
2440b6f089 Merge pull request #2661 from Lukasa/timeouts_are_tricky
Only pass useful timeouts to _get_conn
2015-07-04 15:19:37 -05:00
Cory Benfield
e9a1e35f89 Only pass useful timeouts to _get_conn 2015-07-03 17:00:38 +01:00
Cory Benfield
e4d59bedfd Merge pull request #2656 from dpursehouse/get_netrc_auth_errors
Allow get_netrc_auth to raise parse/permission errors to caller
2015-06-29 08:24:21 +01:00
David Pursehouse
884cb7a7fb Change ignore_errors to raise_errors in get_netrc_auth
Change-Id: Ib82c7c614edafc15e5db858d9c1b9a73aebd8a95
2015-06-29 12:22:57 +09:00
David Pursehouse
a560e09427 Allow get_netrc_auth to raise parse/permission errors to caller
If the netrc file exists but cannot be parsed or read, get_netrc_auth
silently fails.

Add a new argument `ignore_errors` which when set to False will cause
any parse/permission errors to be raised to the caller.  The default
value is True, which means the default behavior is unchanged.

Fixes #2654

Change-Id: I7436aaaf593178673ab84fd9e7ab4bcb0e3fe75e
2015-06-29 10:56:51 +09:00
Ian Cordasco
f5dacf8446 Merge pull request #2648 from msabramo/display_url_for_exceptions
Display URL as part of HTTP error messages
2015-06-22 16:16:28 -05:00
Marc Abramowitz
e3bdec5934 Display URL as part of HTTP error messages
It seems convenient to include the URL in the error message in case you
get an unexpected error.

E.g.:

    In [1]: import requests

    In [2]: resp = requests.get('http://www.google.com/eofdfdfdfdfd')

    In [3]: resp
    Out[3]: <Response [404]>

    In [4]: resp.raise_for_status()
    ---------------------------------------------------------------------------
    HTTPError                                 Traceback (most recent call last)
    <ipython-input-4-00e7077cfb5b> in <module>()
    ----> 1 resp.raise_for_status()

    /Users/marca/dev/git-repos/requests/requests/models.py in raise_for_status(self)
        835
        836         if http_error_msg:
    --> 837             raise HTTPError(http_error_msg, response=self)
        838
        839     def close(self):

    HTTPError: 404 Client Error: Not Found for url: http://www.google.com/eofdfdfdfdfd
2015-06-22 13:22:59 -07:00
Ian Cordasco
9bbab338fd Merge pull request #2640 from Lukasa/issue/2636
Avoid double releasing chunked upload connections
2015-06-13 08:54:40 -05:00
Cory Benfield
2e62df456a Avoid double releasing chunked upload connections
Resolves #2636
2015-06-13 08:10:53 +01:00
Cory Benfield
8b5e457b75 Merge branch 'colindickson-master' 2015-05-26 20:54:51 +01:00
colin
fd31453aa2 fix unittest typo 2015-05-26 20:53:31 +01:00
Ian Cordasco
a92a2a4263 Merge pull request #2615 from awiddersheim/docs/add_section_labels
Docs: Add more section labels for referencing
2015-05-26 09:12:33 -05:00
Andrew Widdersheim
7ba130a0cb Docs: Add more section labels for referencing
Allows for sections to be linked from other projects using Intersphinx.
2015-05-26 09:16:24 -04:00
Cory Benfield
dfdf371a0f Merge pull request #2610 from colindickson/master
fix contextlib.closing bug for sessions where content is not consumed…
2015-05-25 18:15:57 +01:00
colin
f608547bef Merge remote-tracking branch 'origin/master'
Conflicts:
	test_requests.py
2015-05-24 15:02:36 -04:00
colin
1da62a65d9 fix contextlib.closing bug for sessions where content is not consumed (issue #2593) 2015-05-24 15:01:34 -04:00
colin
8d0889b91b fix contextlib.closing bug for sessions where content is not consumed (issue #2593) 2015-05-24 09:59:35 -04:00
Cory Benfield
3c850b3339 Merge pull request #2608 from radarhere/patch-1
Fixed typos
2015-05-21 09:16:24 +01:00
Andrew Murray
d249c0a9c6 Fixed typo in contributing.rst 2015-05-21 10:31:42 +10:00
Andrew Murray
d866d8a44d Fixed typos in HISTORY.rst 2015-05-21 10:25:42 +10:00
Ian Cordasco
ab1f493c8b Merge branch 'max-age-pr' 2015-05-15 10:13:42 -05:00
latyas(懒)
dd0f164f8e Update cookies.py 2015-05-15 10:13:06 -05:00
Cory Benfield
059e105f8f Use complexjson everywhere 2015-05-15 10:13:06 -05:00
Cory Benfield
395a3e8885 Partially revert ab84f9be57 2015-05-15 10:13:06 -05:00
latyas(懒)
d3bd3630ee max-age may be not digits 2015-05-15 10:16:35 +08:00
Ian Cordasco
639f8347a3 Merge pull request #2598 from Lukasa/not-poor
Partially revert ab84f9be57
2015-05-14 17:32:19 -05:00
Cory Benfield
553907aa6b Use complexjson everywhere 2015-05-14 22:30:24 +01:00
Cory Benfield
fb6dade63d Partially revert ab84f9be57 2015-05-14 20:58:20 +01:00
latyas(懒)
cda95d3cde morsel['max-age'] may be a str.
Example:

here are Set-Cookie list:
```python
sclst = ['dwac_bcIBMiaagZmkYaaadeYtg11eVR=j9jHdmPjUhgDOhyH9f89X4lQgehEmflVyeA%3D|dw-only|||CNY|false|Asia%2FShanghai|true; Path=/',
 'sid=j9jHdmPjUhgDOhyH9f89X4lQgehEmflVyeA; Path=/',
 'geoLocation=CN; Path=/',
 'dwpersonalization_fae107a9dd0fc32ed99532ec1977f31f=bc8sEiaagZqRsaaadk8XoNTL8h20150506; Expires=Sun, 14-Jun-2015 10:37:07 GMT; Path=/',
 'dwanonymous_fae107a9dd0fc32ed99532ec1977f31f=abjpA8kng31LjPp8ZEERDT4XVg; Version=1; Comment="Demandware anonymous cookie for site Sites-abercrombie_cn-Site"; Max-Age=15552000; Expires=Tue, 10-Nov-2015 10:37:07 GMT; Path=/',
 'myStore=91156; Path=/',
 'AF_PREF=en_CN; Path=/',
 'dwsid=MiHJy3KYZKDcN0lZg4HS1zSpj1VV4s_tFu39ar0KXNpAx9aX8X2LlvZQ9m5fOOknb6QXtmmukHcOjmivYf31hg==; path=/; HttpOnly']
```

```python
    for sc in sclst:
        C = Cookie.SimpleCookie(sc)
        for morsel in C.values():
            cookie = requests.cookies.morsel_to_cookie(morsel)
            cookiejar.set_cookie(cookie)
```
Then, exception occured
```shell
  File "/Library/Python/2.7/site-packages/requests/cookies.py", line 402, in morsel_to_cookie
    expires = time.time() + morsel['max-age']
TypeError: unsupported operand type(s) for +: 'float' and 'str'
```

As Cookie.SimpleCookie is in STL, should `morsel_to_cookie` check the type of `max-age`? On the other hand, if **max-age** can not be converted to float, it's illegal obviously.
2015-05-14 18:44:33 +08:00
Cory Benfield
c3c4c96a95 Merge pull request #2601 from sahutd/issue2599
Auth handler calls close instead of raw.close_conn
2015-05-14 07:15:10 +01:00
Saimadhav Heblikar
86aff09103 Auth handler calls close instead of raw.close_conn 2015-05-14 11:42:07 +05:30
Cory Benfield
4505f71829 Merge pull request #2597 from t-8ch/defensive_programming
do not blindly catch all AttributeErrors
2015-05-13 07:16:44 +01:00
Thomas Weißschuh
c6c8d649af do not blindly catch all AttributeErrors
see shazow/urllib3#618
2015-05-12 23:21:03 +00:00
Kenneth Reitz
868625532d Merge remote-tracking branch 'origin/master' 2015-05-06 12:42:47 -07:00
Kenneth Reitz
74609c5c8f cleanup styles a bit 2015-05-06 12:42:43 -07:00
Kenneth Reitz
ab84f9be57 removed poor code 2015-05-06 12:36:26 -07:00
Kenneth Reitz
3475399a47 fix captilization 2015-05-06 12:35:14 -07:00
Ian Cordasco
a6937534ce Merge pull request #2588 from gutworth/patch-1
use reST link syntax
2015-05-03 21:18:37 -05:00
Benjamin Peterson
ba7975fd16 use reST link syntax 2015-05-03 20:34:09 -04:00
Markus Unterwaditzer
93216afbaf Add information about the package stub 2015-04-24 15:29:19 +02:00
Markus Unterwaditzer
b7fc3a1250 Import aliases for Debian
Alternative to #2375
2015-04-24 12:05:19 +02:00
exvito
5a69137ac8 Issue #2334 - HTTPDigestAuth - Renamed thread local attribute
Per @sigmavirus24 suggestion: private and more readable.
2015-04-04 14:25:08 +01:00
exvito
36dea43406 Issue #2334 - HTTPDigestAuth - Improved per-thread state init
Inspired in @tardyp approach.
2015-04-03 17:04:33 +01:00
Pierre Tardy
142e24de7b digestauth: threadsafe test
Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
2015-04-03 16:32:39 +01:00
exvito
e8d9bc55bc Issue #2334 - HTTPDigestAuth - All state now in thread local storage
Following feedback from tardyp and @vincentxb.
2015-04-03 14:21:29 +01:00
exvito
e65360dbaf Issue #2334 - HTTPDigestAuth - Replace getattr utilization
Following Lukasa + kennethreitz suggestion.
2015-04-02 21:29:51 +01:00
exvito
845e8f943f Issue #2334 - HTTPAuthDigest - Making it thread-safe
The existing code counts the number of 401 responses in the num_401_calls
authenticator attribute. This is in place so as to ensure the necessary auth
header is sent, while avoiding infinite 401 loops (issue #547).

This commit makes num_401_calls an instance of threading.local() (previously
an integer), using num_401_calls.value as the counter.

It ensures that concurrent authentication requests get each their own counter
and behave as expected (otherwise every other concurrent request would have
its authentication fail).
2015-04-02 14:24:54 +01:00
Cory Benfield
84cc36017a Expand on chunked handling. 2015-03-18 21:31:55 +00:00
Ian Cordasco
87abd9c609 Use calendar.timegm when calculating cookie expiration
Fixes #1859

Credit: @lukasa
2014-01-12 14:27:45 -06:00
51 changed files with 2986 additions and 1402 deletions

View File

@@ -134,7 +134,7 @@ Patches and Suggestions
- Kevin Burke <kev@inburke.com>
- Flavio Curella
- David Pursehouse <david.pursehouse@gmail.com> (`@dpursehouse <https://github.com/dpursehouse>`_)
- Jon Parise
- Jon Parise (`@jparise <https://github.com/jparise>`_)
- Alexander Karpinsky (`@homm86 <https://twitter.com/homm86>`_)
- Marc Schlaich (`@schlamar <https://github.com/schlamar>`_)
- Park Ilsu <daftonshady@gmail.com> (`@daftshady <https://github.com/daftshady>`_)
@@ -158,3 +158,8 @@ Patches and Suggestions
- Ulrich Petri (`@ulope <https://github.com/ulope>`_)
- Muhammad Yasoob Ullah Khalid <yasoob.khld@gmail.com> (`@yasoob <https://github.com/yasoob>`_)
- Paul van der Linden (`@pvanderlinden <https://github.com/pvanderlinden>`_)
- Colin Dickson (`@colindickson <https://github.com/colindickson>`_)
- Smiley Barry (`@smiley <https://github.com/smiley>`_)
- Shagun Sodhani (`@shagunsodhani <https://github.com/shagunsodhani>`_)
- Robin Linderborg (`@vienno <https://github.com/vienno>`_)
- Brian Samek(`@bsamek <https://github.com/bsamek`_)

57
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,57 @@
# Contribution Guidelines
Before opening any issues or proposing any pull requests, please do the
following:
1. Read our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/).
2. Understand our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/).
To get the greatest chance of helpful responses, please also observe the
following additional notes.
## Questions
The GitHub issue tracker is for *bug reports* and *feature requests*. Please do
not use it to ask questions about how to use Requests. These questions should
instead be directed to [Stack Overflow](https://stackoverflow.com/). Make sure
that your question is tagged with the `python-requests` tag when asking it on
Stack Overflow, to ensure that it is answered promptly and accurately.
## Good Bug Reports
Please be aware of the following things when filing bug reports:
1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature
to check whether your bug report or feature request has been mentioned in
the past. Duplicate bug reports and feature requests are a huge maintenance
burden on the limited resources of the project. If it is clear from your
report that you would have struggled to find the original, that's ok, but
if searching for a selection of words in your issue title would have found
the duplicate then the issue will likely be closed extremely abruptly.
2. When filing bug reports about exceptions or tracebacks, please include the
*complete* traceback. Partial tracebacks, or just the exception text, are
not helpful. Issues that do not contain complete tracebacks may be closed
without warning.
3. Make sure you provide a suitable amount of information to work with. This
means you should provide:
- Guidance on **how to reproduce the issue**. Ideally, this should be a
*small* code sample that can be run immediately by the maintainers.
Failing that, let us know what you're doing, how often it happens, what
environment you're using, etc. Be thorough: it prevents us needing to ask
further questions.
- Tell us **what you expected to happen**. When we run your example code,
what are we expecting to happen? What does "success" look like for your
code?
- Tell us **what actually happens**. It's not helpful for you to say "it
doesn't work" or "it fails". Tell us *how* it fails: do you get an
exception? A hang? A non-200 status code? How was the actual result
different from your expected result?
- Tell us **what version of Requests you're using**, and
**how you installed it**. Different versions of Requests behave
differently and have different bugs, and some distributors of Requests
ship patches on top of the code we supply.
If you do not provide all of these things, it will take us much longer to
fix your problem. If we ask you to clarify these and you never respond, we
will close your issue without fixing it.

View File

@@ -3,11 +3,111 @@
Release History
---------------
2.9.1 (2015-12-21)
++++++++++++++++++
**Bugfixes**
- Resolve regression introduced in 2.9.0 that made it impossible to send binary
strings as bodies in Python 3.
- Fixed errors when calculating cookie expiration dates in certain locales.
**Miscellaneous**
- Updated bundled urllib3 to 1.13.1.
2.9.0 (2015-12-15)
++++++++++++++++++
**Minor Improvements** (Backwards compatible)
- The ``verify`` keyword argument now supports being passed a path to a
directory of CA certificates, not just a single-file bundle.
- Warnings are now emitted when sending files opened in text mode.
- Added the 511 Network Authentication Required status code to the status code
registry.
**Bugfixes**
- For file-like objects that are not seeked to the very beginning, we now
send the content length for the number of bytes we will actually read, rather
than the total size of the file, allowing partial file uploads.
- When uploading file-like objects, if they are empty or have no obvious
content length we set ``Transfer-Encoding: chunked`` rather than
``Content-Length: 0``.
- We correctly receive the response in buffered mode when uploading chunked
bodies.
- We now handle being passed a query string as a bytestring on Python 3, by
decoding it as UTF-8.
- Sessions are now closed in all cases (exceptional and not) when using the
functional API rather than leaking and waiting for the garbage collector to
clean them up.
- Correctly handle digest auth headers with a malformed ``qop`` directive that
contains no token, by treating it the same as if no ``qop`` directive was
provided at all.
- Minor performance improvements when removing specific cookies by name.
**Miscellaneous**
- Updated urllib3 to 1.13.
2.8.1 (2015-10-13)
++++++++++++++++++
**Bugfixes**
- Update certificate bundle to match ``certifi`` 2015.9.6.2's weak certificate
bundle.
- Fix a bug in 2.8.0 where requests would raise ``ConnectTimeout`` instead of
``ConnectionError``
- When using the PreparedRequest flow, requests will now correctly respect the
``json`` parameter. Broken in 2.8.0.
- When using the PreparedRequest flow, requests will now correctly handle a
Unicode-string method name on Python 2. Broken in 2.8.0.
2.8.0 (2015-10-05)
++++++++++++++++++
**Minor Improvements** (Backwards Compatible)
- Requests now supports per-host proxies. This allows the ``proxies``
dictionary to have entries of the form
``{'<scheme>://<hostname>': '<proxy>'}``. Host-specific proxies will be used
in preference to the previously-supported scheme-specific ones, but the
previous syntax will continue to work.
- ``Response.raise_for_status`` now prints the URL that failed as part of the
exception message.
- ``requests.utils.get_netrc_auth`` now takes an ``raise_errors`` kwarg,
defaulting to ``False``. When ``True``, errors parsing ``.netrc`` files cause
exceptions to be thrown.
- Change to bundled projects import logic to make it easier to unbundle
requests downstream.
- Changed the default User-Agent string to avoid leaking data on Linux: now
contains only the requests version.
**Bugfixes**
- The ``json`` parameter to ``post()`` and friends will now only be used if
neither ``data`` nor ``files`` are present, consistent with the
documentation.
- We now ignore empty fields in the ``NO_PROXY`` environment variable.
- Fixed problem where ``httplib.BadStatusLine`` would get raised if combining
``stream=True`` with ``contextlib.closing``.
- Prevented bugs where we would attempt to return the same connection back to
the connection pool twice when sending a Chunked body.
- Miscellaneous minor internal changes.
- Digest Auth support is now thread safe.
**Updates**
- Updated urllib3 to 1.12.
2.7.0 (2015-05-03)
++++++++++++++++++
This is the first release that follows our new release process. For more, see
[our documentation](http://docs.python-requests.org/en/latest/community/release-process/).
`our documentation
<http://docs.python-requests.org/en/latest/community/release-process/>`_.
**Bugfixes**
@@ -161,7 +261,7 @@ This is the first release that follows our new release process. For more, see
**Bugfixes**
- Only parse the URL once (#2353)
- Allow Content-Length header to always be overriden (#2332)
- Allow Content-Length header to always be overridden (#2332)
- Properly handle files in HTTPDigestAuth (#2333)
- Cap redirect_cache size to prevent memory abuse (#2299)
- Fix HTTPDigestAuth handling of redirects after authenticating successfully
@@ -473,7 +573,7 @@ This is not a backwards compatible change.
- Improved mime-compatible JSON handling
- Proxy fixes
- Path hack fixes
- Case-Insensistive Content-Encoding headers
- Case-Insensitive Content-Encoding headers
- Support for CJK parameters in form posts
@@ -509,7 +609,7 @@ This is not a backwards compatible change.
- Digest Authentication improvements.
- Ensure proxy exclusions work properly.
- Clearer UnicodeError exceptions.
- Automatic casting of URLs to tsrings (fURL and such)
- Automatic casting of URLs to strings (fURL and such)
- Bugfixes.
0.13.6 (2012-08-06)
@@ -560,8 +660,8 @@ This is not a backwards compatible change.
+++++++++++++++++++
- Removal of Requests.async in favor of `grequests <https://github.com/kennethreitz/grequests>`_
- Allow disabling of cookie persistiance.
- New implimentation of safe_mode
- Allow disabling of cookie persistence.
- New implementation of safe_mode
- cookies.get now supports default argument
- Session cookies not saved when Session.request is called with return_response=False
- Env: no_proxy support.
@@ -678,7 +778,7 @@ This is not a backwards compatible change.
* ``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.
* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Response.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.
@@ -798,7 +898,7 @@ This is not a backwards compatible change.
0.7.5 (2011-11-04)
++++++++++++++++++
* Response.content = None if there was an invalid repsonse.
* Response.content = None if there was an invalid response.
* Redirection auth handling.
0.7.4 (2011-10-26)
@@ -885,7 +985,7 @@ This is not a backwards compatible change.
++++++++++++++++++
* New callback hook system
* New persistient sessions object and context manager
* New persistent sessions object and context manager
* Transparent Dict-cookie handling
* Status code reference object
* Removed Response.cached
@@ -919,7 +1019,7 @@ This is not a backwards compatible change.
* Redirect Fixes
* settings.verbose stream writing
* Querystrings for all methods
* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised
* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicitly raised
``r.requests.get('hwe://blah'); r.raise_for_status()``

View File

@@ -77,7 +77,6 @@ Contribute
----------
#. 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.
#. If you feel uncomfortable or uncertain about an issue or your changes, feel free to email @sigmavirus24 and he will happily help you via email, Skype, remote pairing or whatever you are comfortable with.
#. Fork `the repository`_ on GitHub to start making your changes to the **master** 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_.

View File

@@ -34,13 +34,13 @@
<li><a href="http://jp.python-requests.org/">Japanese</a></li>
<li><a href="http://cn.python-requests.org/">Chinese</a></li>
<li><a href="http://pt.python-requests.org/">Portuguese</a></li>
<li><a href="http://it.python-requests.org/">Italian</a></li>
<li><a href="http://es.python-requests.org/">Spanish</a></li>
</ul>
<h3>Useful Links</h3>
<ul>
<li><a href="http://docs.python-requests.org/en/latest/community/recommended/">Recomended Packages and Extensions</a></li>
<li><a href="http://docs.python-requests.org/en/latest/community/recommended/">Recommended Packages and Extensions</a></li>
<p></p>

View File

@@ -81,6 +81,8 @@ Status Code Lookup
>>> requests.codes['\o/']
200
.. _api-cookies:
Cookies
~~~~~~~

View File

@@ -79,9 +79,9 @@ when servers are using `Virtual Hosting`_. When such servers are hosting
more than one SSL site they need to be able to return the appropriate
certificate based on the hostname the client is connecting to.
Python3's SSL module includes native support for SNI. This support has not been
back ported to Python2. For information on using SNI with Requests on Python2
refer to this `Stack Overflow answer`_.
Python3 and Python 2.7.9+ include native support for SNI in their SSL modules.
For information on using SNI with Requests on Python < 2.7.9 refer to this
`Stack Overflow answer`_.
.. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication
.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting

View File

@@ -136,7 +136,7 @@ of other contributors, and should be avoided as much as possible.
Feature Requests
----------------
Requests is in a perpeptual feature freeze. The maintainers believe that
Requests is in a perpetual feature freeze. The maintainers believe that
requests contains every major feature currently required by the vast majority
of users.

View File

@@ -48,7 +48,7 @@ Testimonials
Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku,
PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington
Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed
use Requests internally. It has been downloaded over 40,000,000 times from PyPI.
use Requests internally. It has been downloaded over 60,000,000 times from PyPI.
**Armin Ronacher**
Requests is the perfect example how beautiful an API can be with the

View File

@@ -12,7 +12,10 @@ Session Objects
The Session object allows you to persist certain parameters across
requests. It also persists cookies across all requests made from the
Session instance.
Session instance, and will use ``urllib3``'s `connection pooling`_. So if
you're making several requests to the same host, the underlying TCP
connection will be reused, which can result in a significant performance
increase (see `HTTP persistent connection`_).
A Session object has all the methods of the main Requests API.
@@ -42,6 +45,33 @@ Any dictionaries that you pass to a request method will be merged with the
session-level values that are set. The method-level parameters override session
parameters.
Note, however, that method-level parameters will *not* be persisted across
requests, even if using a session. This example will only send the cookies
with the first request, but not the second::
s = requests.Session()
r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
r = s.get('http://httpbin.org/cookies')
print(r.text)
# '{"cookies": {}}'
If you want to manually add cookies to your session, use the
:ref:`Cookie utility functions <api-cookies>` to manipulate
:attr:`Session.cookies <requests.Session.cookies>`.
Sessions can also be used as context managers::
with requests.Session() as s:
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
This will make sure the session is closed as soon as the ``with`` block is
exited, even if unhandled exceptions occurred.
.. admonition:: Remove a Value From a Dict Parameter
Sometimes you'll want to omit session-level keys from a dict parameter. To
@@ -171,7 +201,11 @@ I don't have SSL setup on this domain, so it fails. Excellent. GitHub does thoug
>>> requests.get('https://github.com', verify=True)
<Response [200]>
You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs. This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable.
You can pass ``verify`` the path to a CA_BUNDLE file or directory with certificates of trusted CAs::
>>> requests.get('https://github.com', verify='/path/to/certfile')
This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable.
Requests can also ignore verifying the SSL certificate if you set ``verify`` to False.
@@ -194,6 +228,28 @@ If you specify a wrong path or an invalid cert::
>>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
.. _ca-certificates:
CA Certificates
---------------
By default Requests bundles a set of root CAs that it trusts, sourced from the
`Mozilla trust store`_. However, these are only updated once for each Requests
version. This means that if you pin a Requests version your certificates can
become extremely out of date.
From Requests version 2.4.0 onwards, Requests will attempt to use certificates
from `certifi`_ if it is present on the system. This allows for users to update
their trusted certificates without having to change the code that runs on their
system.
For the sake of security we recommend upgrading certifi frequently!
.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection
.. _connection pooling: https://urllib3.readthedocs.org/en/latest/pools.html
.. _certifi: http://certifi.io/
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
.. _body-content-workflow:
Body Content Workflow
@@ -259,6 +315,15 @@ file-like object for your body::
with open('massive-body', 'rb') as f:
requests.post('http://some.url/streamed', data=f)
.. warning:: It is strongly recommended that you open files in `binary mode`_.
This is because Requests may attempt to provide the
``Content-Length`` header for you, and if it does this value will
be set to the number of *bytes* in the file. Errors may occur if
you open the file in *text mode*.
.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
.. _chunk-encoding:
Chunk-Encoded Requests
@@ -268,13 +333,19 @@ Requests also supports Chunked transfer encoding for outgoing and incoming reque
To send a chunk-encoded request, simply provide a generator (or any iterator without
a length) for your body::
def gen():
yield 'hi'
yield 'there'
requests.post('http://some.url/chunked', data=gen())
For chunked encoded responses, it's best to iterate over the data using
:meth:`Response.iter_content() <requests.models.Response.iter_content>`. In
an ideal situation you'll have set ``stream=True`` on the request, in which
case you can iterate chunk-by-chunk by calling ``iter_content`` with a chunk
size parameter of ``None``. If you want to set a maximum size of the chunk,
you can set a chunk size parameter to any integer.
.. _multipart:
@@ -300,6 +371,15 @@ To do that, just set files to a list of tuples of (form_field_name, file_info):
...
}
.. warning:: It is strongly recommended that you open files in `binary mode`_.
This is because Requests may attempt to provide the
``Content-Length`` header for you, and if it does this value will
be set to the number of *bytes* in the file. Errors may occur if
you open the file in *text mode*.
.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
.. _event-hooks:
Event Hooks
@@ -446,8 +526,20 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn
"http": "http://user:pass@10.10.1.10:3128/",
}
To give a proxy for a specific scheme and host, use the
`scheme://hostname` form for the key. This will match for
any request to the given scheme and exact hostname.
::
proxies = {
"http://10.20.1.128": "http://10.10.1.10:5323",
}
Note that proxy URLs must include the scheme.
.. _compliance:
Compliance
----------
@@ -474,6 +566,8 @@ specification in this case. If you require a different encoding, you can
manually set the :attr:`Response.encoding <requests.Response.encoding>`
property, or use the raw :attr:`Response.content <requests.Response.content>`.
.. _http-verbs:
HTTP Verbs
----------
@@ -647,6 +741,8 @@ headers.
Excellent. Time to write a Python program that abuses the GitHub API in all
kinds of exciting ways, 4995 more times.
.. _link-headers:
Link Headers
------------
@@ -669,6 +765,8 @@ Requests will automatically parse these link headers and make them easily consum
>>> r.links["last"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
.. _transport-adapters:
Transport Adapters
------------------
@@ -737,6 +835,8 @@ SSLv3:
.. _`described here`: http://www.kennethreitz.org/essays/the-future-of-python-http
.. _`urllib3`: https://github.com/shazow/urllib3
.. _blocking-or-nonblocking:
Blocking Or Non-Blocking?
-------------------------
@@ -754,6 +854,8 @@ Two excellent examples are `grequests`_ and `requests-futures`_.
.. _`grequests`: https://github.com/kennethreitz/grequests
.. _`requests-futures`: https://github.com/ross/requests-futures
.. _timeouts:
Timeouts
--------
@@ -791,21 +893,3 @@ coffee.
r = requests.get('https://github.com', timeout=None)
.. _`connect()`: http://linux.die.net/man/2/connect
CA Certificates
---------------
By default Requests bundles a set of root CAs that it trusts, sourced from the
`Mozilla trust store`_. However, these are only updated once for each Requests
version. This means that if you pin a Requests version your certificates can
become extremely out of date.
From Requests version 2.4.0 onwards, Requests will attempt to use certificates
from `certifi`_ if it is present on the system. This allows for users to update
their trusted certificates without having to change the code that runs on their
system.
For the sake of security we recommend upgrading certifi frequently!
.. _certifi: http://certifi.io/
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt

View File

@@ -37,12 +37,12 @@ get all the information we need from this object.
Requests' simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request::
>>> r = requests.post("http://httpbin.org/post")
>>> r = requests.post("http://httpbin.org/post", data = {"key":"value"})
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple::
>>> r = requests.put("http://httpbin.org/put")
>>> r = requests.put("http://httpbin.org/put", data = {"key":"value"})
>>> r = requests.delete("http://httpbin.org/delete")
>>> r = requests.head("http://httpbin.org/get")
>>> r = requests.options("http://httpbin.org/get")
@@ -73,13 +73,12 @@ You can see that the URL has been correctly encoded by printing the URL::
Note that any dictionary key whose value is ``None`` will not be added to the
URL's query string.
In order to pass a list of items as a value you must mark the key as
referring to a list like string by appending ``[]`` to the key::
You can also pass a list of items as a value::
>>> payload = {'key1': 'value1', 'key2[]': ['value2', 'value3']}
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
>>> r = requests.get("http://httpbin.org/get", params=payload)
>>> print(r.url)
http://httpbin.org/get?key1=value1&key2%5B%5D=value2&key2%5B%5D=value3
http://httpbin.org/get?key1=value1&key2=value2&key2=value3
Response Content
----------------
@@ -146,8 +145,14 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
In case the JSON decoding fails, ``r.json`` raises an exception. For example, if
the response gets a 401 (Unauthorized), attempting ``r.json`` raises ``ValueError:
No JSON object could be decoded``
the response gets a 204 (No Content), or if the response contains invalid JSON,
attempting ``r.json`` raises ``ValueError: No JSON object could be decoded``.
It should be noted that the success of the call to ``r.json`` does **not**
indicate the success of the response. Some servers may return a JSON object in a
failed response (e.g. error details with HTTP 500). Such JSON will be decoded
and returned. To check that a request is successful, use
``r.raise_for_status()`` or check ``r.status_code`` is what you expect.
Raw Response Content
@@ -182,7 +187,7 @@ Custom Headers
If you'd like to add HTTP headers to a request, simply pass in a ``dict`` to the
``headers`` parameter.
For example, we didn't specify our content-type in the previous example::
For example, we didn't specify our user-agent in the previous example::
>>> import json
>>> url = 'https://api.github.com/some/endpoint'
@@ -230,6 +235,14 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
>>> r = requests.post(url, data=json.dumps(payload))
Instead of encoding the ``dict`` yourself, you can also pass it directly using
the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically::
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, json=payload)
POST a Multipart-Encoded File
-----------------------------
@@ -249,7 +262,7 @@ Requests makes it simple to upload Multipart-encoded files::
...
}
You can set the filename, content_type and headers explicitly:
You can set the filename, content_type and headers explicitly::
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
@@ -288,6 +301,14 @@ support this, but there is a separate package which does -
For sending multiple files in one request refer to the :ref:`advanced <advanced>`
section.
.. warning:: It is strongly recommended that you open files in `binary mode`_.
This is because Requests may attempt to provide the
``Content-Length`` header for you, and if it does this value will
be set to the number of *bytes* in the file. Errors may occur if
you open the file in *text mode*.
.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
Response Status Codes
---------------------
@@ -360,10 +381,10 @@ times with different values, but requests combines them so they can be
represented in the dictionary within a single mapping, as per
`RFC 7230 <http://tools.ietf.org/html/rfc7230#section-3.2>`_:
> A recipient MAY combine multiple header fields with the same field name
> into one "field-name: field-value" pair, without changing the semantics
> of the message, by appending each subsequent field value to the combined
> field value in order, separated by a comma.
A recipient MAY combine multiple header fields with the same field name
into one "field-name: field-value" pair, without changing the semantics
of the message, by appending each subsequent field value to the combined
field value in order, separated by a comma.
Cookies
-------

View File

@@ -6,7 +6,7 @@
# /
"""
requests HTTP library
Requests HTTP library
~~~~~~~~~~~~~~~~~~~~~
Requests is an HTTP library, written in Python, for human beings. Basic GET
@@ -42,8 +42,8 @@ is at <http://python-requests.org>.
"""
__title__ = 'requests'
__version__ = '2.7.0'
__build__ = 0x020700
__version__ = '2.9.1'
__build__ = 0x020901
__author__ = 'Kenneth Reitz'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2015 Kenneth Reitz'
@@ -62,7 +62,8 @@ from .sessions import session, Session
from .status_codes import codes
from .exceptions import (
RequestException, Timeout, URLRequired,
TooManyRedirects, HTTPError, ConnectionError
TooManyRedirects, HTTPError, ConnectionError,
FileModeWarning,
)
# Set default logging handler to avoid "No handler found" warnings.
@@ -75,3 +76,8 @@ except ImportError:
pass
logging.getLogger(__name__).addHandler(NullHandler())
import warnings
# FileModeWarnings go off per the default.
warnings.simplefilter('default', FileModeWarning, append=True)

View File

@@ -8,6 +8,7 @@ This module contains the transport adapters that Requests uses to define
and maintain connections.
"""
import os.path
import socket
from .models import Response
@@ -17,11 +18,14 @@ from .packages.urllib3.util import Timeout as TimeoutSauce
from .packages.urllib3.util.retry import Retry
from .compat import urlparse, basestring
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
prepend_scheme_if_needed, get_auth_from_url, urldefragauth)
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
select_proxy)
from .structures import CaseInsensitiveDict
from .packages.urllib3.exceptions import ClosedPoolError
from .packages.urllib3.exceptions import ConnectTimeoutError
from .packages.urllib3.exceptions import HTTPError as _HTTPError
from .packages.urllib3.exceptions import MaxRetryError
from .packages.urllib3.exceptions import NewConnectionError
from .packages.urllib3.exceptions import ProxyError as _ProxyError
from .packages.urllib3.exceptions import ProtocolError
from .packages.urllib3.exceptions import ReadTimeoutError
@@ -35,6 +39,7 @@ from .auth import _basic_auth_str
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
class BaseAdapter(object):
@@ -103,7 +108,7 @@ class HTTPAdapter(BaseAdapter):
def __setstate__(self, state):
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
# because self.poolmanager uses a lambda function, which isn't pickleable.
# self.poolmanager uses a lambda function, which isn't pickleable.
self.proxy_manager = {}
self.config = {}
@@ -181,10 +186,15 @@ class HTTPAdapter(BaseAdapter):
raise Exception("Could not find a suitable SSL CA certificate bundle.")
conn.cert_reqs = 'CERT_REQUIRED'
conn.ca_certs = cert_loc
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
else:
conn.cert_reqs = 'CERT_NONE'
conn.ca_certs = None
conn.ca_cert_dir = None
if cert:
if not isinstance(cert, basestring):
@@ -237,8 +247,7 @@ class HTTPAdapter(BaseAdapter):
:param url: The URL to connect to.
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
"""
proxies = proxies or {}
proxy = proxies.get(urlparse(url.lower()).scheme)
proxy = select_proxy(url, proxies)
if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http')
@@ -271,12 +280,10 @@ class HTTPAdapter(BaseAdapter):
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param proxies: A dictionary of schemes to proxy URLs.
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
"""
proxies = proxies or {}
proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme
proxy = proxies.get(scheme)
if proxy and scheme != 'https':
url = urldefragauth(request.url)
else:
@@ -309,7 +316,6 @@ class HTTPAdapter(BaseAdapter):
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param proxies: The url of the proxy being used for this request.
:param kwargs: Optional additional keyword arguments.
"""
headers = {}
username, password = get_auth_from_url(proxy)
@@ -326,8 +332,8 @@ class HTTPAdapter(BaseAdapter):
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param stream: (optional) Whether to stream the request content.
:param timeout: (optional) How long to wait for the server to send
data before giving up, as a float, or a (`connect timeout, read
timeout <user/advanced.html#timeouts>`_) tuple.
data before giving up, as a float, or a :ref:`(connect timeout,
read timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param verify: (optional) Whether to verify SSL certificates.
:param cert: (optional) Any user-provided SSL certificate to be trusted.
@@ -375,7 +381,7 @@ class HTTPAdapter(BaseAdapter):
if hasattr(conn, 'proxy_pool'):
conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=timeout)
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try:
low_conn.putrequest(request.method,
@@ -394,7 +400,15 @@ class HTTPAdapter(BaseAdapter):
low_conn.send(b'\r\n')
low_conn.send(b'0\r\n\r\n')
r = low_conn.getresponse()
# Receive the response from the server
try:
# For Python 2.7+ versions, use buffering of HTTP
# responses
r = low_conn.getresponse(buffering=True)
except TypeError:
# For compatibility with Python 2.6 versions and back
r = low_conn.getresponse()
resp = HTTPResponse.from_httplib(
r,
pool=conn,
@@ -407,22 +421,24 @@ class HTTPAdapter(BaseAdapter):
# Then, reraise so that we can handle the actual exception.
low_conn.close()
raise
else:
# All is well, return the connection to the pool.
conn._put_conn(low_conn)
except (ProtocolError, socket.error) as err:
raise ConnectionError(err, request=request)
except MaxRetryError as e:
if isinstance(e.reason, ConnectTimeoutError):
raise ConnectTimeout(e, request=request)
# TODO: Remove this in 3.0.0: see #2811
if not isinstance(e.reason, NewConnectionError):
raise ConnectTimeout(e, request=request)
if isinstance(e.reason, ResponseError):
raise RetryError(e, request=request)
raise ConnectionError(e, request=request)
except ClosedPoolError as e:
raise ConnectionError(e, request=request)
except _ProxyError as e:
raise ProxyError(e)

View File

@@ -27,13 +27,13 @@ def request(method, url, **kwargs):
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload.
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) How long to wait for the server to send data
before giving up, as a float, or a (`connect timeout, read timeout
<user/advanced.html#timeouts>`_) tuple.
before giving up, as a float, or a :ref:`(connect timeout, read
timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
:param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``.
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
:return: :class:`Response <Response>` object
@@ -46,13 +46,11 @@ def request(method, url, **kwargs):
<Response [200]>
"""
session = sessions.Session()
response = session.request(method=method, url=url, **kwargs)
# By explicitly closing the session, we avoid leaving sockets open which
# can trigger a ResourceWarning in some cases, and look like a memory leak
# in others.
session.close()
return response
# By using the 'with' statement we are sure the session is closed, thus we
# avoid leaving sockets open which can trigger a ResourceWarning in some
# cases, and look like a memory leak in others.
with sessions.Session() as session:
return session.request(method=method, url=url, **kwargs)
def get(url, params=None, **kwargs):

View File

@@ -11,6 +11,7 @@ import os
import re
import time
import hashlib
import threading
from base64 import b64encode
@@ -63,19 +64,26 @@ class HTTPDigestAuth(AuthBase):
def __init__(self, username, password):
self.username = username
self.password = password
self.last_nonce = ''
self.nonce_count = 0
self.chal = {}
self.pos = None
self.num_401_calls = 1
# Keep state in per-thread local storage
self._thread_local = threading.local()
def init_per_thread_state(self):
# Ensure state is initialized just once per-thread
if not hasattr(self._thread_local, 'init'):
self._thread_local.init = True
self._thread_local.last_nonce = ''
self._thread_local.nonce_count = 0
self._thread_local.chal = {}
self._thread_local.pos = None
self._thread_local.num_401_calls = None
def build_digest_header(self, method, url):
realm = self.chal['realm']
nonce = self.chal['nonce']
qop = self.chal.get('qop')
algorithm = self.chal.get('algorithm')
opaque = self.chal.get('opaque')
realm = self._thread_local.chal['realm']
nonce = self._thread_local.chal['nonce']
qop = self._thread_local.chal.get('qop')
algorithm = self._thread_local.chal.get('algorithm')
opaque = self._thread_local.chal.get('opaque')
if algorithm is None:
_algorithm = 'MD5'
@@ -114,12 +122,12 @@ class HTTPDigestAuth(AuthBase):
HA1 = hash_utf8(A1)
HA2 = hash_utf8(A2)
if nonce == self.last_nonce:
self.nonce_count += 1
if nonce == self._thread_local.last_nonce:
self._thread_local.nonce_count += 1
else:
self.nonce_count = 1
ncvalue = '%08x' % self.nonce_count
s = str(self.nonce_count).encode('utf-8')
self._thread_local.nonce_count = 1
ncvalue = '%08x' % self._thread_local.nonce_count
s = str(self._thread_local.nonce_count).encode('utf-8')
s += nonce.encode('utf-8')
s += time.ctime().encode('utf-8')
s += os.urandom(8)
@@ -128,7 +136,7 @@ class HTTPDigestAuth(AuthBase):
if _algorithm == 'MD5-SESS':
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
if qop is None:
if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
elif qop == 'auth' or 'auth' in qop.split(','):
noncebit = "%s:%s:%s:%s:%s" % (
@@ -139,7 +147,7 @@ class HTTPDigestAuth(AuthBase):
# XXX handle auth-int.
return None
self.last_nonce = nonce
self._thread_local.last_nonce = nonce
# XXX should the partial digests be encoded too?
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
@@ -158,28 +166,27 @@ class HTTPDigestAuth(AuthBase):
def handle_redirect(self, r, **kwargs):
"""Reset num_401_calls counter on redirects."""
if r.is_redirect:
self.num_401_calls = 1
self._thread_local.num_401_calls = 1
def handle_401(self, r, **kwargs):
"""Takes the given response and tries digest-auth, if needed."""
if self.pos is not None:
if self._thread_local.pos is not None:
# Rewind the file position indicator of the body to where
# it was to resend the request.
r.request.body.seek(self.pos)
num_401_calls = getattr(self, 'num_401_calls', 1)
r.request.body.seek(self._thread_local.pos)
s_auth = r.headers.get('www-authenticate', '')
if 'digest' in s_auth.lower() and num_401_calls < 2:
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
self.num_401_calls += 1
self._thread_local.num_401_calls += 1
pat = re.compile(r'digest ', flags=re.IGNORECASE)
self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
# Consume content and release the original connection
# to allow our new request to reuse the same one.
r.content
r.raw.release_conn()
r.close()
prep = r.request.copy()
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies)
@@ -192,21 +199,25 @@ class HTTPDigestAuth(AuthBase):
return _r
self.num_401_calls = 1
self._thread_local.num_401_calls = 1
return r
def __call__(self, r):
# Initialize per-thread state, if needed
self.init_per_thread_state()
# If we have a saved nonce, skip the 401
if self.last_nonce:
if self._thread_local.last_nonce:
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
try:
self.pos = r.body.tell()
self._thread_local.pos = r.body.tell()
except AttributeError:
# In the case of HTTPDigestAuth being reused and the body of
# the previous request was a file-like object, pos has the
# file position of the previous body. Ensure it's set to
# None.
self.pos = None
self._thread_local.pos = None
r.register_hook('response', self.handle_401)
r.register_hook('response', self.handle_redirect)
self._thread_local.num_401_calls = 1
return r

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ requests.utils imports from here, so be careful with imports.
import copy
import time
import calendar
import collections
from .compat import cookielib, urlparse, urlunparse, Morsel
@@ -143,10 +144,13 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
"""
clearables = []
for cookie in cookiejar:
if cookie.name == name:
if domain is None or domain == cookie.domain:
if path is None or path == cookie.path:
clearables.append((cookie.domain, cookie.path, cookie.name))
if cookie.name != name:
continue
if domain is not None and domain != cookie.domain:
continue
if path is not None and path != cookie.path:
continue
clearables.append((cookie.domain, cookie.path, cookie.name))
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
@@ -365,7 +369,7 @@ def _copy_cookie_jar(jar):
return None
if hasattr(jar, 'copy'):
# We're dealing with an instane of RequestsCookieJar
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
new_jar = copy.copy(jar)
@@ -415,11 +419,15 @@ def morsel_to_cookie(morsel):
expires = None
if morsel['max-age']:
expires = time.time() + morsel['max-age']
try:
expires = int(time.time() + int(morsel['max-age']))
except ValueError:
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
elif morsel['expires']:
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
expires = time.mktime(
time.strptime(morsel['expires'], time_template)) - time.timezone
expires = calendar.timegm(
time.strptime(morsel['expires'], time_template)
)
return create_cookie(
comment=morsel['comment'],
comment_url=bool(morsel['comment']),

View File

@@ -97,3 +97,18 @@ class StreamConsumedError(RequestException, TypeError):
class RetryError(RequestException):
"""Custom retries logic failed"""
# Warnings
class RequestsWarning(Warning):
"""Base warning for Requests."""
pass
class FileModeWarning(RequestsWarning, DeprecationWarning):
"""
A file was opened in text mode, but Requests determined its binary length.
"""
pass

View File

@@ -12,34 +12,23 @@ Available hooks:
The response generated from a Request.
"""
HOOKS = ['response']
def default_hooks():
hooks = {}
for event in HOOKS:
hooks[event] = []
return hooks
return dict((event, []) for event in HOOKS)
# TODO: response is the only one
def dispatch_hook(key, hooks, hook_data, **kwargs):
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or dict()
if key in hooks:
hooks = hooks.get(key)
hooks = hooks.get(key)
if hooks:
if hasattr(hooks, '__call__'):
hooks = [hooks]
for hook in hooks:
_hook_data = hook(hook_data, **kwargs)
if _hook_data is not None:
hook_data = _hook_data
return hook_data

View File

@@ -30,7 +30,8 @@ from .utils import (
iter_slices, guess_json_utf, super_len, to_native_string)
from .compat import (
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
is_py2, chardet, json, builtin_str, basestring)
is_py2, chardet, builtin_str, basestring)
from .compat import json as complexjson
from .status_codes import codes
#: The set of HTTP status codes that indicate an automatically
@@ -42,12 +43,11 @@ REDIRECT_STATI = (
codes.temporary_redirect, # 307
codes.permanent_redirect, # 308
)
DEFAULT_REDIRECT_LIMIT = 30
CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
json_dumps = json.dumps
class RequestEncodingMixin(object):
@property
@@ -149,8 +149,7 @@ class RequestEncodingMixin(object):
else:
fdata = fp.read()
rf = RequestField(name=k, data=fdata,
filename=fn, headers=fh)
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
@@ -193,7 +192,7 @@ class Request(RequestHooksMixin):
:param headers: dictionary of headers to send.
:param files: dictionary of {filename: fileobject} files to multipart upload.
:param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place.
:param json: json for the body to attach to the request (if data is not specified).
:param json: json for the body to attach to the request (if files or data is not specified).
:param params: dictionary of URL parameters to append to the URL.
:param auth: Auth handler or (user, pass) tuple.
:param cookies: dictionary or CookieJar of cookies to attach to this request.
@@ -207,17 +206,8 @@ class Request(RequestHooksMixin):
<PreparedRequest [GET]>
"""
def __init__(self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None):
def __init__(self, method=None, url=None, headers=None, files=None,
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
# Default empty dicts for dict params.
data = [] if data is None else data
@@ -296,8 +286,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.hooks = default_hooks()
def prepare(self, method=None, url=None, headers=None, files=None,
data=None, params=None, auth=None, cookies=None, hooks=None,
json=None):
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
"""Prepares the entire request with the given parameters."""
self.prepare_method(method)
@@ -306,6 +295,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.prepare_cookies(cookies)
self.prepare_body(data, files, json)
self.prepare_auth(auth, url)
# Note that prepare_auth must be last to enable authentication schemes
# such as OAuth to work on a fully prepared request.
@@ -329,12 +319,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
"""Prepares the given HTTP method."""
self.method = method
if self.method is not None:
self.method = self.method.upper()
self.method = to_native_string(self.method.upper())
def prepare_url(self, url, params):
"""Prepares the given HTTP URL."""
#: Accept objects that have string representations.
#: We're unable to blindy call unicode/str functions
#: We're unable to blindly call unicode/str functions
#: as this will include the bytestring indicator (b'')
#: on python 3.x.
#: https://github.com/kennethreitz/requests/pull/2238
@@ -357,9 +347,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise InvalidURL(*e.args)
if not scheme:
raise MissingSchema("Invalid URL {0!r}: No schema supplied. "
"Perhaps you meant http://{0}?".format(
to_native_string(url, 'utf8')))
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
error = error.format(to_native_string(url, 'utf8'))
raise MissingSchema(error)
if not host:
raise InvalidURL("Invalid URL %r: No host supplied" % url)
@@ -394,6 +385,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if isinstance(fragment, str):
fragment = fragment.encode('utf-8')
if isinstance(params, (str, bytes)):
params = to_native_string(params)
enc_params = self._encode_params(params)
if enc_params:
if query:
@@ -423,9 +417,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
content_type = None
length = None
if json is not None:
if not data and json is not None:
content_type = 'application/json'
body = json_dumps(json)
body = complexjson.dumps(json)
is_stream = all([
hasattr(data, '__iter__'),
@@ -443,7 +437,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
if length is not None:
if length:
self.headers['Content-Length'] = builtin_str(length)
else:
self.headers['Transfer-Encoding'] = 'chunked'
@@ -452,7 +446,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if files:
(body, content_type) = self._encode_files(files, data)
else:
if data and json is None:
if data:
body = self._encode_params(data)
if isinstance(data, basestring) or hasattr(data, 'read'):
content_type = None
@@ -537,16 +531,8 @@ class Response(object):
"""
__attrs__ = [
'_content',
'status_code',
'headers',
'url',
'history',
'encoding',
'reason',
'cookies',
'elapsed',
'request',
'_content', 'status_code', 'headers', 'url', 'history',
'encoding', 'reason', 'cookies', 'elapsed', 'request'
]
def __init__(self):
@@ -648,7 +634,7 @@ class Response(object):
@property
def is_permanent_redirect(self):
"""True if this Response one of the permanant versions of redirect"""
"""True if this Response one of the permanent versions of redirect"""
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
@property
@@ -666,9 +652,10 @@ class Response(object):
If decode_unicode is True, content will be decoded using the best
available encoding based on the response.
"""
def generate():
try:
# Special case for urllib3.
# Special case for urllib3.
if hasattr(self.raw, 'stream'):
try:
for chunk in self.raw.stream(chunk_size, decode_content=True):
yield chunk
@@ -678,7 +665,7 @@ class Response(object):
raise ContentDecodingError(e)
except ReadTimeoutError as e:
raise ConnectionError(e)
except AttributeError:
else:
# Standard file-like object.
while True:
chunk = self.raw.read(chunk_size)
@@ -809,14 +796,16 @@ class Response(object):
encoding = guess_json_utf(self.content)
if encoding is not None:
try:
return json.loads(self.content.decode(encoding), **kwargs)
return complexjson.loads(
self.content.decode(encoding), **kwargs
)
except UnicodeDecodeError:
# Wrong UTF codec detected; usually because it's not UTF-8
# but some other 8-bit codec. This is an RFC violation,
# and the server didn't bother to tell us what codec *was*
# used.
pass
return json.loads(self.text, **kwargs)
return complexjson.loads(self.text, **kwargs)
@property
def links(self):
@@ -842,10 +831,10 @@ class Response(object):
http_error_msg = ''
if 400 <= self.status_code < 500:
http_error_msg = '%s Client Error: %s' % (self.status_code, self.reason)
http_error_msg = '%s Client Error: %s for url: %s' % (self.status_code, self.reason, self.url)
elif 500 <= self.status_code < 600:
http_error_msg = '%s Server Error: %s' % (self.status_code, self.reason)
http_error_msg = '%s Server Error: %s for url: %s' % (self.status_code, self.reason, self.url)
if http_error_msg:
raise HTTPError(http_error_msg, response=self)
@@ -856,4 +845,7 @@ class Response(object):
*Note: Should not normally need to be called explicitly.*
"""
if not self._content_consumed:
return self.raw.close()
return self.raw.release_conn()

View File

@@ -1,8 +1,11 @@
If you are planning to submit a pull request to requests with any changes in
this library do not go any further. These are independent libraries which we
vendor into requests. Any changes necessary to these libraries must be made in
this library do not go any further. These are independent libraries which we
vendor into requests. Any changes necessary to these libraries must be made in
them and submitted as separate pull requests to those libraries.
urllib3 pull requests go here: https://github.com/shazow/urllib3
chardet pull requests go here: https://github.com/chardet/chardet
See https://github.com/kennethreitz/requests/pull/1812#issuecomment-30854316
for the reasoning behind this.

View File

@@ -1,3 +1,36 @@
from __future__ import absolute_import
'''
Debian and other distributions "unbundle" requests' vendored dependencies, and
rewrite all imports to use the global versions of ``urllib3`` and ``chardet``.
The problem with this is that not only requests itself imports those
dependencies, but third-party code outside of the distros' control too.
from . import urllib3
In reaction to these problems, the distro maintainers replaced
``requests.packages`` with a magical "stub module" that imports the correct
modules. The implementations were varying in quality and all had severe
problems. For example, a symlink (or hardlink) that links the correct modules
into place introduces problems regarding object identity, since you now have
two modules in `sys.modules` with the same API, but different identities::
requests.packages.urllib3 is not urllib3
With version ``2.5.2``, requests started to maintain its own stub, so that
distro-specific breakage would be reduced to a minimum, even though the whole
issue is not requests' fault in the first place. See
https://github.com/kennethreitz/requests/pull/2375 for the corresponding pull
request.
'''
from __future__ import absolute_import
import sys
try:
from . import urllib3
except ImportError:
import urllib3
sys.modules['%s.urllib3' % __name__] = urllib3
try:
from . import chardet
except ImportError:
import chardet
sys.modules['%s.chardet' % __name__] = chardet

View File

@@ -2,10 +2,8 @@
urllib3 - Thread-safe connection pooling and re-using.
"""
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.10.4'
from __future__ import absolute_import
import warnings
from .connectionpool import (
HTTPConnectionPool,
@@ -32,8 +30,30 @@ except ImportError:
def emit(self, record):
pass
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.13.1'
__all__ = (
'HTTPConnectionPool',
'HTTPSConnectionPool',
'PoolManager',
'ProxyManager',
'HTTPResponse',
'Retry',
'Timeout',
'add_stderr_logger',
'connection_from_url',
'disable_warnings',
'encode_multipart_formdata',
'get_host',
'make_headers',
'proxy_from_url',
)
logging.getLogger(__name__).addHandler(NullHandler())
def add_stderr_logger(level=logging.DEBUG):
"""
Helper for quickly adding a StreamHandler to the logger. Useful for
@@ -55,12 +75,16 @@ def add_stderr_logger(level=logging.DEBUG):
del NullHandler
import warnings
# SecurityWarning's always go off by default.
warnings.simplefilter('always', exceptions.SecurityWarning, append=True)
# SubjectAltNameWarning's should go off once per host
warnings.simplefilter('default', exceptions.SubjectAltNameWarning)
# InsecurePlatformWarning's don't vary between requests, so we keep it default.
warnings.simplefilter('default', exceptions.InsecurePlatformWarning,
append=True)
# SNIMissingWarnings should go off only once.
warnings.simplefilter('default', exceptions.SNIMissingWarning)
def disable_warnings(category=exceptions.HTTPWarning):
"""

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
from collections import Mapping, MutableMapping
try:
from threading import RLock
@@ -97,14 +98,7 @@ class RecentlyUsedContainer(MutableMapping):
return list(iterkeys(self._container))
_dict_setitem = dict.__setitem__
_dict_getitem = dict.__getitem__
_dict_delitem = dict.__delitem__
_dict_contains = dict.__contains__
_dict_setdefault = dict.setdefault
class HTTPHeaderDict(dict):
class HTTPHeaderDict(MutableMapping):
"""
:param headers:
An iterable of field-value pairs. Must not contain multiple field names
@@ -139,7 +133,8 @@ class HTTPHeaderDict(dict):
"""
def __init__(self, headers=None, **kwargs):
dict.__init__(self)
super(HTTPHeaderDict, self).__init__()
self._container = {}
if headers is not None:
if isinstance(headers, HTTPHeaderDict):
self._copy_from(headers)
@@ -149,38 +144,44 @@ class HTTPHeaderDict(dict):
self.extend(kwargs)
def __setitem__(self, key, val):
return _dict_setitem(self, key.lower(), (key, val))
self._container[key.lower()] = (key, val)
return self._container[key.lower()]
def __getitem__(self, key):
val = _dict_getitem(self, key.lower())
val = self._container[key.lower()]
return ', '.join(val[1:])
def __delitem__(self, key):
return _dict_delitem(self, key.lower())
del self._container[key.lower()]
def __contains__(self, key):
return _dict_contains(self, key.lower())
return key.lower() in self._container
def __eq__(self, other):
if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
return False
if not isinstance(other, type(self)):
other = type(self)(other)
return dict((k1, self[k1]) for k1 in self) == dict((k2, other[k2]) for k2 in other)
return (dict((k.lower(), v) for k, v in self.itermerged()) ==
dict((k.lower(), v) for k, v in other.itermerged()))
def __ne__(self, other):
return not self.__eq__(other)
values = MutableMapping.values
get = MutableMapping.get
update = MutableMapping.update
if not PY3: # Python 2
if not PY3: # Python 2
iterkeys = MutableMapping.iterkeys
itervalues = MutableMapping.itervalues
__marker = object()
def __len__(self):
return len(self._container)
def __iter__(self):
# Only provide the originally cased names
for vals in self._container.values():
yield vals[0]
def pop(self, key, default=__marker):
'''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised.
@@ -216,7 +217,7 @@ class HTTPHeaderDict(dict):
key_lower = key.lower()
new_vals = key, val
# Keep the common case aka no item present as fast as possible
vals = _dict_setdefault(self, key_lower, new_vals)
vals = self._container.setdefault(key_lower, new_vals)
if new_vals is not vals:
# new_vals was not inserted, as there was a previous one
if isinstance(vals, list):
@@ -225,7 +226,7 @@ class HTTPHeaderDict(dict):
else:
# vals should be a tuple then, i.e. only one item so far
# Need to convert the tuple to list for further extension
_dict_setitem(self, key_lower, [vals[0], vals[1], val])
self._container[key_lower] = [vals[0], vals[1], val]
def extend(self, *args, **kwargs):
"""Generic import function for any type of header-like object.
@@ -234,9 +235,9 @@ class HTTPHeaderDict(dict):
"""
if len(args) > 1:
raise TypeError("extend() takes at most 1 positional "
"arguments ({} given)".format(len(args)))
"arguments ({0} given)".format(len(args)))
other = args[0] if len(args) >= 1 else ()
if isinstance(other, HTTPHeaderDict):
for key, val in other.iteritems():
self.add(key, val)
@@ -257,7 +258,7 @@ class HTTPHeaderDict(dict):
"""Returns a list of all the values for the named field. Returns an
empty list if the key doesn't exist."""
try:
vals = _dict_getitem(self, key.lower())
vals = self._container[key.lower()]
except KeyError:
return []
else:
@@ -276,11 +277,11 @@ class HTTPHeaderDict(dict):
def _copy_from(self, other):
for key in other:
val = _dict_getitem(other, key)
val = other.getlist(key)
if isinstance(val, list):
# Don't need to convert tuples
val = list(val)
_dict_setitem(self, key, val)
self._container[key.lower()] = [key] + val
def copy(self):
clone = type(self)()
@@ -290,33 +291,33 @@ class HTTPHeaderDict(dict):
def iteritems(self):
"""Iterate over all header lines, including duplicate ones."""
for key in self:
vals = _dict_getitem(self, key)
vals = self._container[key.lower()]
for val in vals[1:]:
yield vals[0], val
def itermerged(self):
"""Iterate over all headers, merging duplicate ones together."""
for key in self:
val = _dict_getitem(self, key)
val = self._container[key.lower()]
yield val[0], ', '.join(val[1:])
def items(self):
return list(self.iteritems())
@classmethod
def from_httplib(cls, message): # Python 2
def from_httplib(cls, message): # Python 2
"""Read headers from a Python 2 httplib message object."""
# python2.7 does not expose a proper API for exporting multiheaders
# efficiently. This function re-reads raw lines from the message
# efficiently. This function re-reads raw lines from the message
# object and extracts the multiheaders properly.
headers = []
for line in message.headers:
if line.startswith((' ', '\t')):
key, value = headers[-1]
headers[-1] = (key, value + '\r\n' + line.rstrip())
continue
key, value = line.split(':', 1)
headers.append((key, value.strip()))

View File

@@ -1,23 +1,20 @@
from __future__ import absolute_import
import datetime
import os
import sys
import socket
from socket import timeout as SocketTimeout
from socket import error as SocketError, timeout as SocketTimeout
import warnings
from .packages import six
try: # Python 3
from http.client import HTTPConnection as _HTTPConnection, HTTPException
from http.client import HTTPConnection as _HTTPConnection
from http.client import HTTPException # noqa: unused in this module
except ImportError:
from httplib import HTTPConnection as _HTTPConnection, HTTPException
class DummyConnection(object):
"Used to detect a failed ConnectionCls import."
pass
from httplib import HTTPConnection as _HTTPConnection
from httplib import HTTPException # noqa: unused in this module
try: # Compiled with SSL?
HTTPSConnection = DummyConnection
import ssl
BaseSSLError = ssl.SSLError
except (ImportError, AttributeError): # Platform-specific: No SSL.
@@ -36,9 +33,10 @@ except NameError: # Python 2:
from .exceptions import (
NewConnectionError,
ConnectTimeoutError,
SubjectAltNameWarning,
SystemTimeWarning,
SecurityWarning,
)
from .packages.ssl_match_hostname import match_hostname
@@ -60,6 +58,11 @@ port_by_scheme = {
RECENT_DATE = datetime.date(2014, 1, 1)
class DummyConnection(object):
"""Used to detect a failed ConnectionCls import."""
pass
class HTTPConnection(_HTTPConnection, object):
"""
Based on httplib.HTTPConnection but provides an extra constructor
@@ -133,11 +136,15 @@ class HTTPConnection(_HTTPConnection, object):
conn = connection.create_connection(
(self.host, self.port), self.timeout, **extra_kw)
except SocketTimeout:
except SocketTimeout as e:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, self.timeout))
except SocketError as e:
raise NewConnectionError(
self, "Failed to establish a new connection: %s" % e)
return conn
def _prepare_conn(self, conn):
@@ -185,19 +192,25 @@ class VerifiedHTTPSConnection(HTTPSConnection):
"""
cert_reqs = None
ca_certs = None
ca_cert_dir = None
ssl_version = None
assert_fingerprint = None
def set_cert(self, key_file=None, cert_file=None,
cert_reqs=None, ca_certs=None,
assert_hostname=None, assert_fingerprint=None):
assert_hostname=None, assert_fingerprint=None,
ca_cert_dir=None):
if (ca_certs or ca_cert_dir) and cert_reqs is None:
cert_reqs = 'CERT_REQUIRED'
self.key_file = key_file
self.cert_file = cert_file
self.cert_reqs = cert_reqs
self.ca_certs = ca_certs
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
def connect(self):
# Add certificate verification
@@ -234,6 +247,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file,
cert_reqs=resolved_cert_reqs,
ca_certs=self.ca_certs,
ca_cert_dir=self.ca_cert_dir,
server_hostname=hostname,
ssl_version=resolved_ssl_version)
@@ -245,15 +259,25 @@ class VerifiedHTTPSConnection(HTTPSConnection):
cert = self.sock.getpeercert()
if not cert.get('subjectAltName', ()):
warnings.warn((
'Certificate has no `subjectAltName`, falling back to check for a `commonName` for now. '
'This feature is being removed by major browsers and deprecated by RFC 2818. '
'(See https://github.com/shazow/urllib3/issues/497 for details.)'),
SecurityWarning
'Certificate for {0} has no `subjectAltName`, falling back to check for a '
'`commonName` for now. This feature is being removed by major browsers and '
'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 '
'for details.)'.format(hostname)),
SubjectAltNameWarning
)
match_hostname(cert, self.assert_hostname or hostname)
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED
or self.assert_fingerprint is not None)
# In case the hostname is an IPv6 address, strip the square
# brackets from it before using it to validate. This is because
# a certificate with an IPv6 address in it won't have square
# brackets around that address. Sadly, match_hostname won't do this
# for us: it expects the plain host part without any extra work
# that might have been done to make it palatable to httplib.
asserted_hostname = self.assert_hostname or hostname
asserted_hostname = asserted_hostname.strip('[]')
match_hostname(cert, asserted_hostname)
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or
self.assert_fingerprint is not None)
if ssl:

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
import errno
import logging
import sys
@@ -10,13 +11,15 @@ try: # Python 3
from queue import LifoQueue, Empty, Full
except ImportError:
from Queue import LifoQueue, Empty, Full
import Queue as _ # Platform-specific: Windows
# Queue is imported for side effects on MS Windows
import Queue as _unused_module_Queue # noqa: unused
from .exceptions import (
ClosedPoolError,
ProtocolError,
EmptyPoolError,
HeaderParsingError,
HostChangedError,
LocationValueError,
MaxRetryError,
@@ -25,6 +28,7 @@ from .exceptions import (
SSLError,
TimeoutError,
InsecureRequestWarning,
NewConnectionError,
)
from .packages.ssl_match_hostname import CertificateError
from .packages import six
@@ -32,15 +36,16 @@ from .connection import (
port_by_scheme,
DummyConnection,
HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
HTTPException, BaseSSLError, ConnectionError
HTTPException, BaseSSLError,
)
from .request import RequestMethods
from .response import HTTPResponse
from .util.connection import is_connection_dropped
from .util.response import assert_header_parsing
from .util.retry import Retry
from .util.timeout import Timeout
from .util.url import get_host
from .util.url import get_host, Url
xrange = six.moves.xrange
@@ -50,7 +55,7 @@ log = logging.getLogger(__name__)
_Default = object()
## Pool objects
# Pool objects
class ConnectionPool(object):
"""
Base class for all connection pools, such as
@@ -64,8 +69,7 @@ class ConnectionPool(object):
if not host:
raise LocationValueError("No host specified.")
# httplib doesn't like it when we include brackets in ipv6 addresses
self.host = host.strip('[]')
self.host = host
self.port = port
def __str__(self):
@@ -120,7 +124,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
:param maxsize:
Number of connections to save that can be reused. More than 1 is useful
in multithreaded situations. If ``block`` is set to false, more
in multithreaded situations. If ``block`` is set to False, more
connections will be created but they will not be saved once they've
been used.
@@ -381,8 +385,19 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
log.debug("\"%s %s %s\" %s %s" % (method, url, http_version,
httplib_response.status,
httplib_response.length))
try:
assert_header_parsing(httplib_response.msg)
except HeaderParsingError as hpe: # Platform-specific: Python 3
log.warning(
'Failed to parse headers (url=%s): %s',
self._absolute_url(url), hpe, exc_info=True)
return httplib_response
def _absolute_url(self, path):
return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url
def close(self):
"""
Close all pooled connections and disable the pool.
@@ -568,27 +583,24 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# Close the connection. If a connection is reused on which there
# was a Certificate error, the next request will certainly raise
# another Certificate error.
if conn:
conn.close()
conn = None
conn = conn and conn.close()
release_conn = True
raise SSLError(e)
except SSLError:
# Treat SSLError separately from BaseSSLError to preserve
# traceback.
if conn:
conn.close()
conn = None
conn = conn and conn.close()
release_conn = True
raise
except (TimeoutError, HTTPException, SocketError, ConnectionError) as e:
if conn:
# Discard the connection for these exceptions. It will be
# be replaced during the next _get_conn() call.
conn.close()
conn = None
except (TimeoutError, HTTPException, SocketError, ProtocolError) as e:
# Discard the connection for these exceptions. It will be
# be replaced during the next _get_conn() call.
conn = conn and conn.close()
release_conn = True
if isinstance(e, SocketError) and self.proxy:
if isinstance(e, (SocketError, NewConnectionError)) and self.proxy:
e = ProxyError('Cannot connect to proxy.', e)
elif isinstance(e, (SocketError, HTTPException)):
e = ProtocolError('Connection aborted.', e)
@@ -626,26 +638,31 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
retries = retries.increment(method, url, response=response, _pool=self)
except MaxRetryError:
if retries.raise_on_redirect:
# Release the connection for this response, since we're not
# returning it to be released manually.
response.release_conn()
raise
return response
log.info("Redirecting %s -> %s" % (url, redirect_location))
return self.urlopen(method, redirect_location, body, headers,
retries=retries, redirect=redirect,
assert_same_host=assert_same_host,
timeout=timeout, pool_timeout=pool_timeout,
release_conn=release_conn, **response_kw)
return self.urlopen(
method, redirect_location, body, headers,
retries=retries, redirect=redirect,
assert_same_host=assert_same_host,
timeout=timeout, pool_timeout=pool_timeout,
release_conn=release_conn, **response_kw)
# Check if we should retry the HTTP response.
if retries.is_forced_retry(method, status_code=response.status):
retries = retries.increment(method, url, response=response, _pool=self)
retries.sleep()
log.info("Forced retry: %s" % url)
return self.urlopen(method, url, body, headers,
retries=retries, redirect=redirect,
assert_same_host=assert_same_host,
timeout=timeout, pool_timeout=pool_timeout,
release_conn=release_conn, **response_kw)
return self.urlopen(
method, url, body, headers,
retries=retries, redirect=redirect,
assert_same_host=assert_same_host,
timeout=timeout, pool_timeout=pool_timeout,
release_conn=release_conn, **response_kw)
return response
@@ -662,10 +679,10 @@ class HTTPSConnectionPool(HTTPConnectionPool):
``assert_hostname`` and ``host`` in this order to verify connections.
If ``assert_hostname`` is False, no verification is done.
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs`` and
``ssl_version`` are only used if :mod:`ssl` is available and are fed into
:meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket
into an SSL socket.
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``,
``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is
available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade
the connection socket into an SSL socket.
"""
scheme = 'https'
@@ -678,15 +695,20 @@ class HTTPSConnectionPool(HTTPConnectionPool):
key_file=None, cert_file=None, cert_reqs=None,
ca_certs=None, ssl_version=None,
assert_hostname=None, assert_fingerprint=None,
**conn_kw):
ca_cert_dir=None, **conn_kw):
HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize,
block, headers, retries, _proxy, _proxy_headers,
**conn_kw)
if ca_certs and cert_reqs is None:
cert_reqs = 'CERT_REQUIRED'
self.key_file = key_file
self.cert_file = cert_file
self.cert_reqs = cert_reqs
self.ca_certs = ca_certs
self.ca_cert_dir = ca_cert_dir
self.ssl_version = ssl_version
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
@@ -702,6 +724,7 @@ class HTTPSConnectionPool(HTTPConnectionPool):
cert_file=self.cert_file,
cert_reqs=self.cert_reqs,
ca_certs=self.ca_certs,
ca_cert_dir=self.ca_cert_dir,
assert_hostname=self.assert_hostname,
assert_fingerprint=self.assert_fingerprint)
conn.ssl_version = self.ssl_version

View File

@@ -0,0 +1,223 @@
from __future__ import absolute_import
import logging
import os
import warnings
from ..exceptions import (
HTTPError,
HTTPWarning,
MaxRetryError,
ProtocolError,
TimeoutError,
SSLError
)
from ..packages.six import BytesIO
from ..request import RequestMethods
from ..response import HTTPResponse
from ..util.timeout import Timeout
from ..util.retry import Retry
try:
from google.appengine.api import urlfetch
except ImportError:
urlfetch = None
log = logging.getLogger(__name__)
class AppEnginePlatformWarning(HTTPWarning):
pass
class AppEnginePlatformError(HTTPError):
pass
class AppEngineManager(RequestMethods):
"""
Connection manager for Google App Engine sandbox applications.
This manager uses the URLFetch service directly instead of using the
emulated httplib, and is subject to URLFetch limitations as described in
the App Engine documentation here:
https://cloud.google.com/appengine/docs/python/urlfetch
Notably it will raise an AppEnginePlatformError if:
* URLFetch is not available.
* If you attempt to use this on GAEv2 (Managed VMs), as full socket
support is available.
* If a request size is more than 10 megabytes.
* If a response size is more than 32 megabtyes.
* If you use an unsupported request method such as OPTIONS.
Beyond those cases, it will raise normal urllib3 errors.
"""
def __init__(self, headers=None, retries=None, validate_certificate=True):
if not urlfetch:
raise AppEnginePlatformError(
"URLFetch is not available in this environment.")
if is_prod_appengine_mvms():
raise AppEnginePlatformError(
"Use normal urllib3.PoolManager instead of AppEngineManager"
"on Managed VMs, as using URLFetch is not necessary in "
"this environment.")
warnings.warn(
"urllib3 is using URLFetch on Google App Engine sandbox instead "
"of sockets. To use sockets directly instead of URLFetch see "
"https://urllib3.readthedocs.org/en/latest/contrib.html.",
AppEnginePlatformWarning)
RequestMethods.__init__(self, headers)
self.validate_certificate = validate_certificate
self.retries = retries or Retry.DEFAULT
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# Return False to re-raise any potential exceptions
return False
def urlopen(self, method, url, body=None, headers=None,
retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT,
**response_kw):
retries = self._get_retries(retries, redirect)
try:
response = urlfetch.fetch(
url,
payload=body,
method=method,
headers=headers or {},
allow_truncated=False,
follow_redirects=(
redirect and
retries.redirect != 0 and
retries.total),
deadline=self._get_absolute_timeout(timeout),
validate_certificate=self.validate_certificate,
)
except urlfetch.DeadlineExceededError as e:
raise TimeoutError(self, e)
except urlfetch.InvalidURLError as e:
if 'too large' in str(e):
raise AppEnginePlatformError(
"URLFetch request too large, URLFetch only "
"supports requests up to 10mb in size.", e)
raise ProtocolError(e)
except urlfetch.DownloadError as e:
if 'Too many redirects' in str(e):
raise MaxRetryError(self, url, reason=e)
raise ProtocolError(e)
except urlfetch.ResponseTooLargeError as e:
raise AppEnginePlatformError(
"URLFetch response too large, URLFetch only supports"
"responses up to 32mb in size.", e)
except urlfetch.SSLCertificateError as e:
raise SSLError(e)
except urlfetch.InvalidMethodError as e:
raise AppEnginePlatformError(
"URLFetch does not support method: %s" % method, e)
http_response = self._urlfetch_response_to_http_response(
response, **response_kw)
# Check for redirect response
if (http_response.get_redirect_location() and
retries.raise_on_redirect and redirect):
raise MaxRetryError(self, url, "too many redirects")
# Check if we should retry the HTTP response.
if retries.is_forced_retry(method, status_code=http_response.status):
retries = retries.increment(
method, url, response=http_response, _pool=self)
log.info("Forced retry: %s" % url)
retries.sleep()
return self.urlopen(
method, url,
body=body, headers=headers,
retries=retries, redirect=redirect,
timeout=timeout, **response_kw)
return http_response
def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):
if is_prod_appengine():
# Production GAE handles deflate encoding automatically, but does
# not remove the encoding header.
content_encoding = urlfetch_resp.headers.get('content-encoding')
if content_encoding == 'deflate':
del urlfetch_resp.headers['content-encoding']
return HTTPResponse(
# In order for decoding to work, we must present the content as
# a file-like object.
body=BytesIO(urlfetch_resp.content),
headers=urlfetch_resp.headers,
status=urlfetch_resp.status_code,
**response_kw
)
def _get_absolute_timeout(self, timeout):
if timeout is Timeout.DEFAULT_TIMEOUT:
return 5 # 5s is the default timeout for URLFetch.
if isinstance(timeout, Timeout):
if timeout.read is not timeout.connect:
warnings.warn(
"URLFetch does not support granular timeout settings, "
"reverting to total timeout.", AppEnginePlatformWarning)
return timeout.total
return timeout
def _get_retries(self, retries, redirect):
if not isinstance(retries, Retry):
retries = Retry.from_int(
retries, redirect=redirect, default=self.retries)
if retries.connect or retries.read or retries.redirect:
warnings.warn(
"URLFetch only supports total retries and does not "
"recognize connect, read, or redirect retry parameters.",
AppEnginePlatformWarning)
return retries
def is_appengine():
return (is_local_appengine() or
is_prod_appengine() or
is_prod_appengine_mvms())
def is_appengine_sandbox():
return is_appengine() and not is_prod_appengine_mvms()
def is_local_appengine():
return ('APPENGINE_RUNTIME' in os.environ and
'Development/' in os.environ['SERVER_SOFTWARE'])
def is_prod_appengine():
return ('APPENGINE_RUNTIME' in os.environ and
'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and
not is_prod_appengine_mvms())
def is_prod_appengine_mvms():
return os.environ.get('GAE_VM', False) == 'true'

View File

@@ -3,6 +3,7 @@ NTLM authenticating pool, contributed by erikcederstran
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
"""
from __future__ import absolute_import
try:
from http.client import HTTPSConnection

View File

@@ -43,6 +43,7 @@ Module Variables
.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
'''
from __future__ import absolute_import
try:
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
@@ -53,7 +54,7 @@ except SyntaxError as e:
import OpenSSL.SSL
from pyasn1.codec.der import decoder as der_decoder
from pyasn1.type import univ, constraint
from socket import _fileobject, timeout
from socket import _fileobject, timeout, error as SocketError
import ssl
import select
@@ -71,6 +72,12 @@ _openssl_versions = {
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
}
if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
_openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
_openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
try:
_openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
except AttributeError:
@@ -79,12 +86,14 @@ except AttributeError:
_openssl_verify = {
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
+ OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
ssl.CERT_REQUIRED:
OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
}
DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS
# OpenSSL will only write 16K at a time
SSL_WRITE_BLOCKSIZE = 16384
orig_util_HAS_SNI = util.HAS_SNI
orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
@@ -104,7 +113,7 @@ def extract_from_urllib3():
util.HAS_SNI = orig_util_HAS_SNI
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
class SubjectAltName(BaseSubjectAltName):
'''ASN.1 implementation for subjectAltNames support'''
@@ -115,7 +124,7 @@ class SubjectAltName(BaseSubjectAltName):
constraint.ValueSizeConstraint(1, 1024)
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
def get_subj_alt_name(peer_cert):
# Search through extensions
dns_name = []
@@ -173,7 +182,7 @@ class WrappedSocket(object):
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
return b''
else:
raise
raise SocketError(e)
except OpenSSL.SSL.ZeroReturnError as e:
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
return b''
@@ -204,13 +213,21 @@ class WrappedSocket(object):
continue
def sendall(self, data):
while len(data):
sent = self._send_until_done(data)
data = data[sent:]
total_sent = 0
while total_sent < len(data):
sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
total_sent += sent
def shutdown(self):
# FIXME rethrow compatible exceptions should we ever use this
self.connection.shutdown()
def close(self):
if self._makefile_refs < 1:
return self.connection.shutdown()
try:
return self.connection.close()
except OpenSSL.SSL.Error:
return
else:
self._makefile_refs -= 1
@@ -251,7 +268,7 @@ def _verify_callback(cnx, x509, err_no, err_depth, return_code):
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ca_certs=None, server_hostname=None,
ssl_version=None):
ssl_version=None, ca_cert_dir=None):
ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
if certfile:
keyfile = keyfile or certfile # Match behaviour of the normal python ssl library
@@ -260,9 +277,9 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ctx.use_privatekey_file(keyfile)
if cert_reqs != ssl.CERT_NONE:
ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
if ca_certs:
if ca_certs or ca_cert_dir:
try:
ctx.load_verify_locations(ca_certs, None)
ctx.load_verify_locations(ca_certs, ca_cert_dir)
except OpenSSL.SSL.Error as e:
raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
else:
@@ -287,7 +304,7 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
raise timeout('select timed out')
continue
except OpenSSL.SSL.Error as e:
raise ssl.SSLError('bad handshake', e)
raise ssl.SSLError('bad handshake: %r' % e)
break
return WrappedSocket(cnx, sock)

View File

@@ -1,16 +1,17 @@
from __future__ import absolute_import
# Base Exceptions
## Base Exceptions
class HTTPError(Exception):
"Base exception used by this module."
pass
class HTTPWarning(Warning):
"Base warning used by this module."
pass
class PoolError(HTTPError):
"Base exception for errors caused within a pool."
def __init__(self, pool, message):
@@ -57,7 +58,7 @@ class ProtocolError(HTTPError):
ConnectionError = ProtocolError
## Leaf Exceptions
# Leaf Exceptions
class MaxRetryError(RequestError):
"""Raised when the maximum number of retries is exceeded.
@@ -113,6 +114,11 @@ class ConnectTimeoutError(TimeoutError):
pass
class NewConnectionError(ConnectTimeoutError, PoolError):
"Raised when we fail to establish a new connection. Usually ECONNREFUSED."
pass
class EmptyPoolError(PoolError):
"Raised when a pool runs out of connections and no more are allowed."
pass
@@ -149,6 +155,11 @@ class SecurityWarning(HTTPWarning):
pass
class SubjectAltNameWarning(SecurityWarning):
"Warned when connecting to a host with a certificate missing a SAN."
pass
class InsecureRequestWarning(SecurityWarning):
"Warned when making an unverified HTTPS request."
pass
@@ -164,6 +175,27 @@ class InsecurePlatformWarning(SecurityWarning):
pass
class SNIMissingWarning(HTTPWarning):
"Warned when making a HTTPS request without SNI available."
pass
class ResponseNotChunked(ProtocolError, ValueError):
"Response needs to be chunked in order to read it as chunks."
pass
class ProxySchemeUnknown(AssertionError, ValueError):
"ProxyManager does not support the supplied scheme"
# TODO(t-8ch): Stop inheriting from AssertionError in v2.0.
def __init__(self, scheme):
message = "Not supported proxy scheme %s" % scheme
super(ProxySchemeUnknown, self).__init__(message)
class HeaderParsingError(HTTPError):
"Raised by assert_header_parsing, but we convert it to a log.warning statement."
def __init__(self, defects, unparsed_data):
message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data)
super(HeaderParsingError, self).__init__(message)

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
import email.utils
import mimetypes

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
import codecs
from uuid import uuid4

View File

@@ -2,3 +2,4 @@ from __future__ import absolute_import
from . import ssl_match_hostname
__all__ = ('ssl_match_hostname', )

View File

@@ -0,0 +1 @@
env

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
import logging
try: # Python 3
@@ -8,7 +9,7 @@ except ImportError:
from ._collections import RecentlyUsedContainer
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from .connectionpool import port_by_scheme
from .exceptions import LocationValueError, MaxRetryError
from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown
from .request import RequestMethods
from .util.url import parse_url
from .util.retry import Retry
@@ -25,7 +26,7 @@ pool_classes_by_scheme = {
log = logging.getLogger(__name__)
SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs',
'ssl_version')
'ssl_version', 'ca_cert_dir')
class PoolManager(RequestMethods):
@@ -227,8 +228,8 @@ class ProxyManager(PoolManager):
port = port_by_scheme.get(proxy.scheme, 80)
proxy = proxy._replace(port=port)
assert proxy.scheme in ("http", "https"), \
'Not supported proxy scheme %s' % proxy.scheme
if proxy.scheme not in ("http", "https"):
raise ProxySchemeUnknown(proxy.scheme)
self.proxy = proxy
self.proxy_headers = proxy_headers or {}

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
try:
from urllib.parse import urlencode
except ImportError:
@@ -71,14 +72,22 @@ class RequestMethods(object):
headers=headers,
**urlopen_kw)
def request_encode_url(self, method, url, fields=None, **urlopen_kw):
def request_encode_url(self, method, url, fields=None, headers=None,
**urlopen_kw):
"""
Make a request using :meth:`urlopen` with the ``fields`` encoded in
the url. This is useful for request methods like GET, HEAD, DELETE, etc.
"""
if headers is None:
headers = self.headers
extra_kw = {'headers': headers}
extra_kw.update(urlopen_kw)
if fields:
url += '?' + urlencode(fields)
return self.urlopen(method, url, **urlopen_kw)
return self.urlopen(method, url, **extra_kw)
def request_encode_body(self, method, url, fields=None, headers=None,
encode_multipart=True, multipart_boundary=None,
@@ -125,7 +134,8 @@ class RequestMethods(object):
if fields:
if 'body' in urlopen_kw:
raise TypeError('request got values for both \'fields\' and \'body\', can only specify one.')
raise TypeError(
"request got values for both 'fields' and 'body', can only specify one.")
if encode_multipart:
body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)

View File

@@ -1,18 +1,18 @@
try:
import http.client as httplib
except ImportError:
import httplib
from __future__ import absolute_import
from contextlib import contextmanager
import zlib
import io
from socket import timeout as SocketTimeout
from socket import error as SocketError
from ._collections import HTTPHeaderDict
from .exceptions import (
ProtocolError, DecodeError, ReadTimeoutError, ResponseNotChunked
)
from .packages.six import string_types as basestring, binary_type, PY3
from .packages.six.moves import http_client as httplib
from .connection import HTTPException, BaseSSLError
from .util.response import is_fp_closed
from .util.response import is_fp_closed, is_response_to_head
class DeflateDecoder(object):
@@ -132,8 +132,8 @@ class HTTPResponse(io.IOBase):
if "chunked" in encodings:
self.chunked = True
# We certainly don't want to preload content when the response is chunked.
if not self.chunked and preload_content and not self._body:
# If requested, preload the body.
if preload_content and not self._body:
self._body = self.read(decode_content=decode_content)
def get_redirect_location(self):
@@ -196,12 +196,70 @@ class HTTPResponse(io.IOBase):
"Received response with content-encoding: %s, but "
"failed to decode it." % content_encoding, e)
if flush_decoder and decode_content and self._decoder:
buf = self._decoder.decompress(binary_type())
data += buf + self._decoder.flush()
if flush_decoder and decode_content:
data += self._flush_decoder()
return data
def _flush_decoder(self):
"""
Flushes the decoder. Should only be called if the decoder is actually
being used.
"""
if self._decoder:
buf = self._decoder.decompress(b'')
return buf + self._decoder.flush()
return b''
@contextmanager
def _error_catcher(self):
"""
Catch low-level python exceptions, instead re-raising urllib3
variants, so that low-level exceptions are not leaked in the
high-level api.
On exit, release the connection back to the pool.
"""
try:
try:
yield
except SocketTimeout:
# FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
# there is yet no clean way to get at it from this context.
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
except BaseSSLError as e:
# FIXME: Is there a better way to differentiate between SSLErrors?
if 'read operation timed out' not in str(e): # Defensive:
# This shouldn't happen but just in case we're missing an edge
# case, let's avoid swallowing SSL errors.
raise
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
except (HTTPException, SocketError) as e:
# This includes IncompleteRead.
raise ProtocolError('Connection broken: %r' % e, e)
except Exception:
# The response may not be closed but we're not going to use it anymore
# so close it now to ensure that the connection is released back to the pool.
if self._original_response and not self._original_response.isclosed():
self._original_response.close()
# Closing the response may not actually be sufficient to close
# everything, so if we have a hold of the connection close that
# too.
if self._connection is not None:
self._connection.close()
raise
finally:
if self._original_response and self._original_response.isclosed():
self.release_conn()
def read(self, amt=None, decode_content=None, cache_content=False):
"""
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
@@ -231,45 +289,28 @@ class HTTPResponse(io.IOBase):
return
flush_decoder = False
data = None
try:
try:
if amt is None:
# cStringIO doesn't like amt=None
data = self._fp.read()
with self._error_catcher():
if amt is None:
# cStringIO doesn't like amt=None
data = self._fp.read()
flush_decoder = True
else:
cache_content = False
data = self._fp.read(amt)
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
# Close the connection when no data is returned
#
# This is redundant to what httplib/http.client _should_
# already do. However, versions of python released before
# December 15, 2012 (http://bugs.python.org/issue16298) do
# not properly close the connection in all cases. There is
# no harm in redundantly calling close.
self._fp.close()
flush_decoder = True
else:
cache_content = False
data = self._fp.read(amt)
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
# Close the connection when no data is returned
#
# This is redundant to what httplib/http.client _should_
# already do. However, versions of python released before
# December 15, 2012 (http://bugs.python.org/issue16298) do
# not properly close the connection in all cases. There is
# no harm in redundantly calling close.
self._fp.close()
flush_decoder = True
except SocketTimeout:
# FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
# there is yet no clean way to get at it from this context.
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
except BaseSSLError as e:
# FIXME: Is there a better way to differentiate between SSLErrors?
if 'read operation timed out' not in str(e): # Defensive:
# This shouldn't happen but just in case we're missing an edge
# case, let's avoid swallowing SSL errors.
raise
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
except HTTPException as e:
# This includes IncompleteRead.
raise ProtocolError('Connection broken: %r' % e, e)
if data:
self._fp_bytes_read += len(data)
data = self._decode(data, decode_content, flush_decoder)
@@ -277,11 +318,7 @@ class HTTPResponse(io.IOBase):
if cache_content:
self._body = data
return data
finally:
if self._original_response and self._original_response.isclosed():
self.release_conn()
return data
def stream(self, amt=2**16, decode_content=None):
"""
@@ -319,10 +356,11 @@ class HTTPResponse(io.IOBase):
with ``original_response=r``.
"""
headers = r.msg
if not isinstance(headers, HTTPHeaderDict):
if PY3: # Python 3
if PY3: # Python 3
headers = HTTPHeaderDict(headers.items())
else: # Python 2
else: # Python 2
headers = HTTPHeaderDict.from_httplib(headers)
# HTTPResponse objects in Python 3 don't have a .strict attribute
@@ -434,33 +472,43 @@ class HTTPResponse(io.IOBase):
self._init_decoder()
# FIXME: Rewrite this method and make it a class with a better structured logic.
if not self.chunked:
raise ResponseNotChunked("Response is not chunked. "
raise ResponseNotChunked(
"Response is not chunked. "
"Header 'transfer-encoding: chunked' is missing.")
if self._original_response and self._original_response._method.upper() == 'HEAD':
# Don't bother reading the body of a HEAD request.
# FIXME: Can we do this somehow without accessing private httplib _method?
# Don't bother reading the body of a HEAD request.
if self._original_response and is_response_to_head(self._original_response):
self._original_response.close()
return
while True:
self._update_chunk_length()
if self.chunk_left == 0:
break
chunk = self._handle_chunk(amt)
yield self._decode(chunk, decode_content=decode_content,
flush_decoder=True)
with self._error_catcher():
while True:
self._update_chunk_length()
if self.chunk_left == 0:
break
chunk = self._handle_chunk(amt)
decoded = self._decode(chunk, decode_content=decode_content,
flush_decoder=False)
if decoded:
yield decoded
# Chunk content ends with \r\n: discard it.
while True:
line = self._fp.fp.readline()
if not line:
# Some sites may not end with '\r\n'.
break
if line == b'\r\n':
break
if decode_content:
# On CPython and PyPy, we should never need to flush the
# decoder. However, on Jython we *might* need to, so
# lets defensively do it anyway.
decoded = self._flush_decoder()
if decoded: # Platform-specific: Jython.
yield decoded
# We read everything; close the "file".
if self._original_response:
self._original_response.close()
self.release_conn()
# Chunk content ends with \r\n: discard it.
while True:
line = self._fp.fp.readline()
if not line:
# Some sites may not end with '\r\n'.
break
if line == b'\r\n':
break
# We read everything; close the "file".
if self._original_response:
self._original_response.close()

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
# For backwards compatibility, provide imports that used to be here.
from .connection import is_connection_dropped
from .request import make_headers
@@ -22,3 +23,22 @@ from .url import (
split_first,
Url,
)
__all__ = (
'HAS_SNI',
'SSLContext',
'Retry',
'Timeout',
'Url',
'assert_fingerprint',
'current_time',
'is_connection_dropped',
'is_fp_closed',
'get_host',
'parse_url',
'make_headers',
'resolve_cert_reqs',
'resolve_ssl_version',
'split_first',
'ssl_wrap_socket',
)

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
import socket
try:
from select import poll, POLLIN
@@ -60,6 +61,8 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
"""
host, port = address
if host.startswith('['):
host = host.strip('[]')
err = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
@@ -78,16 +81,16 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
sock.connect(sa)
return sock
except socket.error as _:
err = _
except socket.error as e:
err = e
if sock is not None:
sock.close()
sock = None
if err is not None:
raise err
else:
raise socket.error("getaddrinfo returns an empty list")
raise socket.error("getaddrinfo returns an empty list")
def _set_socket_options(sock, options):

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
from base64 import b64encode
from ..packages.six import b

View File

@@ -1,3 +1,9 @@
from __future__ import absolute_import
from ..packages.six.moves import http_client as httplib
from ..exceptions import HeaderParsingError
def is_fp_closed(obj):
"""
Checks whether a given file-like object is closed.
@@ -20,3 +26,49 @@ def is_fp_closed(obj):
pass
raise ValueError("Unable to determine whether fp is closed.")
def assert_header_parsing(headers):
"""
Asserts whether all headers have been successfully parsed.
Extracts encountered errors from the result of parsing headers.
Only works on Python 3.
:param headers: Headers to verify.
:type headers: `httplib.HTTPMessage`.
:raises urllib3.exceptions.HeaderParsingError:
If parsing errors are found.
"""
# This will fail silently if we pass in the wrong kind of parameter.
# To make debugging easier add an explicit check.
if not isinstance(headers, httplib.HTTPMessage):
raise TypeError('expected httplib.Message, got {0}.'.format(
type(headers)))
defects = getattr(headers, 'defects', None)
get_payload = getattr(headers, 'get_payload', None)
unparsed_data = None
if get_payload: # Platform-specific: Python 3.
unparsed_data = get_payload()
if defects or unparsed_data:
raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data)
def is_response_to_head(response):
"""
Checks, wether a the request of a response has been a HEAD-request.
Handles the quirks of AppEngine.
:param conn:
:type conn: :class:`httplib.HTTPResponse`
"""
# FIXME: Can we do this somehow without accessing private httplib _method?
method = response._method
if isinstance(method, int): # Platform-specific: Appengine
return method == 3
return method.upper() == 'HEAD'

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
import time
import logging
@@ -94,7 +95,7 @@ class Retry(object):
seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer
than :attr:`Retry.MAX_BACKOFF`.
than :attr:`Retry.BACKOFF_MAX`.
By default, backoff is disabled (set to 0).
@@ -126,7 +127,7 @@ class Retry(object):
self.method_whitelist = method_whitelist
self.backoff_factor = backoff_factor
self.raise_on_redirect = raise_on_redirect
self._observed_errors = _observed_errors # TODO: use .history instead?
self._observed_errors = _observed_errors # TODO: use .history instead?
def new(self, **kw):
params = dict(
@@ -206,7 +207,8 @@ class Retry(object):
return min(retry_counts) < 0
def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None):
def increment(self, method=None, url=None, response=None, error=None,
_pool=None, _stacktrace=None):
""" Return a new Retry object with incremented retry counters.
:param response: A response object, or None, if the server did not
@@ -274,7 +276,6 @@ class Retry(object):
return new_retry
def __repr__(self):
return ('{cls.__name__}(total={self.total}, connect={self.connect}, '
'read={self.read}, redirect={self.redirect})').format(

View File

@@ -1,15 +1,42 @@
from __future__ import absolute_import
import errno
import warnings
import hmac
from binascii import hexlify, unhexlify
from hashlib import md5, sha1, sha256
from ..exceptions import SSLError, InsecurePlatformWarning
from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
SSLContext = None
HAS_SNI = False
create_default_context = None
import errno
import warnings
# Maps the length of a digest to a possible hash function producing this digest
HASHFUNC_MAP = {
32: md5,
40: sha1,
64: sha256,
}
def _const_compare_digest_backport(a, b):
"""
Compare two digests of equal length in constant time.
The digests must be of type str/bytes.
Returns True if the digests match, and False otherwise.
"""
result = abs(len(a) - len(b))
for l, r in zip(bytearray(a), bytearray(b)):
result |= l ^ r
return result == 0
_const_compare_digest = getattr(hmac, 'compare_digest',
_const_compare_digest_backport)
try: # Test for SSL features
import ssl
@@ -68,8 +95,11 @@ except ImportError:
self.certfile = certfile
self.keyfile = keyfile
def load_verify_locations(self, location):
self.ca_certs = location
def load_verify_locations(self, cafile=None, capath=None):
self.ca_certs = cafile
if capath is not None:
raise SSLError("CA directories not supported in older Pythons")
def set_ciphers(self, cipher_suite):
if not self.supports_set_ciphers:
@@ -112,31 +142,21 @@ def assert_fingerprint(cert, fingerprint):
Fingerprint as string of hexdigits, can be interspersed by colons.
"""
# Maps the length of a digest to a possible hash function producing
# this digest.
hashfunc_map = {
16: md5,
20: sha1,
32: sha256,
}
fingerprint = fingerprint.replace(':', '').lower()
digest_length, odd = divmod(len(fingerprint), 2)
if odd or digest_length not in hashfunc_map:
raise SSLError('Fingerprint is of invalid length.')
digest_length = len(fingerprint)
hashfunc = HASHFUNC_MAP.get(digest_length)
if not hashfunc:
raise SSLError(
'Fingerprint of invalid length: {0}'.format(fingerprint))
# We need encode() here for py32; works on py2 and p33.
fingerprint_bytes = unhexlify(fingerprint.encode())
hashfunc = hashfunc_map[digest_length]
cert_digest = hashfunc(cert).digest()
if not cert_digest == fingerprint_bytes:
if not _const_compare_digest(cert_digest, fingerprint_bytes):
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
.format(hexlify(fingerprint_bytes),
hexlify(cert_digest)))
.format(fingerprint, hexlify(cert_digest)))
def resolve_cert_reqs(candidate):
@@ -243,10 +263,11 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None,
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ca_certs=None, server_hostname=None,
ssl_version=None, ciphers=None, ssl_context=None):
ssl_version=None, ciphers=None, ssl_context=None,
ca_cert_dir=None):
"""
All arguments except for server_hostname and ssl_context have the same
meaning as they do when using :func:`ssl.wrap_socket`.
All arguments except for server_hostname, ssl_context, and ca_cert_dir have
the same meaning as they do when using :func:`ssl.wrap_socket`.
:param server_hostname:
When SNI is supported, the expected hostname of the certificate
@@ -256,15 +277,19 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
:param ciphers:
A string of ciphers we wish the client to support. This is not
supported on Python 2.6 as the ssl module does not support it.
:param ca_cert_dir:
A directory containing CA certificates in multiple separate files, as
supported by OpenSSL's -CApath flag or the capath argument to
SSLContext.load_verify_locations().
"""
context = ssl_context
if context is None:
context = create_urllib3_context(ssl_version, cert_reqs,
ciphers=ciphers)
if ca_certs:
if ca_certs or ca_cert_dir:
try:
context.load_verify_locations(ca_certs)
context.load_verify_locations(ca_certs, ca_cert_dir)
except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2
raise SSLError(e)
# Py33 raises FileNotFoundError which subclasses OSError
@@ -273,8 +298,20 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
if e.errno == errno.ENOENT:
raise SSLError(e)
raise
if certfile:
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
return context.wrap_socket(sock, server_hostname=server_hostname)
warnings.warn(
'An HTTPS request has been made, but the SNI (Subject Name '
'Indication) extension to TLS is not available on this platform. '
'This may cause the server to present an incorrect TLS '
'certificate, which can cause validation failures. For more '
'information, see '
'https://urllib3.readthedocs.org/en/latest/security.html'
'#snimissingwarning.',
SNIMissingWarning
)
return context.wrap_socket(sock)

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
# The default socket timeout, used by httplib to indicate that no timeout was
# specified by the user
from socket import _GLOBAL_DEFAULT_TIMEOUT
@@ -9,6 +10,7 @@ from ..exceptions import TimeoutStateError
# urllib3
_Default = object()
def current_time():
"""
Retrieve the current time. This function is mocked out in unit testing.
@@ -226,9 +228,9 @@ class Timeout(object):
has not yet been called on this object.
"""
if (self.total is not None and
self.total is not self.DEFAULT_TIMEOUT and
self._read is not None and
self._read is not self.DEFAULT_TIMEOUT):
self.total is not self.DEFAULT_TIMEOUT and
self._read is not None and
self._read is not self.DEFAULT_TIMEOUT):
# In case the connect timeout has not yet been established.
if self._start_connect is None:
return self._read

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import
from collections import namedtuple
from ..exceptions import LocationParseError
@@ -85,6 +86,7 @@ class Url(namedtuple('Url', url_attrs)):
def __str__(self):
return self.url
def split_first(s, delims):
"""
Given a string and an iterable of delimiters, split on the first found
@@ -115,7 +117,7 @@ def split_first(s, delims):
if min_idx is None or min_idx < 0:
return s, '', None
return s[:min_idx], s[min_idx+1:], min_delim
return s[:min_idx], s[min_idx + 1:], min_delim
def parse_url(url):
@@ -206,6 +208,7 @@ def parse_url(url):
return Url(scheme, auth, host, port, path, query, fragment)
def get_host(url):
"""
Deprecated. Use :func:`.parse_url` instead.

View File

@@ -62,12 +62,11 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
# Remove keys that are set to None.
for (k, v) in request_setting.items():
if v is None:
del merged_setting[k]
merged_setting = dict((k, v) for (k, v) in merged_setting.items() if v is not None)
# Remove keys that are set to None. Extract keys first to avoid altering
# the dictionary during iteration.
none_keys = [k for (k, v) in merged_setting.items() if v is None]
for key in none_keys:
del merged_setting[key]
return merged_setting
@@ -274,7 +273,13 @@ class Session(SessionRedirectMixin):
>>> import requests
>>> s = requests.Session()
>>> s.get('http://httpbin.org/get')
200
<Response [200]>
Or as a context manager::
>>> with requests.Session() as s:
>>> s.get('http://httpbin.org/get')
<Response [200]>
"""
__attrs__ = [
@@ -294,9 +299,9 @@ class Session(SessionRedirectMixin):
#: :class:`Request <Request>`.
self.auth = None
#: Dictionary mapping protocol to the URL of the proxy (e.g.
#: {'http': 'foo.bar:3128'}) to be used on each
#: :class:`Request <Request>`.
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
#: be used on each :class:`Request <Request>`.
self.proxies = {}
#: Event-handling hooks.
@@ -320,7 +325,8 @@ class Session(SessionRedirectMixin):
#: limit, a :class:`TooManyRedirects` exception is raised.
self.max_redirects = DEFAULT_REDIRECT_LIMIT
#: Should we trust the environment?
#: Trust environment settings for proxy configuration, default
#: authentication and similar.
self.trust_env = True
#: A CookieJar containing all currently outstanding cookies set on this
@@ -405,8 +411,8 @@ class Session(SessionRedirectMixin):
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query
string for the :class:`Request`.
:param data: (optional) Dictionary or bytes to send in the body of the
:class:`Request`.
:param data: (optional) Dictionary, bytes, or file-like object to send
in the body of the :class:`Request`.
:param json: (optional) json to send in the body of the
:class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the
@@ -418,23 +424,20 @@ class Session(SessionRedirectMixin):
:param auth: (optional) Auth tuple or callable to enable
Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) How long to wait for the server to send
data before giving up, as a float, or a (`connect timeout, read
timeout <user/advanced.html#timeouts>`_) tuple.
data before giving up, as a float, or a :ref:`(connect timeout,
read timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param allow_redirects: (optional) Set to True by default.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol to the URL of
the proxy.
:param proxies: (optional) Dictionary mapping protocol or protocol and
hostname to the URL of the proxy.
:param stream: (optional) whether to immediately download the response
content. Defaults to ``False``.
:param verify: (optional) if ``True``, the SSL cert will be verified.
A CA_BUNDLE path can also be provided.
:param verify: (optional) whether the SSL cert will be verified.
A CA_BUNDLE path can also be provided. Defaults to ``True``.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
"""
method = to_native_string(method)
# Create the Request.
req = Request(
method = method.upper(),
@@ -631,7 +634,7 @@ class Session(SessionRedirectMixin):
'cert': cert}
def get_adapter(self, url):
"""Returns the appropriate connnection adapter for the given URL."""
"""Returns the appropriate connection adapter for the given URL."""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix):

View File

@@ -78,11 +78,12 @@ _codes = {
507: ('insufficient_storage',),
509: ('bandwidth_limit_exceeded', 'bandwidth'),
510: ('not_extended',),
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
}
codes = LookupDict(name='status_codes')
for (code, titles) in list(_codes.items()):
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
if not title.startswith('\\'):

View File

@@ -29,7 +29,7 @@ from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
basestring)
from .cookies import RequestsCookieJar, cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import InvalidURL
from .exceptions import InvalidURL, FileModeWarning
_hush_pyflakes = (RequestsCookieJar,)
@@ -48,26 +48,47 @@ def dict_to_sequence(d):
def super_len(o):
total_length = 0
current_position = 0
if hasattr(o, '__len__'):
return len(o)
total_length = len(o)
if hasattr(o, 'len'):
return o.len
elif hasattr(o, 'len'):
total_length = o.len
if hasattr(o, 'fileno'):
elif hasattr(o, 'getvalue'):
# e.g. BytesIO, cStringIO.StringIO
total_length = len(o.getvalue())
elif hasattr(o, 'fileno'):
try:
fileno = o.fileno()
except io.UnsupportedOperation:
pass
else:
return os.fstat(fileno).st_size
total_length = os.fstat(fileno).st_size
if hasattr(o, 'getvalue'):
# e.g. BytesIO, cStringIO.StringIO
return len(o.getvalue())
# Having used fstat to determine the file length, we need to
# confirm that this file was opened up in binary mode.
if 'b' not in o.mode:
warnings.warn((
"Requests has determined the content-length for this "
"request using the binary size of the file: however, the "
"file has been opened in text mode (i.e. without the 'b' "
"flag in the mode). This may lead to an incorrect "
"content-length. In Requests 3.0, support will be removed "
"for files in text mode."),
FileModeWarning
)
if hasattr(o, 'tell'):
current_position = o.tell()
return max(0, total_length - current_position)
def get_netrc_auth(url):
def get_netrc_auth(url, raise_errors=False):
"""Returns the Requests tuple auth for a given url from netrc."""
try:
@@ -94,8 +115,12 @@ def get_netrc_auth(url):
ri = urlparse(url)
# Strip port numbers from netloc
host = ri.netloc.split(':')[0]
# Strip port numbers from netloc. This weird `if...encode`` dance is
# used for Python 3.2, which doesn't support unicode literals.
splitstr = b':'
if isinstance(url, str):
splitstr = splitstr.decode('ascii')
host = ri.netloc.split(splitstr)[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
@@ -105,8 +130,9 @@ def get_netrc_auth(url):
return (_netrc[login_i], _netrc[2])
except (NetrcParseError, IOError):
# If there was a parsing error or a permissions issue reading the file,
# we'll just skip netrc auth
pass
# we'll just skip netrc auth unless explicitly asked to raise errors.
if raise_errors:
raise
# AppEngine hackiness.
except (ImportError, AttributeError):
@@ -498,7 +524,9 @@ def should_bypass_proxies(url):
if no_proxy:
# We need to check whether we match here. We need to see if we match
# the end of the netloc, both with and without the port.
no_proxy = no_proxy.replace(' ', '').split(',')
no_proxy = (
host for host in no_proxy.replace(' ', '').split(',') if host
)
ip = netloc.split(':')[0]
if is_ipv4_address(ip):
@@ -536,36 +564,22 @@ def get_environ_proxies(url):
else:
return getproxies()
def select_proxy(url, proxies):
"""Select a proxy for the url, if applicable.
:param url: The url being for the request
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
"""
proxies = proxies or {}
urlparts = urlparse(url)
proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname)
if proxy is None:
proxy = proxies.get(urlparts.scheme)
return proxy
def default_user_agent(name="python-requests"):
"""Return a string representing the default user agent."""
_implementation = platform.python_implementation()
if _implementation == 'CPython':
_implementation_version = platform.python_version()
elif _implementation == 'PyPy':
_implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
sys.pypy_version_info.minor,
sys.pypy_version_info.micro)
if sys.pypy_version_info.releaselevel != 'final':
_implementation_version = ''.join([_implementation_version, sys.pypy_version_info.releaselevel])
elif _implementation == 'Jython':
_implementation_version = platform.python_version() # Complete Guess
elif _implementation == 'IronPython':
_implementation_version = platform.python_version() # Complete Guess
else:
_implementation_version = 'Unknown'
try:
p_system = platform.system()
p_release = platform.release()
except IOError:
p_system = 'Unknown'
p_release = 'Unknown'
return " ".join(['%s/%s' % (name, __version__),
'%s/%s' % (_implementation, _implementation_version),
'%s/%s' % (p_system, p_release)])
return '%s/%s' % (name, __version__)
def default_headers():

View File

@@ -1,4 +1,6 @@
py==1.4.12
pytest==2.3.4
pytest-cov==1.6
py==1.4.30
pytest==2.8.1
pytest-cov==2.1.0
pytest-httpbin==0.0.7
httpbin==0.4.0
wheel

View File

@@ -62,13 +62,13 @@ setup(
'Natural Language :: English',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4'
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
),
extras_require={
'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'],
'security': ['pyOpenSSL>=0.13', 'ndg-httpsclient', 'pyasn1'],
},
)

View File

@@ -9,6 +9,7 @@ import os
import pickle
import unittest
import collections
import contextlib
import io
import requests
@@ -16,7 +17,9 @@ import pytest
from requests.adapters import HTTPAdapter
from requests.auth import HTTPDigestAuth, _basic_auth_str
from requests.compat import (
Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str)
Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3,
builtin_str, OrderedDict
)
from requests.cookies import cookiejar_from_dict, morsel_to_cookie
from requests.exceptions import (ConnectionError, ConnectTimeout,
InvalidSchema, InvalidURL, MissingSchema,
@@ -32,6 +35,11 @@ try:
except ImportError:
import io as StringIO
try:
from multiprocessing.pool import ThreadPool
except ImportError:
ThreadPool = None
if is_py3:
def u(s):
return s
@@ -40,20 +48,33 @@ else:
return s.decode('unicode-escape')
@pytest.fixture
def httpbin(httpbin):
# Issue #1483: Make sure the URL always has a trailing slash
httpbin_url = httpbin.url.rstrip('/') + '/'
def inner(*suffix):
return urljoin(httpbin_url, '/'.join(suffix))
return inner
@pytest.fixture
def httpsbin_url(httpbin_secure):
# Issue #1483: Make sure the URL always has a trailing slash
httpbin_url = httpbin_secure.url.rstrip('/') + '/'
def inner(*suffix):
return urljoin(httpbin_url, '/'.join(suffix))
return inner
# Requests to this URL should always fail with a connection timeout (nothing
# listening on that port)
TARPIT = "http://10.255.255.1"
HTTPBIN = os.environ.get('HTTPBIN_URL', 'http://httpbin.org/')
# Issue #1483: Make sure the URL always has a trailing slash
HTTPBIN = HTTPBIN.rstrip('/') + '/'
def httpbin(*suffix):
"""Returns url for HTTPBIN resource."""
return urljoin(HTTPBIN, '/'.join(suffix))
class RequestsTestCase(unittest.TestCase):
class TestRequests(object):
_multiprocess_can_split_ = True
@@ -97,13 +118,13 @@ class RequestsTestCase(unittest.TestCase):
assert pr.url == req.url
assert pr.body == 'life=42'
def test_no_content_length(self):
def test_no_content_length(self, httpbin):
get_req = requests.Request('GET', httpbin('get')).prepare()
assert 'Content-Length' not in get_req.headers
head_req = requests.Request('HEAD', httpbin('head')).prepare()
assert 'Content-Length' not in head_req.headers
def test_override_content_length(self):
def test_override_content_length(self, httpbin):
headers = {
'Content-Length': 'not zero'
}
@@ -124,19 +145,35 @@ class RequestsTestCase(unittest.TestCase):
"http://example.com/path?key=value#fragment", params={"a": "b"}).prepare()
assert request.url == "http://example.com/path?key=value&a=b#fragment"
def test_mixed_case_scheme_acceptable(self):
def test_params_original_order_is_preserved_by_default(self):
param_ordered_dict = OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1)))
session = requests.Session()
request = requests.Request('GET', 'http://example.com/', params=param_ordered_dict)
prep = session.prepare_request(request)
assert prep.url == 'http://example.com/?z=1&a=1&k=1&d=1'
def test_params_bytes_are_encoded(self):
request = requests.Request('GET', 'http://example.com',
params=b'test=foo').prepare()
assert request.url == 'http://example.com/?test=foo'
def test_binary_put(self):
request = requests.Request('PUT', 'http://example.com',
data=u"ööö".encode("utf-8")).prepare()
assert isinstance(request.body, bytes)
def test_mixed_case_scheme_acceptable(self, httpbin):
s = requests.Session()
s.proxies = getproxies()
parts = urlparse(httpbin('get'))
schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://',
'https://', 'HTTPS://', 'hTTps://', 'HttPs://']
schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://']
for scheme in schemes:
url = scheme + parts.netloc + parts.path
r = requests.Request('GET', url)
r = s.send(r.prepare())
assert r.status_code == 200, 'failed for scheme {0}'.format(scheme)
def test_HTTP_200_OK_GET_ALTERNATIVE(self):
def test_HTTP_200_OK_GET_ALTERNATIVE(self, httpbin):
r = requests.Request('GET', httpbin('get'))
s = requests.Session()
s.proxies = getproxies()
@@ -145,7 +182,7 @@ class RequestsTestCase(unittest.TestCase):
assert r.status_code == 200
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
def test_HTTP_302_ALLOW_REDIRECT_GET(self, httpbin):
r = requests.get(httpbin('redirect', '1'))
assert r.status_code == 200
assert r.history[0].status_code == 302
@@ -155,7 +192,7 @@ class RequestsTestCase(unittest.TestCase):
# r = requests.post(httpbin('status', '302'), data={'some': 'data'})
# self.assertEqual(r.status_code, 200)
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
def test_HTTP_200_OK_GET_WITH_PARAMS(self, httpbin):
heads = {'User-agent': 'Mozilla/5.0'}
r = requests.get(httpbin('user-agent'), headers=heads)
@@ -163,25 +200,25 @@ class RequestsTestCase(unittest.TestCase):
assert heads['User-agent'] in r.text
assert r.status_code == 200
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self, httpbin):
heads = {'User-agent': 'Mozilla/5.0'}
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
assert r.status_code == 200
def test_set_cookie_on_301(self):
def test_set_cookie_on_301(self, httpbin):
s = requests.session()
url = httpbin('cookies/set?foo=bar')
s.get(url)
assert s.cookies['foo'] == 'bar'
def test_cookie_sent_on_redirect(self):
def test_cookie_sent_on_redirect(self, httpbin):
s = requests.session()
s.get(httpbin('cookies/set?foo=bar'))
r = s.get(httpbin('redirect/1')) # redirects to httpbin('get')
assert 'Cookie' in r.json()['headers']
def test_cookie_removed_on_expire(self):
def test_cookie_removed_on_expire(self, httpbin):
s = requests.session()
s.get(httpbin('cookies/set?foo=bar'))
assert s.cookies['foo'] == 'bar'
@@ -194,18 +231,18 @@ class RequestsTestCase(unittest.TestCase):
)
assert 'foo' not in s.cookies
def test_cookie_quote_wrapped(self):
def test_cookie_quote_wrapped(self, httpbin):
s = requests.session()
s.get(httpbin('cookies/set?foo="bar:baz"'))
assert s.cookies['foo'] == '"bar:baz"'
def test_cookie_persists_via_api(self):
def test_cookie_persists_via_api(self, httpbin):
s = requests.session()
r = s.get(httpbin('redirect/1'), cookies={'foo': 'bar'})
assert 'foo' in r.request.headers['Cookie']
assert 'foo' in r.history[0].request.headers['Cookie']
def test_request_cookie_overrides_session_cookie(self):
def test_request_cookie_overrides_session_cookie(self, httpbin):
s = requests.session()
s.cookies['foo'] = 'bar'
r = s.get(httpbin('cookies'), cookies={'foo': 'baz'})
@@ -213,13 +250,13 @@ class RequestsTestCase(unittest.TestCase):
# Session cookie should not be modified
assert s.cookies['foo'] == 'bar'
def test_request_cookies_not_persisted(self):
def test_request_cookies_not_persisted(self, httpbin):
s = requests.session()
s.get(httpbin('cookies'), cookies={'foo': 'baz'})
# Sending a request with cookies should not add cookies to the session
assert not s.cookies
def test_generic_cookiejar_works(self):
def test_generic_cookiejar_works(self, httpbin):
cj = cookielib.CookieJar()
cookiejar_from_dict({'foo': 'bar'}, cj)
s = requests.session()
@@ -230,7 +267,7 @@ class RequestsTestCase(unittest.TestCase):
# Make sure the session cj is still the custom one
assert s.cookies is cj
def test_param_cookiejar_works(self):
def test_param_cookiejar_works(self, httpbin):
cj = cookielib.CookieJar()
cookiejar_from_dict({'foo': 'bar'}, cj)
s = requests.session()
@@ -238,13 +275,13 @@ class RequestsTestCase(unittest.TestCase):
# Make sure the cookie was sent
assert r.json()['cookies']['foo'] == 'bar'
def test_requests_in_history_are_not_overridden(self):
def test_requests_in_history_are_not_overridden(self, httpbin):
resp = requests.get(httpbin('redirect/3'))
urls = [r.url for r in resp.history]
req_urls = [r.request.url for r in resp.history]
assert urls == req_urls
def test_history_is_always_a_list(self):
def test_history_is_always_a_list(self, httpbin):
"""
Show that even with redirects, Response.history is always a list.
"""
@@ -254,7 +291,7 @@ class RequestsTestCase(unittest.TestCase):
assert isinstance(resp.history, list)
assert not isinstance(resp.history, tuple)
def test_headers_on_session_with_None_are_not_sent(self):
def test_headers_on_session_with_None_are_not_sent(self, httpbin):
"""Do not send headers in Session.headers with None values."""
ses = requests.Session()
ses.headers['Accept-Encoding'] = None
@@ -262,7 +299,7 @@ class RequestsTestCase(unittest.TestCase):
prep = ses.prepare_request(req)
assert 'Accept-Encoding' not in prep.headers
def test_user_agent_transfers(self):
def test_user_agent_transfers(self, httpbin):
heads = {
'User-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)'
@@ -278,15 +315,15 @@ class RequestsTestCase(unittest.TestCase):
r = requests.get(httpbin('user-agent'), headers=heads)
assert heads['user-agent'] in r.text
def test_HTTP_200_OK_HEAD(self):
def test_HTTP_200_OK_HEAD(self, httpbin):
r = requests.head(httpbin('get'))
assert r.status_code == 200
def test_HTTP_200_OK_PUT(self):
def test_HTTP_200_OK_PUT(self, httpbin):
r = requests.put(httpbin('put'))
assert r.status_code == 200
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self, httpbin):
auth = ('user', 'pass')
url = httpbin('basic-auth', 'user', 'pass')
@@ -309,42 +346,47 @@ class RequestsTestCase(unittest.TestCase):
def test_connection_error_invalid_port(self):
"""Connecting to an invalid port should raise a ConnectionError"""
with pytest.raises(ConnectionError):
requests.get("http://httpbin.org:1", timeout=1)
requests.get("http://localhost:1", timeout=1)
def test_LocationParseError(self):
"""Inputing a URL that cannot be parsed should raise an InvalidURL error"""
with pytest.raises(InvalidURL):
requests.get("http://fe80::5054:ff:fe5a:fc0")
def test_basicauth_with_netrc(self):
def test_basicauth_with_netrc(self, httpbin):
auth = ('user', 'pass')
wrong_auth = ('wronguser', 'wrongpass')
url = httpbin('basic-auth', 'user', 'pass')
def get_netrc_auth_mock(url):
return auth
requests.sessions.get_netrc_auth = get_netrc_auth_mock
old_auth = requests.sessions.get_netrc_auth
# Should use netrc and work.
r = requests.get(url)
assert r.status_code == 200
try:
def get_netrc_auth_mock(url):
return auth
requests.sessions.get_netrc_auth = get_netrc_auth_mock
# Given auth should override and fail.
r = requests.get(url, auth=wrong_auth)
assert r.status_code == 401
# Should use netrc and work.
r = requests.get(url)
assert r.status_code == 200
s = requests.session()
# Given auth should override and fail.
r = requests.get(url, auth=wrong_auth)
assert r.status_code == 401
# Should use netrc and work.
r = s.get(url)
assert r.status_code == 200
s = requests.session()
# Given auth should override and fail.
s.auth = wrong_auth
r = s.get(url)
assert r.status_code == 401
# Should use netrc and work.
r = s.get(url)
assert r.status_code == 200
def test_DIGEST_HTTP_200_OK_GET(self):
# Given auth should override and fail.
s.auth = wrong_auth
r = s.get(url)
assert r.status_code == 401
finally:
requests.sessions.get_netrc_auth = old_auth
def test_DIGEST_HTTP_200_OK_GET(self, httpbin):
auth = HTTPDigestAuth('user', 'pass')
url = httpbin('digest-auth', 'auth', 'user', 'pass')
@@ -360,7 +402,7 @@ class RequestsTestCase(unittest.TestCase):
r = s.get(url)
assert r.status_code == 200
def test_DIGEST_AUTH_RETURNS_COOKIE(self):
def test_DIGEST_AUTH_RETURNS_COOKIE(self, httpbin):
url = httpbin('digest-auth', 'auth', 'user', 'pass')
auth = HTTPDigestAuth('user', 'pass')
r = requests.get(url)
@@ -369,14 +411,14 @@ class RequestsTestCase(unittest.TestCase):
r = requests.get(url, auth=auth)
assert r.status_code == 200
def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self):
def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self, httpbin):
url = httpbin('digest-auth', 'auth', 'user', 'pass')
auth = HTTPDigestAuth('user', 'pass')
s = requests.Session()
s.get(url, auth=auth)
assert s.cookies['fake'] == 'fake_value'
def test_DIGEST_STREAM(self):
def test_DIGEST_STREAM(self, httpbin):
auth = HTTPDigestAuth('user', 'pass')
url = httpbin('digest-auth', 'auth', 'user', 'pass')
@@ -387,7 +429,7 @@ class RequestsTestCase(unittest.TestCase):
r = requests.get(url, auth=auth, stream=False)
assert r.raw.read() == b''
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self):
def test_DIGESTAUTH_WRONG_HTTP_401_GET(self, httpbin):
auth = HTTPDigestAuth('user', 'wrongpass')
url = httpbin('digest-auth', 'auth', 'user', 'pass')
@@ -403,7 +445,7 @@ class RequestsTestCase(unittest.TestCase):
r = s.get(url)
assert r.status_code == 401
def test_DIGESTAUTH_QUOTES_QOP_VALUE(self):
def test_DIGESTAUTH_QUOTES_QOP_VALUE(self, httpbin):
auth = HTTPDigestAuth('user', 'pass')
url = httpbin('digest-auth', 'auth', 'user', 'pass')
@@ -411,7 +453,7 @@ class RequestsTestCase(unittest.TestCase):
r = requests.get(url, auth=auth)
assert '"auth"' in r.request.headers['Authorization']
def test_POSTBIN_GET_POST_FILES(self):
def test_POSTBIN_GET_POST_FILES(self, httpbin):
url = httpbin('post')
post1 = requests.post(url).raise_for_status()
@@ -429,7 +471,7 @@ class RequestsTestCase(unittest.TestCase):
with pytest.raises(ValueError):
requests.post(url, files=['bad file data'])
def test_POSTBIN_GET_POST_FILES_WITH_DATA(self):
def test_POSTBIN_GET_POST_FILES_WITH_DATA(self, httpbin):
url = httpbin('post')
post1 = requests.post(url).raise_for_status()
@@ -448,17 +490,17 @@ class RequestsTestCase(unittest.TestCase):
with pytest.raises(ValueError):
requests.post(url, files=['bad file data'])
def test_conflicting_post_params(self):
def test_conflicting_post_params(self, httpbin):
url = httpbin('post')
with open('requirements.txt') as f:
pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})")
pytest.raises(ValueError, "requests.post(url, data=u('[{\"some\": \"data\"}]'), files={'some': f})")
def test_request_ok_set(self):
def test_request_ok_set(self, httpbin):
r = requests.get(httpbin('status', '404'))
assert not r.ok
def test_status_raising(self):
def test_status_raising(self, httpbin):
r = requests.get(httpbin('status', '404'))
with pytest.raises(requests.exceptions.HTTPError):
r.raise_for_status()
@@ -466,11 +508,11 @@ class RequestsTestCase(unittest.TestCase):
r = requests.get(httpbin('status', '500'))
assert not r.ok
def test_decompress_gzip(self):
def test_decompress_gzip(self, httpbin):
r = requests.get(httpbin('gzip'))
r.content.decode('ascii')
def test_unicode_get(self):
def test_unicode_get(self, httpbin):
url = httpbin('/get')
requests.get(url, params={'foo': 'føø'})
requests.get(url, params={'føø': 'føø'})
@@ -478,29 +520,29 @@ class RequestsTestCase(unittest.TestCase):
requests.get(url, params={'foo': 'foo'})
requests.get(httpbin('ø'), params={'foo': 'foo'})
def test_unicode_header_name(self):
def test_unicode_header_name(self, httpbin):
requests.put(
httpbin('put'),
headers={str('Content-Type'): 'application/octet-stream'},
data='\xff') # compat.str is unicode.
def test_pyopenssl_redirect(self):
requests.get('https://httpbin.org/status/301')
def test_pyopenssl_redirect(self, httpsbin_url, httpbin_ca_bundle):
requests.get(httpsbin_url('status', '301'), verify=httpbin_ca_bundle)
def test_urlencoded_get_query_multivalued_param(self):
def test_urlencoded_get_query_multivalued_param(self, httpbin):
r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz']))
assert r.status_code == 200
assert r.url == httpbin('get?test=foo&test=baz')
def test_different_encodings_dont_break_post(self):
def test_different_encodings_dont_break_post(self, httpbin):
r = requests.post(httpbin('post'),
data={'stuff': json.dumps({'a': 123})},
params={'blah': 'asdf1234'},
files={'file': ('test_requests.py', open(__file__, 'rb'))})
assert r.status_code == 200
def test_unicode_multipart_post(self):
def test_unicode_multipart_post(self, httpbin):
r = requests.post(httpbin('post'),
data={'stuff': u('ëlïxr')},
files={'file': ('test_requests.py', open(__file__, 'rb'))})
@@ -521,7 +563,7 @@ class RequestsTestCase(unittest.TestCase):
files={'file': ('test_requests.py', open(__file__, 'rb'))})
assert r.status_code == 200
def test_unicode_multipart_post_fieldnames(self):
def test_unicode_multipart_post_fieldnames(self, httpbin):
filename = os.path.splitext(__file__)[0] + '.py'
r = requests.Request(method='POST',
url=httpbin('post'),
@@ -532,13 +574,24 @@ class RequestsTestCase(unittest.TestCase):
assert b'name="stuff"' in prep.body
assert b'name="b\'stuff\'"' not in prep.body
def test_unicode_method_name(self):
def test_unicode_method_name(self, httpbin):
files = {'file': open('test_requests.py', 'rb')}
r = requests.request(
method=u('POST'), url=httpbin('post'), files=files)
assert r.status_code == 200
def test_custom_content_type(self):
def test_unicode_method_name_with_request_object(self, httpbin):
files = {'file': open('test_requests.py', 'rb')}
s = requests.Session()
req = requests.Request(u("POST"), httpbin('post'), files=files)
prep = s.prepare_request(req)
assert isinstance(prep.method, builtin_str)
assert prep.method == "POST"
resp = s.send(prep)
assert resp.status_code == 200
def test_custom_content_type(self, httpbin):
r = requests.post(
httpbin('post'),
data={'stuff': json.dumps({'a': 123})},
@@ -548,38 +601,38 @@ class RequestsTestCase(unittest.TestCase):
assert r.status_code == 200
assert b"text/py-content-type" in r.request.body
def test_hook_receives_request_arguments(self):
def test_hook_receives_request_arguments(self, httpbin):
def hook(resp, **kwargs):
assert resp is not None
assert kwargs != {}
requests.Request('GET', HTTPBIN, hooks={'response': hook})
requests.Request('GET', httpbin(), hooks={'response': hook})
def test_session_hooks_are_used_with_no_request_hooks(self):
def test_session_hooks_are_used_with_no_request_hooks(self, httpbin):
hook = lambda x, *args, **kwargs: x
s = requests.Session()
s.hooks['response'].append(hook)
r = requests.Request('GET', HTTPBIN)
r = requests.Request('GET', httpbin())
prep = s.prepare_request(r)
assert prep.hooks['response'] != []
assert prep.hooks['response'] == [hook]
def test_session_hooks_are_overriden_by_request_hooks(self):
def test_session_hooks_are_overridden_by_request_hooks(self, httpbin):
hook1 = lambda x, *args, **kwargs: x
hook2 = lambda x, *args, **kwargs: x
assert hook1 is not hook2
s = requests.Session()
s.hooks['response'].append(hook2)
r = requests.Request('GET', HTTPBIN, hooks={'response': [hook1]})
r = requests.Request('GET', httpbin(), hooks={'response': [hook1]})
prep = s.prepare_request(r)
assert prep.hooks['response'] == [hook1]
def test_prepared_request_hook(self):
def test_prepared_request_hook(self, httpbin):
def hook(resp, **kwargs):
resp.hook_working = True
return resp
req = requests.Request('GET', HTTPBIN, hooks={'response': hook})
req = requests.Request('GET', httpbin(), hooks={'response': hook})
prep = req.prepare()
s = requests.Session()
@@ -588,7 +641,7 @@ class RequestsTestCase(unittest.TestCase):
assert hasattr(resp, 'hook_working')
def test_prepared_from_session(self):
def test_prepared_from_session(self, httpbin):
class DummyAuth(requests.auth.AuthBase):
def __call__(self, r):
r.headers['Dummy-Auth-Test'] = 'dummy-auth-test-ok'
@@ -741,7 +794,7 @@ class RequestsTestCase(unittest.TestCase):
# make sure one can use items multiple times
assert list(items) == list(items)
def test_time_elapsed_blank(self):
def test_time_elapsed_blank(self, httpbin):
r = requests.get(httpbin('get'))
td = r.elapsed
total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600)
@@ -780,7 +833,7 @@ class RequestsTestCase(unittest.TestCase):
chunks = r.iter_content(decode_unicode=True)
assert all(isinstance(chunk, str) for chunk in chunks)
def test_request_and_response_are_pickleable(self):
def test_request_and_response_are_pickleable(self, httpbin):
r = requests.get(httpbin('get'))
# verify we can pickle the original request
@@ -812,8 +865,8 @@ class RequestsTestCase(unittest.TestCase):
url = 'http://user:pass%23pass@complex.url.com/path?query=yes'
assert ('user', 'pass#pass') == requests.utils.get_auth_from_url(url)
def test_cannot_send_unprepared_requests(self):
r = requests.Request(url=HTTPBIN)
def test_cannot_send_unprepared_requests(self, httpbin):
r = requests.Request(url=httpbin())
with pytest.raises(ValueError):
requests.Session().send(r)
@@ -827,7 +880,7 @@ class RequestsTestCase(unittest.TestCase):
assert str(error) == 'message'
assert error.response == response
def test_session_pickling(self):
def test_session_pickling(self, httpbin):
r = requests.Request('GET', httpbin('get'))
s = requests.Session()
@@ -837,7 +890,7 @@ class RequestsTestCase(unittest.TestCase):
r = s.send(r.prepare())
assert r.status_code == 200
def test_fixes_1329(self):
def test_fixes_1329(self, httpbin):
"""
Ensure that header updates are done case-insensitively.
"""
@@ -850,7 +903,7 @@ class RequestsTestCase(unittest.TestCase):
assert headers['Accept'] == 'application/json'
assert headers['ACCEPT'] == 'application/json'
def test_uppercase_scheme_redirect(self):
def test_uppercase_scheme_redirect(self, httpbin):
parts = urlparse(httpbin('html'))
url = "HTTP://" + parts.netloc + parts.path
r = requests.get(httpbin('redirect-to'), params={'url': url})
@@ -895,14 +948,14 @@ class RequestsTestCase(unittest.TestCase):
assert 'http://' in s2.adapters
assert 'https://' in s2.adapters
def test_header_remove_is_case_insensitive(self):
def test_header_remove_is_case_insensitive(self, httpbin):
# From issue #1321
s = requests.Session()
s.headers['foo'] = 'bar'
r = s.get(httpbin('get'), headers={'FOO': None})
assert 'foo' not in r.request.headers
def test_params_are_merged_case_sensitive(self):
def test_params_are_merged_case_sensitive(self, httpbin):
s = requests.Session()
s.params['foo'] = 'bar'
r = s.get(httpbin('get'), params={'FOO': 'bar'})
@@ -917,7 +970,7 @@ class RequestsTestCase(unittest.TestCase):
r = requests.Request('GET', url).prepare()
assert r.url == url
def test_header_keys_are_native(self):
def test_header_keys_are_native(self, httpbin):
headers = {u('unicode'): 'blah', 'byte'.encode('ascii'): 'blah'}
r = requests.Request('GET', httpbin('get'), headers=headers)
p = r.prepare()
@@ -927,7 +980,7 @@ class RequestsTestCase(unittest.TestCase):
assert 'unicode' in p.headers.keys()
assert 'byte' in p.headers.keys()
def test_can_send_nonstring_objects_with_files(self):
def test_can_send_nonstring_objects_with_files(self, httpbin):
data = {'a': 0.0}
files = {'b': 'foo'}
r = requests.Request('POST', httpbin('post'), data=data, files=files)
@@ -935,7 +988,7 @@ class RequestsTestCase(unittest.TestCase):
assert 'multipart/form-data' in p.headers['Content-Type']
def test_can_send_bytes_bytearray_objects_with_files(self):
def test_can_send_bytes_bytearray_objects_with_files(self, httpbin):
# Test bytes:
data = {'a': 'this is a string'}
files = {'b': b'foo'}
@@ -948,7 +1001,7 @@ class RequestsTestCase(unittest.TestCase):
p = r.prepare()
assert 'multipart/form-data' in p.headers['Content-Type']
def test_can_send_file_object_with_non_string_filename(self):
def test_can_send_file_object_with_non_string_filename(self, httpbin):
f = io.BytesIO()
f.name = 2
r = requests.Request('POST', httpbin('post'), files={'f': f})
@@ -956,7 +1009,7 @@ class RequestsTestCase(unittest.TestCase):
assert 'multipart/form-data' in p.headers['Content-Type']
def test_autoset_header_values_are_native(self):
def test_autoset_header_values_are_native(self, httpbin):
data = 'this is a string'
length = '16'
req = requests.Request('POST', httpbin('post'), data=data)
@@ -975,7 +1028,7 @@ class RequestsTestCase(unittest.TestCase):
preq = req.prepare()
assert test_url == preq.url
def test_auth_is_stripped_on_redirect_off_host(self):
def test_auth_is_stripped_on_redirect_off_host(self, httpbin):
r = requests.get(
httpbin('redirect-to'),
params={'url': 'http://www.google.co.uk'},
@@ -984,14 +1037,14 @@ class RequestsTestCase(unittest.TestCase):
assert r.history[0].request.headers['Authorization']
assert not r.request.headers.get('Authorization', '')
def test_auth_is_retained_for_redirect_on_host(self):
def test_auth_is_retained_for_redirect_on_host(self, httpbin):
r = requests.get(httpbin('redirect/1'), auth=('user', 'pass'))
h1 = r.history[0].request.headers['Authorization']
h2 = r.request.headers['Authorization']
assert h1 == h2
def test_manual_redirect_with_partial_body_read(self):
def test_manual_redirect_with_partial_body_read(self, httpbin):
s = requests.Session()
r1 = s.get(httpbin('redirect/2'), allow_redirects=False, stream=True)
assert r1.is_redirect
@@ -1024,7 +1077,7 @@ class RequestsTestCase(unittest.TestCase):
adapter.build_response = build_response
def test_redirect_with_wrong_gzipped_header(self):
def test_redirect_with_wrong_gzipped_header(self, httpbin):
s = requests.Session()
url = httpbin('redirect/1')
self._patch_adapter_gzipped_redirect(s, url)
@@ -1035,7 +1088,7 @@ class RequestsTestCase(unittest.TestCase):
assert isinstance(s, builtin_str)
assert s == "Basic dGVzdDp0ZXN0"
def test_requests_history_is_saved(self):
def test_requests_history_is_saved(self, httpbin):
r = requests.get(httpbin('redirect/5'))
total = r.history[-1].history
i = 0
@@ -1043,7 +1096,7 @@ class RequestsTestCase(unittest.TestCase):
assert item.history == total[0:i]
i = i + 1
def test_json_param_post_content_type_works(self):
def test_json_param_post_content_type_works(self, httpbin):
r = requests.post(
httpbin('post'),
json={'life': 42}
@@ -1052,7 +1105,14 @@ class RequestsTestCase(unittest.TestCase):
assert 'application/json' in r.request.headers['Content-Type']
assert {'life': 42} == r.json()['json']
def test_response_iter_lines(self):
def test_json_param_post_should_not_override_data_param(self, httpbin):
r = requests.Request(method='POST', url=httpbin('post'),
data={'stuff': 'elixr'},
json={'music': 'flute'})
prep = r.prepare()
assert 'stuff=elixr' == prep.body
def test_response_iter_lines(self, httpbin):
r = requests.get(httpbin('stream/4'), stream=True)
assert r.status_code == 200
@@ -1060,8 +1120,17 @@ class RequestsTestCase(unittest.TestCase):
next(it)
assert len(list(it)) == 3
def test_unconsumed_session_response_closes_connection(self, httpbin):
s = requests.session()
with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response:
pass
assert response._content_consumed is False
assert response.raw.closed
@pytest.mark.xfail
def test_response_iter_lines_reentrant(self):
def test_response_iter_lines_reentrant(self, httpbin):
"""Response.iter_lines() is not reentrant safe"""
r = requests.get(httpbin('stream/4'), stream=True)
assert r.status_code == 200
@@ -1214,6 +1283,7 @@ class TestCaseInsensitiveDict(unittest.TestCase):
del othercid['spam']
assert cid != othercid
assert cid == {'spam': 'blueval', 'eggs': 'redval'}
assert cid != object()
def test_setdefault(self):
cid = CaseInsensitiveDict({'Spam': 'blueval'})
@@ -1251,6 +1321,16 @@ class TestCaseInsensitiveDict(unittest.TestCase):
assert frozenset(cid.keys()) == keyset
assert frozenset(cid) == keyset
def test_copy(self):
cid = CaseInsensitiveDict({
'Accept': 'application/json',
'user-Agent': 'requests',
})
cid_copy = cid.copy()
assert cid == cid_copy
cid['changed'] = True
assert cid != cid_copy
class UtilsTestCase(unittest.TestCase):
@@ -1276,6 +1356,13 @@ class UtilsTestCase(unittest.TestCase):
assert super_len(
cStringIO.StringIO('but some how, some way...')) == 25
def test_super_len_correctly_calculates_len_of_partially_read_file(self):
"""Ensure that we handle partially consumed file like objects."""
from requests.utils import super_len
s = StringIO.StringIO()
s.write('foobarbogus')
assert super_len(s) == 0
def test_get_environ_proxies_ip_ranges(self):
"""Ensures that IP addresses are correctly matches with ranges
in no_proxy variable."""
@@ -1297,6 +1384,15 @@ class UtilsTestCase(unittest.TestCase):
'http://localhost.localdomain:5000/v1.0/') == {}
assert get_environ_proxies('http://www.requests.com/') != {}
def test_select_proxies(self):
"""Make sure we can select per-host proxies correctly."""
from requests.utils import select_proxy
proxies = {'http': 'http://http.proxy',
'http://some.host': 'http://some.host.proxy'}
assert select_proxy('hTTp://u:p@Some.Host/path', proxies) == 'http://some.host.proxy'
assert select_proxy('hTTp://u:p@Other.Host/path', proxies) == 'http://http.proxy'
assert select_proxy('hTTps://Other.Host', proxies) is None
def test_guess_filename_when_int(self):
from requests.utils import guess_filename
assert None is guess_filename(1)
@@ -1435,13 +1531,13 @@ class TestMorselToCookieMaxAge(unittest.TestCase):
class TestTimeout:
def test_stream_timeout(self):
def test_stream_timeout(self, httpbin):
try:
requests.get(httpbin('delay/10'), timeout=2.0)
except requests.exceptions.Timeout as e:
assert 'Read timed out' in e.args[0].args[0]
def test_invalid_timeout(self):
def test_invalid_timeout(self, httpbin):
with pytest.raises(ValueError) as e:
requests.get(httpbin('get'), timeout=(3, 4, 5))
assert '(connect, read)' in str(e)
@@ -1450,7 +1546,7 @@ class TestTimeout:
requests.get(httpbin('get'), timeout="foo")
assert 'must be an int or float' in str(e)
def test_none_timeout(self):
def test_none_timeout(self, httpbin):
""" Check that you can set None as a valid timeout value.
To actually test this behavior, we'd want to check that setting the
@@ -1462,7 +1558,7 @@ class TestTimeout:
r = requests.get(httpbin('get'), timeout=None)
assert r.status_code == 200
def test_read_timeout(self):
def test_read_timeout(self, httpbin):
try:
requests.get(httpbin('delay/10'), timeout=(None, 0.1))
assert False, "The recv() request should time out."
@@ -1484,7 +1580,7 @@ class TestTimeout:
except ConnectTimeout:
pass
def test_encoded_methods(self):
def test_encoded_methods(self, httpbin):
"""See: https://github.com/kennethreitz/requests/issues/2316"""
r = requests.request(b'GET', httpbin('get'))
assert r.ok
@@ -1535,7 +1631,7 @@ class TestRedirects:
'proxies': {},
}
def test_requests_are_updated_each_time(self):
def test_requests_are_updated_each_time(self, httpbin):
session = RedirectSession([303, 307])
prep = requests.Request('POST', httpbin('post')).prepare()
r0 = session.send(prep)
@@ -1617,7 +1713,7 @@ def test_prepare_unicode_url():
assert_copy(p, p.copy())
def test_urllib3_retries():
def test_urllib3_retries(httpbin):
from requests.packages.urllib3.util import Retry
s = requests.Session()
s.mount('http://', HTTPAdapter(max_retries=Retry(
@@ -1627,6 +1723,17 @@ def test_urllib3_retries():
with pytest.raises(RetryError):
s.get(httpbin('status/500'))
def test_urllib3_pool_connection_closed(httpbin):
s = requests.Session()
s.mount('http://', HTTPAdapter(pool_connections=0, pool_maxsize=0))
try:
s.get(httpbin('status/200'))
except ConnectionError as e:
assert u"Pool is closed." in str(e)
def test_vendor_aliases():
from requests.packages import urllib3
from requests.packages import chardet
@@ -1634,5 +1741,6 @@ def test_vendor_aliases():
with pytest.raises(ImportError):
from requests.packages import webbrowser
if __name__ == '__main__':
unittest.main()