Compare commits

...

544 Commits

Author SHA1 Message Date
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
Cory Benfield
b7bd297340 v2.7.0 2015-05-03 15:47:20 +01:00
Cory Benfield
ee7389da98 Synchronize urllib3 to 1.10.4 2015-05-03 15:16:07 +01:00
Cory Benfield
6acca21bae Merge pull request #2582 from mhavard999/master
Make docstring of api.get more clear
2015-05-01 06:59:46 +01:00
Matt Havard
6e9ebc06d1 Make docstring of api.get more clear 2015-04-30 23:54:34 -04:00
Kenneth Reitz
ff71b25efc release and version history 2015-04-25 17:50:03 -04:00
Kenneth Reitz
c8004e3dd7 Merge remote-tracking branch 'origin/master' 2015-04-25 17:49:14 -04:00
Kenneth Reitz
e859204554 new community 2015-04-25 17:49:12 -04:00
Kenneth Reitz
5d722bd0f5 Merge pull request #2570 from sigmavirus24/release-process-docs
Add new release process to docs
2015-04-25 17:46:31 -04:00
Kenneth Reitz
1f85ce8670 be cordial 2015-04-25 17:44:25 -04:00
Kenneth Reitz
51938de6b7 improve contributing section 2015-04-25 17:42:48 -04:00
Kenneth Reitz
535c51f548 style of contributing.rst 2015-04-25 17:36:14 -04:00
Kenneth Reitz
09023f5894 Merge pull request #2572 from Lukasa/contribution
Add basic contributing guide
2015-04-25 17:34:45 -04:00
Cory Benfield
9dba203952 Add basic contributing guide 2015-04-25 21:39:00 +01:00
Ian Cordasco
f74fc486be Update version number examples. Strip whitespace
Also I've included a bit more verbiage around why a major release
would happen and some reassurances that they will be rare.
2015-04-25 09:29:40 -05:00
Ian Cordasco
773e730d80 Add new release process to docs
Closes #2569
2015-04-24 19:28:23 -05: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
Ian Cordasco
ca66267d2c Add release notes for 2.6.2
Closes #2561
2015-04-23 11:29:10 -05:00
Ian Cordasco
73b9b6906d Update urllib3 to 7b82da0fa3a13514d11a863e379e7541d0ec46ef 2015-04-23 11:27:42 -05:00
Ian Cordasco
3880cf1255 Merge pull request #2556 from sigmavirus24/release/2.6.1
Prepare v2.6.1
2015-04-22 17:12:26 -05:00
Ian Cordasco
ae7c28eb8e Update notes for 2.6.1 2015-04-22 16:52:40 -05:00
Ian Cordasco
3e3fc7683c Remove VendorAlias meta_path hook 2015-04-22 16:26:14 -05:00
Cory Benfield
3d339ed7e6 Update urllib3 to 0b744993
(cherry picked from commit df63ee3e13672caa6b3256c6579fa85caefb2729)
2015-04-22 13:16:12 -05:00
Ian Cordasco
cfb9e66b04 Update history and version number for v2.6.1 2015-04-22 09:10:48 -05:00
Ian Cordasco
5fcd843eb2 Update urllib3 to 10b7a0fefa6596f47a9a6afc80f1f4d1ae950b66 2015-04-22 08:17:39 -05:00
Kenneth Reitz
1f0e5775f2 add betamax 2015-04-21 17:18:04 -04:00
Kenneth Reitz
4cfc5bcd96 add @sigmavirus42's blog post 2015-04-21 17:15:51 -04:00
Cory Benfield
b906c04a6f Merge pull request #2553 from sigmavirus24/bug/2552
Simplify PreparedRequest.prepare API
2015-04-21 06:59:55 +01:00
Ian Cordasco
35045e851a Simplify PreparedRequest.prepare API
Do not require that hooks be passed as an empty list to
PreparedRequest.prepare. In the event hooks is None in prepare or
prepare_hooks, use an empty list as a default.

Related to #2552
2015-04-20 20:13:58 -05:00
Kenneth Reitz
16eeee1436 and extensions 2015-04-17 08:52:47 -04:00
Kenneth Reitz
576e18a796 recommended packages and extensions 2015-04-17 08:51:02 -04:00
Kenneth Reitz
d0e64d32ba Merge pull request #2550 from Lukasa/recommended
Recommended packages.
2015-04-17 08:44:38 -04:00
Cory Benfield
a0d9e0bc57 Merge pull request #2504 from pvanderlinden/bug/2503-resolve-redirect-pass-all-arguments
fix resolve redirect to pass all original arguments
2015-04-16 06:57:56 +01:00
Kenneth Reitz
5a799dd0f5 Merge pull request #2546 from Lukasa/crystals
Keepers of the Three Crystals.
2015-04-14 14:03:08 -04:00
Cory Benfield
8cf5e67041 Recommended packages. 2015-04-13 11:39:23 -04:00
Ian Cordasco
a0632d8df0 Minor formatting change 2015-04-10 22:03:26 -05:00
Ian Cordasco
5478dd6271 Merge pull request #2547 from sh1buy/bug_2540
Fix MisssingSchema error message does not handle unicode input correctly #2540
2015-04-10 21:59:13 -05:00
sh1buy
599f834351 fix bug 2540 2015-04-11 03:29:21 +03:00
sh1buy
f02d0f9e79 Fix bug 2540 2015-04-11 01:41:02 +03:00
sh1buy
f89b5a5d90 fix bug 2540 2015-04-11 00:23:07 +03:00
Cory Benfield
0cd9f5855e Keepers of the Three Crystals. 2015-04-10 14:40:52 -04:00
Kenneth Reitz
ccc03b93ef Test 2015-04-10 13:22:14 -04:00
Kenneth Reitz
b0c008c593 Merge pull request #2536 from smiley/master
Document that "PreparedRequest.prepare_cookies" can only be called once
2015-04-06 18:29:17 -04:00
Cory Benfield
a57c87a459 Merge pull request #2535 from sigmavirus24/bug/2530
Fix build version number in metadata
2015-04-06 17:54:22 -04:00
Smiley Barry
e3c7b43fc9 #2532: Documented strange behaviour in "PreparedRequest.prepare_cookies", caused by cookielib. Also documented a workaround if absolutely necessary. 2015-04-06 21:00:51 +03:00
Ian Cordasco
956ec8321a Merge pull request #2427 from luozhaoyu/master
Bug fix: field uri in digest authentication should not be empty when enc...
2015-04-06 10:19:15 -05:00
Cory Benfield
0581f2c7d2 Merge pull request #2533 from sigmavirus24/bug/2465-2
Place VendorAlias first in meta_path
2015-04-06 07:19:15 -04:00
Cory Benfield
1ff9a05181 Merge pull request #2534 from sigmavirus24/bug/2527
Copy a PreparedRequest's CookieJar reliably
2015-04-06 07:15:55 -04:00
Ian Cordasco
36093e69c7 Copy a PreparedRequest's CookieJar reliably
When a PreparedRequests's cookie jar is not a RequestsCookieJar instance, it
will not have a "copy" method. By adding _copy_cookie_jar we can reliably copy
cookie jars so that we have an actual copy instead of the same instance on
different prepared requests.

This also updates the RequestsCookieJar.update logic to create copies of
cookies from the other jar.

Closes #2527
2015-04-05 21:49:52 -05:00
Ian Cordasco
dacf69a16d Fix build version number in metadata
Closes #2530
2015-04-05 21:49:10 -05:00
Ian Cordasco
1b5bfe681b Place VendorAlias first in meta_path
When other libraries or tools add items to the meta_path, we need to preempt
some of their import hooks to be sure modules can be properly found. This also
prevents problems importing built-in modules on Python 2 where it will first
attempt to import something like:

    requests.packages.chardet.sys

By placing our VendorAlias first, the above will fail and then it will fall
back to trying to import sys directly instead.

Closes #2465
2015-04-05 20:48:36 -05:00
Cory Benfield
18e678c1f8 Merge pull request #2531 from benjaminran/issue2062
Issue 2062: Updated custom header precedence info with .netrc example
2015-04-05 23:33:29 +01:00
benjaminran
1b1866934b updated custom header precedence info with .netrc example 2015-04-05 14:23:10 -07: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
Cory Benfield
d294de38c4 Merge pull request #2525 from benjaminran/issue2062
Issue 2062: Added custom header precedence info
2015-04-03 16:57:54 +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
benjaminran
898465756a added custom header precedence info 2015-04-03 07:58:42 -07: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
Kenneth Reitz
0ad075c430 Update index.rst 2015-03-31 12:44:50 -04:00
Ian Cordasco
460caf71a4 Merge pull request #2518 from deronnax/shorter_version_extraction
shorter and faster version extraction from __init__.py
2015-03-27 07:49:29 -05:00
Cory Benfield
452ba32edf Merge pull request #2497 from Lukasa/secprocess
Requests security release policy
2015-03-27 11:03:41 +00:00
Paul van der Linden
587eb193dd fix resolve redirect to pass all original arguments 2015-03-26 19:57:16 +00:00
deronnax
1145fe64a5 matching the version only at the begining of lines 2015-03-26 15:53:19 +01:00
deronnax
ce234d53a5 shorter and faster version extraction from __init__.py 2015-03-25 18:21:34 +01:00
Cory Benfield
e95e173b8c Merge pull request #2514 from kennethreitz/revert-2513-master
Revert "Minor Patch TypeError thrown"
2015-03-23 15:13:14 +00:00
Ian Cordasco
b2ce5be9d0 Revert "Minor Patch TypeError thrown" 2015-03-23 10:11:13 -05:00
Cory Benfield
a0f5776f35 Merge pull request #2513 from Montycarlo/master
Minor Patch TypeError thrown
2015-03-23 06:18:48 +00:00
Josh Brown
1b5317ea08 Minor Patch TypeError thrown 2015-03-23 16:01:48 +11:00
Ian Cordasco
8dda20f8e3 Merge pull request #2512 from Lukasa/elapsed
Expand on what 'elapsed' means.
2015-03-22 14:51:17 -05:00
Cory Benfield
54791d1970 Expand on what 'elapsed' means. 2015-03-21 16:27:21 +00:00
Cory Benfield
b7e3370762 'At least' 2015-03-18 21:40:03 +00:00
Cory Benfield
84cc36017a Expand on chunked handling. 2015-03-18 21:31:55 +00:00
Cory Benfield
bedd2be0a7 Clarify that we ship patches early. 2015-03-18 21:27:20 +00:00
Cory Benfield
b974cd3741 Describe our new vulnerabilities process. 2015-03-17 20:07:28 +00:00
Cory Benfield
86f9b1616a Merge pull request #2501 from yasoob/master
Added myself to authors.rst
2015-03-16 20:31:09 +00:00
M.Yasoob Ullah Khalid ☺
dfa0a2925f added myself to authors.rst 2015-03-17 01:29:33 +05:00
M.Yasoob Ullah Khalid ☺
81d2d5535a Merge pull request #1 from kennethreitz/master
Only for me
2015-03-17 01:25:05 +05:00
Cory Benfield
cd3f7eb388 Merge pull request #2493 from yasoob/master
made the quickstart more reader friendly
2015-03-15 18:39:54 +00:00
M.Yasoob Ullah Khalid ☺
3711e04f37 edited the useragent string 2015-03-15 22:42:43 +05:00
Cory Benfield
8c6d82a244 Merge pull request #2496 from Lukasa/cve
Update 2.6.0 changelog with CVE number CVE-2015-2296
2015-03-15 11:50:51 +00:00
Cory Benfield
3385c78999 Update 2.6.0 changelog with CVE number. 2015-03-15 11:49:32 +00:00
M.Yasoob Ullah Khalid ☺
6e2f1eb402 made the quickstart more reader friendly. Closes #2418 2015-03-15 01:02:09 +05:00
Ian Cordasco
fa338da717 Merge pull request #2492 from sigmavirus24/release/v2.6.0
Bump version and add release notes for 2.6.0
2015-03-14 11:43:32 -05:00
Ian Cordasco
80a3e87bf3 Bump version and add release notes for 2.6.0 2015-03-14 11:42:38 -05:00
Ian Cordasco
bbbd9de6fd Merge pull request #2472 from scholer/master
Avoid data duplication when creating a Request with str/bytes/bytearray input
2015-03-14 07:43:17 -05:00
Ian Cordasco
e776bb2656 Merge pull request #2489 from Lukasa/cookiebad
Don't label cookies for the target domain.
2015-03-14 07:05:48 -05:00
Cory Benfield
f7c85685a8 Add explanatory comment for cookie change 2015-03-14 11:29:05 +00:00
Cory Benfield
8a2fd7ce6d Merge pull request #2486 from sigmavirus24/bug/2462
Extract version from requests/__init__.py instead of importing requests
2015-03-14 11:20:33 +00:00
Cory Benfield
76f7ce8a24 Merge pull request #2487 from ulope/return_type
Add return type field to entry-point docstrings
2015-03-14 11:19:12 +00:00
Ulrich Petri
573d460aa7 Add return type field to entry-point docstrings
Fixes: #2483
2015-03-14 10:49:32 +01:00
Cory Benfield
3bd8afbff2 Don't ascribe cookies to the target domain. 2015-03-14 08:49:55 +00:00
Ian Cordasco
1acc4992d4 Parse version number out of requests/__init__.py
With the new import machinery in requests/packages/__init__.py people
using `python setup.py test` will not be able to run the tests the first
time on a new environment. The VendorAlias logic breaks the import
machinery and the tests fail. By removing the import of requests in
setup.py, we avoid the issue where sys.meta_path continues to have the
version of VendorAlias from the import in setup.py.

Closes #2462
2015-03-13 21:47:29 -05:00
Cory Benfield
bc746e158c Merge pull request #2485 from sigmavirus24/bug/shazow/urllib3/567
Import urllib3's Retry location from the right place
2015-03-12 06:50:33 +00:00
Ian Cordasco
c4bd6ea150 Import urllib3's Retry location from the right place
Importing from urllib3's top-level location causes the namespace to be
urllib3.util.retry.Retry instead of
requests.packages.urllib3.util.retry.Retry. Without this fix, an using
requests with an un-vendored version of urllib3 will break when urllib3's
retry handling kicks in.

Closes shazow/urllib3#567
2015-03-11 20:52:12 -05:00
Ian Cordasco
9adaae6785 Merge pull request #2482 from sigmavirus24/update-urllib3
Update urllib3 to 43b5b2b452e4344374de7d08ececcca495079b8d
2015-03-11 09:52:11 -05:00
Ian Cordasco
a0790f37b7 Update urllib3 to 43b5b2b452e4344374de7d08ececcca495079b8d 2015-03-10 19:34:34 -05:00
Cory Benfield
14b653ab3c Merge pull request #2478 from plaes/iter-lines
Document Response.iter_lines() deficiencies
2015-03-07 10:03:42 +00:00
Priit Laes
4497a26c37 Document Response.iter_lines() reentrancy issues 2015-03-07 12:00:41 +02:00
Priit Laes
62200cb07e Add testcase to demonstrate r.iter_lines() reentrancy issue 2015-03-07 12:00:37 +02:00
Ian Cordasco
c53aa08cce Merge pull request #2429 from kennethreitz/sigmavirus24/docs/support
Update support documentation to be more accurate
2015-03-04 16:20:33 -06:00
Cory Benfield
bcfcd9f980 Add Lukasa's GPG key fingerprint. 2015-03-04 20:16:25 +00:00
Rasmus Scholer (TimelineX)
ec700a9a13 Avoid duplicating files data when creating a Request with str/bytes/bytearray in the files argument. 30-40% performance improvement. 2015-03-04 10:21:38 -05:00
Ian Cordasco
e7ac8ea0c1 Add PGP Key fingerprints 2015-03-04 08:11:39 -06:00
Ian Cordasco
39e49e5fb9 Merge pull request #2466 from sigmavirus24/bug/2465
Only add VendorAliases for vendored dependencies
2015-03-04 07:49:12 -06:00
Ian Cordasco
61f4faae70 Merge pull request #2468 from scholer/master
Support for bytearray when creating Request with files argument, just like str and bytes.
2015-03-03 14:01:31 -06:00
Rasmus Scholer (TimelineX)
1887f32b17 test_can_send_bytes_bytearray_objects_with_files test now uses string input, not float. 2015-03-03 14:37:39 -05:00
Rasmus Scholer (TimelineX)
326f77d57c Created test case for using bytes and bytearray objects with files argument to Request. 2015-03-03 03:41:00 +01:00
Rasmus Scholer (TimelineX)
11b12c3e07 models.RequestEncodingMixin._encode_files now takes bytearray as fp in addition to the currently supported bytes and str input. 2015-03-03 02:44:52 +01:00
Ian Cordasco
2669f0c0b1 Fix when packages are unvendored
When working these changes back upstream to pip, we realized that the
previous fix wasn't ideal since unvendoring the packages broke the
imports. For example, if urllib3 were unvendored, then the following
would fail:

    from requests.packages import urllib3
2015-02-28 23:04:24 -06:00
Ian Cordasco
9d8a57f2a2 Add a test to ensure the alias does not regress 2015-02-28 20:19:13 -06:00
Ian Cordasco
6ea3f2ada8 Only alias the imports for vendored dependencies
While discussion the issue, Donald Stufft (@dstufft) and I realized the
simplest solution is to simply add an alias per vendored dependency. The
resulting changes are simple and effective. It prevents the issue in
2.5.2 and 2.5.3 where the following would work:

    from requests.packages import webbrowser

This now appropriately raises an ImportError.

Closes #2465
2015-02-28 20:13:46 -06:00
Cory Benfield
461b740db6 Merge pull request #2459 from kennethreitz/release-notes/2.5.3
Update HISTORY and release version for 2.5.3
2015-02-24 16:32:15 +00:00
Ian Cordasco
628474d626 Update HISTORY and release version for 2.5.3 2015-02-24 10:30:17 -06:00
Cory Benfield
de674fc82c Merge pull request #2458 from kennethreitz/revert-2442-certs
Revert "Update certificate bundle."
2015-02-24 16:18:16 +00:00
Ian Cordasco
b58318163c Revert "Update certificate bundle." 2015-02-24 09:44:51 -06:00
Ian Cordasco
d8be2473d1 Update HISTORY and version for v2.5.2 2015-02-23 16:28:42 -06:00
Ian Cordasco
58e513579a Update urllib3 to 29aa09bde9c42cc9a8d79aac47ee3d362b438cca 2015-02-23 16:02:30 -06:00
Ian Cordasco
0b6d2e451b Merge pull request #2444 from Yasumoto/urllib_upgrade
Upgrade urllib3 to 490d3a227fadb626cd54a240b9d0922f849914b4
2015-02-23 16:01:06 -06:00
Cory Benfield
6f3c91b834 Merge pull request #2453 from requiredfield/master
document combination of repeated response headers
2015-02-21 10:20:57 +00:00
requiredfield
ed73d9e45d document combination of repeated response headers 2015-02-20 12:20:18 -06:00
Ian Cordasco
5a22d2060f Merge pull request #2450 from iKevinY/shields-badges
Update README to use Shields badges
2015-02-19 20:01:55 -06:00
Kevin Yap
344b51327c Update README to use Shields badges
Shields badges are friendlier to retina displays.
2015-02-19 11:10:55 -08:00
Joe Smith
62df872871 Upgrade urllib3 to 490d3a227fadb626cd54a240b9d0922f849914b4 2015-02-11 11:04:25 -08:00
Ian Cordasco
a5a314e8ee Merge pull request #2442 from Lukasa/certs
Update certificate bundle.
2015-02-10 13:05:56 -06:00
Cory Benfield
b86330d1f1 Update certificate bundle. 2015-02-10 19:03:59 +00:00
Ian Cordasco
69d9594338 Merge pull request #2440 from aquarion/patch-1
Update to use readthedocs.org instead of rtfd.org

The requests-toolbelt link can't mix HTTPS with the short domain rtfd.
2015-02-06 08:02:58 -06:00
Nicholas Avenell
291b6b24b7 Update to use readthedocs.org instead of rtfd.org
https://toolbelt.rtfd.org causes an SSL error because the wildcard SSL cert is for *.readthedocs.org.
2015-02-06 13:46:38 +00:00
Cory Benfield
426493f388 Merge pull request #2438 from colindickson/master
add a timeout value to connection_error_invalid_port test to accelerate failure
2015-02-04 07:01:10 +00:00
Colin Dickson
80aee7a225 add a timeout value to connection_error_invalid_port test to accelerate failure 2015-02-04 21:05:03 -05:00
Colin Dickson
ced6550a25 split test_connection_error into two distinct tests, and changed "unknown url" test URL since fooobarbangbazbing.httpbin.org currently gives a valid response. 2015-02-03 21:23:07 -05:00
Cory Benfield
c7beb703bf Merge pull request #2437 from tomscytale/docs_query_param_array
quickstart: using a list as a value in query params
2015-02-03 10:34:06 +00:00
Tom S
53f33eb473 quickstart: using a list as a value in query params 2015-02-03 09:19:30 +00:00
Cory Benfield
69b7c45c2e Merge pull request #2436 from colindickson/master
Fix for failing test: test_connection_error
2015-02-03 06:50:11 +00:00
Ian Cordasco
e644857877 Fix typo. Use smaller paragraphs. Add CVE list 2015-01-28 20:48:22 -06:00
Ian Cordasco
3a5e2b2f3d Add a missing comma 2015-01-28 08:58:43 -06:00
Ian Cordasco
02f852cc41 Add Cory's twitter account
Added some more detail to the Vulnerability section
2015-01-28 08:58:05 -06:00
Ian Cordasco
ff56e431ad Update support documentation to be more accurate
We were missing instructions to report security vulnerabilities,
and all of the documentation referred to Kenneth as the only
source of support. We were also missing a link to StackOverflow.
2015-01-28 08:16:05 -06:00
Kenneth Reitz
b137472936 Merge pull request #2393 from sigmavirus24/bug/2356
Attempt to quote anyway if unquoting fails
2015-01-27 10:24:34 -08:00
Kenneth Reitz
383b20b023 Merge pull request #2412 from sigmavirus24/remove-cruft
Remove entirely unnecessary and unused bits from requests.compat
2015-01-27 10:24:25 -08:00
Kenneth Reitz
3314eb9a62 Merge pull request #2413 from sigmavirus24/bug/2411
Check that filenames are unicode or bytes
2015-01-27 10:23:18 -08:00
Kenneth Reitz
cf3a7f4945 Merge pull request #2415 from sigmavirus24/bug/2408
Move noncebit to the only place it is used
2015-01-27 10:22:53 -08:00
Zhaoyu Luo
fb1971778f Bug fix: field uri in digest authentication should not be empty when encounter http redirections 2015-01-25 18:11:22 -06:00
Cory Benfield
2f7f6a57f3 Merge pull request #2420 from chriskuehl/master
Replace n-dash in HISTORY to workaround pip bug
2015-01-23 07:19:39 +00:00
Chris Kuehl
74d85d0a2f HISTORY: replace n-dash to workaround pip bug
There exists a problem (maybe a bug?) in pip when using a locale like
`LC_ALL=C` (which is commonly used by CI environments and system
configuration tools such as Puppet), where the PKG-INFO file is decoded
using ascii, raising a UnicodeDecodeError when PKG-INFO contains
non-ASCII characters (such as the n-dash removed by this commit):

> Downloading/unpacking requests from https://pypi.python.org/packages/source/r/requests/requests-2.5.1.tar.gz
>   Downloading requests-2.5.1.tar.gz (443Kb): 443Kb downloaded
>   Running setup.py egg_info for package requests
>
> Exception:
> Traceback (most recent call last):
>   File "/usr/lib/python3/dist-packages/pip/basecommand.py", line 104, in main
>     status = self.run(options, args)
>   File "/usr/lib/python3/dist-packages/pip/commands/install.py", line 245, in run
>     requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
>   File "/usr/lib/python3/dist-packages/pip/req.py", line 1014, in prepare_files
>     req_to_install.assert_source_matches_version()
>   File "/usr/lib/python3/dist-packages/pip/req.py", line 359, in assert_source_matches_version
>     version = self.installed_version
>   File "/usr/lib/python3/dist-packages/pip/req.py", line 351, in installed_version
>     return self.pkg_info()['version']
>   File "/usr/lib/python3/dist-packages/pip/req.py", line 318, in pkg_info
>     data = self.egg_info_data('PKG-INFO')
>   File "/usr/lib/python3/dist-packages/pip/req.py", line 261, in egg_info_data
>     data = fp.read()
>   File "/usr/lib/python3.2/encodings/ascii.py", line 26, in decode
>     return codecs.ascii_decode(input, self.errors)[0]
> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 8161: ordinal not in range(128)

I'm not able to reproduce this by installing the package directly, only by
installing another package which depends on it (via `install_requires`).

The command I used was `LC_ALL=C /usr/bin/pip-3.2 install --upgrade ocflib`,
with "pip 1.1 from /usr/lib/python3/dist-packages (python 3.2)" (which
is the packaged version for Debian 7).

Although this should probably be addressed ultimately in pip, replacing
this character in HISTORY is a pain-free and pragmatic way to help
developers and system administrators who may be stuck with old versions
of pip for many years to come.
2015-01-22 19:45:49 -08:00
Ian Cordasco
eec44dbbb1 Check that a filename is a basestring instance
Instead of only checking one or another type of string-like object that
we accept, let's be able to check both. Previously, we only checked if
the filename was an instance of the native str type which on Python 2
excluded unicode filenames and bytes-like filenames on Python 3.

Fixes #2411
2015-01-20 20:44:52 -06:00
Ian Cordasco
677bbe30d7 Move noncebit to the only place it is used
Since we only allow for "auth" qop-value, hardcode it

Fixes #2408
2015-01-19 18:50:50 -06:00
Ian Cordasco
f37fe551bb Remove entirely unnecessary and unused bits from requests.compat 2015-01-18 21:17:11 -06:00
Ian Cordasco
751b35c346 Attempt to quote anyway if unquoting fails
Fixes #2356
2015-01-18 21:07:43 -06:00
Ian Cordasco
d2d576b6b1 Update urllib3 to a27758625e4169330fcf965652b1093faf5aaaa2 2015-01-09 20:55:54 -06:00
Kenneth Reitz
a57eacf080 Merge pull request #2375 from sigmavirus24/fix-imports-when-unbundled
Copy pip's import machinery wholesale
2015-01-09 15:38:30 -05:00
Kenneth Reitz
b176ccd481 drawn towards it rather than pushed away 2015-01-08 10:04:58 -05:00
Kenneth Reitz
176b365287 certifi 2015-01-08 10:01:39 -05:00
Kenneth Reitz
09658b80c0 certifi 2015-01-08 09:58:31 -05:00
Kenneth Reitz
ac34e3b346 simpler button 2015-01-08 09:55:40 -05:00
Kenneth Reitz
5549f50258 cleanups 2015-01-08 09:52:56 -05:00
Kenneth Reitz
15597c27cd Requests Pro 2015-01-08 09:52:50 -05:00
Kenneth Reitz
673bd6afce javascripts 2015-01-06 00:56:47 -05:00
Cory Benfield
7612015424 Merge pull request #2395 from shrayasr/happy-new-year
Changing year in all copyright information
2015-01-01 08:28:11 +00:00
Shrayas
892f2560c1 Changing year in all copyright information
- Happy new year! Thanks for this awesome library :)
2015-01-01 09:04:50 +05:30
Kenneth Reitz
2d1ffad80b Merge pull request #2390 from Lukasa/cookiedocs
Clean up cookie docstrings, document cookie jar.
2014-12-24 10:15:37 -05:00
Cory Benfield
101425ebfc Enhance documentation for clarity. 2014-12-24 14:54:49 +00:00
Cory Benfield
f4ec3d2309 Clean up cookie docs and display them. 2014-12-24 14:33:14 +00:00
Ian Cordasco
b83131779c Bump version to 2.5.1 2014-12-23 11:54:01 -06:00
Ian Cordasco
ce5b5fe227 Add release notes for 2.5.1 2014-12-23 11:50:31 -06:00
Ian Cordasco
eccbf4a412 Merge pull request #2389 from sigmavirus24/bug/fix-digest-auth
Fix bug in renegotiating a nonce with the server
2014-12-23 11:40:35 -06:00
Ian Cordasco
86c3ecfd34 Fix bug in renegotiating a nonce with the server
If a session runs long enough (without constant activity) then the server can
expire the nonce the session has negotiated. If that happens the session will
get a new 401 response which we were immediately returning to the user. A user
would then have to essentially reinitialize session.auth each time they get an
unexpected 401.

Also, there's no need for setattr calls when we can simply assign the
attribute on the instance.
2014-12-23 10:40:31 -06:00
Ian Cordasco
bd3cf95e34 Fix error handling on Python 3 2014-12-16 22:49:24 -06:00
Ian Cordasco
386c589ba0 Merge branch 'master' of https://github.com/ContinuousFunction/requests into pr/2344 2014-12-16 22:18:51 -06:00
Cory Benfield
f723ab538f Merge pull request #2382 from daftshady/exception
catch exception more specifically
2014-12-16 09:19:50 +00:00
daftshady
925e975295 catch exception more specifically in Response.ok 2014-12-16 16:55:13 +09:00
ContinuousFunction
4c61fef13f Uncommented test in test_requests.py
Uncommented test_sesion_pickling in test_requests.py and ran the file to
make sure the test passes.
2014-12-15 13:41:10 -05:00
ContinuousFunction
39090cfba6 Merge branch 'master' of https://github.com/kennethreitz/requests 2014-12-15 13:24:06 -05:00
Cory Benfield
d7c0249aa8 Merge pull request #2381 from namlede/patch-1
Fix a typo in a comment
2014-12-15 07:59:47 +00:00
Ben Edelman
01b58ba04e Fix a typo in a comment
I just fixed a minor typo: "throws" is misspelled as "thows".
2014-12-15 01:37:19 -05:00
Ian Cordasco
a8205bb509 Merge pull request #2379 from arthurdarcet/master
utils.guess_filename fails if the given parameter looks like a file object but has a non-string name attribute
2014-12-13 13:49:04 -06:00
Ian Cordasco
da82fb4b1d Give proper attribution to pip 2014-12-12 21:12:31 -06:00
Arthur Darcet
e8d02ea0bb utils.guess_filename fails if the given parameter looks like a file object but has a non-string name attribute
e.g. a cherrypy uploaded file behave like a regular file, except that its name attribute is an int and passing it directly to requests fails because of that
2014-12-12 17:32:43 +01:00
Ian Cordasco
d615405519 Copy pip's import machinery wholesale 2014-12-08 20:48:40 -06:00
Ian Cordasco
e23bf10cf4 Merge pull request #2374 from krvc/fix_link
Updated the broken link to twitter streaming API documentation
2014-12-08 17:46:08 -06:00
rakesh
508f4b1ca5 Updated the broken link to twitter streaming API documentation 2014-12-09 01:59:55 +05:30
Ian Cordasco
1d9bf43def Merge pull request #2373 from frewsxcv/patch-1
Enable GitHub syntax highlighting on README
2014-12-08 12:40:44 -06:00
Corey Farwell
bf2e73522f Enable GitHub syntax highlighting on README 2014-12-08 13:04:27 -05:00
Kenneth Reitz
4e90aa7bd0 Merge pull request #2363 from sigmavirus24/fix-tests
Update tests to work offline
2014-12-02 20:46:10 -08:00
Ian Cordasco
f172b30356 Merge pull request #2365 from sigmavirus24/release/2.5
Release v2.5.0
2014-12-01 17:22:17 -06:00
Ian Cordasco
33b25176b8 Bump version to 2.5.0 2014-12-01 17:03:24 -06:00
Ian Cordasco
9a0ce93935 Add updates for 2.5.0 2014-12-01 17:02:42 -06:00
Ian Cordasco
cf82d4406b Update tests to work offline 2014-12-01 16:21:41 -06:00
Kenneth Reitz
90857b8b59 Merge pull request #2216 from sigmavirus24/retries-take-2
Retries logic
2014-12-01 14:45:27 -05:00
Kenneth Reitz
7aa6c62d6d updatee chardet, urllib3 2014-12-01 14:40:32 -05:00
Kenneth Reitz
53d02381e2 Merge pull request #2349 from sigmavirus24/fix-pickling
Properly serialize RecentlyUsedContainers for cache
2014-11-30 14:12:43 -05:00
Kenneth Reitz
8ae4d02d73 Merge pull request #2348 from danmichaelo/master
Docs: Add more section labels for referencing
2014-11-30 14:12:04 -05:00
Kenneth Reitz
1c4a666b16 Merge pull request #2353 from mattrobenolt/patch-1
url was already parsed, don't urlparse twice
2014-11-30 14:11:52 -05:00
Matt Robenolt
2a5364cf72 url was already parsed, don't urlparse twice 2014-11-23 17:54:44 -08:00
Ian Cordasco
c8baf88125 Properly serialize RecentlyUsedContainers for cache
RecentlyUsedContainers are threadsafe so they require a lock and as such
cannot be serialized with pickle directly. To handle it, we need to
convert it to a dictionary first and then back when deserializing.

Fixes #2345
2014-11-17 22:16:32 -06:00
ContinuousFunction
3246b1fe17 Changed ConnectionError to InvalidURL 2014-11-17 20:06:24 -08:00
Dan Michael O. Heggø
8b03f60278 Docs: Add more section labels for referencing
so sections can be linked from other projects using Intersphinx
2014-11-17 11:39:33 +01:00
ContinuousFunction
087a27aba9 Partially addresses Issue #1572
Addresses the LocationParseError but not the DecodeError from
kennethreitz#1572. When running
test_requests.py, I got an error in test_session_pickling which resulted
in a TypeError. I'm not sure of the reason for the TypeError but I have
commented out that test.
2014-11-15 16:58:25 -08:00
Ian Cordasco
adf475ef82 Update HTTPAdapter docstring 2014-11-12 13:58:03 -06:00
Ian Cordasco
2eb7e3c80b Add last few changes and add a quick test 2014-11-12 13:56:28 -06:00
Ian Cordasco
f54a4e3de1 Update urllib3 to df4ec5cce1 2014-11-12 13:56:28 -06:00
Ian Cordasco
2bc7762cb6 Update how we handle retries to be consistent with documentation 2014-11-12 13:56:28 -06:00
Kenneth Reitz
9dc660269f Merge pull request #2309 from sigmavirus24/add-deprecation-warnings
Add DeprecationWarnings to inform users of plans
2014-11-12 12:36:03 -05:00
Kenneth Reitz
1601387cbe Merge pull request #2308 from kevinburke/read-timeout-retry
Note about read timeout errors and max_retries
2014-11-12 12:35:24 -05:00
Kenneth Reitz
45ef1f8eeb Merge pull request #2332 from asnelzin/fix-2329
Add overriding Content-Length
2014-11-12 12:34:48 -05:00
Kenneth Reitz
e4ddca0f8b Merge pull request #2326 from sigmavirus24/close-functional-sessions
Close sessions created in the functional API
2014-11-12 12:34:14 -05:00
Ian Cordasco
19b8e3042e Merge pull request #2333 from akitada/reinit-pos
Fix HTTPDigestAuth not to treat non-file as a file
2014-11-12 09:53:32 -06:00
Akira Kitada
67c505781c Fix HTTPDigestAuth not to treat non-file as a file
Ensure pos is set to None when the body is not a file
so that HTTPDigestAuth detects the type of the body correctly.
2014-11-13 00:51:35 +09:00
Alexander Nelzin
ee50afef59 Fixed. 2014-11-12 15:35:27 +03:00
Alexander Nelzin
34f6088c8f Added test for overriding Content-Length. 2014-11-12 15:23:23 +03:00
Ian Cordasco
431282e778 Merge pull request #2299 from mattrobenolt/master
Cap the redirect_cache size to prevent memory abuse
2014-11-07 14:18:41 -06:00
Ian Cordasco
3155bc9936 Close sessions created in the functional API
This is related to #1882 and #1685. By calling close on the session, we
clear the PoolManager operated by the Session and close all sockets.

Fixes #1882
Partially-fixes #1685
2014-11-07 08:55:18 -06:00
Ian Cordasco
1c52d15d97 Merge pull request #2323 from Lukasa/strict
Pass strict to urllib3.
2014-11-07 08:12:23 -06:00
Cory Benfield
f28bde7250 Pass strict to urllib3. 2014-11-07 09:19:18 +01:00
Kenneth Reitz
122c92e590 Merge pull request #2253 from yossigo/digest_redirect_fix
A fix for #1979: repeat HTTP digest authentication after redirect.
2014-11-01 10:04:29 -04:00
Kenneth Reitz
124c0940e9 Merge pull request #2317 from sigmavirus24/bug/2316
Use to_native_string instead of builtin_str
2014-11-01 10:04:09 -04:00
Ian Cordasco
29ef108158 Use to_native_string instead of builtin_str
Fixes #2316
2014-10-31 21:20:04 -05:00
Kevin Burke
a703e24573 Update documentation about max_retries to reflect code 2014-10-27 09:56:18 -07:00
Matt Robenolt
387c8f852c Cap the redirect_cache size to prevent memory abuse 2014-10-27 09:20:06 -07:00
Ian Cordasco
091991be0d Merge pull request #2306 from ssadler/fix-failing-test-test_prepare_unicode_url
Fix failing test test_prepare_unicode_url
2014-10-26 19:32:46 -05:00
Ian Cordasco
d505b94fa4 Add DeprecationWarnings to inform users of plans
After a long discussion in IRC and on several issues, the developers of
requests have decided to remove specific functions from requests.utils
in version 3.0.0. To give users ample time to prepare for this, we've
added DeprecationWarnings long in advance. See also the planning of this
in issue #2266.
2014-10-26 19:24:41 -05:00
Kevin Burke
f2f3bdea8d Note about read timeout errors and max_retries 2014-10-26 12:46:23 -07:00
Scott Sadler
583859b820 fix failing test "test_prepare_unicode_url" \(take 3\) 2014-10-26 14:45:12 +01:00
Scott Sadler
8e69c9f0a1 Revert "fix failing test "test_prepare_unicode_url""
This reverts commit 26ed7c2e1d.
2014-10-26 14:41:56 +01:00
Scott Sadler
26ed7c2e1d fix failing test "test_prepare_unicode_url" 2014-10-26 12:49:46 +01:00
Scott Sadler
77687de509 Revert "fix failing test "test_prepare_unicode_url""
This reverts commit cfd6e25749.
2014-10-26 12:40:05 +01:00
Kenneth Reitz
be805f942e Merge pull request #2295 from 2deviant/feature-custom-newline
Adding a custom line delimiter to iter_lines()
2014-10-25 14:33:52 -04:00
Scott Sadler
c044a70796 update AUTHORS 2014-10-25 16:52:02 +02:00
Scott Sadler
cfd6e25749 fix failing test "test_prepare_unicode_url" 2014-10-25 16:51:51 +02:00
Val Tenyotkin
3602690886 Changing parameter name newline ==> delimiter. 2014-10-24 16:37:35 -07:00
Yossi Gottlieb
3d8823cafc Clean up handle_redirect. 2014-10-23 13:46:44 +03:00
Val Tenyotkin
f5ff05be1e Adding a custom line delimiter to iter_lines() 2014-10-21 18:03:11 -07:00
Cory Benfield
6b58a359e8 Merge pull request #2290 from syedsuhail/fix-2282
Fix #2279. Update layout css
2014-10-19 10:42:28 +01:00
syedsuhail
96efb5ec96 Fix #2279. Update layout css 2014-10-19 14:06:02 +05:30
Cory Benfield
e1e1855b85 Merge pull request #2289 from syedsuhail/fix-2288
Fix #2288 . Change urllib3 and chardet workflow
2014-10-19 08:56:36 +01:00
syedsuhail
3a6348dbb9 Fix #2288. Change urllib3 and chardet workflow 2014-10-19 13:18:26 +05:30
Kenneth Reitz
f7ecad843b Merge pull request #2282 from morganthrapp/patch-1
Update sidebarintro.html
2014-10-14 11:22:48 -04:00
morganthrapp
4d498db3ed Update sidebarintro.html
Fix issue #2279. "Gittip" changed to "Gratipay". Tip button extended from 48px to 60px.
2014-10-14 10:56:05 -04:00
Kenneth Reitz
c41eb27091 Merge pull request #2273 from sigmavirus24/add-changelog-to-sidebar
Add Release History to the sidebar
2014-10-12 10:55:34 -04:00
Ian Cordasco
2ac771b35e Merge pull request #2271 from mikecool1000/fix-2250
Fixed #2250
2014-10-10 13:30:18 -05:00
Michael DeLay
8339b60fd5 Made more better 2014-10-10 09:58:30 -07:00
Ian Cordasco
589f2f6d6e Add Release History to the sidebar
Closes #2269
2014-10-09 21:07:05 -05:00
mikecool1000
ce1ac88543 Fixed #2250 2014-10-09 12:22:18 -07:00
mikecool1000
4594667f07 Update utils.py
Fixed -2250
2014-10-08 19:26:07 -07:00
Yossi Gottlieb
6ff6f756c0 Clean up, support all redirects, fix potential endless 401 loop. 2014-10-08 00:06:01 +03:00
Kenneth Reitz
46d646064c v2.4.3 2014-10-06 05:44:25 -04:00
Kenneth Reitz
78d12bbbe9 v2.4.3 2014-10-06 05:42:55 -04:00
Kenneth Reitz
6da438dabd Merge pull request #2268 from sigmavirus24/fix-params
Re-order params for backwards compat
2014-10-06 05:40:03 -04:00
Kenneth Reitz
4ec26da2f7 Merge pull request #2267 from minrk/unicode-url-py2
allow unicode URLs on Python 2
2014-10-06 05:39:54 -04:00
Ian Cordasco
0e4efab270 Re-order params for backwards compat 2014-10-05 18:55:59 -05:00
MinRK
ca2ebc5b68 allow unicode URLs on Python 2
on Python 2 u'é'.decode('utf8') fails with UnicodeEncodeError,
but only AttributeError is caught.

This only calls decode on known bytes objects.
2014-10-05 16:30:11 -07:00
Kenneth Reitz
5850b1f53b Merge pull request #2249 from t-8ch/fix_2247
fix #2247
2014-10-05 13:19:14 -04:00
Kenneth Reitz
779c001e44 requests v2.4.2 2014-10-05 13:15:33 -04:00
Kenneth Reitz
79f56e2e7e fix documentation for utils.get_unicode_from_response() :) 2014-10-05 13:14:38 -04:00
Kenneth Reitz
90dc467fc0 v2.4.2 changelog 2014-10-05 13:13:42 -04:00
Kenneth Reitz
65cdccb70f Merge pull request #2241 from tijko/master
raise RuntimeError when a single streamed request calls *iter methods th...
2014-10-05 12:48:40 -04:00
Kenneth Reitz
1e79cf6a47 Merge pull request #2258 from willingc/add-json
Add json parameter
2014-10-05 12:46:09 -04:00
Cory Benfield
20ca9feeb3 Merge pull request #2265 from sigmavirus24/session-attrs
Remove timeout from __attrs__
2014-10-05 09:05:29 +01:00
Ian Cordasco
b1f3a2dd66 Remove timeout from __attrs__
We do not allow the user to set the timeout value on the Session any longer so
this is extraneous
2014-10-04 23:04:36 -05:00
Kenneth Reitz
e74791d054 Merge pull request #2238 from buttscicles/byte-urls
Support bytestring URLs on Python 3.x
2014-10-02 12:20:05 -04:00
Ian Cordasco
8ed941fa69 Fix a couple of issues I noticed
- Don't _ prefix json in prepare_body
- Don't initialize json to []
- Don't initialize json to {}
- Reorder parameters to PreparedRequest.prepare
- Remove extra parentheses
- Update docstring
2014-09-30 16:03:31 -05:00
Joe Alcorn
a68d1b4517 Support bytestring URLs on Python 3.x 2014-09-30 19:29:10 +01:00
Yossi Gottlieb
c28da22e9c A fix for #1979 repeat HTTP digest authentication after redirect. 2014-09-27 21:05:59 +03:00
Thomas Weißschuh
cd5eb62961 remove unused import 2014-09-26 22:28:29 +00:00
Thomas Weißschuh
6e1db21733 fix #2247
We have to pass urllib3 the url without the authentication information,
else it will be parsed by httplib as a netloc and included in the request line
and Host header
2014-09-25 19:49:28 +00:00
Cory Benfield
a718a81d27 Merge pull request #2248 from jktjkt/docs-CAs
docs: Clarify how to pass a custom set of CAs
2014-09-25 19:28:05 +01:00
Jan Kundrát
3635b1501d docs: Clarify how to pass a custom set of CAs
This new wording makes it hopefully easier to find how to override the
system-provided list of trusted CAs. I failed to find this through some googling
and had to resort to asking on IRC previously.
2014-09-25 20:06:04 +02:00
Kenneth Reitz
de76fbeefb Merge pull request #2244 from sigmavirus24/bug/2231
Avoid getting stuck in a loop
2014-09-24 22:57:38 -04:00
Cory Benfield
e0d88c2814 Merge pull request #2245 from sigmavirus24/doc-fix
Correct redirection introduction
2014-09-23 07:25:26 +01:00
Ian Cordasco
671ba85d41 Correct redirection introduction
The history attribute contains responses, not requests.

Thanks to Yang Yang for reporting this to me via email
2014-09-22 21:08:29 -05:00
Ian Cordasco
ac44b48297 Avoid getting stuck in a loop
This prevents a case where we make a request to URL A, which 301s to B which
would then 301 back to A. Alternatively, for less simple schemes, this will
also prevent us from getting stuck in a loop, e.g., it will prevent the
following from causing an endless loop:

    A -> B -> C -> D -> E -> F --
    ^                             \
    |                             /
    ---<------------<----------<-
2014-09-22 21:01:36 -05:00
Tim Konick
64ba451049 make StreamConsumedError doubly inherit 2014-09-22 15:58:54 -04:00
Tim Konick
6e5f7bb9d3 using the StreamConsumedError 2014-09-22 15:51:59 -04:00
Tim Konick
58e0a6f4a0 add-in StreamConsumedError 2014-09-22 15:51:10 -04:00
Tim Konick
d6470870d0 rm else after if then raise block 2014-09-22 12:46:07 -04:00
Tim Konick
fdf932c61c raise RuntimeError when a single streamed request calls *iter methods than once 2014-09-22 12:04:29 -04:00
Cory Benfield
d28d45af91 Merge pull request #2237 from buttscicles/contributing-update
Remove invoke from requirements.txt, docs
2014-09-20 23:12:38 +01:00
Joe Alcorn
d243668d16 Remove invoke from requirements.txt, docs 2014-09-20 20:56:00 +01:00
Cory Benfield
466a70f7c1 Fixup the remaining references to timeline.json. 2014-09-20 15:00:36 +01:00
Kenneth Reitz
082c8d0db8 Merge pull request #2227 from mjul/master
Added example of how to send multiple files in one request.
2014-09-18 10:06:54 +09:00
Martin Jul
0924069b68 Moved multiple files upload example to advanced section. 2014-09-16 11:54:31 +02:00
Martin Jul
4707d11004 Added example of how to send multiple files in one request. 2014-09-16 11:38:40 +02:00
Ian Cordasco
9b41308b9f Merge pull request #2223 from blueyed/doc-followup-2222
Document skipping in PreparedRequest; followup to #2222
2014-09-12 11:54:40 -05:00
Daniel Hahler
bff8b91638 Document skipping in PreparedRequest; followup to #2222 2014-09-12 18:07:28 +02:00
Cory Benfield
1a44a99f17 Merge pull request #2222 from blueyed/fix-MissingScheme-for-hostport
Add tests to test_invalid_url for `InvalidSchema`
2014-09-12 15:16:11 +01:00
Daniel Hahler
d3566ee1b0 Add more tests to test_invalid_url
This adds tests for the behavior introduced in b149be5d, where
`PreparedRequest` was made to skip `parse_url` for e.g.
`localhost:3128/`.
2014-09-12 16:11:30 +02:00
Kenneth Reitz
9e8702d3d5 lawl 2014-09-09 12:42:00 -04:00
Kenneth Reitz
27b6bfc213 mils 2014-09-09 12:39:36 -04:00
Kenneth Reitz
dff383ce8f requests v2.4.1 2014-09-09 12:34:34 -04:00
Cory Benfield
5e94c81e98 Merge pull request #2210 from sigmavirus24/bug/2207
Fix #2207
2014-09-09 15:24:24 +01:00
Cory Benfield
2416e2343a Merge pull request #2213 from kennethreitz/req-rsp
Request->Response.
2014-09-09 08:45:30 +01:00
Cory Benfield
bf8f791a93 Request->Response. 2014-09-09 08:44:40 +01:00
Ian Cordasco
27c83f7177 Fix #2207
@Lukasa wrote the fix in #2207
2014-09-07 12:02:23 -05:00
Kenneth Reitz
359659cf4b Merge pull request #2203 from Lukasa/remove_certifi
Remove hard certifi dependency and document.
2014-09-05 08:11:57 -07:00
Cory Benfield
9c6ae1dc0b Link to Mozilla trust store. 2014-09-04 19:44:37 +01:00
Kenneth Reitz
0df32de02d Merge pull request #2199 from mtorromeo/patch-1
Open README and HISTORY as utf-8 encoded files (fixes #2196)
2014-09-04 11:41:46 -07:00
Cory Benfield
a77054f90f Remove hard certifi dependency and document. 2014-09-04 19:40:15 +01:00
Kenneth Reitz
1324ca1a0f Merge pull request #2193 from sigmavirus24/bug/2192
Capture and re-raise urllib3 ProtocolError
2014-09-04 11:39:41 -07:00
Kenneth Reitz
209a871b63 Merge pull request #2191 from kevinburke/comma
add trailing comma
2014-09-04 11:39:23 -07:00
Kenneth Reitz
07bbe214aa Merge pull request #2187 from kevinburke/more-docs
Document Timeout behaviour
2014-09-04 11:39:12 -07:00
Kenneth Reitz
958845ae35 requests[security] 2014-09-04 11:37:45 -07:00
Kenneth Reitz
bc2560480e Merge pull request #2195 from sigmavirus24/betterssl
Add betterssl extra
2014-09-04 11:36:56 -07:00
Ian Cordasco
9354855648 Consolidate error handling 2014-09-03 16:01:25 -05:00
Massimiliano Torromeo
226b5c7b0a Open README and HISTORY as utf-8 encoded files (fixes #2196) 2014-09-03 09:40:27 +02:00
Ian Cordasco
ce7957c085 Add test for invalid port 2014-08-29 16:50:45 -05:00
Ian Cordasco
c6a1167e8b Merge remote-tracking branch 'kevinburke/domain-failure-connection-error' into bug/2192 2014-08-29 16:43:51 -05:00
Ian Cordasco
c809867035 Add betterssl extra 2014-08-29 16:39:22 -05:00
Kevin Burke
2c76122a48 Add test that invalid domain raises a ConnectionError 2014-08-29 14:07:28 -07:00
Ian Cordasco
da122231e4 Capture and re-raise urllib3 ProtocolError 2014-08-29 15:16:30 -05:00
Kevin Burke
34ea836d77 tildes 2014-08-29 12:08:03 -07:00
Kevin Burke
59f5a1089a add trailing comma 2014-08-29 12:06:58 -07:00
Kevin Burke
81d6b3e435 s/behavior/behaviour 2014-08-29 11:51:09 -07:00
Kevin Burke
dfa41afd43 Document Timeout behaviour 2014-08-29 11:50:11 -07:00
Carol Willing
0713e09526 Fixes typo in test 2014-08-28 19:42:21 -07:00
Carol Willing
402f3b4993 Changes check on base and json. Fails on tests. 2014-08-28 19:27:45 -07:00
Carol Willing
b34a496649 Adds review changes 2014-08-28 16:45:24 -07:00
Carol Willing
8f17741849 Adds json parameter for POST requests 2014-08-28 12:53:19 -07:00
71 changed files with 4927 additions and 1814 deletions

View File

@@ -1,14 +1,10 @@
Requests is written and maintained by Kenneth Reitz and
various contributors:
Development Lead
````````````````
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_
Core Contributors
`````````````````
Keepers of the Three Crystals
`````````````````````````````
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_, Keeper of the Master Crystal.
- Cory Benfield <cory@lukasa.co.uk> `@lukasa <https://github.com/lukasa>`_
- Ian Cordasco <graffatcolmingov@gmail.com> `@sigmavirus24 <https://github.com/sigmavirus24>`_
@@ -138,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>`_)
@@ -154,3 +150,16 @@ Patches and Suggestions
- Константин Подшумок (`@podshumok <https://github.com/podshumok>`_)
- Ben Bass (`@codedstructure <https://github.com/codedstructure>`_)
- Jonathan Wong <evolutionace@gmail.com> (`@ContinuousFunction <https://github.com/ContinuousFunction>`_)
- Martin Jul (`@mjul <https://github.com/mjul>`_)
- Joe Alcorn (`@buttscicles <https://github.com/buttscicles>`_)
- Syed Suhail Ahmed <ssuhail.ahmed93@gmail.com> (`@syedsuhail <https://github.com/syedsuhail>`_)
- Scott Sadler (`@ssadler <https://github.com/ssadler>`_)
- Arthur Darcet (`@arthurdarcet <https://github.com/arthurdarcet>`_)
- 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,6 +3,303 @@
Release History
---------------
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.
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/>`_.
**Bugfixes**
- Updated urllib3 to 1.10.4, resolving several bugs involving chunked transfer
encoding and response framing.
2.6.2 (2015-04-23)
++++++++++++++++++
**Bugfixes**
- Fix regression where compressed data that was sent as chunked data was not
properly decompressed. (#2561)
2.6.1 (2015-04-22)
++++++++++++++++++
**Bugfixes**
- Remove VendorAlias import machinery introduced in v2.5.2.
- Simplify the PreparedRequest.prepare API: We no longer require the user to
pass an empty list to the hooks keyword argument. (c.f. #2552)
- Resolve redirects now receives and forwards all of the original arguments to
the adapter. (#2503)
- Handle UnicodeDecodeErrors when trying to deal with a unicode URL that
cannot be encoded in ASCII. (#2540)
- Populate the parsed path of the URI field when performing Digest
Authentication. (#2426)
- Copy a PreparedRequest's CookieJar more reliably when it is not an instance
of RequestsCookieJar. (#2527)
2.6.0 (2015-03-14)
++++++++++++++++++
**Bugfixes**
- CVE-2015-2296: Fix handling of cookies on redirect. Previously a cookie
without a host value set would use the hostname for the redirected URL
exposing requests users to session fixation attacks and potentially cookie
stealing. This was disclosed privately by Matthew Daley of
`BugFuzz <https://bugfuzz.com>`_. This affects all versions of requests from
v2.1.0 to v2.5.3 (inclusive on both ends).
- Fix error when requests is an ``install_requires`` dependency and ``python
setup.py test`` is run. (#2462)
- Fix error when urllib3 is unbundled and requests continues to use the
vendored import location.
- Include fixes to ``urllib3``'s header handling.
- Requests' handling of unvendored dependencies is now more restrictive.
**Features and Improvements**
- Support bytearrays when passed as parameters in the ``files`` argument.
(#2468)
- Avoid data duplication when creating a request with ``str``, ``bytes``, or
``bytearray`` input to the ``files`` argument.
2.5.3 (2015-02-24)
++++++++++++++++++
**Bugfixes**
- Revert changes to our vendored certificate bundle. For more context see
(#2455, #2456, and http://bugs.python.org/issue23476)
2.5.2 (2015-02-23)
++++++++++++++++++
**Features and Improvements**
- Add sha256 fingerprint support. (`shazow/urllib3#540`_)
- Improve the performance of headers. (`shazow/urllib3#544`_)
**Bugfixes**
- Copy pip's import machinery. When downstream redistributors remove
requests.packages.urllib3 the import machinery will continue to let those
same symbols work. Example usage in requests' documentation and 3rd-party
libraries relying on the vendored copies of urllib3 will work without having
to fallback to the system urllib3.
- Attempt to quote parts of the URL on redirect if unquoting and then quoting
fails. (#2356)
- Fix filename type check for multipart form-data uploads. (#2411)
- Properly handle the case where a server issuing digest authentication
challenges provides both auth and auth-int qop-values. (#2408)
- Fix a socket leak. (`shazow/urllib3#549`_)
- Fix multiple ``Set-Cookie`` headers properly. (`shazow/urllib3#534`_)
- Disable the built-in hostname verification. (`shazow/urllib3#526`_)
- Fix the behaviour of decoding an exhausted stream. (`shazow/urllib3#535`_)
**Security**
- Pulled in an updated ``cacert.pem``.
- Drop RC4 from the default cipher list. (`shazow/urllib3#551`_)
.. _shazow/urllib3#551: https://github.com/shazow/urllib3/pull/551
.. _shazow/urllib3#549: https://github.com/shazow/urllib3/pull/549
.. _shazow/urllib3#544: https://github.com/shazow/urllib3/pull/544
.. _shazow/urllib3#540: https://github.com/shazow/urllib3/pull/540
.. _shazow/urllib3#535: https://github.com/shazow/urllib3/pull/535
.. _shazow/urllib3#534: https://github.com/shazow/urllib3/pull/534
.. _shazow/urllib3#526: https://github.com/shazow/urllib3/pull/526
2.5.1 (2014-12-23)
++++++++++++++++++
**Behavioural Changes**
- Only catch HTTPErrors in raise_for_status (#2382)
**Bugfixes**
- Handle LocationParseError from urllib3 (#2344)
- Handle file-like object filenames that are not strings (#2379)
- Unbreak HTTPDigestAuth handler. Allow new nonces to be negotiated (#2389)
2.5.0 (2014-12-01)
++++++++++++++++++
**Improvements**
- Allow usage of urllib3's Retry object with HTTPAdapters (#2216)
- The ``iter_lines`` method on a response now accepts a delimiter with which
to split the content (#2295)
**Behavioural Changes**
- Add deprecation warnings to functions in requests.utils that will be removed
in 3.0 (#2309)
- Sessions used by the functional API are always closed (#2326)
- Restrict requests to HTTP/1.1 and HTTP/1.0 (stop accepting HTTP/0.9) (#2323)
**Bugfixes**
- Only parse the URL once (#2353)
- 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
(#2253)
- Fix crash with custom method parameter to Session.request (#2317)
- Fix how Link headers are parsed using the regular expression library (#2271)
**Documentation**
- Add more references for interlinking (#2348)
- Update CSS for theme (#2290)
- Update width of buttons and sidebar (#2289)
- Replace references of Gittip with Gratipay (#2282)
- Add link to changelog in sidebar (#2273)
2.4.3 (2014-10-06)
++++++++++++++++++
**Bugfixes**
- Unicode URL improvements for Python 2.
- Re-order JSON param for backwards compat.
- Automatically defrag authentication schemes from host/pass URIs. (`#2249 <https://github.com/kennethreitz/requests/issues/2249>`_)
2.4.2 (2014-10-05)
++++++++++++++++++
**Improvements**
- FINALLY! Add json parameter for uploads! (`#2258 <https://github.com/kennethreitz/requests/pull/2258>`_)
- Support for bytestring URLs on Python 3.x (`#2238 <https://github.com/kennethreitz/requests/pull/2238>`_)
**Bugfixes**
- Avoid getting stuck in a loop (`#2244 <https://github.com/kennethreitz/requests/pull/2244>`_)
- Multiple calls to iter* fail with unhelpful error. (`#2240 <https://github.com/kennethreitz/requests/issues/2240>`_, `#2241 <https://github.com/kennethreitz/requests/issues/2241>`_)
**Documentation**
- Correct redirection introduction (`#2245 <https://github.com/kennethreitz/requests/pull/2245/>`_)
- Added example of how to send multiple files in one request. (`#2227 <https://github.com/kennethreitz/requests/pull/2227/>`_)
- Clarify how to pass a custom set of CAs (`#2248 <https://github.com/kennethreitz/requests/pull/2248/>`_)
2.4.1 (2014-09-09)
++++++++++++++++++
- Now has a "security" package extras set, ``$ pip install requests[security]``
- Requests will now use Certifi if it is available.
- Capture and re-raise urllib3 ProtocolError
- Bugfix for responses that attempt to redirect to themselves forever (wtf?).
2.4.0 (2014-08-29)
++++++++++++++++++
@@ -15,7 +312,7 @@ Release History
- Support for connect timeouts! Timeout now accepts a tuple (connect, read) which is used to set individual connect and read timeouts.
- Allow copying of PreparedRequests without headers/cookies.
- Updated bundled urllib3 version.
- Refactored settings loading from environment new `Session.merge_environment_settings`.
- Refactored settings loading from environment -- new `Session.merge_environment_settings`.
- Handle socket errors in iter_content.
@@ -259,7 +556,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
@@ -295,7 +592,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)
@@ -346,8 +643,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.
@@ -464,7 +761,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.
@@ -584,7 +881,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)
@@ -671,7 +968,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
@@ -705,7 +1002,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

@@ -1,4 +1,4 @@
Copyright 2014 Kenneth Reitz
Copyright 2015 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -20,16 +20,10 @@ certs:
deps: urllib3 chardet
urllib3:
rm -fr requests/packages/urllib3
git clone https://github.com/shazow/urllib3.git
mv urllib3/urllib3 requests/packages/
rm -fr urllib3
git clone https://github.com/shazow/urllib3.git && rm -fr requests/packages/urllib3 && mv urllib3/urllib3 requests/packages/ && rm -fr urllib3
chardet:
rm -fr requests/packages/chardet
git clone https://github.com/chardet/chardet.git
mv chardet/chardet requests/packages/
rm -fr chardet
git clone https://github.com/chardet/chardet.git && rm -fr requests/packages/chardet && mv chardet/chardet requests/packages/ && rm -fr chardet
publish:
python setup.py register

View File

@@ -1,11 +1,13 @@
Requests: HTTP for Humans
=========================
.. image:: https://badge.fury.io/py/requests.png
:target: http://badge.fury.io/py/requests
.. image:: https://img.shields.io/pypi/v/requests.svg
:target: https://pypi.python.org/pypi/requests
.. image:: https://img.shields.io/pypi/dm/requests.svg
:target: https://pypi.python.org/pypi/requests
.. image:: https://pypip.in/d/requests/badge.png
:target: https://crate.io/packages/requests/
Requests is an Apache2 Licensed HTTP library, written in Python, for human
@@ -19,7 +21,7 @@ perform the simplest of tasks.
Things shouldn't be this way. Not in Python.
.. code-block:: pycon
.. code-block:: python
>>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
>>> r.status_code
@@ -75,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

@@ -15,15 +15,9 @@
</p>
<h3>Donate</h3>
<p>
If you love Requests, consider supporting the author <a href="https://www.gittip.com/kennethreitz/">on Gittip</a>:
</p>
<p>
<iframe style="border: 0; margin: 0; padding: 0;"
src="https://www.gittip.com/kennethreitz/widget.html"
width="48pt" height="20pt"></iframe>
</p>
<p>
<a href="https://gumroad.com/l/RRZc" class="gumroad-button">Buy Requests Pro</a>
</p>
<h3>Get Updates</h3>
@@ -40,13 +34,18 @@
<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/">Recommended Packages and Extensions</a></li>
<p></p>
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
<li><a href="http://docs.python-requests.org/en/latest/community/updates/#software-updates">Release History</a></li>
</ul>

View File

@@ -14,16 +14,9 @@
development release.
</p>
<h3>Donate</h3>
<p>
If you love Requests, consider supporting the author <a href="https://www.gittip.com/kennethreitz/">on Gittip</a>:
</p>
<p>
<iframe style="border: 0; margin: 0; padding: 0;"
src="https://www.gittip.com/kennethreitz/widget.html"
width="48pt" height="20pt"></iframe>
</p>
<p>
<a href="https://gumroad.com/l/RRZc" class="gumroad-button">Buy Requests Pro</a>
</p>
<h3>Get Updates</h3>
<p>Receive updates on new releases and upcoming projects.</p>

View File

@@ -15,6 +15,9 @@
<a href="https://github.com/kennethreitz/requests" class="github">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" class="github"/>
</a>
<script type="text/javascript" src="https://gumroad.com/js/gumroad.js"></script>
<script type="text/javascript">
/* <![CDATA[ */
(function() {
@@ -68,5 +71,15 @@
})();
</script>
<script type="text/javascript">
(function() {
window._pa = window._pa || {};
_pa.productId = "requests-docs";
var pa = document.createElement('script'); pa.type = 'text/javascript'; pa.async = true;
pa.src = ('https:' == document.location.protocol ? 'https:' : 'http:') + "//tag.perfectaudience.com/serve/5226171f87bc6890da0000a0.js";
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(pa, s);
})();
</script>
{%- endblock %}

View File

@@ -60,6 +60,8 @@ Exceptions
.. autoexception:: requests.exceptions.HTTPError
.. autoexception:: requests.exceptions.URLRequired
.. autoexception:: requests.exceptions.TooManyRedirects
.. autoexception:: requests.exceptions.ConnectTimeout
.. autoexception:: requests.exceptions.ReadTimeout
.. autoexception:: requests.exceptions.Timeout
@@ -79,6 +81,8 @@ Status Code Lookup
>>> requests.codes['\o/']
200
.. _api-cookies:
Cookies
~~~~~~~
@@ -86,6 +90,12 @@ Cookies
.. autofunction:: requests.utils.cookiejar_from_dict
.. autofunction:: requests.utils.add_dict_to_cookiejar
.. autoclass:: requests.cookies.RequestsCookieJar
:inherited-members:
.. autoclass:: requests.cookies.CookieConflictError
:inherited-members:
Encodings
~~~~~~~~~
@@ -247,29 +257,11 @@ API Changes
requests.get("http://example.org", proxies=proxies)
Behavioral Changes
~~~~~~~~~~~~~~~~~~
Behavioural Changes
~~~~~~~~~~~~~~~~~~~~~~~
* Keys in the ``headers`` dictionary are now native strings on all Python
versions, i.e. bytestrings on Python 2 and unicode on Python 3. If the
keys are not native strings (unicode on Python2 or bytestrings on Python 3)
they will be converted to the native string type assuming UTF-8 encoding.
* Timeouts behave slightly differently. On streaming requests, the timeout
only applies to the connection attempt. On regular requests, the timeout
is applied to the connection process and on to all attempts to read data from
the underlying socket. It does *not* apply to the total download time for the
request.
::
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
# One second timeout for the connection attempt
# Unlimited time to download the tarball
r = requests.get(tarball_url, stream=True, timeout=1)
# One second timeout for the connection attempt
# Another full second timeout to download the tarball
r = requests.get(tarball_url, timeout=1)

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

@@ -30,3 +30,4 @@ Articles & Talks
- `Issac Kelly's 'Consuming Web APIs' talk <http://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
- `Sending JSON in Requests <http://www.coglib.com/~icordasc/blog/2014/11/sending-json-in-requests.html>`_

View File

@@ -0,0 +1,57 @@
.. _recommended:
Recommended Packages and Extensions
===================================
Requests has a great variety of powerful and useful third-party extensions.
This page provides an overview of some of the best of them.
Certifi CA Bundle
-----------------
`Certifi`_ is a carefully curated collection of Root Certificates for
validating the trustworthiness of SSL certificates while verifying the
identity of TLS hosts. It has been extracted from the Requests project.
.. _Certifi: http://certifi.io/en/latest/
CacheControl
------------
`CacheControl`_ is an extension that adds a full HTTP cache to Requests. This
makes your web requests substantially more efficient, and should be used
whenever you're making a lot of web requests.
.. _CacheControl: https://cachecontrol.readthedocs.org/en/latest/
Requests-Toolbelt
-----------------
`Requests-Toolbelt`_ is a collection of utilities that some users of Requests may desire,
but do not belong in Requests proper. This library is actively maintained
by members of the Requests core team, and reflects the functionality most
requested by users within the community.
.. _Requests-Toolbelt: http://toolbelt.readthedocs.org/en/latest/index.html
Requests-OAuthlib
-----------------
`requests-oauthlib`_ makes it possible to do the OAuth dance from Requests
automatically. This is useful for the large number of websites that use OAuth
to provide authentication. It also provides a lot of tweaks that handle ways
that specific OAuth providers differ from the standard specifications.
.. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/en/latest/
Betamax
-------
`Betamax`_ records your HTTP interactions so the NSA does not have to.
A VCR imitation designed only for Python-Requests.
.. _betamax: https://github.com/sigmavirus24/betamax

View File

@@ -0,0 +1,54 @@
Release Process and Rules
=========================
.. versionadded:: v2.6.2
Starting with the version to be released after ``v2.6.2``, the following rules
will govern and describe how the Requests core team produces a new release.
Major Releases
--------------
A major release will include breaking changes. When it is versioned, it will
be versioned as ``vX.0.0``. For example, if the previous release was
``v10.2.7`` the next version will be ``v11.0.0``.
Breaking changes are changes that break backwards compatibility with prior
versions. If the project were to change the ``text`` attribute on a
``Response`` object to a method, that would only happen in a Major release.
Major releases may also include miscellaneous bug fixes and upgrades to
vendored packages. The core developers of Requests are committed to providing
a good user experience. This means we're also committed to preserving
backwards compatibility as much as possible. Major releases will be infrequent
and will need strong justifications before they are considered.
Minor Releases
--------------
A minor release will not include breaking changes but may include
miscellaneous bug fixes and upgrades to vendored packages. If the previous
version of Requests released was ``v10.2.7`` a minor release would be
versioned as ``v10.3.0``.
Minor releases will be backwards compatible with releases that have the same
major version number. In other words, all versions that would start with
``v10.`` should be compatible with each other.
Hotfix Releases
---------------
A hotfix release will only include bug fixes that were missed when the project
released the previous version. If the previous version of Requests released
``v10.2.7`` the hotfix release would be versioned as ``v10.2.8``.
Hotfixes will **not** include upgrades to vendored dependences after
``v2.6.2``
Reasoning
---------
In the 2.5 and 2.6 release series, the Requests core team upgraded vendored
dependencies and caused a great deal of headaches for both users and the core
team. To reduce this pain, we're forming a concrete set of procedures so
expectations will be properly set.

View File

@@ -5,17 +5,26 @@ Support
If you have questions or issues about Requests, there are several options:
StackOverflow
-------------
If your question does not contain sensitive (possibly proprietary)
information or can be properly anonymized, please ask a question on
`StackOverflow <https://stackoverflow.com/questions/tagged/python-requests>`_
and use the tag ``python-requests``.
Send a Tweet
------------
If your question is less than 140 characters, feel free to send a tweet to
`@kennethreitz <http://twitter.com/kennethreitz>`_.
`@kennethreitz <https://twitter.com/kennethreitz>`_,
`@sigmavirus24 <https://twitter.com/sigmavirus24>`_, or
`@lukasaoz <https://twitter.com/lukasaoz>`_.
File an Issue
-------------
If you notice some unexpected behavior in Requests, or want to see support
If you notice some unexpected behaviour in Requests, or want to see support
for a new feature,
`file an issue on GitHub <https://github.com/kennethreitz/requests/issues>`_.
@@ -34,4 +43,9 @@ IRC
The official Freenode channel for Requests is
`#python-requests <irc://irc.freenode.net/python-requests>`_
I'm also available as **kennethreitz** on Freenode.
The core developers of requests are on IRC throughout the day.
You can find them in ``#python-requests`` as:
- kennethreitz
- lukasa
- sigmavirus24

View File

@@ -1,8 +1,6 @@
.. _updates:
Community Updates
=================
@@ -19,23 +17,14 @@ The best way to track the development of Requests is through
Twitter
-------
I often tweet about new features and releases of Requests.
The author, Kenneth Reitz, often tweets about new features and releases of Requests.
Follow `@kennethreitz <https://twitter.com/kennethreitz>`_ for updates.
Mailing List
------------
There's a low-volume mailing list for Requests. To subscribe to the
mailing list, send an email to
`requests@librelist.org <mailto:requests@librelist.org>`_.
Software Updates
================
Release and Version History
===========================
.. include:: ../../HISTORY.rst

View File

@@ -0,0 +1,105 @@
Vulnerability Disclosure
========================
If you think you have found a potential security vulnerability in requests,
please email `sigmavirus24 <mailto:graffatcolmingov@gmail.com>`_ and
`Lukasa <mailto:cory@lukasa.co.uk>`_ directly. **Do not file a public issue.**
Our PGP Key fingerprints are:
- 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24)
- 90DC AE40 FEA7 4B14 9B70 662D F25F 2144 EEC1 373D (@lukasa)
If English is not your first language, please try to describe the problem and
its impact to the best of your ability. For greater detail, please use your
native language and we will try our best to translate it using online services.
Please also include the code you used to find the problem and the shortest
amount of code necessary to reproduce it.
Please do not disclose this to anyone else. We will retrieve a CVE identifier
if necessary and give you full credit under whatever name or alias you provide.
We will only request an identifier when we have a fix and can publish it in a
release.
We will respect your privacy and will only publicize your involvement if you
grant us permission.
Process
-------
This following information discusses the process the requests project follows
in response to vulnerability disclosures. If you are disclosing a
vulnerability, this section of the documentation lets you know how we will
respond to your disclosure.
Timeline
~~~~~~~~
When you report an issue, one of the project members will respond to you within
two days *at the outside*. In most cases responses will be faster, usually
within 12 hours. This initial response will at the very least confirm receipt
of the report.
If we were able to rapidly reproduce the issue, the initial response will also
contain confirmation of the issue. If we are not, we will often ask for more
information about the reproduction scenario.
Our goal is to have a fix for any vulnerability released within two weeks of
the initial disclosure. This may potentially involve shipping an interim
release that simply disables function while a more mature fix can be prepared,
but will in the vast majority of cases mean shipping a complete release as soon
as possible.
Throughout the fix process we will keep you up to speed with how the fix is
progressing. Once the fix is prepared, we will notify you that we believe we
have a fix. Often we will ask you to confirm the fix resolves the problem in
your environment, especially if we are not confident of our reproduction
scenario.
At this point, we will prepare for the release. We will obtain a CVE number
if one is required, providing you with full credit for the discovery. We will
also decide on a planned release date, and let you know when it is. This
release date will *always* be on a weekday.
At this point we will reach out to our major downstream packagers to notify
them of an impending security-related patch so they can make arrangements. In
addition, these packagers will be provided with the intended patch ahead of
time, to ensure that they are able to promptly release their downstream
packages. Currently the list of people we actively contact *ahead of a public
release* is:
- Ralph Bean, Red Hat (@ralphbean)
- Daniele Tricoli, Debian (@eriol)
We will notify these individuals at least a week ahead of our planned release
date to ensure that they have sufficient time to prepare. If you believe you
should be on this list, please let one of the maintainers know at one of the
email addresses at the top of this article.
On release day, we will push the patch to our public repository, along with an
updated changelog that describes the issue and credits you. We will then issue
a PyPI release containing the patch.
At this point, we will publicise the release. This will involve mails to
mailing lists, Tweets, and all other communication mechanisms available to the
core team.
We will also explicitly mention which commits contain the fix to make it easier
for other distributors and users to easily patch their own versions of requests
if upgrading is not an option.
Previous CVEs
-------------
- Fixed in 2.6.0
- `CVE 2015-2296 <http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2015-2296>`_,
reported by Matthew Daley of `BugFuzz <https://bugfuzz.com/>`_.
- Fixed in 2.3.0
- `CVE 2014-1829 <http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2014-1829>`_
- `CVE 2014-1830 <http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2014-1830>`_

View File

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

145
docs/dev/contributing.rst Normal file
View File

@@ -0,0 +1,145 @@
.. _contributing:
Contributor's Guide
===================
If you're reading this you're probably interested in contributing to
Requests. First, We'd like to say: thank you! Open source projects
live-and-die based on the support they receive from others, and the fact that
you're even considering supporting Requests is very generous of
you.
This document lays out guidelines and advice for contributing to Requests.
If you're thinking of contributing, start by reading this thoroughly and
getting a feel for how contributing to the project works. If you have any
questions, feel free to reach out to either `Ian Cordasco`_ or `Cory Benfield`_,
the primary maintainers.
The guide is split into sections based on the type of contribution you're
thinking of making, with a section that covers general guidelines for all
contributors.
.. _Ian Cordasco: http://www.coglib.com/~icordasc/
.. _Cory Benfield: https://lukasa.co.uk/about
All Contributions
-----------------
Be Cordial
~~~~~~~~~~
**Be cordial or be on your way.**
Requests has one very important rule governing all forms of contribution,
including reporting bugs or requesting features. This golden rule is
`be cordial or be on your way`_. **All contributions are welcome**, as long as
everyone involved is treated with respect.
.. _be cordial or be on your way: http://kennethreitz.org/be-cordial-or-be-on-your-way/
.. _early-feedback:
Get Early Feedback
~~~~~~~~~~~~~~~~~~
If you are contributing, do not feel the need to sit on your contribution until
it is perfectly polished and complete. It helps everyone involved for you to
seek feedback as early as you possibly can. Submitting an early, unfinished
version of your contribution for feedback in no way prejudices your chances of
getting that contribution accepted, and can save you from putting a lot of work
into a contribution that is not suitable for the project.
Contribution Suitability
~~~~~~~~~~~~~~~~~~~~~~~~
The project maintainer has the last word on whether or not a contribution is
suitable for Requests. All contributions will be considered, but from time
to time contributions will be rejected because they do not suit the project.
If your contribution is rejected, don't despair! So long as you followed these
guidelines, you'll have a much better chance of getting your next contribution
accepted.
Code Contributions
------------------
Steps
~~~~~
When contributing code, you'll want to follow this checklist:
1. Fork the repository on GitHub.
2. Run the tests to confirm they all pass on your system. If they don't, you'll
need to investigate why they fail. If you're unable to diagnose this
yourself, raise it as a bug report by following the guidelines in this
document: :ref:`bug-reports`.
3. Write tests that demonstrate your bug or feature. Ensure that they fail.
4. Make your change.
5. Run the entire test suite again, confirming that all tests pass *including
the ones you just added*.
6. Send a GitHub Pull Request to the main repository's ``master`` branch.
GitHub Pull Requests are the expected method of code collaboration on this
project.
The following sub-sections go into more detail on some of the points above.
Code Review
~~~~~~~~~~~
Contributions will not be merged until they've been code reviewed. You should
implement any code review feedback unless you strongly object to it. In the
event that you object to the code review feedback, you should make your case
clearly and calmly. If, after doing so, the feedback is judged to still apply,
you must either apply the feedback or withdraw your contribution.
New Contributors
~~~~~~~~~~~~~~~~
If you are new or relatively new to Open Source, welcome! Requests aims to
be a gentle introduction to the world of Open Source. If you're concerned about
how best to contribute, please consider mailing a maintainer (listed above) and
asking for help.
Please also check the :ref:`early-feedback` section.
Documentation Contributions
---------------------------
Documentation improvements are always welcome! The documentation files live in
the ``docs/`` directory of the codebase. They're written in
`reStructuredText`_, and use `Sphinx`_ to generate the full suite of
documentation.
When contributing documentation, please attempt to follow the style of the
documentation files. This means a soft-limit of 79 characters wide in your text
files and a semi-formal prose style.
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: http://sphinx-doc.org/index.html
.. _bug-reports:
Bug Reports
-----------
Bug reports are hugely important! Before you raise one, though, please check
through the `GitHub issues`_, **both open and closed**, to confirm that the bug
hasn't been reported before. Duplicate bug reports are a huge drain on the time
of other contributors, and should be avoided as much as possible.
.. _GitHub issues: https://github.com/kennethreitz/requests/issues
Feature Requests
----------------
Requests is in a perpetual feature freeze. The maintainers believe that
requests contains every major feature currently required by the vast majority
of users.
If you believe there is a feature missing, feel free to raise a feature
request, but please do be aware that the overwhelming likelihood is that your
feature request will not be accepted.

View File

@@ -24,8 +24,7 @@ Development Dependencies
You'll need to install py.test in order to run the Requests' test suite::
$ pip install -r requirements.txt
$ invoke test
py.test
$ py.test
platform darwin -- Python 2.7.3 -- pytest-2.3.4
collected 25 items

View File

@@ -47,8 +47,8 @@ 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
use Requests internally. It has been downloaded over 21,000,000 times from PyPI.
Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed
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
@@ -114,9 +114,12 @@ Requests ecosystem and community.
:maxdepth: 1
community/faq
community/out-there.rst
community/recommended
community/out-there
community/support
community/vulnerabilities
community/updates
community/release-process
API Documentation
-----------------
@@ -139,6 +142,7 @@ you.
.. toctree::
:maxdepth: 1
dev/contributing
dev/philosophy
dev/todo
dev/authors

View File

@@ -5,13 +5,17 @@ Advanced Usage
This document covers some of Requests more advanced features.
.. _session-objects:
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.
@@ -41,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
@@ -50,6 +81,8 @@ parameters.
All values that are contained within a session are directly available to you.
See the :ref:`Session API Docs <sessionapi>` to learn more.
.. _request-and-response-objects:
Request and Response Objects
----------------------------
@@ -82,6 +115,8 @@ request, and then the request's headers::
{'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}
.. _prepared-requests:
Prepared Requests
-----------------
@@ -166,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 also pass ``verify`` the path to a CA_BUNDLE file for private certs. You can also set 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.
@@ -189,12 +228,35 @@ 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
---------------------
By default, when you make a request, the body of the response is downloaded
immediately. You can override this behavior and defer downloading the response
immediately. You can override this behaviour and defer downloading the response
body until you access the :class:`Response.content <requests.Response.content>`
attribute with the ``stream`` parameter::
@@ -228,6 +290,7 @@ consider using ``contextlib.closing`` (`documented here`_), like this::
.. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing
.. _keep-alive:
Keep-Alive
----------
@@ -240,6 +303,7 @@ Note that connections are only released back to the pool for reuse once all body
data has been read; be sure to either set ``stream`` to ``False`` or read the
``content`` property of the ``Response`` object.
.. _streaming-uploads:
Streaming Uploads
-----------------
@@ -251,6 +315,16 @@ 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
----------------------
@@ -259,13 +333,54 @@ 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:
POST Multiple Multipart-Encoded Files
-------------------------------------
You can send multiple files in one request. For example, suppose you want to
upload image files to an HTML form with a multiple file field 'images':
<input type="file" name="images" multiple="true" required="true"/>
To do that, just set files to a list of tuples of (form_field_name, file_info):
>>> url = 'http://httpbin.org/post'
>>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
>>> r = requests.post(url, files=multiple_files)
>>> r.text
{
...
'files': {'images': 'data:image/png;base64,iVBORw ....'}
'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
...
}
.. 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
-----------
@@ -305,6 +420,7 @@ Let's print some request method arguments at runtime::
http://httpbin.org
<Response [200]>
.. _custom-auth:
Custom Authentication
---------------------
@@ -348,7 +464,7 @@ Streaming Requests
With :class:`requests.Response.iter_lines()` you can easily
iterate over streaming APIs such as the `Twitter Streaming
API <https://dev.twitter.com/docs/streaming-api>`_. Simply
API <https://dev.twitter.com/streaming/overview>`_. Simply
set ``stream`` to ``True`` and iterate over the response with
:class:`~requests.Response.iter_lines()`::
@@ -363,6 +479,20 @@ set ``stream`` to ``True`` and iterate over the response with
if line:
print(json.loads(line))
.. warning::
:class:`~requests.Response.iter_lines()` is not reentrant safe.
Calling this method multiple times causes some of the received data
being lost. In case you need to call it from multiple places, use
the resulting iterator object instead::
lines = r.iter_lines()
# Save the first line for later or just skip it
first_line = next(lines)
for line in lines:
print(line)
.. _proxies:
Proxies
-------
@@ -396,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
----------
@@ -424,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
----------
@@ -597,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
------------
@@ -619,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
------------------
@@ -687,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?
-------------------------
@@ -704,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
--------

View File

@@ -31,18 +31,18 @@ timeline ::
>>> r = requests.get('https://api.github.com/events')
Now, we have a :class:`Request <requests.Request>` object called ``r``. We can
Now, we have a :class:`Response <requests.Response>` object called ``r``. We can
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,6 +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.
You can also pass a list of items as a value::
>>> 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=value2&key2=value3
Response Content
----------------
@@ -81,7 +87,7 @@ We can read the content of the server's response. Consider the GitHub timeline
again::
>>> import requests
>>> r = requests.get('https://github.com/timeline.json')
>>> r = requests.get('https://api.github.com/events')
>>> r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
@@ -134,13 +140,19 @@ JSON Response Content
There's also a builtin JSON decoder, in case you're dealing with JSON data::
>>> import requests
>>> r = requests.get('https://github.com/timeline.json')
>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{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
@@ -150,7 +162,7 @@ In the rare case that you'd like to get the raw socket response from the
server, you can access ``r.raw``. If you want to do this, make sure you set
``stream=True`` in your initial request. Once you do, you can do this::
>>> r = requests.get('https://github.com/timeline.json', stream=True)
>>> r = requests.get('https://api.github.com/events', stream=True)
>>> r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
>>> r.raw.read(10)
@@ -175,14 +187,22 @@ 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'
>>> payload = {'some': 'data'}
>>> headers = {'content-type': 'application/json'}
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> r = requests.post(url, data=json.dumps(payload), headers=headers)
>>> r = requests.get(url, headers=headers)
Note: Custom headers are given less precedence than more specific sources of information. For instance:
* Authorization headers will be overridden if credentials are passed via the ``auth`` parameter or are specified in a ``.netrc`` accessible in the environment.
* Authorization headers will be removed if you get redirected off-host.
* Proxy-Authorization headers will be overridden by proxy credentials provided in the URL.
* Content-Length headers will be overridden when we can determine the length of the content.
Furthermore, Requests does not change its behavior at all based on which custom headers are specified. The headers are simply passed on into the final request.
More complicated POST requests
@@ -215,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
-----------------------------
@@ -234,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'})}
@@ -268,7 +296,18 @@ In the event you are posting a very large file as a ``multipart/form-data``
request, you may want to stream the request. By default, ``requests`` does not
support this, but there is a separate package which does -
``requests-toolbelt``. You should read `the toolbelt's documentation
<https://toolbelt.rtfd.org>`_ for more details about how to use it.
<https://toolbelt.readthedocs.org>`_ for more details about how to use it.
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
@@ -337,6 +376,15 @@ So, we can access the headers using any capitalization we want::
>>> r.headers.get('content-type')
'application/json'
It is also special in that the server could have sent the same header multiple
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.
Cookies
-------
@@ -369,9 +417,9 @@ HEAD.
We can use the ``history`` property of the Response object to track redirection.
The :meth:`Response.history <requests.Response.history>` list contains the
:class:`Request <requests.Request>` objects that were created in order to
:class:`Response <requests.Response>` objects that were created in order to
complete the request. The list is sorted from the oldest to the most recent
request.
response.
For example, GitHub redirects all HTTP requests to HTTPS::

View File

@@ -6,14 +6,14 @@
# /
"""
requests HTTP library
Requests HTTP library
~~~~~~~~~~~~~~~~~~~~~
Requests is an HTTP library, written in Python, for human beings. Basic GET
usage:
>>> import requests
>>> r = requests.get('http://python.org')
>>> r = requests.get('https://www.python.org')
>>> r.status_code
200
>>> 'Python is a programming language' in r.content
@@ -22,7 +22,7 @@ usage:
... or POST:
>>> payload = dict(key1='value1', key2='value2')
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> r = requests.post('http://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
@@ -36,17 +36,17 @@ usage:
The other HTTP methods are supported - see `requests.api`. Full documentation
is at <http://python-requests.org>.
:copyright: (c) 2014 by Kenneth Reitz.
:copyright: (c) 2015 by Kenneth Reitz.
:license: Apache 2.0, see LICENSE for more details.
"""
__title__ = 'requests'
__version__ = '2.4.0'
__build__ = 0x020400
__version__ = '2.9.0'
__build__ = 0x020900
__author__ = 'Kenneth Reitz'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2014 Kenneth Reitz'
__copyright__ = 'Copyright 2015 Kenneth Reitz'
# Attempt to enable urllib3's SNI support, if possible
try:
@@ -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,31 +8,38 @@ This module contains the transport adapters that Requests uses to define
and maintain connections.
"""
import os.path
import socket
from .models import Response
from .packages.urllib3 import Retry
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
from .packages.urllib3.response import HTTPResponse
from .packages.urllib3.util import Timeout as TimeoutSauce
from .compat import urlparse, basestring, urldefrag
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)
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
from .packages.urllib3.exceptions import SSLError as _SSLError
from .packages.urllib3.exceptions import ResponseError
from .cookies import extract_cookies_to_jar
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
ProxyError)
ProxyError, RetryError)
from .auth import _basic_auth_str
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
class BaseAdapter(object):
@@ -59,8 +66,12 @@ class HTTPAdapter(BaseAdapter):
:param pool_connections: The number of urllib3 connection pools to cache.
:param pool_maxsize: The maximum number of connections to save in the pool.
:param int max_retries: The maximum number of retries each connection
should attempt. Note, this applies only to failed connections and
timeouts, never to requests where the server returns a response.
should attempt. Note, this applies only to failed DNS lookups, socket
connections and connection timeouts, never to requests where data has
made it to the server. By default, Requests does not retry failed
connections. If you need granular control over the conditions under
which we retry a request, import urllib3's ``Retry`` class and pass
that instead.
:param pool_block: Whether the connection pool should block for connections.
Usage::
@@ -76,7 +87,10 @@ class HTTPAdapter(BaseAdapter):
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK):
self.max_retries = max_retries
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
else:
self.max_retries = Retry.from_int(max_retries)
self.config = {}
self.proxy_manager = {}
@@ -94,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 = {}
@@ -122,7 +136,7 @@ class HTTPAdapter(BaseAdapter):
self._pool_block = block
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
block=block, **pool_kwargs)
block=block, strict=True, **pool_kwargs)
def proxy_manager_for(self, proxy, **proxy_kwargs):
"""Return urllib3 ProxyManager for the given proxy.
@@ -172,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):
@@ -228,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')
@@ -262,14 +280,12 @@ 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, _ = urldefrag(request.url)
url = urldefragauth(request.url)
else:
url = request.path_url
@@ -300,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)
@@ -316,8 +331,10 @@ class HTTPAdapter(BaseAdapter):
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param stream: (optional) Whether to stream the request content.
:param timeout: (optional) The timeout on the request.
:type timeout: float or tuple (connect timeout, read timeout), eg (3.1, 20)
:param timeout: (optional) How long to wait for the server to send
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.
:param proxies: (optional) The proxies dictionary to apply to the request.
@@ -355,7 +372,7 @@ class HTTPAdapter(BaseAdapter):
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=Retry(self.max_retries, read=False),
retries=self.max_retries,
timeout=timeout
)
@@ -364,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,
@@ -383,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,
@@ -396,17 +421,22 @@ 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 socket.error as sockerr:
raise ConnectionError(sockerr, request=request)
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:

View File

@@ -16,22 +16,28 @@ from . import sessions
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
Returns :class:`Response <Response>` object.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload.
: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) Float describing the timeout of the request in seconds.
:param timeout: (optional) How long to wait for the server to send 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) 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
:rtype: requests.Response
Usage::
@@ -40,26 +46,34 @@ def request(method, url, **kwargs):
<Response [200]>
"""
session = sessions.Session()
return session.request(method=method, url=url, **kwargs)
# 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, **kwargs):
"""Sends a GET request. Returns :class:`Response` object.
def get(url, params=None, **kwargs):
"""Sends a GET request.
: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 \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, **kwargs)
return request('get', url, params=params, **kwargs)
def options(url, **kwargs):
"""Sends a OPTIONS request. Returns :class:`Response` object.
"""Sends a OPTIONS request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
@@ -67,54 +81,65 @@ def options(url, **kwargs):
def head(url, **kwargs):
"""Sends a HEAD request. Returns :class:`Response` object.
"""Sends a HEAD request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', False)
return request('head', url, **kwargs)
def post(url, data=None, **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
def post(url, data=None, json=None, **kwargs):
"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('post', url, data=data, **kwargs)
return request('post', url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs):
"""Sends a PUT request. Returns :class:`Response` object.
"""Sends a PUT request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('put', url, data=data, **kwargs)
def patch(url, data=None, **kwargs):
"""Sends a PATCH request. Returns :class:`Response` object.
"""Sends a PATCH request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('patch', url, data=data, **kwargs)
def delete(url, **kwargs):
"""Sends a DELETE request. Returns :class:`Response` object.
"""Sends a DELETE request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('delete', url, **kwargs)

View File

@@ -11,12 +11,14 @@ import os
import re
import time
import hashlib
import threading
from base64 import b64encode
from .compat import urlparse, str
from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header, to_native_string
from .status_codes import codes
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
@@ -62,18 +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
# 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'
@@ -101,7 +111,8 @@ class HTTPDigestAuth(AuthBase):
# XXX not implemented yet
entdig = None
p_parsed = urlparse(url)
path = p_parsed.path
#: path is request-uri defined in RFC 2616 which should not be empty
path = p_parsed.path or "/"
if p_parsed.query:
path += '?' + p_parsed.query
@@ -111,30 +122,32 @@ 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)
cnonce = (hashlib.sha1(s).hexdigest()[:16])
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, HA2)
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" % (
nonce, ncvalue, cnonce, 'auth', HA2
)
respdig = KD(HA1, noncebit)
else:
# 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", ' \
@@ -150,26 +163,30 @@ class HTTPDigestAuth(AuthBase):
return 'Digest %s' % (base)
def handle_redirect(self, r, **kwargs):
"""Reset num_401_calls counter on redirects."""
if r.is_redirect:
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:
setattr(self, 'num_401_calls', 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)
@@ -182,16 +199,25 @@ class HTTPDigestAuth(AuthBase):
return _r
setattr(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:
pass
# 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._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

@@ -21,62 +21,10 @@ is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
#: Python 3.0.x
is_py30 = (is_py3 and _ver[1] == 0)
#: Python 3.1.x
is_py31 = (is_py3 and _ver[1] == 1)
#: Python 3.2.x
is_py32 = (is_py3 and _ver[1] == 2)
#: Python 3.3.x
is_py33 = (is_py3 and _ver[1] == 3)
#: Python 3.4.x
is_py34 = (is_py3 and _ver[1] == 4)
#: Python 2.7.x
is_py27 = (is_py2 and _ver[1] == 7)
#: Python 2.6.x
is_py26 = (is_py2 and _ver[1] == 6)
#: Python 2.5.x
is_py25 = (is_py2 and _ver[1] == 5)
#: Python 2.4.x
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
# ---------
# Platforms
# ---------
# Syntax sugar.
_ver = sys.version.lower()
is_pypy = ('pypy' in _ver)
is_jython = ('jython' in _ver)
is_ironpython = ('iron' in _ver)
# Assume CPython, if nothing else.
is_cpython = not any((is_pypy, is_jython, is_ironpython))
# Windows-based system.
is_windows = 'win32' in str(sys.platform).lower()
# Standard Linux 2+ system.
is_linux = ('linux' in str(sys.platform).lower())
is_osx = ('darwin' in str(sys.platform).lower())
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
try:
import simplejson as json
except (ImportError, SyntaxError):
# simplejson does not support Python 3.2, it thows a SyntaxError
# simplejson does not support Python 3.2, it throws a SyntaxError
# because of u'...' Unicode literals.
import json
@@ -99,7 +47,6 @@ if is_py2:
basestring = basestring
numeric_types = (int, long, float)
elif is_py3:
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
from urllib.request import parse_http_list, getproxies, proxy_bypass

View File

@@ -6,6 +6,7 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests.
requests.utils imports from here, so be careful with imports.
"""
import copy
import time
import collections
from .compat import cookielib, urlparse, urlunparse, Morsel
@@ -142,10 +143,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)
@@ -157,26 +161,28 @@ class CookieConflictError(RuntimeError):
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
interface.
This is the CookieJar we create by default for requests and sessions that
don't specify one, since some clients may expect response.cookies and
session.cookies to support dict operations.
Don't use the dict interface internally; it's just for compatibility with
with external client code. All `requests` code should work out of the box
with externally provided instances of CookieJar, e.g., LWPCookieJar and
FileCookieJar.
Caution: dictionary operations that are normally O(1) may be O(n).
Requests does not use the dict interface internally; it's just for
compatibility with external client code. All requests code should work
out of the box with externally provided instances of ``CookieJar``, e.g.
``LWPCookieJar`` and ``FileCookieJar``.
Unlike a regular CookieJar, this class is pickleable.
"""
.. warning:: dictionary operations that are normally O(1) may be O(n).
"""
def get(self, name, default=None, domain=None, path=None):
"""Dict-like get() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains. Caution: operation is O(n), not O(1)."""
multiple domains.
.. warning:: operation is O(n), not O(1)."""
try:
return self._find_no_duplicates(name, domain, path)
except KeyError:
@@ -199,37 +205,38 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
return c
def iterkeys(self):
"""Dict-like iterkeys() that returns an iterator of names of cookies from the jar.
See itervalues() and iteritems()."""
"""Dict-like iterkeys() that returns an iterator of names of cookies
from the jar. See itervalues() and iteritems()."""
for cookie in iter(self):
yield cookie.name
def keys(self):
"""Dict-like keys() that returns a list of names of cookies from the jar.
See values() and items()."""
"""Dict-like keys() that returns a list of names of cookies from the
jar. See values() and items()."""
return list(self.iterkeys())
def itervalues(self):
"""Dict-like itervalues() that returns an iterator of values of cookies from the jar.
See iterkeys() and iteritems()."""
"""Dict-like itervalues() that returns an iterator of values of cookies
from the jar. See iterkeys() and iteritems()."""
for cookie in iter(self):
yield cookie.value
def values(self):
"""Dict-like values() that returns a list of values of cookies from the jar.
See keys() and items()."""
"""Dict-like values() that returns a list of values of cookies from the
jar. See keys() and items()."""
return list(self.itervalues())
def iteritems(self):
"""Dict-like iteritems() that returns an iterator of name-value tuples from the jar.
See iterkeys() and itervalues()."""
"""Dict-like iteritems() that returns an iterator of name-value tuples
from the jar. See iterkeys() and itervalues()."""
for cookie in iter(self):
yield cookie.name, cookie.value
def items(self):
"""Dict-like items() that returns a list of name-value tuples from the jar.
See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
and get a vanilla python dict of key value pairs."""
"""Dict-like items() that returns a list of name-value tuples from the
jar. See keys() and values(). Allows client-code to call
``dict(RequestsCookieJar)`` and get a vanilla python dict of key value
pairs."""
return list(self.iteritems())
def list_domains(self):
@@ -259,8 +266,9 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
return False # there is only one domain in jar
def get_dict(self, domain=None, path=None):
"""Takes as an argument an optional domain and path and returns a plain old
Python dict of name-value pairs of cookies that meet the requirements."""
"""Takes as an argument an optional domain and path and returns a plain
old Python dict of name-value pairs of cookies that meet the
requirements."""
dictionary = {}
for cookie in iter(self):
if (domain is None or cookie.domain == domain) and (path is None
@@ -269,21 +277,24 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
return dictionary
def __getitem__(self, name):
"""Dict-like __getitem__() for compatibility with client code. Throws exception
if there are more than one cookie with name. In that case, use the more
explicit get() method instead. Caution: operation is O(n), not O(1)."""
"""Dict-like __getitem__() for compatibility with client code. Throws
exception if there are more than one cookie with name. In that case,
use the more explicit get() method instead.
.. warning:: operation is O(n), not O(1)."""
return self._find_no_duplicates(name)
def __setitem__(self, name, value):
"""Dict-like __setitem__ for compatibility with client code. Throws exception
if there is already a cookie of that name in the jar. In that case, use the more
explicit set() method instead."""
"""Dict-like __setitem__ for compatibility with client code. Throws
exception if there is already a cookie of that name in the jar. In that
case, use the more explicit set() method instead."""
self.set(name, value)
def __delitem__(self, name):
"""Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
"""Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
``remove_cookie_by_name()``."""
remove_cookie_by_name(self, name)
def set_cookie(self, cookie, *args, **kwargs):
@@ -295,15 +306,16 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""Updates this jar with cookies from another CookieJar or dict-like"""
if isinstance(other, cookielib.CookieJar):
for cookie in other:
self.set_cookie(cookie)
self.set_cookie(copy.copy(cookie))
else:
super(RequestsCookieJar, self).update(other)
def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values. Takes as args name
and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
_find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
if there are conflicting cookies."""
"""Requests uses this method internally to get cookie values. Takes as
args name and optional domain and path. Returns a cookie.value. If
there are conflicting cookies, _find arbitrarily chooses one. See
_find_no_duplicates if you want an exception thrown if there are
conflicting cookies."""
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
@@ -313,10 +325,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
def _find_no_duplicates(self, name, domain=None, path=None):
"""__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
Takes as args name and optional domain and path. Returns a cookie.value.
Throws KeyError if cookie is not found and CookieConflictError if there are
multiple cookies that match name and optionally domain and path."""
"""Both ``__get_item__`` and ``get`` call this function: it's never
used elsewhere in Requests. Takes as args name and optional domain and
path. Returns a cookie.value. Throws KeyError if cookie is not found
and CookieConflictError if there are multiple cookies that match name
and optionally domain and path."""
toReturn = None
for cookie in iter(self):
if cookie.name == name:
@@ -350,6 +363,21 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
return new_cj
def _copy_cookie_jar(jar):
if jar is None:
return None
if hasattr(jar, 'copy'):
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
new_jar = copy.copy(jar)
new_jar.clear()
for cookie in jar:
new_jar.set_cookie(copy.copy(cookie))
return new_jar
def create_cookie(name, value, **kwargs):
"""Make a cookie from underspecified parameters.
@@ -390,11 +418,14 @@ 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 = int(time.mktime(
time.strptime(morsel['expires'], time_template)) - time.timezone)
return create_cookie(
comment=morsel['comment'],
comment_url=bool(morsel['comment']),
@@ -440,7 +471,7 @@ def merge_cookies(cookiejar, cookies):
"""
if not isinstance(cookiejar, cookielib.CookieJar):
raise ValueError('You can only merge into CookieJar')
if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict(
cookies, cookiejar=cookiejar, overwrite=False)

View File

@@ -46,15 +46,16 @@ class SSLError(ConnectionError):
class Timeout(RequestException):
"""The request timed out.
Catching this error will catch both :exc:`ConnectTimeout` and
:exc:`ReadTimeout` errors.
Catching this error will catch both
:exc:`~requests.exceptions.ConnectTimeout` and
:exc:`~requests.exceptions.ReadTimeout` errors.
"""
class ConnectTimeout(ConnectionError, Timeout):
"""The request timed out while trying to connect to the server.
"""The request timed out while trying to connect to the remote server.
Requests that produce this error are safe to retry
Requests that produced this error are safe to retry.
"""
@@ -88,3 +89,26 @@ class ChunkedEncodingError(RequestException):
class ContentDecodingError(RequestException, BaseHTTPError):
"""Failed to decode response content"""
class StreamConsumedError(RequestException, TypeError):
"""The content for this response was already consumed"""
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

@@ -15,22 +15,23 @@ from .hooks import default_hooks
from .structures import CaseInsensitiveDict
from .auth import HTTPBasicAuth
from .cookies import cookiejar_from_dict, get_cookie_header
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
from .packages.urllib3.fields import RequestField
from .packages.urllib3.filepost import encode_multipart_formdata
from .packages.urllib3.util import parse_url
from .packages.urllib3.exceptions import (
DecodeError, ReadTimeoutError, ProtocolError)
DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
from .exceptions import (
HTTPError, RequestException, MissingSchema, InvalidURL,
ChunkedEncodingError, ContentDecodingError, ConnectionError)
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
ContentDecodingError, ConnectionError, StreamConsumedError)
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
stream_decode_response_unicode, to_key_val_list, parse_header_links,
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,6 +43,7 @@ REDIRECT_STATI = (
codes.temporary_redirect, # 307
codes.permanent_redirect, # 308
)
DEFAULT_REDIRECT_LIMIT = 30
CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
@@ -79,7 +81,7 @@ class RequestEncodingMixin(object):
"""
if isinstance(data, (str, bytes)):
return data
return to_native_string(data)
elif hasattr(data, 'read'):
return data
elif hasattr(data, '__iter__'):
@@ -141,13 +143,13 @@ class RequestEncodingMixin(object):
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, str):
fp = StringIO(fp)
if isinstance(fp, bytes):
fp = BytesIO(fp)
rf = RequestField(name=k, data=fp.read(),
filename=fn, headers=fh)
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
else:
fdata = fp.read()
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
@@ -189,7 +191,8 @@ class Request(RequestHooksMixin):
:param url: URL to send.
:param headers: dictionary of headers to send.
:param files: dictionary of {filename: fileobject} files to multipart upload.
:param data: the body to attach the request. If a dictionary is provided, form-encoding will take place.
: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 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.
@@ -203,16 +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):
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
@@ -230,6 +225,7 @@ class Request(RequestHooksMixin):
self.headers = headers
self.files = files
self.data = data
self.json = json
self.params = params
self.auth = auth
self.cookies = cookies
@@ -246,6 +242,7 @@ class Request(RequestHooksMixin):
headers=self.headers,
files=self.files,
data=self.data,
json=self.json,
params=self.params,
auth=self.auth,
cookies=self.cookies,
@@ -289,15 +286,16 @@ 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):
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
"""Prepares the entire request with the given parameters."""
self.prepare_method(method)
self.prepare_url(url, params)
self.prepare_headers(headers)
self.prepare_cookies(cookies)
self.prepare_body(data, files)
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.
@@ -312,7 +310,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
p.method = self.method
p.url = self.url
p.headers = self.headers.copy() if self.headers is not None else None
p._cookies = self._cookies.copy() if self._cookies is not None else None
p._cookies = _copy_cookie_jar(self._cookies)
p.body = self.body
p.hooks = self.hooks
return p
@@ -321,30 +319,38 @@ 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.
try:
url = unicode(url)
except NameError:
# We're on Python 3.
url = str(url)
except UnicodeDecodeError:
pass
#: 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
if isinstance(url, bytes):
url = url.decode('utf8')
else:
url = unicode(url) if is_py2 else str(url)
# Don't do any URL preparation for oddball schemes
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
if ':' in url and not url.lower().startswith('http'):
self.url = url
return
# Support for unicode domain names and paths.
scheme, auth, host, port, path, query, fragment = parse_url(url)
try:
scheme, auth, host, port, path, query, fragment = parse_url(url)
except LocationParseError as e:
raise InvalidURL(*e.args)
if not scheme:
raise MissingSchema("Invalid URL {0!r}: No schema supplied. "
"Perhaps you meant http://{0}?".format(url))
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)
@@ -397,7 +403,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
else:
self.headers = CaseInsensitiveDict()
def prepare_body(self, data, files):
def prepare_body(self, data, files, json=None):
"""Prepares the given HTTP body data."""
# Check if file, fo, generator, iterator.
@@ -408,6 +414,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
content_type = None
length = None
if not data and json is not None:
content_type = 'application/json'
body = complexjson.dumps(json)
is_stream = all([
hasattr(data, '__iter__'),
not isinstance(data, (basestring, list, tuple, dict))
@@ -424,7 +434,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'
@@ -443,7 +453,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.prepare_content_length(body)
# Add content-type if it wasn't explicitly provided.
if (content_type) and (not 'content-type' in self.headers):
if content_type and ('content-type' not in self.headers):
self.headers['Content-Type'] = content_type
self.body = body
@@ -457,7 +467,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
l = super_len(body)
if l:
self.headers['Content-Length'] = builtin_str(l)
elif self.method not in ('GET', 'HEAD'):
elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None):
self.headers['Content-Length'] = '0'
def prepare_auth(self, auth, url=''):
@@ -483,7 +493,15 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.prepare_content_length(self.body)
def prepare_cookies(self, cookies):
"""Prepares the given HTTP cookie data."""
"""Prepares the given HTTP cookie data.
This function eventually generates a ``Cookie`` header from the
given cookies using cookielib. Due to cookielib's design, the header
will not be regenerated if it already exists, meaning this function
can only be called once for the life of the
:class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
header is removed beforehand."""
if isinstance(cookies, cookielib.CookieJar):
self._cookies = cookies
@@ -496,6 +514,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
def prepare_hooks(self, hooks):
"""Prepares the given hooks."""
# hooks can be passed as None to the prepare method and to this
# method. To prevent iterating over None, simply use an empty list
# if hooks is False-y
hooks = hooks or []
for event in hooks:
self.register_hook(event, hooks[event])
@@ -506,16 +528,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):
@@ -555,7 +569,11 @@ class Response(object):
self.cookies = cookiejar_from_dict({})
#: The amount of time elapsed between sending the request
#: and the arrival of the response (as a timedelta)
#: and the arrival of the response (as a timedelta).
#: This property specifically measures the time taken between sending
#: the first byte of the request and finishing parsing the headers. It
#: is therefore unaffected by consuming the response content or the
#: value of the ``stream`` keyword argument.
self.elapsed = datetime.timedelta(0)
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
@@ -600,7 +618,7 @@ class Response(object):
def ok(self):
try:
self.raise_for_status()
except RequestException:
except HTTPError:
return False
return True
@@ -613,7 +631,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
@@ -631,9 +649,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
@@ -643,7 +662,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)
@@ -653,6 +672,8 @@ class Response(object):
self._content_consumed = True
if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError()
# simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size)
@@ -665,10 +686,12 @@ class Response(object):
return chunks
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None):
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None):
"""Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the
content at once into memory for large responses.
.. note:: This method is not reentrant safe.
"""
pending = None
@@ -677,7 +700,11 @@ class Response(object):
if pending is not None:
chunk = pending + chunk
lines = chunk.splitlines()
if delimiter:
lines = chunk.split(delimiter)
else:
lines = chunk.splitlines()
if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
pending = lines.pop()
@@ -766,14 +793,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):
@@ -799,10 +828,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)
@@ -813,4 +842,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

@@ -15,7 +15,7 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
__version__ = "2.2.1"
__version__ = "2.3.0"
from sys import version_info

View File

@@ -12,34 +12,68 @@ Example::
If no paths are provided, it takes its input from stdin.
"""
from io import open
from sys import argv, stdin
from __future__ import absolute_import, print_function, unicode_literals
import argparse
import sys
from io import open
from chardet import __version__
from chardet.universaldetector import UniversalDetector
def description_of(file, name='stdin'):
"""Return a string describing the probable encoding of a file."""
def description_of(lines, name='stdin'):
"""
Return a string describing the probable encoding of a file or
list of strings.
:param lines: The lines to get the encoding of.
:type lines: Iterable of bytes
:param name: Name of file or collection of lines
:type name: str
"""
u = UniversalDetector()
for line in file:
for line in lines:
u.feed(line)
u.close()
result = u.result
if result['encoding']:
return '%s: %s with confidence %s' % (name,
result['encoding'],
result['confidence'])
return '{0}: {1} with confidence {2}'.format(name, result['encoding'],
result['confidence'])
else:
return '%s: no result' % name
return '{0}: no result'.format(name)
def main():
if len(argv) <= 1:
print(description_of(stdin))
else:
for path in argv[1:]:
with open(path, 'rb') as f:
print(description_of(f, path))
def main(argv=None):
'''
Handles command line arguments and gets things started.
:param argv: List of arguments, as if specified on the command-line.
If None, ``sys.argv[1:]`` is used instead.
:type argv: list of str
'''
# Get command line arguments
parser = argparse.ArgumentParser(
description="Takes one or more file paths and reports their detected \
encodings",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
conflict_handler='resolve')
parser.add_argument('input',
help='File whose encoding we would like to determine.',
type=argparse.FileType('rb'), nargs='*',
default=[sys.stdin])
parser.add_argument('--version', action='version',
version='%(prog)s {0}'.format(__version__))
args = parser.parse_args(argv)
for f in args.input:
if f.isatty():
print("You are running chardetect interactively. Press " +
"CTRL-D twice at the start of a blank line to signal the " +
"end of your input. If you want help, run chardetect " +
"--help\n", file=sys.stderr)
print(description_of(f, f.name))
if __name__ == '__main__':

View File

@@ -177,6 +177,12 @@ class JapaneseContextAnalysis:
return -1, 1
class SJISContextAnalysis(JapaneseContextAnalysis):
def __init__(self):
self.charset_name = "SHIFT_JIS"
def get_charset_name(self):
return self.charset_name
def get_order(self, aBuf):
if not aBuf:
return -1, 1
@@ -184,6 +190,8 @@ class SJISContextAnalysis(JapaneseContextAnalysis):
first_char = wrap_ord(aBuf[0])
if ((0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC)):
charLen = 2
if (first_char == 0x87) or (0xFA <= first_char <= 0xFC):
self.charset_name = "CP932"
else:
charLen = 1

View File

@@ -129,11 +129,11 @@ class Latin1Prober(CharSetProber):
if total < 0.01:
confidence = 0.0
else:
confidence = ((self._mFreqCounter[3] / total)
- (self._mFreqCounter[1] * 20.0 / total))
confidence = ((self._mFreqCounter[3] - self._mFreqCounter[1] * 20.0)
/ total)
if confidence < 0.0:
confidence = 0.0
# lower the confidence of latin1 so that other more accurate
# detector can take priority.
confidence = confidence * 0.5
confidence = confidence * 0.73
return confidence

View File

@@ -353,7 +353,7 @@ SJIS_cls = (
2,2,2,2,2,2,2,2, # 68 - 6f
2,2,2,2,2,2,2,2, # 70 - 77
2,2,2,2,2,2,2,1, # 78 - 7f
3,3,3,3,3,3,3,3, # 80 - 87
3,3,3,3,3,2,2,3, # 80 - 87
3,3,3,3,3,3,3,3, # 88 - 8f
3,3,3,3,3,3,3,3, # 90 - 97
3,3,3,3,3,3,3,3, # 98 - 9f
@@ -369,9 +369,8 @@ SJIS_cls = (
2,2,2,2,2,2,2,2, # d8 - df
3,3,3,3,3,3,3,3, # e0 - e7
3,3,3,3,3,4,4,4, # e8 - ef
4,4,4,4,4,4,4,4, # f0 - f7
4,4,4,4,4,0,0,0 # f8 - ff
)
3,3,3,3,3,3,3,3, # f0 - f7
3,3,3,3,3,0,0,0) # f8 - ff
SJIS_st = (
@@ -571,5 +570,3 @@ UTF8SMModel = {'classTable': UTF8_cls,
'stateTable': UTF8_st,
'charLenTable': UTF8CharLenTable,
'name': 'UTF-8'}
# flake8: noqa

View File

@@ -47,7 +47,7 @@ class SJISProber(MultiByteCharSetProber):
self._mContextAnalyzer.reset()
def get_charset_name(self):
return "SHIFT_JIS"
return self._mContextAnalyzer.get_charset_name()
def feed(self, aBuf):
aLen = len(aBuf)

View File

@@ -71,9 +71,9 @@ class UniversalDetector:
if not self._mGotData:
# If the data starts with BOM, we know it is UTF
if aBuf[:3] == codecs.BOM:
if aBuf[:3] == codecs.BOM_UTF8:
# EF BB BF UTF-8 with BOM
self.result = {'encoding': "UTF-8", 'confidence': 1.0}
self.result = {'encoding': "UTF-8-SIG", 'confidence': 1.0}
elif aBuf[:4] == codecs.BOM_UTF32_LE:
# FF FE 00 00 UTF-32, little-endian BOM
self.result = {'encoding': "UTF-32LE", 'confidence': 1.0}

View File

@@ -2,10 +2,8 @@
urllib3 - Thread-safe connection pooling and re-using.
"""
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = 'dev'
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'
__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,9 +75,16 @@ def add_stderr_logger(level=logging.DEBUG):
del NullHandler
# Set security warning to only go off once by default.
import warnings
warnings.simplefilter('module', exceptions.SecurityWarning)
# 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,7 +1,8 @@
from __future__ import absolute_import
from collections import Mapping, MutableMapping
try:
from threading import RLock
except ImportError: # Platform-specific: No threads available
except ImportError: # Platform-specific: No threads available
class RLock:
def __enter__(self):
pass
@@ -10,11 +11,11 @@ except ImportError: # Platform-specific: No threads available
pass
try: # Python 2.7+
try: # Python 2.7+
from collections import OrderedDict
except ImportError:
from .packages.ordered_dict import OrderedDict
from .packages.six import itervalues
from .packages.six import iterkeys, itervalues, PY3
__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict']
@@ -85,8 +86,7 @@ class RecentlyUsedContainer(MutableMapping):
def clear(self):
with self.lock:
# Copy pointers to all values, then wipe the mapping
# under Python 2, this copies the list of values twice :-|
values = list(self._container.values())
values = list(itervalues(self._container))
self._container.clear()
if self.dispose_func:
@@ -95,7 +95,7 @@ class RecentlyUsedContainer(MutableMapping):
def keys(self):
with self.lock:
return self._container.keys()
return list(iterkeys(self._container))
class HTTPHeaderDict(MutableMapping):
@@ -130,25 +130,82 @@ class HTTPHeaderDict(MutableMapping):
'foo=bar, baz=quxx'
>>> headers['Content-Length']
'7'
If you want to access the raw headers with their original casing
for debugging purposes you can access the private ``._data`` attribute
which is a normal python ``dict`` that maps the case-insensitive key to a
list of tuples stored as (case-sensitive-original-name, value). Using the
structure from above as our example:
>>> headers._data
{'set-cookie': [('Set-Cookie', 'foo=bar'), ('set-cookie', 'baz=quxx')],
'content-length': [('content-length', '7')]}
"""
def __init__(self, headers=None, **kwargs):
self._data = {}
if headers is None:
headers = {}
self.update(headers, **kwargs)
super(HTTPHeaderDict, self).__init__()
self._container = {}
if headers is not None:
if isinstance(headers, HTTPHeaderDict):
self._copy_from(headers)
else:
self.extend(headers)
if kwargs:
self.extend(kwargs)
def add(self, key, value):
def __setitem__(self, key, val):
self._container[key.lower()] = (key, val)
return self._container[key.lower()]
def __getitem__(self, key):
val = self._container[key.lower()]
return ', '.join(val[1:])
def __delitem__(self, key):
del self._container[key.lower()]
def __contains__(self, key):
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((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)
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.
'''
# Using the MutableMapping function directly fails due to the private marker.
# Using ordinary dict.pop would expose the internal structures.
# So let's reinvent the wheel.
try:
value = self[key]
except KeyError:
if default is self.__marker:
raise
return default
else:
del self[key]
return value
def discard(self, key):
try:
del self[key]
except KeyError:
pass
def add(self, key, val):
"""Adds a (name, value) pair, doesn't overwrite the value if it already
exists.
@@ -157,43 +214,111 @@ class HTTPHeaderDict(MutableMapping):
>>> headers['foo']
'bar, baz'
"""
self._data.setdefault(key.lower(), []).append((key, value))
key_lower = key.lower()
new_vals = key, val
# Keep the common case aka no item present as fast as possible
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):
# If already several items got inserted, we have a list
vals.append(val)
else:
# vals should be a tuple then, i.e. only one item so far
# Need to convert the tuple to list for further extension
self._container[key_lower] = [vals[0], vals[1], val]
def extend(self, *args, **kwargs):
"""Generic import function for any type of header-like object.
Adapted version of MutableMapping.update in order to insert items
with self.add instead of self.__setitem__
"""
if len(args) > 1:
raise TypeError("extend() takes at most 1 positional "
"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)
elif isinstance(other, Mapping):
for key in other:
self.add(key, other[key])
elif hasattr(other, "keys"):
for key in other.keys():
self.add(key, other[key])
else:
for key, value in other:
self.add(key, value)
for key, value in kwargs.items():
self.add(key, value)
def getlist(self, key):
"""Returns a list of all the values for the named field. Returns an
empty list if the key doesn't exist."""
return self[key].split(', ') if key in self else []
try:
vals = self._container[key.lower()]
except KeyError:
return []
else:
if isinstance(vals, tuple):
return [vals[1]]
else:
return vals[1:]
def copy(self):
h = HTTPHeaderDict()
for key in self._data:
for rawkey, value in self._data[key]:
h.add(rawkey, value)
return h
def __eq__(self, other):
if not isinstance(other, Mapping):
return False
other = HTTPHeaderDict(other)
return dict((k1, self[k1]) for k1 in self._data) == \
dict((k2, other[k2]) for k2 in other._data)
def __getitem__(self, key):
values = self._data[key.lower()]
return ', '.join(value[1] for value in values)
def __setitem__(self, key, value):
self._data[key.lower()] = [(key, value)]
def __delitem__(self, key):
del self._data[key.lower()]
def __len__(self):
return len(self._data)
def __iter__(self):
for headers in itervalues(self._data):
yield headers[0][0]
# Backwards compatibility for httplib
getheaders = getlist
getallmatchingheaders = getlist
iget = getlist
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
def _copy_from(self, other):
for key in other:
val = other.getlist(key)
if isinstance(val, list):
# Don't need to convert tuples
val = list(val)
self._container[key.lower()] = [key] + val
def copy(self):
clone = type(self)()
clone._copy_from(self)
return clone
def iteritems(self):
"""Iterate over all header lines, including duplicate ones."""
for key in self:
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 = 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
"""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
# 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()))
return cls(headers)

View File

@@ -1,22 +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.
@@ -26,12 +24,21 @@ except (ImportError, AttributeError): # Platform-specific: No SSL.
pass
try: # Python 3:
# Not a no-op, we're adding this to the namespace so it can be imported.
ConnectionError = ConnectionError
except NameError: # Python 2:
class ConnectionError(Exception):
pass
from .exceptions import (
NewConnectionError,
ConnectTimeoutError,
SubjectAltNameWarning,
SystemTimeWarning,
)
from .packages.ssl_match_hostname import match_hostname
from .packages import six
from .util.ssl_ import (
resolve_cert_reqs,
@@ -40,8 +47,8 @@ from .util.ssl_ import (
assert_fingerprint,
)
from .util import connection
from .util import connection
port_by_scheme = {
'http': 80,
@@ -51,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
@@ -124,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):
@@ -176,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
@@ -225,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)
@@ -233,14 +256,24 @@ class VerifiedHTTPSConnection(HTTPSConnection):
self.assert_fingerprint)
elif resolved_cert_reqs != ssl.CERT_NONE \
and self.assert_hostname is not False:
match_hostname(self.sock.getpeercert(),
self.assert_hostname or hostname)
cert = self.sock.getpeercert()
if not cert.get('subjectAltName', ()):
warnings.warn((
'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)
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or
self.assert_fingerprint is not None)
if ssl:
# Make a copy for testing.
UnverifiedHTTPSConnection = HTTPSConnection
HTTPSConnection = VerifiedHTTPSConnection
else:
HTTPSConnection = DummyConnection

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
@@ -38,9 +42,10 @@ 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,14 +69,28 @@ 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):
return '%s(host=%r, port=%r)' % (type(self).__name__,
self.host, self.port)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
# Return False to re-raise any potential exceptions
return False
def close():
"""
Close all pooled connections and disable the pool.
"""
pass
# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK])
@@ -105,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.
@@ -266,6 +285,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
"""
pass
def _prepare_proxy(self, conn):
# Nothing to do for HTTP connections.
pass
def _get_timeout(self, timeout):
""" Helper that always returns a :class:`urllib3.util.Timeout` """
if timeout is _Default:
@@ -278,6 +301,23 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# can be removed later
return Timeout.from_float(timeout)
def _raise_timeout(self, err, url, timeout_value):
"""Is the error actually a timeout? Will raise a ReadTimeout or pass"""
if isinstance(err, SocketTimeout):
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
# See the above comment about EAGAIN in Python 3. In Python 2 we have
# to specifically catch it and throw the timeout error
if hasattr(err, 'errno') and err.errno in _blocking_errnos:
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
# Catch possible read timeouts thrown as SSL errors. If not the
# case, rethrow the original. We need to do this because of:
# http://bugs.python.org/issue10272
if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python 2.6
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
def _make_request(self, conn, method, url, timeout=_Default,
**httplib_request_kw):
"""
@@ -301,7 +341,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
conn.timeout = timeout_obj.connect_timeout
# Trigger any extra validation we need to do.
self._validate_conn(conn)
try:
self._validate_conn(conn)
except (SocketTimeout, BaseSSLError) as e:
# Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout.
self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
raise
# conn.request() calls httplib.*.request, not the method in
# urllib3.request. It also calls makefile (recv) on the socket.
@@ -327,32 +372,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# Receive the response from the server
try:
try: # Python 2.7+, use buffering of HTTP responses
try: # Python 2.7, use buffering of HTTP responses
httplib_response = conn.getresponse(buffering=True)
except TypeError: # Python 2.6 and older
httplib_response = conn.getresponse()
except SocketTimeout:
raise ReadTimeoutError(
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
except BaseSSLError as e:
# Catch possible read timeouts thrown as SSL errors. If not the
# case, rethrow the original. We need to do this because of:
# http://bugs.python.org/issue10272
if 'timed out' in str(e) or \
'did not complete (read)' in str(e): # Python 2.6
raise ReadTimeoutError(
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
raise
except SocketError as e: # Platform-specific: Python 2
# See the above comment about EAGAIN in Python 3. In Python 2 we
# have to specifically catch it and throw the timeout error
if e.errno in _blocking_errnos:
raise ReadTimeoutError(
self, url, "Read timed out. (read timeout=%s)" % read_timeout)
except (SocketTimeout, BaseSSLError, SocketError) as e:
self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
raise
# AppEngine doesn't have a version attr.
@@ -360,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.
@@ -508,11 +544,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
try:
# Request a connection from the queue.
timeout_obj = self._get_timeout(timeout)
conn = self._get_conn(timeout=pool_timeout)
conn.timeout = timeout_obj.connect_timeout
is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)
if is_new_proxy_conn:
self._prepare_proxy(conn)
# Make the request on the httplib connection object.
httplib_response = self._make_request(conn, method, url,
timeout=timeout,
timeout=timeout_obj,
body=body, headers=headers)
# If we're going to release the connection in ``finally:``, then
@@ -537,26 +580,33 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
raise EmptyPoolError(self, "No pool connections are available.")
except (BaseSSLError, CertificateError) as e:
# Release connection unconditionally because there is no way to
# close it externally in case of exception.
# Close the connection. If a connection is reused on which there
# was a Certificate error, the next request will certainly raise
# another Certificate error.
conn = conn and conn.close()
release_conn = True
raise SSLError(e)
except (TimeoutError, HTTPException, SocketError) 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 SSLError:
# Treat SSLError separately from BaseSSLError to preserve
# traceback.
conn = conn and conn.close()
release_conn = True
raise
stacktrace = sys.exc_info()[2]
if isinstance(e, SocketError) and self.proxy:
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, NewConnectionError)) and self.proxy:
e = ProxyError('Cannot connect to proxy.', e)
elif isinstance(e, (SocketError, HTTPException)):
e = ProtocolError('Connection aborted.', e)
retries = retries.increment(method, url, error=e,
_pool=self, _stacktrace=stacktrace)
retries = retries.increment(method, url, error=e, _pool=self,
_stacktrace=sys.exc_info()[2])
retries.sleep()
# Keep track of the error for the retry warning.
@@ -588,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
@@ -624,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'
@@ -640,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
@@ -664,28 +724,31 @@ 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
if self.proxy is not None:
# Python 2.7+
try:
set_tunnel = conn.set_tunnel
except AttributeError: # Platform-specific: Python 2.6
set_tunnel = conn._set_tunnel
if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older
set_tunnel(self.host, self.port)
else:
set_tunnel(self.host, self.port, self.proxy_headers)
# Establish tunnel connection early, because otherwise httplib
# would improperly set Host: header to proxy's IP:port.
conn.connect()
return conn
def _prepare_proxy(self, conn):
"""
Establish tunnel connection early, because otherwise httplib
would improperly set Host: header to proxy's IP:port.
"""
# Python 2.7+
try:
set_tunnel = conn.set_tunnel
except AttributeError: # Platform-specific: Python 2.6
set_tunnel = conn._set_tunnel
if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older
set_tunnel(self.host, self.port)
else:
set_tunnel(self.host, self.port, self.proxy_headers)
conn.connect()
def _new_conn(self):
"""
Return a fresh :class:`httplib.HTTPSConnection`.
@@ -695,7 +758,6 @@ class HTTPSConnectionPool(HTTPConnectionPool):
% (self.num_connections, self.host))
if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
# Platform-specific: Python without ssl
raise SSLError("Can't connect to HTTPS URL because the SSL "
"module is not available.")
@@ -725,8 +787,7 @@ class HTTPSConnectionPool(HTTPConnectionPool):
warnings.warn((
'Unverified HTTPS request is being made. '
'Adding certificate verification is strongly advised. See: '
'https://urllib3.readthedocs.org/en/latest/security.html '
'(This warning will only appear once by default.)'),
'https://urllib3.readthedocs.org/en/latest/security.html'),
InsecureRequestWarning)

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

@@ -29,7 +29,7 @@ Now you can use :mod:`urllib3` as you normally would, and it will support SNI
when the required modules are installed.
Activating this module also has the positive side effect of disabling SSL/TLS
encryption in Python 2 (see `CRIME attack`_).
compression in Python 2 (see `CRIME attack`_).
If you want to configure the default list of supported cipher suites, you can
set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable.
@@ -38,13 +38,12 @@ Module Variables
----------------
:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
Default: ``ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:
ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS``
.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
.. _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
@@ -55,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
@@ -70,33 +69,31 @@ HAS_SNI = SUBJ_ALT_NAME_SUPPORT
# Map from urllib3 to PyOpenSSL compatible parameter-values.
_openssl_versions = {
ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD,
ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD,
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:
pass
_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,
}
# A secure default.
# Sources for more information on TLS ciphers:
#
# - https://wiki.mozilla.org/Security/Server_Side_TLS
# - https://www.ssllabs.com/projects/best-practices/index.html
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
#
# The general intent is:
# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
# - prefer ECDHE over DHE for better performance,
# - prefer any AES-GCM over any AES-CBC for better performance and security,
# - use 3DES as fallback which is secure but slow,
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
DEFAULT_SSL_CIPHER_LIST = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:" + \
"ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:" + \
"!aNULL:!MD5:!DSS"
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
@@ -116,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'''
@@ -127,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 = []
@@ -184,6 +181,11 @@ class WrappedSocket(object):
except OpenSSL.SSL.SysCallError as e:
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
return b''
else:
raise SocketError(e)
except OpenSSL.SSL.ZeroReturnError as e:
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
return b''
else:
raise
except OpenSSL.SSL.WantReadError:
@@ -199,12 +201,33 @@ class WrappedSocket(object):
def settimeout(self, timeout):
return self.socket.settimeout(timeout)
def _send_until_done(self, data):
while True:
try:
return self.connection.send(data)
except OpenSSL.SSL.WantWriteError:
_, wlist, _ = select.select([], [self.socket], [],
self.socket.gettimeout())
if not wlist:
raise timeout()
continue
def sendall(self, data):
return self.connection.sendall(data)
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
@@ -245,17 +268,18 @@ 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
ctx.use_certificate_file(certfile)
if keyfile:
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:
@@ -275,10 +299,12 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
try:
cnx.do_handshake()
except OpenSSL.SSL.WantReadError:
select.select([sock], [], [])
rd, _, _ = select.select([sock], [], [], sock.gettimeout())
if not rd:
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.
@@ -72,11 +73,8 @@ class MaxRetryError(RequestError):
def __init__(self, pool, url, reason=None):
self.reason = reason
message = "Max retries exceeded with url: %s" % url
if reason:
message += " (Caused by %r)" % reason
else:
message += " (Caused by redirect)"
message = "Max retries exceeded with url: %s (Caused by %r)" % (
url, reason)
RequestError.__init__(self, pool, url, message)
@@ -116,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
@@ -141,11 +144,22 @@ class LocationParseError(LocationValueError):
self.location = location
class ResponseError(HTTPError):
"Used as a container for an error reason supplied in a MaxRetryError."
GENERIC_ERROR = 'too many error responses'
SPECIFIC_ERROR = 'too many {status_code} error responses'
class SecurityWarning(HTTPWarning):
"Warned when perfoming security reducing actions"
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
@@ -154,3 +168,34 @@ class InsecureRequestWarning(SecurityWarning):
class SystemTimeWarning(SecurityWarning):
"Warned when system time is suspected to be wrong"
pass
class InsecurePlatformWarning(SecurityWarning):
"Warned when certain SSL configuration is not available on a platform."
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
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):
@@ -64,6 +65,14 @@ class PoolManager(RequestMethods):
self.pools = RecentlyUsedContainer(num_pools,
dispose_func=lambda p: p.close())
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.clear()
# Return False to re-raise any potential exceptions
return False
def _new_pool(self, scheme, host, port):
"""
Create a new :class:`ConnectionPool` based on host, port and scheme.
@@ -167,7 +176,14 @@ class PoolManager(RequestMethods):
if not isinstance(retries, Retry):
retries = Retry.from_int(retries, redirect=redirect)
kw['retries'] = retries.increment(method, redirect_location)
try:
retries = retries.increment(method, url, response=response, _pool=conn)
except MaxRetryError:
if retries.raise_on_redirect:
raise
return response
kw['retries'] = retries
kw['redirect'] = redirect
log.info("Redirecting %s -> %s" % (url, redirect_location))
@@ -212,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,
@@ -118,18 +127,25 @@ class RequestMethods(object):
which is used to compose the body of the request. The random boundary
string can be explicitly set with the ``multipart_boundary`` parameter.
"""
if encode_multipart:
body, content_type = encode_multipart_formdata(
fields or {}, boundary=multipart_boundary)
else:
body, content_type = (urlencode(fields or {}),
'application/x-www-form-urlencoded')
if headers is None:
headers = self.headers
headers_ = {'Content-Type': content_type}
headers_.update(headers)
extra_kw = {'headers': {}}
return self.urlopen(method, url, body=body, headers=headers_,
**urlopen_kw)
if fields:
if 'body' in urlopen_kw:
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)
else:
body, content_type = urlencode(fields), 'application/x-www-form-urlencoded'
extra_kw['body'] = body
extra_kw['headers'] = {'Content-Type': content_type}
extra_kw['headers'].update(headers)
extra_kw.update(urlopen_kw)
return self.urlopen(method, url, **extra_kw)

View File

@@ -1,13 +1,18 @@
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
from .packages.six import string_types as basestring, binary_type
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):
@@ -21,6 +26,9 @@ class DeflateDecoder(object):
return getattr(self._obj, name)
def decompress(self, data):
if not data:
return data
if not self._first_try:
return self._obj.decompress(data)
@@ -36,9 +44,23 @@ class DeflateDecoder(object):
self._data = None
class GzipDecoder(object):
def __init__(self):
self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS)
def __getattr__(self, name):
return getattr(self._obj, name)
def decompress(self, data):
if not data:
return data
return self._obj.decompress(data)
def _get_decoder(mode):
if mode == 'gzip':
return zlib.decompressobj(16 + zlib.MAX_WBITS)
return GzipDecoder()
return DeflateDecoder()
@@ -76,9 +98,10 @@ class HTTPResponse(io.IOBase):
strict=0, preload_content=True, decode_content=True,
original_response=None, pool=None, connection=None):
self.headers = HTTPHeaderDict()
if headers:
self.headers.update(headers)
if isinstance(headers, HTTPHeaderDict):
self.headers = headers
else:
self.headers = HTTPHeaderDict(headers)
self.status = status
self.version = version
self.reason = reason
@@ -100,6 +123,16 @@ class HTTPResponse(io.IOBase):
if hasattr(body, 'read'):
self._fp = body
# Are we using the chunked-style of transfer encoding?
self.chunked = False
self.chunk_left = None
tr_enc = self.headers.get('transfer-encoding', '').lower()
# Don't incur the penalty of creating a list and then discarding it
encodings = (enc.strip() for enc in tr_enc.split(","))
if "chunked" in encodings:
self.chunked = True
# If requested, preload the body.
if preload_content and not self._body:
self._body = self.read(decode_content=decode_content)
@@ -140,6 +173,93 @@ class HTTPResponse(io.IOBase):
"""
return self._fp_bytes_read
def _init_decoder(self):
"""
Set-up the _decoder attribute if necessar.
"""
# Note: content-encoding value should be case-insensitive, per RFC 7230
# Section 3.2
content_encoding = self.headers.get('content-encoding', '').lower()
if self._decoder is None and content_encoding in self.CONTENT_DECODERS:
self._decoder = _get_decoder(content_encoding)
def _decode(self, data, decode_content, flush_decoder):
"""
Decode the data passed in and potentially flush the decoder.
"""
try:
if decode_content and self._decoder:
data = self._decoder.decompress(data)
except (IOError, zlib.error) as e:
content_encoding = self.headers.get('content-encoding', '').lower()
raise DecodeError(
"Received response with content-encoding: %s, but "
"failed to decode it." % content_encoding, e)
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
@@ -161,12 +281,7 @@ class HTTPResponse(io.IOBase):
after having ``.read()`` the file object. (Overridden if ``amt`` is
set.)
"""
# Note: content-encoding value should be case-insensitive, per RFC 7230
# Section 3.2
content_encoding = self.headers.get('content-encoding', '').lower()
if self._decoder is None:
if content_encoding in self.CONTENT_DECODERS:
self._decoder = _get_decoder(content_encoding)
self._init_decoder()
if decode_content is None:
decode_content = self.decode_content
@@ -174,67 +289,36 @@ 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 not 'read operation timed out' 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)
try:
if decode_content and self._decoder:
data = self._decoder.decompress(data)
except (IOError, zlib.error) as e:
raise DecodeError(
"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()
data = self._decode(data, decode_content, flush_decoder)
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):
"""
@@ -252,11 +336,15 @@ class HTTPResponse(io.IOBase):
If True, will attempt to decode the body based on the
'content-encoding' header.
"""
while not is_fp_closed(self._fp):
data = self.read(amt=amt, decode_content=decode_content)
if self.chunked:
for line in self.read_chunked(amt, decode_content=decode_content):
yield line
else:
while not is_fp_closed(self._fp):
data = self.read(amt=amt, decode_content=decode_content)
if data:
yield data
if data:
yield data
@classmethod
def from_httplib(ResponseCls, r, **response_kw):
@@ -267,14 +355,17 @@ class HTTPResponse(io.IOBase):
Remaining parameters are passed to the HTTPResponse constructor, along
with ``original_response=r``.
"""
headers = r.msg
headers = HTTPHeaderDict()
for k, v in r.getheaders():
headers.add(k, v)
if not isinstance(headers, HTTPHeaderDict):
if PY3: # Python 3
headers = HTTPHeaderDict(headers.items())
else: # Python 2
headers = HTTPHeaderDict.from_httplib(headers)
# HTTPResponse objects in Python 3 don't have a .strict attribute
strict = getattr(r, 'strict', 0)
return ResponseCls(body=r,
resp = ResponseCls(body=r,
headers=headers,
status=r.status,
version=r.version,
@@ -282,6 +373,7 @@ class HTTPResponse(io.IOBase):
strict=strict,
original_response=r,
**response_kw)
return resp
# Backwards-compatibility methods for httplib.HTTPResponse
def getheaders(self):
@@ -331,3 +423,92 @@ class HTTPResponse(io.IOBase):
else:
b[:len(temp)] = temp
return len(temp)
def _update_chunk_length(self):
# First, we'll figure out length of a chunk and then
# we'll try to read it from socket.
if self.chunk_left is not None:
return
line = self._fp.fp.readline()
line = line.split(b';', 1)[0]
try:
self.chunk_left = int(line, 16)
except ValueError:
# Invalid chunked protocol response, abort.
self.close()
raise httplib.IncompleteRead(line)
def _handle_chunk(self, amt):
returned_chunk = None
if amt is None:
chunk = self._fp._safe_read(self.chunk_left)
returned_chunk = chunk
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
self.chunk_left = None
elif amt < self.chunk_left:
value = self._fp._safe_read(amt)
self.chunk_left = self.chunk_left - amt
returned_chunk = value
elif amt == self.chunk_left:
value = self._fp._safe_read(amt)
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
self.chunk_left = None
returned_chunk = value
else: # amt > self.chunk_left
returned_chunk = self._fp._safe_read(self.chunk_left)
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
self.chunk_left = None
return returned_chunk
def read_chunked(self, amt=None, decode_content=None):
"""
Similar to :meth:`HTTPResponse.read`, but with an additional
parameter: ``decode_content``.
:param decode_content:
If True, will attempt to decode the body based on the
'content-encoding' header.
"""
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. "
"Header 'transfer-encoding: chunked' is missing.")
# 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
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
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
# 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,15 +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,11 +1,13 @@
from __future__ import absolute_import
import time
import logging
from ..exceptions import (
ProtocolError,
ConnectTimeoutError,
ReadTimeoutError,
MaxRetryError,
ProtocolError,
ReadTimeoutError,
ResponseError,
)
from ..packages import six
@@ -36,7 +38,6 @@ class Retry(object):
Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless
retries are disabled, in which case the causing exception will be raised.
:param int total:
Total number of retries to allow. Takes precedence over other counts.
@@ -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(
@@ -184,13 +185,13 @@ class Retry(object):
return isinstance(err, ConnectTimeoutError)
def _is_read_error(self, err):
""" Errors that occur after the request has been started, so we can't
assume that the server did not process any of it.
""" Errors that occur after the request has been started, so we should
assume that the server began processing it.
"""
return isinstance(err, (ReadTimeoutError, ProtocolError))
def is_forced_retry(self, method, status_code):
""" Is this method/response retryable? (Based on method/codes whitelists)
""" Is this method/status code retryable? (Based on method/codes whitelists)
"""
if self.method_whitelist and method.upper() not in self.method_whitelist:
return False
@@ -198,8 +199,7 @@ class Retry(object):
return self.status_forcelist and status_code in self.status_forcelist
def is_exhausted(self):
""" Are we out of retries?
"""
""" Are we out of retries? """
retry_counts = (self.total, self.connect, self.read, self.redirect)
retry_counts = list(filter(None, retry_counts))
if not retry_counts:
@@ -207,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
@@ -230,6 +231,7 @@ class Retry(object):
connect = self.connect
read = self.read
redirect = self.redirect
cause = 'unknown'
if error and self._is_connection_error(error):
# Connect retry?
@@ -251,10 +253,16 @@ class Retry(object):
# Redirect retry?
if redirect is not None:
redirect -= 1
cause = 'too many redirects'
else:
# FIXME: Nothing changed, scenario doesn't make sense.
# Incrementing because of a server error like a 500 in
# status_forcelist and a the given method is in the whitelist
_observed_errors += 1
cause = ResponseError.GENERIC_ERROR
if response and response.status:
cause = ResponseError.SPECIFIC_ERROR.format(
status_code=response.status)
new_retry = self.new(
total=total,
@@ -262,13 +270,12 @@ class Retry(object):
_observed_errors=_observed_errors)
if new_retry.is_exhausted():
raise MaxRetryError(_pool, url, error)
raise MaxRetryError(_pool, url, error or ResponseError(cause))
log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry))
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,21 +1,137 @@
from binascii import hexlify, unhexlify
from hashlib import md5, sha1
from __future__ import absolute_import
import errno
import warnings
import hmac
from ..exceptions import SSLError
from binascii import hexlify, unhexlify
from hashlib import md5, sha1, sha256
from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
SSLContext = None
HAS_SNI = False
create_default_context = None
# 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
SSLContext = None
HAS_SNI = False
import ssl
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
from ssl import SSLContext # Modern SSL?
from ssl import HAS_SNI # Has SNI?
except ImportError:
pass
try:
from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION
except ImportError:
OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
OP_NO_COMPRESSION = 0x20000
# A secure default.
# Sources for more information on TLS ciphers:
#
# - https://wiki.mozilla.org/Security/Server_Side_TLS
# - https://www.ssllabs.com/projects/best-practices/index.html
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
#
# The general intent is:
# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
# - prefer ECDHE over DHE for better performance,
# - prefer any AES-GCM over any AES-CBC for better performance and security,
# - use 3DES as fallback which is secure but slow,
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
DEFAULT_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
'!eNULL:!MD5'
)
try:
from ssl import SSLContext # Modern SSL?
except ImportError:
import sys
class SSLContext(object): # Platform-specific: Python 2 & 3.1
supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or
(3, 2) <= sys.version_info)
def __init__(self, protocol_version):
self.protocol = protocol_version
# Use default values from a real SSLContext
self.check_hostname = False
self.verify_mode = ssl.CERT_NONE
self.ca_certs = None
self.options = 0
self.certfile = None
self.keyfile = None
self.ciphers = None
def load_cert_chain(self, certfile, keyfile):
self.certfile = certfile
self.keyfile = keyfile
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:
raise TypeError(
'Your version of Python does not support setting '
'a custom cipher suite. Please upgrade to Python '
'2.7, 3.2, or later if you need this functionality.'
)
self.ciphers = cipher_suite
def wrap_socket(self, socket, server_hostname=None):
warnings.warn(
'A true SSLContext object is not available. This prevents '
'urllib3 from configuring SSL appropriately and may cause '
'certain SSL connections to fail. For more information, see '
'https://urllib3.readthedocs.org/en/latest/security.html'
'#insecureplatformwarning.',
InsecurePlatformWarning
)
kwargs = {
'keyfile': self.keyfile,
'certfile': self.certfile,
'ca_certs': self.ca_certs,
'cert_reqs': self.verify_mode,
'ssl_version': self.protocol,
}
if self.supports_set_ciphers: # Platform-specific: Python 2.7+
return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
else: # Platform-specific: Python 2.6
return wrap_socket(socket, **kwargs)
def assert_fingerprint(cert, fingerprint):
"""
Checks if given fingerprint matches the supplied certificate.
@@ -26,30 +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
}
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):
@@ -91,42 +198,120 @@ def resolve_ssl_version(candidate):
return candidate
if SSLContext is not None: # Python 3.2+
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ca_certs=None, server_hostname=None,
ssl_version=None):
"""
All arguments except `server_hostname` have the same meaning as for
:func:`ssl.wrap_socket`
def create_urllib3_context(ssl_version=None, cert_reqs=None,
options=None, ciphers=None):
"""All arguments have the same meaning as ``ssl_wrap_socket``.
:param server_hostname:
Hostname of the expected certificate
"""
context = SSLContext(ssl_version)
context.verify_mode = cert_reqs
By default, this function does a lot of the same work that
``ssl.create_default_context`` does on Python 3.4+. It:
# Disable TLS compression to migitate CRIME attack (issue #309)
OP_NO_COMPRESSION = 0x20000
context.options |= OP_NO_COMPRESSION
- Disables SSLv2, SSLv3, and compression
- Sets a restricted set of server ciphers
if ca_certs:
try:
context.load_verify_locations(ca_certs)
# Py32 raises IOError
# Py33 raises FileNotFoundError
except Exception as e: # Reraise as SSLError
If you wish to enable SSLv3, you can do::
from urllib3.util import ssl_
context = ssl_.create_urllib3_context()
context.options &= ~ssl_.OP_NO_SSLv3
You can do the same to enable compression (substituting ``COMPRESSION``
for ``SSLv3`` in the last line above).
:param ssl_version:
The desired protocol version to use. This will default to
PROTOCOL_SSLv23 which will negotiate the highest protocol that both
the server and your installation of OpenSSL support.
:param cert_reqs:
Whether to require the certificate verification. This defaults to
``ssl.CERT_REQUIRED``.
:param options:
Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``,
``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``.
:param ciphers:
Which cipher suites to allow the server to select.
:returns:
Constructed SSLContext object with specified options
:rtype: SSLContext
"""
context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23)
# Setting the default here, as we may have no ssl module on import
cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
if options is None:
options = 0
# SSLv2 is easily broken and is considered harmful and dangerous
options |= OP_NO_SSLv2
# SSLv3 has several problems and is now dangerous
options |= OP_NO_SSLv3
# Disable compression to prevent CRIME attacks for OpenSSL 1.0+
# (issue #309)
options |= OP_NO_COMPRESSION
context.options |= options
if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6
context.set_ciphers(ciphers or DEFAULT_CIPHERS)
context.verify_mode = cert_reqs
if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2
# We do our own verification, including fingerprints and alternative
# hostnames. So disable it here
context.check_hostname = False
return context
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,
ca_cert_dir=None):
"""
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
:param ssl_context:
A pre-made :class:`SSLContext` object. If none is provided, one will
be created using :func:`create_urllib3_context`.
: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 or ca_cert_dir:
try:
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
# These are not equivalent unless we check the errno attribute
except OSError as e: # Platform-specific: Python 3.3 and beyond
if e.errno == errno.ENOENT:
raise SSLError(e)
if certfile:
# FIXME: This block needs a test.
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
return context.wrap_socket(sock, server_hostname=server_hostname)
return context.wrap_socket(sock)
raise
else: # Python 3.1 and earlier
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
ca_certs=None, server_hostname=None,
ssl_version=None):
return wrap_socket(sock, keyfile=keyfile, certfile=certfile,
ca_certs=ca_certs, cert_reqs=cert_reqs,
ssl_version=ssl_version)
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
@@ -15,6 +16,8 @@ class Url(namedtuple('Url', url_attrs)):
def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None,
query=None, fragment=None):
if path and not path.startswith('/'):
path = '/' + path
return super(Url, cls).__new__(cls, scheme, auth, host, port, path,
query, fragment)
@@ -40,6 +43,49 @@ class Url(namedtuple('Url', url_attrs)):
return '%s:%d' % (self.host, self.port)
return self.host
@property
def url(self):
"""
Convert self into a url
This function should more or less round-trip with :func:`.parse_url`. The
returned url may not be exactly the same as the url inputted to
:func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
with a blank port will have : removed).
Example: ::
>>> U = parse_url('http://google.com/mail/')
>>> U.url
'http://google.com/mail/'
>>> Url('http', 'username:password', 'host.com', 80,
... '/path', 'query', 'fragment').url
'http://username:password@host.com:80/path?query#fragment'
"""
scheme, auth, host, port, path, query, fragment = self
url = ''
# We use "is not None" we want things to happen with empty strings (or 0 port)
if scheme is not None:
url += scheme + '://'
if auth is not None:
url += auth + '@'
if host is not None:
url += host
if port is not None:
url += ':' + str(port)
if path is not None:
url += path
if query is not None:
url += '?' + query
if fragment is not None:
url += '#' + fragment
return url
def __str__(self):
return self.url
def split_first(s, delims):
"""
@@ -71,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):
@@ -84,7 +130,7 @@ def parse_url(url):
Example::
>>> parse_url('http://google.com/mail/')
Url(scheme='http', host='google.com', port=None, path='/', ...)
Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
>>> parse_url('google.com:80')
Url(scheme=None, host='google.com', port=80, path=None, ...)
>>> parse_url('/foo?bar')

View File

@@ -13,7 +13,7 @@ from collections import Mapping
from datetime import datetime
from .auth import _basic_auth_str
from .compat import cookielib, OrderedDict, urljoin, urlparse, builtin_str
from .compat import cookielib, OrderedDict, urljoin, urlparse
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
@@ -21,6 +21,7 @@ from .hooks import default_hooks, dispatch_hook
from .utils import to_key_val_list, default_headers, to_native_string
from .exceptions import (
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
from .packages.urllib3._collections import RecentlyUsedContainer
from .structures import CaseInsensitiveDict
from .adapters import HTTPAdapter
@@ -35,6 +36,8 @@ from .status_codes import codes
# formerly defined here, reexposed here for backward compatibility
from .models import REDIRECT_STATI
REDIRECT_CACHE_SIZE = 1000
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
"""
@@ -59,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
@@ -87,7 +89,7 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
class SessionRedirectMixin(object):
def resolve_redirects(self, resp, req, stream=False, timeout=None,
verify=True, cert=None, proxies=None):
verify=True, cert=None, proxies=None, **adapter_kwargs):
"""Receives a Response. Returns a generator of Responses."""
i = 0
@@ -128,14 +130,14 @@ class SessionRedirectMixin(object):
# Facilitate relative 'location' headers, as allowed by RFC 7231.
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
# Compliant with RFC3986, we percent encode the url.
if not urlparse(url).netloc:
if not parsed.netloc:
url = urljoin(resp.url, requote_uri(url))
else:
url = requote_uri(url)
prepared_request.url = to_native_string(url)
# cache the url
if resp.is_permanent_redirect:
# Cache the url, unless it redirects to itself.
if resp.is_permanent_redirect and req.url != prepared_request.url:
self.redirect_cache[req.url] = prepared_request.url
# http://tools.ietf.org/html/rfc7231#section-6.4.4
@@ -168,7 +170,10 @@ class SessionRedirectMixin(object):
except KeyError:
pass
extract_cookies_to_jar(prepared_request._cookies, prepared_request, resp.raw)
# Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared
# request, use the old one that we haven't yet touched.
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
prepared_request._cookies.update(self.cookies)
prepared_request.prepare_cookies(prepared_request._cookies)
@@ -187,6 +192,7 @@ class SessionRedirectMixin(object):
cert=cert,
proxies=proxies,
allow_redirects=False,
**adapter_kwargs
)
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
@@ -267,13 +273,20 @@ 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__ = [
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream',
'trust_env', 'max_redirects', 'redirect_cache']
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
'max_redirects',
]
def __init__(self):
@@ -286,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.
@@ -312,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
@@ -326,7 +340,8 @@ class Session(SessionRedirectMixin):
self.mount('https://', HTTPAdapter())
self.mount('http://', HTTPAdapter())
self.redirect_cache = {}
# Only store 1000 redirects to prevent using infinite memory
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
def __enter__(self):
return self
@@ -365,6 +380,7 @@ class Session(SessionRedirectMixin):
url=request.url,
files=request.files,
data=request.data,
json=request.json,
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
@@ -386,7 +402,8 @@ class Session(SessionRedirectMixin):
hooks=None,
stream=None,
verify=None,
cert=None):
cert=None,
json=None):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object.
@@ -394,31 +411,33 @@ 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
: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
:class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the
:class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects
:param files: (optional) Dictionary of ``'filename': file-like-objects``
for multipart encoding upload.
:param auth: (optional) Auth tuple or callable to enable
Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) Float describing the timeout of the
request in seconds.
:param allow_redirects: (optional) Boolean. Set to True by default.
:param proxies: (optional) Dictionary mapping protocol to the URL of
the proxy.
:param timeout: (optional) How long to wait for the server to send
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 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 = builtin_str(method)
# Create the Request.
req = Request(
method = method.upper(),
@@ -426,6 +445,7 @@ class Session(SessionRedirectMixin):
headers = headers,
files = files,
data = data or {},
json = json,
params = params or {},
auth = auth,
cookies = cookies,
@@ -479,15 +499,16 @@ class Session(SessionRedirectMixin):
kwargs.setdefault('allow_redirects', False)
return self.request('HEAD', url, **kwargs)
def post(self, url, data=None, **kwargs):
def post(self, url, data=None, json=None, **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
: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 \*\*kwargs: Optional arguments that ``request`` takes.
"""
return self.request('POST', url, data=data, **kwargs)
return self.request('POST', url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs):
"""Sends a PUT request. Returns :class:`Response` object.
@@ -532,16 +553,17 @@ class Session(SessionRedirectMixin):
if not isinstance(request, PreparedRequest):
raise ValueError('You can only send PreparedRequests.')
checked_urls = set()
while request.url in self.redirect_cache:
request.url = self.redirect_cache.get(request.url)
checked_urls.add(request.url)
new_url = self.redirect_cache.get(request.url)
if new_url in checked_urls:
break
request.url = new_url
# Set up variables needed for resolve_redirects and dispatching of hooks
allow_redirects = kwargs.pop('allow_redirects', True)
stream = kwargs.get('stream')
timeout = kwargs.get('timeout')
verify = kwargs.get('verify')
cert = kwargs.get('cert')
proxies = kwargs.get('proxies')
hooks = request.hooks
# Get the appropriate adapter to use
@@ -569,12 +591,7 @@ class Session(SessionRedirectMixin):
extract_cookies_to_jar(self.cookies, request, r.raw)
# Redirect resolving generator.
gen = self.resolve_redirects(r, request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies)
gen = self.resolve_redirects(r, request, **kwargs)
# Resolve redirects if allowed.
history = [resp for resp in gen] if allow_redirects else []
@@ -617,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):
@@ -643,12 +660,19 @@ class Session(SessionRedirectMixin):
self.adapters[key] = self.adapters.pop(key)
def __getstate__(self):
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
state['redirect_cache'] = dict(self.redirect_cache)
return state
def __setstate__(self, state):
redirect_cache = state.pop('redirect_cache', {})
for attr, value in state.items():
setattr(self, attr, value)
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
for redirect, to in redirect_cache.items():
self.redirect_cache[redirect] = to
def session():
"""Returns a :class:`Session` for context-management."""

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

@@ -19,15 +19,17 @@ import re
import sys
import socket
import struct
import warnings
from . import __version__
from . import certs
from .compat import parse_http_list as _parse_list_header
from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
builtin_str, getproxies, proxy_bypass, urlunparse)
builtin_str, getproxies, proxy_bypass, urlunparse,
basestring)
from .cookies import RequestsCookieJar, cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import InvalidURL
from .exceptions import InvalidURL, FileModeWarning
_hush_pyflakes = (RequestsCookieJar,)
@@ -46,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:
@@ -103,8 +126,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):
@@ -114,7 +138,8 @@ def get_netrc_auth(url):
def guess_filename(obj):
"""Tries to guess the filename of the given object."""
name = getattr(obj, 'name', None)
if name and name[0] != '<' and name[-1] != '>':
if (name and isinstance(name, basestring) and name[0] != '<' and
name[-1] != '>'):
return os.path.basename(name)
@@ -287,6 +312,11 @@ def get_encodings_from_content(content):
:param content: bytestring to extract encodings from.
"""
warnings.warn((
'In requests 3.0, get_encodings_from_content will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
@@ -351,12 +381,14 @@ def get_unicode_from_response(r):
Tried:
1. charset from content-type
2. every encodings from ``<meta ... charset=XXX>``
3. fall back and replace all unicode characters
2. fall back and replace all unicode characters
"""
warnings.warn((
'In requests 3.0, get_unicode_from_response will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
tried_encodings = []
@@ -410,10 +442,18 @@ def requote_uri(uri):
This function passes the given URI through an unquote/quote cycle to
ensure that it is fully and consistently quoted.
"""
# Unquote only the unreserved characters
# Then quote only illegal characters (do not quote reserved, unreserved,
# or '%')
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
safe_with_percent = "!#$%&'()*+,/:;=?@[]~"
safe_without_percent = "!#$&'()*+,/:;=?@[]~"
try:
# Unquote only the unreserved characters
# Then quote only illegal characters (do not quote reserved,
# unreserved, or '%')
return quote(unquote_unreserved(uri), safe=safe_with_percent)
except InvalidURL:
# We couldn't unquote the given URI, so let's try quoting it, but
# there may be unquoted '%'s in the URI. We need to make sure they're
# properly quoted so they do not cause issues elsewhere.
return quote(uri, safe=safe_without_percent)
def address_in_network(ip, net):
@@ -480,7 +520,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):
@@ -518,36 +560,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():
@@ -555,7 +583,7 @@ def default_headers():
'User-Agent': default_user_agent(),
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
'Accept': '*/*',
'Connection': 'keep-alive'
'Connection': 'keep-alive',
})
@@ -570,7 +598,7 @@ def parse_header_links(value):
replace_chars = " '\""
for val in value.split(","):
for val in re.split(", *<", value):
try:
url, params = val.split(";", 1)
except ValueError:
@@ -672,3 +700,18 @@ def to_native_string(string, encoding='ascii'):
out = string.decode(encoding)
return out
def urldefragauth(url):
"""
Given a url remove the fragment and the authentication part
"""
scheme, netloc, path, params, query, fragment = urlparse(url)
# see func:`prepend_scheme_if_needed`
if not netloc:
netloc, path = path, netloc
netloc = netloc.rsplit('@', 1)[-1]
return urlunparse((scheme, netloc, path, params, query, ''))

View File

@@ -1,5 +1,6 @@
py==1.4.12
pytest==2.3.4
pytest-cov==1.6
invoke==0.2.0
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

@@ -1,9 +1,10 @@
#!/usr/bin/env python
import os
import re
import sys
import requests
from codecs import open
try:
from setuptools import setup
@@ -25,16 +26,24 @@ packages = [
'requests.packages.urllib3.packages.ssl_match_hostname',
]
requires = ['certifi']
requires = []
with open('README.rst') as f:
version = ''
with open('requests/__init__.py', 'r') as fd:
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
fd.read(), re.MULTILINE).group(1)
if not version:
raise RuntimeError('Cannot find version information')
with open('README.rst', 'r', 'utf-8') as f:
readme = f.read()
with open('HISTORY.rst') as f:
with open('HISTORY.rst', 'r', 'utf-8') as f:
history = f.read()
setup(
name='requests',
version=requests.__version__,
version=version,
description='Python HTTP for Humans.',
long_description=readme + '\n\n' + history,
author='Kenneth Reitz',
@@ -53,11 +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>=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,10 +17,13 @@ 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 (InvalidURL, MissingSchema, ConnectTimeout,
ReadTimeout, ConnectionError, Timeout)
from requests.exceptions import (ConnectionError, ConnectTimeout,
InvalidSchema, InvalidURL, MissingSchema,
ReadTimeout, Timeout, RetryError)
from requests.models import PreparedRequest
from requests.structures import CaseInsensitiveDict
from requests.sessions import SessionRedirectMixin
@@ -31,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
@@ -39,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
@@ -78,6 +100,12 @@ class RequestsTestCase(unittest.TestCase):
def test_invalid_url(self):
with pytest.raises(MissingSchema):
requests.get('hiwpefhipowhefopw')
with pytest.raises(InvalidSchema):
requests.get('localhost:3128')
with pytest.raises(InvalidSchema):
requests.get('localhost.localdomain:3128/')
with pytest.raises(InvalidSchema):
requests.get('10.122.1.1:3128/')
with pytest.raises(InvalidURL):
requests.get('http://')
@@ -90,12 +118,20 @@ 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, httpbin):
headers = {
'Content-Length': 'not zero'
}
r = requests.Request('POST', httpbin('post'), headers=headers).prepare()
assert 'Content-Length' in r.headers
assert r.headers['Content-Length'] == 'not zero'
def test_path_is_not_double_encoded(self):
request = requests.Request('GET', "http://0.0.0.0/get/test case").prepare()
@@ -109,19 +145,30 @@ 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_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()
@@ -130,7 +177,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
@@ -140,7 +187,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)
@@ -148,25 +195,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'
@@ -179,18 +226,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'})
@@ -198,13 +245,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()
@@ -215,7 +262,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()
@@ -223,13 +270,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.
"""
@@ -239,15 +286,15 @@ 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
req = requests.Request('GET', 'http://httpbin.org/get')
req = requests.Request('GET', httpbin('get'))
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)'
@@ -263,15 +310,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')
@@ -286,35 +333,55 @@ class RequestsTestCase(unittest.TestCase):
r = s.get(url)
assert r.status_code == 200
def test_basicauth_with_netrc(self):
def test_connection_error_invalid_domain(self):
"""Connecting to an unknown domain should raise a ConnectionError"""
with pytest.raises(ConnectionError):
requests.get("http://doesnotexist.google.com")
def test_connection_error_invalid_port(self):
"""Connecting to an invalid port should raise a ConnectionError"""
with pytest.raises(ConnectionError):
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, 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')
@@ -330,7 +397,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)
@@ -339,14 +406,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')
@@ -357,7 +424,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')
@@ -373,7 +440,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')
@@ -381,7 +448,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()
@@ -399,7 +466,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()
@@ -418,17 +485,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()
@@ -436,11 +503,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øø'})
@@ -448,29 +515,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'))})
@@ -491,7 +558,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'),
@@ -502,13 +569,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})},
@@ -518,38 +596,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()
@@ -558,7 +636,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'
@@ -576,6 +654,12 @@ class RequestsTestCase(unittest.TestCase):
assert resp.json()['headers'][
'Dummy-Auth-Test'] == 'dummy-auth-test-ok'
def test_prepare_request_with_bytestring_url(self):
req = requests.Request('GET', b'https://httpbin.org/')
s = requests.Session()
prep = s.prepare_request(req)
assert prep.url == "https://httpbin.org/"
def test_links(self):
r = requests.Response()
r.headers = {
@@ -705,7 +789,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)
@@ -744,7 +828,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
@@ -776,8 +860,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)
@@ -791,7 +875,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()
@@ -801,7 +885,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.
"""
@@ -814,7 +898,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})
@@ -859,14 +943,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'})
@@ -881,7 +965,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()
@@ -891,7 +975,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)
@@ -899,7 +983,28 @@ class RequestsTestCase(unittest.TestCase):
assert 'multipart/form-data' in p.headers['Content-Type']
def test_autoset_header_values_are_native(self):
def test_can_send_bytes_bytearray_objects_with_files(self, httpbin):
# Test bytes:
data = {'a': 'this is a string'}
files = {'b': b'foo'}
r = requests.Request('POST', httpbin('post'), data=data, files=files)
p = r.prepare()
assert 'multipart/form-data' in p.headers['Content-Type']
# Test bytearrays:
files = {'b': bytearray(b'foo')}
r = requests.Request('POST', httpbin('post'), data=data, files=files)
p = r.prepare()
assert 'multipart/form-data' in p.headers['Content-Type']
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})
p = r.prepare()
assert 'multipart/form-data' in p.headers['Content-Type']
def test_autoset_header_values_are_native(self, httpbin):
data = 'this is a string'
length = '16'
req = requests.Request('POST', httpbin('post'), data=data)
@@ -907,7 +1012,7 @@ class RequestsTestCase(unittest.TestCase):
assert p.headers['Content-Length'] == length
def test_oddball_schemes_dont_check_URLs(self):
def test_nonhttp_schemes_dont_check_URLs(self):
test_urls = (
'data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==',
'file:///etc/passwd',
@@ -918,7 +1023,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'},
@@ -927,14 +1032,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
@@ -967,7 +1072,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)
@@ -978,13 +1083,55 @@ class RequestsTestCase(unittest.TestCase):
assert isinstance(s, builtin_str)
assert s == "Basic dGVzdDp0ZXN0"
def test_requests_history_is_saved(self):
r = requests.get('https://httpbin.org/redirect/5')
def test_requests_history_is_saved(self, httpbin):
r = requests.get(httpbin('redirect/5'))
total = r.history[-1].history
i = 0
for item in r.history:
assert item.history == total[0:i]
i=i+1
i = i + 1
def test_json_param_post_content_type_works(self, httpbin):
r = requests.post(
httpbin('post'),
json={'life': 42}
)
assert r.status_code == 200
assert 'application/json' in r.request.headers['Content-Type']
assert {'life': 42} == r.json()['json']
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
it = r.iter_lines()
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, httpbin):
"""Response.iter_lines() is not reentrant safe"""
r = requests.get(httpbin('stream/4'), stream=True)
assert r.status_code == 200
next(r.iter_lines())
assert len(list(r.iter_lines())) == 3
class TestContentEncodingDetection(unittest.TestCase):
@@ -1131,6 +1278,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'})
@@ -1168,6 +1316,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):
@@ -1193,6 +1351,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."""
@@ -1214,6 +1379,41 @@ 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)
def test_guess_filename_when_filename_is_an_int(self):
from requests.utils import guess_filename
fake = type('Fake', (object,), {'name': 1})()
assert None is guess_filename(fake)
def test_guess_filename_with_file_like_obj(self):
from requests.utils import guess_filename
from requests import compat
fake = type('Fake', (object,), {'name': b'value'})()
guessed_name = guess_filename(fake)
assert b'value' == guessed_name
assert isinstance(guessed_name, compat.bytes)
def test_guess_filename_with_unicode_name(self):
from requests.utils import guess_filename
from requests import compat
filename = b'value'.decode('utf-8')
fake = type('Fake', (object,), {'name': filename})()
guessed_name = guess_filename(fake)
assert filename == guessed_name
assert isinstance(guessed_name, compat.str)
def test_is_ipv4_address(self):
from requests.utils import is_ipv4_address
assert is_ipv4_address('8.8.8.8')
@@ -1250,6 +1450,22 @@ class UtilsTestCase(unittest.TestCase):
assert username == percent_encoding_test_chars
assert password == percent_encoding_test_chars
def test_requote_uri_with_unquoted_percents(self):
"""Ensure we handle unquoted percent signs in redirects.
See: https://github.com/kennethreitz/requests/issues/2356
"""
from requests.utils import requote_uri
bad_uri = 'http://example.com/fiz?buz=%ppicture'
quoted = 'http://example.com/fiz?buz=%25ppicture'
assert quoted == requote_uri(bad_uri)
def test_requote_uri_properly_requotes(self):
"""Ensure requoting doesn't break expectations."""
from requests.utils import requote_uri
quoted = 'http://example.com/fiz?buz=%25ppicture'
assert quoted == requote_uri(quoted)
class TestMorselToCookieExpires(unittest.TestCase):
@@ -1310,13 +1526,13 @@ class TestMorselToCookieMaxAge(unittest.TestCase):
class TestTimeout:
def test_stream_timeout(self):
def test_stream_timeout(self, httpbin):
try:
requests.get('https://httpbin.org/delay/10', timeout=2.0)
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)
@@ -1325,7 +1541,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
@@ -1337,7 +1553,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."
@@ -1359,6 +1575,11 @@ class TestTimeout:
except ConnectTimeout:
pass
def test_encoded_methods(self, httpbin):
"""See: https://github.com/kennethreitz/requests/issues/2316"""
r = requests.request(b'GET', httpbin('get'))
assert r.ok
SendCall = collections.namedtuple('SendCall', ('args', 'kwargs'))
@@ -1405,9 +1626,9 @@ 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', 'http://httpbin.org/post').prepare()
prep = requests.Request('POST', httpbin('post')).prepare()
r0 = session.send(prep)
assert r0.request.method == 'POST'
assert session.calls[-1] == SendCall((r0.request,), {})
@@ -1477,5 +1698,44 @@ def test_prepared_request_complete_copy():
)
assert_copy(p, p.copy())
def test_prepare_unicode_url():
p = PreparedRequest()
p.prepare(
method='GET',
url=u('http://www.example.com/üniçø∂é'),
)
assert_copy(p, p.copy())
def test_urllib3_retries(httpbin):
from requests.packages.urllib3.util import Retry
s = requests.Session()
s.mount('http://', HTTPAdapter(max_retries=Retry(
total=2, status_forcelist=[500]
)))
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
with pytest.raises(ImportError):
from requests.packages import webbrowser
if __name__ == '__main__':
unittest.main()