Compare commits

...

279 Commits

Author SHA1 Message Date
Kenneth Reitz
dd777b3d7e urllib3/chardet note in release for v2.0.1 2013-10-24 10:32:05 -04:00
Kenneth Reitz
df272b7740 update internal dependencies 2013-10-24 10:31:05 -04:00
Kenneth Reitz
db108a19c8 Merge pull request #1700 from kennethreitz/v2.0.1
v2.0.1
2013-10-24 07:29:11 -07:00
Kenneth Reitz
c244f8ea87 more release notes for v2.0.1 2013-10-24 10:28:30 -04:00
Kenneth Reitz
f045979f8d release notes 2013-10-24 10:26:47 -04:00
Kenneth Reitz
3d59569264 v2.0.1 2013-10-24 10:00:49 -04:00
Kenneth Reitz
5469291296 $ make ci 2013-10-24 09:54:14 -04:00
Kenneth Reitz
3b35f1c37d replace invoke with makefiles 2013-10-24 09:53:00 -04:00
Kenneth Reitz
e813574497 remove travis support 2013-10-24 09:47:31 -04:00
Kenneth Reitz
e1e6255b48 Merge pull request #1699 from kennethreitz/cert-update
Latest Mozilla Certs
2013-10-24 06:44:26 -07:00
Kenneth Reitz
1a9f0be6df Latest Mozilla Certs
generated via https://github.com/tiran/extract-nss-root-certs
2013-10-24 09:40:39 -04:00
Kenneth Reitz
d9ec7000db Merge pull request #1696 from canibanoglu/remove-parentheses
Removed extra extra parentheses in SessionRedirectMixin.resolve_redirects
2013-10-24 04:10:14 -07:00
Cory Benfield
6b975cbd82 Merge pull request #1697 from canibanoglu/docstring-PreparedRequest
Deleted the extra word in PreparedRequest.prepare's docstring
2013-10-22 14:19:47 -07:00
Can Ibanoglu
76be17b00a Deleted the extra word in PreparedRequest.prepare's docstring 2013-10-23 00:15:41 +03:00
Can Ibanoglu
0cd6959f19 Removed extra extra parentheses in SessionRedirectMixin.resolve_redirects 2013-10-22 23:48:04 +03:00
Cory Benfield
741fd3ded5 Remove requests.*() from docs.
This looks like a typo, even though it isn't.
2013-10-21 11:57:06 +01:00
Cory Benfield
ff2b496e2d Merge pull request #1695 from sigmavirus24/docs
Add bit to docs about iter_content
2013-10-21 01:31:50 -07:00
Ian Cordasco
6a24eb6d20 Add bit to docs about iter_content 2013-10-20 12:29:43 -05:00
Kenneth Reitz
e91ee0e246 Merge remote-tracking branch 'origin/master' 2013-10-17 02:33:05 -04:00
Kenneth Reitz
ab8e43a196 fix unprotected build status link 2013-10-17 02:32:16 -04:00
Kenneth Reitz
c05b1bfca9 Update README.rst 2013-10-17 01:51:50 -04:00
Kenneth Reitz
011f6dac82 Merge remote-tracking branch 'origin/master' 2013-10-17 01:47:39 -04:00
Kenneth Reitz
f35363e584 test out requests-trunk badge 2013-10-17 01:47:00 -04:00
Cory Benfield
c8d3dcb315 Small grammar fix. 2013-10-16 22:11:54 +01:00
Cory Benfield
2ec1bdd0ab Merge pull request #1675 from houqp/master
fix doc for redirction and history
2013-10-16 14:10:31 -07:00
Qingping Hou
3d0a1eb90d fix doc for redirction and history
In current version of requests, allow_redirect is disabled by default
only for HEAD requests
2013-10-16 16:59:22 -04:00
Kenneth Reitz
65ec319f01 Merge pull request #1673 from daftshady/dev
fixed typo
2013-10-15 02:47:55 -07:00
daftshady
17dd3c0a76 fixed typo 2013-10-15 18:33:51 +09:00
Kenneth Reitz
3f84190a7b Merge pull request #1667 from Lukasa/json2
Second time lucky for json related fixes.
2013-10-12 14:15:30 -07:00
Cory Benfield
bea2060182 'or content' does us no good. 2013-10-12 21:05:34 +01:00
Kenneth Reitz
097121d214 Merge pull request #1662 from voberoi/patch-1
Adding myself to AUTHORS.rst
2013-10-10 15:45:14 -07:00
Vikram Oberoi
21c9680a11 Adding myself to AUTHORS.rst
Taking a page out of @mattspitz's book.

https://github.com/kennethreitz/requests/pull/1439

#dreamsdocometrue
2013-10-10 18:13:51 -04:00
Kenneth Reitz
8d63577b78 Merge pull request #1661 from mattspitz/super_len_compatibility
Increasing super_len compatibilty to include BytesIO and cStringIO objects.
2013-10-10 12:11:14 -07:00
Matt Spitz
2c2af651c4 Adding myself to AUTHORS.rst. 2013-10-10 15:08:34 -04:00
Matt Spitz
13a6e02ccd Increasing super_len compatibilty to include BytesIO and cStringIO objects.
Added a check for 'getvalue' attr, calling it to retrieve the length if we can.

We also try/except the fileno() call, which can throw
io.UnsupportedOperation for BytesIO because, well, they're not files.
2013-10-10 14:54:47 -04:00
Cory Benfield
025543b604 Merge pull request #1658 from randymorris/master
False -> True typo
2013-10-08 11:36:12 -07:00
Randy Morris
f32b86dba2 False -> True typo 2013-10-08 14:19:30 -04:00
Kenneth Reitz
43477edc91 Merge pull request #1636 from Lukasa/httpsprox
Don't send the full URL on CONNECT messages.
2013-10-07 16:23:18 -07:00
Kenneth Reitz
318ca102cd Merge pull request #1635 from daftshady/dev
Fixed #1630 and added test case.
2013-10-07 16:22:49 -07:00
Kenneth Reitz
14c12fb151 Merge pull request #1637 from Lukasa/redir
Correctly lowercase a redirect scheme.
2013-10-07 16:21:29 -07:00
Kenneth Reitz
3246fc088a Merge pull request #1640 from abarnert/master
Accept per-file headers in multipart file POST messages.
2013-10-07 16:21:16 -07:00
Cory Benfield
45873e2a0a Merge pull request #1656 from randymorris/master
Update verbiage about streaming requests
2013-10-07 13:45:11 -07:00
Randy Morris
66339b21fa update verbiage about streaming requests
The Twitter API is no longer used in the example.
2013-10-07 10:43:48 -04:00
Cory Benfield
d8180f5dac Merge pull request #1654 from riyadparvez/deprecation-removal
Deprecation removal and other idiomatic updates
2013-10-05 10:02:46 -07:00
Riyad Parvez
48f59aa1f7 back to BaseException 2013-10-05 22:49:54 +06:00
Riyad Parvez
56c82c52b0 fixed print statement 2013-10-05 20:12:18 +06:00
Riyad Parvez
36485983ca fixed print statement 2013-10-05 20:11:30 +06:00
Riyad Parvez
6087015f1c Update advanced.rst 2013-10-05 20:10:27 +06:00
Riyad Parvez
5c92c72e2c removed deprecated while 1: 2013-10-05 09:59:47 +06:00
Riyad Parvez
4656c9b89b fixed print statement 2013-10-05 09:57:18 +06:00
Riyad Parvez
8370e66464 fixed print statement 2013-10-05 09:56:11 +06:00
Riyad Parvez
f9b7d92b91 updated print statement 2013-10-05 09:55:14 +06:00
Riyad Parvez
c8a68334a7 replaced deprecated BaseException 2013-10-05 09:53:34 +06:00
Cory Benfield
953b234aff Merge pull request #1649 from dpursehouse/minor-doc-updates
Minor doc updates
2013-10-04 01:55:19 -07:00
Kenneth Reitz
c9bee155c2 Merge pull request #1651 from GrahamDumpleton/master
Translate urllib3 ProxyError into a requests ProxyError derived from ConnectionError.
2013-10-03 23:14:26 -07:00
Graham Dumpleton
4291f76c99 Translate urllib3 ProxyError into a requests ProxyError derived from ConnectionError. 2013-10-04 16:07:11 +10:00
David Pursehouse
82e6706722 Minor doc updates
- Improve wording of sentence describing proxy environment
  variable setting.
- Mention that proxy URLs must include the scheme.
2013-10-04 10:19:33 +09:00
Andrew Barnert
af4fb8cedc Accept per-file headers in multipart file POST messages.
- Each file in the `files` argument can now be a 4-tuple, instead of
  just a file, 2-tuple, or 3-tuple. If it is, the last value in the
  tuple is a dictionary of extra headers.
2013-09-30 17:07:13 -07:00
daftshady
c8fc19a779 add myself to authors. 2013-09-29 02:27:20 +09:00
daftshady
012f0334ce added param to cookiejar_from_dict instead of creating new method. 2013-09-29 01:08:58 +09:00
Cory Benfield
0051837af4 Correctly lowercase a redirect scheme. 2013-09-28 17:01:32 +01:00
Cory Benfield
8b7fcfb49a Don't send the full URL on CONNECT messages. 2013-09-28 15:46:43 +01:00
daftshady
d22eaf96e2 Fixed #1630 2013-09-28 23:44:27 +09:00
daftshady
373166d833 checkout auth 2013-09-28 16:41:08 +09:00
daftshady
39fe0068aa Fixed #1630, added test case for cookie persistence. 2013-09-28 16:33:05 +09:00
daftshady
1d1831ba40 fixed extra bonus bug 2013-09-27 22:17:40 +09:00
daftshady
3501ea4634 Fixed #1623. Added 'MD5-sess' algorithm to HTTPDigestAuth 2013-09-26 15:40:12 +09:00
Cory Benfield
9968a10fcf Typo in changelog. 2013-09-25 14:00:17 +01:00
Kenneth Reitz
768b175fee Merge pull request #1621 from davidfischer/migrate-2-0
Added migrating to 2.x docs
2013-09-24 19:02:18 -07:00
David Fischer
a17f5592f8 Added link to @Lukasa's blog - the source material 2013-09-24 18:59:04 -07:00
David Fischer
fbb8d32c4e Added migrating to 2.x docs 2013-09-24 18:35:22 -07:00
Kenneth Reitz
4401620111 add history to the docs 2013-09-24 15:08:12 -04:00
Kenneth Reitz
b8a8835151 readme fix 2013-09-24 14:41:39 -04:00
Kenneth Reitz
4f463e897a Merge remote-tracking branch 'origin/master' 2013-09-24 14:31:25 -04:00
Kenneth Reitz
58eacfa1ad Merge pull request #1619 from Lukasa/master
Up to date CL.
2013-09-24 11:31:13 -07:00
Kenneth Reitz
c93419f512 TimeoutSauce! 2013-09-24 14:31:00 -04:00
Cory Benfield
53ba629a23 Up to date CL. 2013-09-24 19:26:31 +01:00
Kenneth Reitz
62925fb668 note on timeouts 2013-09-24 14:22:02 -04:00
Kenneth Reitz
94da24e387 v2.0.0 2013-09-24 14:19:07 -04:00
Kenneth Reitz
2d283c9f2d 2.0.0 2013-09-24 14:17:17 -04:00
Kenneth Reitz
54ad646067 merge 2.0 insto master 2013-09-24 14:13:28 -04:00
Kenneth Reitz
12760f2295 runscope! 2013-09-24 14:08:28 -04:00
Kenneth Reitz
c64c0ab121 prototype for new urllib3 timeouts 2013-09-24 14:06:24 -04:00
Kenneth Reitz
2f39e0e2aa new urllib3 2013-09-24 13:59:38 -04:00
Kenneth Reitz
23ced527f7 Merge pull request #1503 from skastel/master
Fixing error return to be consistent with tuple return at the end of the method
2013-09-24 10:48:07 -07:00
Kenneth Reitz
2affd6385e Merge pull request #1602 from sigmavirus24/handle_multiple_qop_types
Handle multiple qop types
2013-09-24 10:46:16 -07:00
Kenneth Reitz
58376489af Merge pull request #1585 from rvoicilas/master
Use values() when the keys are not being used
2013-09-24 10:45:46 -07:00
Kenneth Reitz
a2dbf54456 Merge pull request #1541 from Lukasa/releasenote
[2.0] Tracking PR for the 2.0 changelog
2013-09-24 10:45:11 -07:00
Kenneth Reitz
03d844f926 Merge pull request #1565 from Lukasa/relative
Don't absolute import things we've vendored.
2013-09-24 10:39:09 -07:00
Kenneth Reitz
9ffc5e6a95 runscope 2013-09-24 13:31:29 -04:00
Ian Cordasco
22e31b4b73 Handle case when WWW-Authenticate returns multiple qops
In Digest Access Authentication there are two possible values (four if you
count the not-present and both cases) for authentication. We were narrowly
handling one of the four cases. Now we handle two.
2013-09-13 22:29:42 -05:00
Kenneth Reitz
c0e9f411fc Merge pull request #1598 from Lukasa/defaults
[2.0] Python default arguments are hard
2013-09-13 06:17:41 -07:00
Cory Benfield
61421843bc Python defaults arguments are hard. 2013-09-13 14:10:09 +01:00
Cory Benfield
ce6762ff63 Fixup unicode in AUTHORS file 2013-09-12 10:34:34 +01:00
Cory Benfield
d8f2463b57 Merge pull request #1592 from homm/master
remove extra mention of dangerous `r.raw`
2013-09-12 02:29:16 -07:00
homm
f0c592b09e Update AUTHORS.rst 2013-09-12 13:27:06 +04:00
homm
a4cd1a85d6 fix spelling 2013-09-12 13:21:54 +04:00
homm
3440fca2e8 clearly describe r.raw in Body Content Workflow 2013-09-12 13:13:51 +04:00
homm
10b6d77945 remove extra mention of dangerous r.raw 2013-09-12 10:20:47 +04:00
Cory Benfield
917a15f154 Merge pull request #1591 from sayanchowdhury/patch-1
minor typo: Fix requests spelling
2013-09-11 08:16:29 -07:00
Sayan Chowdhury
0a52d9405e minor typo: Fix requests spelling 2013-09-11 20:21:57 +05:30
Cory Benfield
cde6fdd00f Merge pull request #1587 from kracekumar/patch-1
Fixed persistence spelling
2013-09-10 06:43:04 -07:00
kracekumar
a829ef9b46 Fixed persistence spelling 2013-09-10 17:52:29 +05:30
Radu Voicilas
056da86f6d Use values() when the keys are not being used 2013-09-09 15:21:36 +03:00
Cory Benfield
c1eb7027fc Merge pull request #1579 from yang/patch-1
Clarify timeout behavior in quickstart doc
2013-09-08 08:05:00 -07:00
Yang Zhang
2cb523b267 Fix typo in Timeouts doc 2013-09-07 00:16:09 -07:00
Yang Zhang
6d0315f78f Clarify timeout behavior in quickstart doc 2013-09-06 19:27:57 -07:00
Cory Benfield
b891877ee6 Merge pull request #1578 from jparise/jparise-authors
Adding myself to the list of contributors.
2013-09-04 23:44:28 -07:00
Jon Parise
9d0d11a369 Adding myself to the list of contributors. 2013-09-04 09:00:03 -07:00
Kenneth Reitz
6c73d864ef Merge pull request #1575 from jparise/content-encoding
Improved content encoding detection.
2013-09-03 19:42:43 -07:00
Jon Parise
b9b5be7c4c Improved content encoding detection.
get_encodings_from_content() can now detect HTML in-document content
encoding declarations in the following formats:

 - HTML5
 - HTML4
 - XHTML 1.x served with text/html MIME type
 - XHTML 1.x served as XML

Ref: http://www.w3.org/International/questions/qa-html-encoding-declarations
2013-09-03 16:16:46 -07:00
Cory Benfield
d991fb032a Merge pull request #1566 from sigmavirus24/update_readme
Add link to Contributor Friendly tag in README
2013-09-01 11:25:04 -07:00
Ian Cordasco
9f42a50742 Add link to Contributor Friendly tag
Add note that I'm willing to help anyone who is looking to submit a pull request
2013-09-01 13:21:55 -05:00
Cory Benfield
88011f4032 Bring release note current to 1st Sept. 2013-09-01 18:46:46 +01:00
Cory Benfield
1aa7658256 Don't absolute import vendored things. 2013-09-01 18:10:09 +01:00
Cory Benfield
e77344345a Merge pull request #1556 from ssbarnea/master
Added link to StackOverflow questions related to python-requests. This s...
2013-08-27 08:03:33 -07:00
Sorin Sbarnea
a0ffeaa56e Added link to StackOverflow questions related to python-requests. This should be the default support way for the library, as clearly most questions are supposed to be programming related.
Signed-off-by: Sorin Sbarnea <sorin.sbarnea@citrix.com>
2013-08-27 15:39:07 +01:00
Kenneth Reitz
efbae1855b Merge pull request #1552 from Lukasa/noproxy
Allow spaces in the no_proxy environ variable
2013-08-23 17:23:57 -07:00
Cory Benfield
f040b2aa5d Allow spaces in the no_proxy environ variable. 2013-08-23 12:37:03 +01:00
Kenneth Reitz
09c52e7282 Merge pull request #1537 from Lukasa/datetime
Allow non-string objects to be posted as data alongside files.
2013-08-21 11:46:27 -07:00
Kenneth Reitz
4b6649e37b Merge pull request #1527 from paul-matthews/restrict-hooks-access
Raise an error for unsupported hook event
2013-08-21 11:28:23 -07:00
Kenneth Reitz
aeeb0af3a2 Merge pull request #1536 from Lukasa/IOError
RequestsException subclasses IOError
2013-08-21 11:25:20 -07:00
Cory Benfield
148c1b5c13 Begin work on the 2.0 changelog. 2013-08-19 13:28:05 +01:00
Cory Benfield
377ac9501a Merge pull request #1540 from libbkmz/master
Event hooks update
2013-08-18 13:03:03 -07:00
libbkmz
78e5262dbd Update advanced.rst 2013-08-19 00:01:37 +04:00
Cory Benfield
0f40290039 Merge pull request #1538 from enkore/patch-1
Remove superfluous double dot
2013-08-17 23:26:59 -07:00
enkore
4ffc3e4d1f Remove superfluous double dot 2013-08-18 03:06:34 +02:00
Cory Benfield
f44b86e039 Allow non-string objects to be data with files 2013-08-17 07:27:58 +01:00
Cory Benfield
3bc01eed6f RequestException subclasses IOError. 2013-08-17 07:17:03 +01:00
Kenneth Reitz
3dc61e5d68 Merge pull request #1535 from sigmavirus24/ignore_original_response_if_none
Skip cookie extraction if necessary
2013-08-16 20:15:18 -07:00
Ian Cordasco
98680b64f4 Skip cookie extraction if necessary
If _original_response is never set/is None, then don't try to extract cookies
from the response.
2013-08-16 21:38:20 -05:00
Kenneth Reitz
d8268fb7b4 Merge pull request #1515 from schlamar/https-proxy-2.0
[2.0] Https proxy support
2013-08-14 13:41:08 -07:00
Cory Benfield
2e5a674601 Add proxy_headers functionality.
This brings us in line with urllib3's fancy new magic for sending
headers to HTTP(S) proxies.
2013-08-14 09:47:44 +01:00
Paul Matthews
61617e74ed Raise an error for unsupported hook event
* Raises a ValueError for an unsupported hook event
2013-08-12 15:27:17 +01:00
Stephen Huenneke
b676bf3214 Fixing tests to verify thrown ValueError's. 2013-08-08 09:20:41 -07:00
Stephen Huenneke
9dd823c289 Raising ValueError's when we're passed inconsistent POST data and files dict. 2013-08-06 11:04:03 -07:00
Kenneth Reitz
d3b786399e Merge pull request #1516 from Lukasa/io
UnsupportedOperation errors
2013-08-06 08:43:44 -07:00
Cory Benfield
a5e5a51fb4 Catch UnsupportedOperation. 2013-08-03 19:24:23 +01:00
schlamar
09d97c1126 Added entry to AUTHORS. 2013-08-03 13:59:38 +02:00
schlamar
6a3ca7372f Added proxy support to feature list. 2013-08-03 13:59:38 +02:00
ms4py
c951f0543c Cache manager for proxy connection. 2013-08-03 13:59:28 +02:00
ms4py
134699faf9 Fix for new proxy API. 2013-08-03 13:57:15 +02:00
ms4py
d06dabab59 Update urllib3 to 14b8945 2013-08-03 13:55:19 +02:00
Kenneth Reitz
a05aba1033 Merge pull request #1514 from Lukasa/verror
Better percent-escape exception.
2013-08-02 09:48:33 -07:00
Cory Benfield
5a26241287 Better percent-escape exception. 2013-08-02 12:29:23 +01:00
Kenneth Reitz
abfb38bd57 Merge pull request #1508 from dieterv/master
remove duplicate comment
2013-07-31 18:36:07 -07:00
Kenneth Reitz
bd1f1404a0 Merge branch 'fix_401_cookies' of git://github.com/sigmavirus24/requests into 2.0 2013-07-31 21:33:24 -04:00
Kenneth Reitz
f865d61713 Merge branch 'proxyscheme' of git://github.com/Lukasa/requests into 2.0 2013-07-31 21:27:01 -04:00
Kenneth Reitz
1a6637c285 Merge branch 'keytype' of git://github.com/Lukasa/requests into 2.0 2013-07-31 21:25:57 -04:00
Kenneth Reitz
22701d149a Merge pull request #1500 from gavrie/master
Update urllib3 to d89d508
2013-07-31 18:23:43 -07:00
Kenneth Reitz
d5fd3d3331 Merge pull request #1487 from dpursehouse/rewrite-test_mixed_case_scheme_acceptable
Rewrite test cases to remove dependency on httpbin.org and example.com
2013-07-31 18:23:34 -07:00
Kenneth Reitz
d5a8816926 Merge pull request #1498 from Lukasa/incompleteread
Wrap IncompleteRead in ChunkedEncodingError.
2013-07-31 18:23:14 -07:00
Kenneth Reitz
6b13ecdc85 Merge pull request #1501 from sigmavirus24/remove_setting_of_hooks
Fix duplication of efforts caught by @dofelw
2013-07-31 18:22:58 -07:00
Kenneth Reitz
7eb9a9298e Merge pull request #1506 from Lukasa/mockrequest
Provide 'host' parameter to MockRequest.
2013-07-31 18:22:02 -07:00
Kenneth Reitz
3e6e68baa1 Merge pull request #1507 from buzztabapp/prepared-session-requests
Allow preparing of Requests from Session settings without sending.
2013-07-31 18:21:28 -07:00
Robert Estelle
0ab7a52f7c Merge settings when creating PreparedRequest, don't copy Request. 2013-07-31 13:42:02 -07:00
Dieter Verfaillie
852de116dd remove duplicate comment
This is already mentioned at line 357...
2013-07-31 09:29:24 +02:00
Robert Estelle
0eccb547a2 Add Session.prepare_request test case. 2013-07-30 23:02:13 -07:00
Robert Estelle
d4385f6fc8 Add Request.copy() test case. 2013-07-30 23:01:56 -07:00
Robert Estelle
ee90f0af60 Shallow copy of Request fields in Request.copy()
This prevents e.g. modifying the headers of a copied request from
affecting the headers of its source and vice versa. Copying is used with
the intent to mutuate, so allowing this kind of mutation of fields makes
sense.

Is a deep copy better?
2013-07-30 22:59:53 -07:00
Robert Estelle
9edba838b3 Skip unneccessary Request.copy in Session.request 2013-07-30 22:59:11 -07:00
Robert Estelle
b5c136152f Prepare requests with session settings separately from sending. 2013-07-30 21:39:36 -07:00
Cory Benfield
6ef3710970 Provide 'host' parameter to MockRequest. 2013-07-30 18:21:37 +01:00
Stephen Huenneke
f75ee9fb6e Making sure we check for str/unicode. 2013-07-29 14:42:47 -07:00
Stephen Huenneke
8d9d9fb064 Fixing error return to be consistent with tuple return at the end of the method. 2013-07-29 13:31:06 -07:00
Cory Benfield
7de6b1e162 Instantiate the CID directly.
No need to do this the slow way now. Thanks to Colin (@cdunklau) for the
idea.
2013-07-29 14:52:44 +01:00
Cory Benfield
ec2859c0de Modify new CID tests to use native string keys. 2013-07-29 14:52:44 +01:00
Cory Benfield
f5775594cc Header keys should be native strings.
This commit follows a discussion on IRC. For more information, see the
Pull Request associated with it.
2013-07-29 14:52:44 +01:00
Ian Cordasco
37786613e0 Fix duplication of efforts caught by @dofelw 2013-07-29 08:09:07 -05:00
Gavrie Philipson
ea2b639d31 Update urllib3 to d89d508 2013-07-29 11:29:41 +03:00
Cory Benfield
79f3e69f5e Wrap IncompleteRead in ChunkedEncodingError. 2013-07-28 07:42:17 +01:00
Cory Benfield
840540b6b1 Proxy urls should have explicit schemes. 2013-07-28 07:16:06 +01:00
Cory Benfield
9473f15909 Merge pull request #1494 from Lukasa/close
Document the Response.close() method.
2013-07-27 23:04:34 -07:00
Ian Cordasco
286ddb672d Take advantage of the new copy method 2013-07-27 23:08:46 -04:00
Ian Cordasco
972089826e Fixed finally. Also requires updated httpbin.org 2013-07-27 23:07:25 -04:00
Ian Cordasco
c25a72ea24 Make the regular tests pass
I broke Digest Auth completely
2013-07-27 23:07:25 -04:00
Ian Ross and Ian Cordasco
73635df1b2 Start work on sending cookies back
On 401's the cookies received aren't sent back to the server. See: #1336
2013-07-27 23:07:25 -04:00
Kenneth Reitz
77bd9c4a9d Merge pull request #1476 from sigmavirus24/add_copy_to_prepared_requests
[2.0] Add copy method to PreparedRequest objects
2013-07-26 18:16:00 -07:00
Cory Benfield
f401287afb Document the Response.close() method. 2013-07-25 19:10:42 +01:00
Cory Benfield
3becc47366 Comment markups, courtesy of @sigmavirus24 2013-07-24 16:03:24 +01:00
Kenneth Reitz
7d3d074e66 Merge pull request #1490 from Lukasa/cookiedocs
Better cookie docs on sessions.
2013-07-24 06:12:10 -07:00
Cory Benfield
f40a1d6a8a Better cookie docs on sessions. 2013-07-24 13:25:59 +01:00
David Pursehouse
82c9aa912e Check the response URL in test_uppercase_scheme_redirect
Update the test to check that the URL in the response is the
one that we expect, i.e. the one it was supposed to redirect to.
2013-07-24 17:37:35 +09:00
David Pursehouse
56b5f02551 Rewrite test_uppercase_scheme_redirect to use local httpbin
Instead of redirecting to hard-coded 'example.com', use the URL
defined in `HTTPBIN_URL` with the path set to the 'html' endpoint.
2013-07-24 17:18:16 +09:00
David Pursehouse
c4f16c351c Remove redundant test case test_uppercase_scheme
This test verifies that an upper case scheme ('HTTP') works correctly,
but this is already tested in `test_mixed_case_scheme_acceptable`.
2013-07-24 16:58:09 +09:00
David Pursehouse
012cc33d44 Rewrite test_mixed_case_scheme_acceptable to work with local httpbin
Instead of using hard-coded urls to httpbin.org, use the url defined
in `HTTPBIN_URL` replacing the scheme as necessary to test the mixed
cases.

Refs #1485
2013-07-24 16:36:10 +09:00
David Pursehouse
415fb53cb4 Remove redundant session in test_mixed_case_scheme_acceptable 2013-07-24 12:15:46 +09:00
Kenneth Reitz
b65b8c5ed3 Merge pull request #1486 from sigmavirus24/use_urljoin
Use urlparse.urljointo construct httpbin url
2013-07-23 09:03:05 -07:00
Ian Cordasco
c0cb698191 Derp. Use compat 2013-07-23 10:48:45 -05:00
Ian Cordasco
d2176cf052 Use urlparse.urljointo construct httpbin url 2013-07-23 10:24:30 -05:00
Kenneth Reitz
f71bff1226 Merge pull request #1482 from dpursehouse/fix-testcases-with-proxy
Fix test cases that fail when running behind a proxy
2013-07-23 05:23:35 -07:00
Kenneth Reitz
47f5a0db64 Merge pull request #1483 from dpursehouse/httpbin-url-without-trailing-slash
Test cases fail when `HTTPBIN_URL` does not have trailing slash
2013-07-23 05:23:11 -07:00
David Pursehouse
9e771aa79c Fix test cases that fail when running behind a proxy
When sending a request via `Session.send()` the proxies must be
explicitly given with the `proxies` argument.  This is not done
in the test cases, which means that they fail when run on a system
that is behind a proxy.

Update test cases to make sure the proxies are set in the sessions.
2013-07-23 16:55:27 +09:00
David Pursehouse
29db7b7216 Test cases fail when HTTPBIN_URL does not have trailing slash
Test cases can be run against a local httpbin server defined by
the `HTTPBIN_URL` environment variable, but it causes tests to
fail if the given URL does not end with a slash.

Ensure that the URL always ends with a slash.
2013-07-23 15:29:36 +09:00
Kenneth Reitz
66de6b528d Merge pull request #1479 from dpursehouse/fix-warnings
Fix a few warnings flagged by PyDev
2013-07-22 01:31:16 -07:00
David Pursehouse
4f64938ff0 Fix a few warnings flagged by PyDev
- Unused import of urlparse
- Unnecessary definition of variable
- Incorrect indentation
2013-07-22 17:14:45 +09:00
Kenneth Reitz
e5597dfcce Merge pull request #1478 from dpursehouse/add-me-to-authors
Add myself to the authors list
2013-07-22 00:09:35 -07:00
David Pursehouse
787c1e8c6e Add myself to the authors list 2013-07-22 15:19:10 +09:00
Cory Benfield
ec6584f2d1 Merge pull request #1477 from dpursehouse/other-auth-basic-example
Add a simple example of custom authentication in the documentation
2013-07-21 23:04:31 -07:00
David Pursehouse
62f0df4434 Add a simple example of custom authentication in the documentation
Refs #1471
2013-07-22 09:14:33 +09:00
David Pursehouse
5cdcf58b3b Wrap long lines in the authentication documentation 2013-07-22 09:08:47 +09:00
Ian Cordasco
b84547d786 Add copy method to PreparedRequest objects 2013-07-20 17:08:35 -04:00
Kenneth Reitz
d83919a6f7 Merge pull request #1474 from Kwpolska/docfix
Some tiny fixes to the documentation
2013-07-20 05:15:06 -07:00
Kwpolska
2ef782b8e6 further treatment
Signed-off-by: Kwpolska <kwpolska@gmail.com>
2013-07-20 13:54:33 +02:00
Kwpolska
b738c97ec3 session object should be uppercase
Signed-off-by: Kwpolska <kwpolska@gmail.com>
2013-07-20 13:39:50 +02:00
Kwpolska
022c370bcd fixed underline
Signed-off-by: Kwpolska <kwpolska@gmail.com>
2013-07-20 13:31:37 +02:00
Kwpolska
587385bfe0 Grammar fixes and such
Signed-off-by: Kwpolska <kwpolska@gmail.com>
2013-07-20 12:56:25 +02:00
Kwpolska
ca033b83fe Some cosmetic updates to the docs
Signed-off-by: Kwpolska <kwpolska@gmail.com>
2013-07-20 12:12:57 +02:00
Cory Benfield
949a29c34f Merge pull request #1470 from Lukasa/carset
Remove charset from JSON types: not valid.
2013-07-19 06:45:56 -07:00
Cory Benfield
be884341f4 Merge pull request #1465 from dpursehouse/doc-netrc-authentication
Improve documentation of netrc authentication
2013-07-19 06:45:17 -07:00
David Pursehouse
c88fd8cc9d Move netrc authentication documentation under the Basic Auth section 2013-07-19 22:37:57 +09:00
Cory Benfield
8385fb397b Remove charset from JSON types: not valid. 2013-07-19 14:03:20 +01:00
Cory Benfield
e0df4b5f18 Merge pull request #1466 from s7v7nislands/fix_doc
Fix doc
2013-07-19 04:12:29 -07:00
s7v7nislands
39ad5e7388 merge 2013-07-19 17:12:01 +08:00
s7v7nislands
0df505bd7c fix doc 2013-07-19 17:04:53 +08:00
David Pursehouse
6f6a920a68 Improve documentation of netrc authentication
The documentation does not make it clear that when the credentials
from netrc are used, Requests authenticates with HTTP Basic Auth.

I just spent ages trying to figure out why it wasn't working, and
it was because although the credentials in the netrc were correct,
the server actually required HTTP Digest Auth.

Add a section in the authentication documentation to make it clear
that HTTP Basic Auth is used.
2013-07-19 17:30:54 +09:00
Cory Benfield
32293655ff Merge pull request #1464 from dpursehouse/document-none-not-sent
Fix #1322: Add note in docs about None not being sent as data
2013-07-19 01:19:55 -07:00
David Pursehouse
07ad75ee04 Fix #1322: Add note in docs about None not being sent as data
In the case:

  payload = {'key1': 'value1', 'key2': 'value2', 'key3': None}
  r = requests.get("http://httpbin.org", params=payload)

the parameter `key3` will not be sent as a parameter in the URL.

Mention this in the documentation.
2013-07-19 17:14:59 +09:00
Kenneth Reitz
e4b41320e6 Merge pull request #1463 from AudriusButkevicius/master
Update urllib3 to a43319f
2013-07-18 15:00:28 -07:00
Audrius Butkevicius
9f119ee420 Update urllib3 to a43319f 2013-07-18 21:06:19 +00:00
Kenneth Reitz
e0df2168b7 Merge remote-tracking branch 'origin/master' 2013-07-16 02:20:28 -04:00
Kenneth Reitz
d7e8073198 badge.fury.io 2013-07-16 02:20:23 -04:00
Kenneth Reitz
65a0fd88a9 Merge pull request #1440 from fcurella/patch-0
unquote double-quotes cookie values
2013-07-15 06:54:00 -07:00
Kenneth Reitz
4b7cf389e4 Merge pull request #1439 from voberoi/master
Make sure netrc doesn't override any authentication settings explicitly set by the client
2013-07-15 06:22:53 -07:00
Kenneth Reitz
86d466c745 Merge pull request #1441 from Lukasa/1395
Remove urllib3-specific kwargs from non-urllib3 branch.
2013-07-15 06:22:41 -07:00
Kenneth Reitz
de9d84489b Merge pull request #1456 from phndiaye/master
Changed the "im_used" informational status code for the value given by IANA (226)
2013-07-15 06:21:31 -07:00
Philippe Ndiaye
18a736fbe6 Set 208 status_code to "already_reported" 2013-07-13 11:08:01 +02:00
Philippe Ndiaye
7bdf37bc27 Changed the "im_used" informational status code for the value given by IANA (226)
See RFC 3229 at http://tools.ietf.org/html/rfc3229#section-10.4.1 and HTTP status codes at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
2013-07-13 09:55:50 +02:00
Kenneth Reitz
41517e2111 Delete invokefile.py 2013-07-06 14:04:50 +10:00
Cory Benfield
555472bf1e Remove urllib3-specific kwargs from general code 2013-07-04 10:34:43 +01:00
Flavio Curella
7da16c584a Added myself to AUTHORS 2013-07-01 15:54:59 -05:00
Flavio Curella
a8cf5b8502 keep the double quotes, but don't escape them 2013-07-01 15:48:48 -05:00
Flavio Curella
cdab4fabf4 unquote double-quotes cookie values 2013-07-01 13:51:40 -05:00
Vikram Oberoi
d9c49ad30d Add test to verify .netrc authentication behavior.
Here's what should happen:

- If no credentials are given, use netrc if there's a netrc entry.
- If credentials are given, they should override netrc.
2013-06-27 17:16:42 -04:00
Vikram Oberoi
188e7609b3 .netrc settings shouldn't blow away explicit auth settings on a session 2013-06-27 16:43:40 -04:00
Cory Benfield
2b62980e26 Merge pull request #1437 from lukaszb/patch-1
Fixed wrong method call at streaming example
2013-06-27 11:18:56 -07:00
Lukasz Balcerzak
805abee9b4 Fixed wrong method call at streaming example
405 is returned if POST request is performed to http://httpbin.org/stream/20
2013-06-27 13:37:39 +02:00
Kenneth Reitz
a1bb7fb0d2 Merge pull request #1435 from chinux23/master
@1434 https://github.com/kennethreitz/requests/issues/1434
2013-06-25 15:49:04 -07:00
Chen Huang
9083735963 @1434 Fix https://github.com/kennethreitz/requests/issues/1434 2013-06-25 18:38:59 -04:00
Cory Benfield
dcf20c4cfe Merge pull request #1429 from Lukasa/docs
Packaging warning, in via @andrewgross.
2013-06-24 00:48:48 -07:00
Cory Benfield
d57a5efab3 Packaging warning, in via @andrewgross. 2013-06-22 15:02:18 +01:00
Kenneth Reitz
3781c96125 Merge pull request #1425 from Lukasa/stream
Use new urllib3 'stream' parameter.
2013-06-21 02:23:10 -07:00
Cory Benfield
1faa76a86f Use the new urllib3 stream generator. 2013-06-18 17:56:35 +01:00
Cory Benfield
ecf57cac5c Update urllib3 to cffbd6b317 2013-06-18 17:47:23 +01:00
Kenneth Reitz
25db83fe85 Merge pull request #1419 from kevinburke/exception-links
Exception links
2013-06-14 22:50:44 -07:00
Kevin Burke
ba0db9db86 Add self to AUTHORS 2013-06-12 15:56:10 -07:00
Kevin Burke
a6415cf895 Link to the actual exception references
Sphinx has a neat cross-referencing feature where if you include the tilde
character in front of a :py: class, it'll link to the full object but provide
only the last part of class name in the text. For more info see
http://sphinx-doc.org/domains.html#cross-referencing-syntax
2013-06-12 15:53:09 -07:00
Kenneth Reitz
df935f5b03 Merge pull request #1414 from jschneier/master
fixed typo in docs
2013-06-12 11:12:17 -07:00
Josh Schneier
43a64d9515 fix doc typo 2013-06-09 11:14:14 -04:00
Kenneth Reitz
70a6d035d9 Merge pull request #1315 from reclosedev/fix-redirects
Don't reuse the same prepared request for all redirects
2013-06-08 03:53:16 -07:00
Roman Haritonov
716b627c1e Don't reuse PreparedRequest on redirects 2013-06-08 14:41:34 +04:00
Roman Haritonov
798a1ffdec new failing test_requests_in_history_are_not_overridden() 2013-06-08 14:41:34 +04:00
Kenneth Reitz
8028103b71 Merge pull request #1400 from jam/master
Retrieve environment proxies using standard library functions
2013-06-08 03:20:18 -07:00
Kenneth Reitz
8430e6ccbb Merge pull request #1334 from rcarz/master
resolve_redirects no longer throws an InvalidSchema exception when the scheme is uppercase
2013-06-08 03:18:25 -07:00
Kenneth Reitz
d4c461042b Merge pull request #1385 from ViktorHaag/master
Cope with mixed-case URL schemes (like 'HTTP') by lower-ifying 'url' string before calling startswith() on it
2013-06-08 03:17:38 -07:00
Kenneth Reitz
60b1a0ba13 Merge pull request #1381 from expandrive/master
Don't force chunked transfer on 0-length file-like object.
2013-06-08 03:16:32 -07:00
Kenneth Reitz
cfd84d74ee Merge pull request #1392 from revolunet/patch-2
Update quickstart.rst
2013-06-08 03:16:21 -07:00
Kenneth Reitz
eec9bdd434 Merge pull request #1408 from wasw100/master
cookies.morsel_to_cookie morsel['expires'] can't be strtime, and morsel['max-age'] convert to expires problem repair
2013-06-08 03:14:32 -07:00
Kenneth Reitz
f74d5e96dd Merge pull request #1412 from t-8ch/update_urllib3
update urllib3 to 60ba176f5d
2013-06-08 03:13:34 -07:00
Kenneth Reitz
e63e68ef48 Merge pull request #1413 from Lukasa/docs
Assorted docs updates
2013-06-08 03:13:14 -07:00
Cory Benfield
73425fbffb Remove development reference from docs sidebar. 2013-06-08 11:10:07 +01:00
Cory Benfield
f53aed1611 Document blocking calls. 2013-06-08 11:09:39 +01:00
Thomas Weißschuh
2ed976ea71 update urllib3 to 60ba176f5d 2013-06-08 08:22:15 +00:00
wasw100
767f758aac cookies.morsel_to_cookie morsel['expires'] can't be strtime, and max-age convert to expires problem repair 2013-06-07 00:02:45 +08:00
wasw100
961790f95c cookies.morsel_to_cookie(morsel) raise TypeError repaired.
morsel_to_cookie(mosel) method raise TypeError: create_cookie() got unexpected keyword arguments: ['path_specified', 'domain_specified', 'port_specified', 'domain_initial_dot'].

so we should remove these param from create_cookie(...)
2013-06-06 19:15:09 +08:00
James Clarke
f705c41c1d Added myself to AUTHORS. 2013-05-31 18:19:42 -07:00
James Clarke
93be6916f9 Use urllib to retrieve environment proxies.
This has the added benefit of including proxies defined by the OS X System Configuration framework and in the Windows registry, rather than only checking os.environ.
2013-05-31 18:19:34 -07:00
Julien Bouquillon
340931ba8e Update quickstart.rst
rephrase misleading info about `raise_for_status`
2013-05-29 12:57:49 +03:00
Bob Carroll
2c5ea69e5d rebased with upstream/master 2013-05-26 14:46:36 -07:00
Bob Carroll
d59510481a this didn't merge properly 2013-05-26 14:45:49 -07:00
Bob Carroll
f3036adc4f added assertion to test_uppercase_scheme_redirect for the response code 2013-05-26 14:44:37 -07:00
Bob Carroll
489a412f96 I probably should add my name too 2013-05-26 14:44:37 -07:00
Bob Carroll
72e155e529 resolve_redirects now checks for a scheme before converting the scheme to lowercase, added tests or the scheme casing 2013-05-26 14:44:22 -07:00
Bob Carroll
e715d7184b resolve_redirects no longer throws an InvalidSchema exception when the scheme is uppercase 2013-05-26 14:43:00 -07:00
Kenneth Reitz
3bb13f8fbb v1.2.3 2013-05-25 12:48:10 -04:00
Viktor Haag
01993d21dc added tests for mixed-case scheme URLs, changed adapters passing down URLs into urllib3 by lower-ifying them so that the underlying pool manager can effectively pool by scheme as dictionary key 2013-05-24 16:14:14 -04:00
Viktor Haag
5e94f38001 - fixed func call syntax on lower to lower()
- added test cases for trying to test GETS on mixed-case schemas
2013-05-24 14:01:30 -04:00
Viktor Haag
3004ad5398 Lower-ify url before checking against prefix with startswith() 2013-05-24 11:26:00 -04:00
Jeff Mancuso
e7c9bbb96f Only switch to chunked if we don't know the length of a file like object. This fixes the case of trying to upload a 0-length file - chunked upload was being forced. Services like S3 that disallow chunked upload will fail. 2013-05-23 11:21:29 -04:00
50 changed files with 6736 additions and 3449 deletions

View File

@@ -1,11 +0,0 @@
language: python
python:
- 2.6
- 2.7
- 3.3
env: HTTPBIN_URL=http://httpbin.org/
script: invoke test
install:
- pip install -r requirements.txt
notifications:
email: false

View File

@@ -125,6 +125,18 @@ Patches and Suggestions
- Dmitry Medvinsky <me@dmedvinsky.name>
- Bryce Boe <bbzbryce@gmail.com> @bboe
- Colin Dunklau <colin.dunklau@gmail.com> @cdunklau
- Bob Carroll <bob.carroll@alum.rit.edu> @rcarz
- Hugo Osvaldo Barrera <hugo@osvaldobarrera.com.ar> @hobarrera
- Łukasz Langa <lukasz@langa.pl> @llanga
- Dave Shawley <daveshawley@gmail.com>
- James Clarke (jam)
- Kevin Burke <kev@inburke.com>
- Flavio Curella
- David Pursehouse <david.pursehouse@gmail.com> @dpursehouse
- Jon Parise
- Alexander Karpinsky @homm86
- Marc Schlaich @schlamar
- Park Ilsu <daftonshady@gmail.com> @daftshady
- Matt Spitz @mattspitz
- Vikram Oberoi @voberoi
- Can Ibanoglu <can.ibanoglu@gmail.com> @canibanoglu

View File

@@ -1,7 +1,81 @@
.. :changelog:
History
-------
Release History
---------------
2.0.1 (2013-10-24)
++++++++++++++++++
- Updated included CA Bundle with new mistrusts and automated process for the future
- Added MD5-sess to Digest Auth
- Accept per-file headers in multipart file POST messages.
- Fixed: Don't send the full URL on CONNECT messages.
- Fixed: Correctly lowercase a redirect scheme.
- Fixed: Cookies not persisted when set via functional API.
- Fixed: Translate urllib3 ProxyError into a requests ProxyError derived from ConnectionError.
- Updated internal urllib3 and chardet.
2.0.0 (2013-09-24)
++++++++++++++++++
**API Changes:**
- Keys in the Headers dictionary are now native strings on all Python versions,
i.e. bytestrings on Python 2, unicode on Python 3.
- Proxy URLs now *must* have an explicit scheme. A ``MissingSchema`` exception
will be raised if they don't.
- Timeouts now apply to read time if ``Stream=False``.
- ``RequestException`` is now a subclass of ``IOError``, not ``RuntimeError``.
- Added new method to ``PreparedRequest`` objects: ``PreparedRequest.copy()``.
- Added new method to ``Session`` objects: ``Session.update_request()``. This
method updates a ``Request`` object with the data (e.g. cookies) stored on
the ``Session``.
- Added new method to ``Session`` objects: ``Session.prepare_request()``. This
method updates and prepares a ``Request`` object, and returns the
corresponding ``PreparedRequest`` object.
- Added new method to ``HTTPAdapter`` objects: ``HTTPAdapter.proxy_headers()``.
This should not be called directly, but improves the subclass interface.
- ``httplib.IncompleteRead`` exceptions caused by incorrect chunked encoding
will now raise a Requests ``ChunkedEncodingError`` instead.
- Invalid percent-escape sequences now cause a Requests ``InvalidURL``
exception to be raised.
- HTTP 208 no longer uses reason phrase ``"im_used"``. Correctly uses
``"already_reported"``.
- HTTP 226 reason added (``"im_used"``).
**Bugfixes:**
- Vastly improved proxy support, including the CONNECT verb. Special thanks to
the many contributors who worked towards this improvement.
- Cookies are now properly managed when 401 authentication responses are
received.
- Chunked encoding fixes.
- Support for mixed case schemes.
- Better handling of streaming downloads.
- Retrieve environment proxies from more locations.
- Minor cookies fixes.
- Improved redirect behaviour.
- Improved streaming behaviour, particularly for compressed data.
- Miscellaneous small Python 3 text encoding bugs.
- ``.netrc`` no longer overrides explicit auth.
- Cookies set by hooks are now correctly persisted on Sessions.
- Fix problem with cookies that specify port numbers in their host field.
- ``BytesIO`` can be used to perform streaming uploads.
- More generous parsing of the ``no_proxy`` environment variable.
- Non-string objects can be passed in data values alongside files.
1.2.3 (2013-05-25)
++++++++++++++++++
- Simple packaging fix
1.2.2 (2013-05-23)
++++++++++++++++++
- Simple packaging fix
1.2.1 (2013-05-20)
++++++++++++++++++

25
Makefile Normal file
View File

@@ -0,0 +1,25 @@
init:
pip install -r requirements.txt
test:
py.test
ci: init
py.test --junitxml=junit.xml
certs:
curl http://ci.kennethreitz.org/job/ca-bundle/lastSuccessfulBuild/artifact/cacerts.pem -o requests/cacert.pem
deps: urllib3 charade
urllib3:
rm -fr requests/packages/urllib3
git clone https://github.com/shazow/urllib3.git
mv urllib3/urllib3 requests/packages/
rm -fr urllib3
charade:
rm -fr requests/packages/charade
git clone https://github.com/sigmavirus24/charade.git
mv charade/charade requests/packages/
rm -fr charade

View File

@@ -1,13 +1,16 @@
Requests: HTTP for Humans
=========================
.. image:: https://travis-ci.org/kennethreitz/requests.png?branch=master
:target: https://travis-ci.org/kennethreitz/requests
.. image:: https://badge.fury.io/py/requests.png
:target: http://badge.fury.io/py/requests
.. image:: https://pypip.in/d/requests/badge.png
:target: https://crate.io/packages/requests/
.. image:: http://ci.kennethreitz.org/buildStatus/icon?job=requests-trunk
:target: http://ci.kennethreitz.org/job/requests-trunk/
Requests is an Apache2 Licensed HTTP library, written in Python, for human
beings.
@@ -52,12 +55,13 @@ Features
- Multipart File Uploads
- Connection Timeouts
- Thread-safety
- HTTP(S) proxy support
Installation
------------
To install requests, simply:
To install Requests, simply:
.. code-block:: bash
@@ -81,10 +85,12 @@ Documentation is available at http://docs.python-requests.org/.
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.
#. Fork `the repository`_ on Github to start making your changes to the **master** branch (or branch off of it).
#. 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_.
.. _`the repository`: http://github.com/kennethreitz/requests
.. _AUTHORS: https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst
.. _Contributor Friendly: https://github.com/kennethreitz/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open

View File

@@ -11,8 +11,7 @@
<p>
Requests is an elegant and simple HTTP library for Python, built for
human beings. You are currently looking at the documentation of the
development release.
human beings.
</p>
@@ -52,5 +51,6 @@
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
<li><a href="https://flattr.com/thing/442264/Requests">Requests @ Flattr</a></li>
<li><a href="http://stackoverflow.com/questions/tagged/python-requests" title="Use it for support regarding python-request usage">Requests @ StackOverflow</a></li>
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
</ul>

View File

@@ -153,7 +153,8 @@ API Changes
# in 0.x, passing prefetch=False would accomplish the same thing
r = requests.get('https://github.com/timeline.json', stream=True)
r.raw.read(10)
for chunk in r.iter_content(8192):
...
* The ``config`` parameter to the requests method has been removed. Some of
these options are now configured on a ``Session`` such as keep-alive and
@@ -170,13 +171,13 @@ API Changes
# the only thing missing will be the response.body which is not logged.
import httplib
httplib.HTTPConnection.debuglevel = 1
logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
requests.get('http://httpbin.org/headers')
@@ -186,9 +187,79 @@ Licensing
One key difference that has nothing to do with the API is a change in the
license from the ISC_ license to the `Apache 2.0`_ license. The Apache 2.0
license ensures that contributions to requests are also covered by the Apache
license ensures that contributions to Requests are also covered by the Apache
2.0 license.
.. _ISC: http://opensource.org/licenses/ISC
.. _Apache 2.0: http://opensource.org/licenses/Apache-2.0
Migrating to 2.x
----------------
Compared with the 1.0 release, there were relatively few backwards
incompatible changes, but there are still a few issues to be aware of with
this major release.
For more details on the changes in this release including new APIs, links
to the relevant GitHub issues and some of the bug fixes, read Cory's blog_
on the subject.
.. _blog: http://lukasa.co.uk/2013/09/Requests_20/
API Changes
~~~~~~~~~~~
* There were a couple changes to how Requests handles exceptions.
``RequestException`` is now a subclass of ``IOError`` rather than
``RuntimeError`` as that more accurately categorizes the type of error.
In addition, an invalid URL escape sequence now raises a subclass of
``RequestException`` rather than a ``ValueError``.
::
requests.get('http://%zz/') # raises requests.exceptions.InvalidURL
Lastly, ``httplib.IncompleteRead`` exceptions caused by incorrect chunked
encoding will now raise a Requests ``ChunkedEncodingError`` instead.
* The proxy API has changed slightly. The scheme for a proxy URL is now
required.
::
proxies = {
"http": "10.10.1.10:3128", # use http://10.10.1.10:3128 instead
}
# In requests 1.x, this was legal, in requests 2.x,
# this raises requests.exceptions.MissingSchema
requests.get("http://example.org", proxies=proxies)
Behavioral 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 downloading the full body.
::
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

@@ -1,11 +1,15 @@
.. _updates:
Updates
=======
Community Updates
=================
If you'd like to stay up to date on the community and development of Requests,
there are several options:
GitHub
------
@@ -28,3 +32,10 @@ mailing list, send an email to
`requests@librelist.org <mailto:requests@librelist.org>`_.
Software Updates
================
.. include:: ../../HISTORY.rst

View File

@@ -38,4 +38,4 @@ Linux Distro Packages
Distributions have been made for many Linux repositories, including: Ubuntu, Debian, RHEL, and Arch.
These distributions are sometimes divergent forks, or are otherwise not kept up-to-date with the latest code and bugfixes. PyPI (and its mirrors) and GitHub are the official distribution sources; alternatives are not supported by the requests project.
These distributions are sometimes divergent forks, or are otherwise not kept up-to-date with the latest code and bugfixes. PyPI (and its mirrors) and GitHub are the official distribution sources; alternatives are not supported by the Requests project.

View File

@@ -6,7 +6,7 @@ Requests is under active development, and contributions are more than welcome!
#. Check for open issues or open a fresh issue to start a discussion around a bug.
There is a Contributor Friendly tag for issues that should be ideal for people who are not very
familiar with the codebase yet.
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ on Github and start making your
#. Fork `the repository <https://github.com/kennethreitz/requests>`_ on GitHub and start making your
changes to a new branch.
#. Write a test which shows that the bug was fixed.
#. Send a pull request and bug the maintainer until it gets merged and published. :)
@@ -46,10 +46,15 @@ Requests currently supports the following versions of Python:
Support for Python 3.1 and 3.2 may be dropped at any time.
Google App Engine will never be officially supported. Pull requests for compatibility will be accepted, as long as they don't complicate the codebase.
Google App Engine will never be officially supported. Pull Requests for compatibility will be accepted, as long as they don't complicate the codebase.
Are you crazy?
--------------
- SPDY support would be awesome. No C extensions.
Downstream Repackaging
----------------------
If you are repackaging Requests, please note that you must also redistribute the ``cacerts.pem`` file in order to get correct SSL functionality.

View File

@@ -38,7 +38,7 @@ Requests takes all of the work out of Python HTTP/1.1 — making your integrati
Testimonials
------------
Her Majesty's Government, Amazon, Google, Twilio, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 3,000,000 times from PyPI.
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, and Federal US Institutions use Requests internally. It has been downloaded over 5,000,000 times from PyPI.
**Armin Ronacher**
Requests is the perfect example how beautiful an API can be with the

View File

@@ -13,7 +13,7 @@ The Session object allows you to persist certain parameters across
requests. It also persists cookies across all requests made from the
Session instance.
A session object has all the methods of the main Requests API.
A Session object has all the methods of the main Requests API.
Let's persist some cookies across requests::
@@ -22,12 +22,12 @@ Let's persist some cookies across requests::
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")
print r.text
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'
Sessions can also be used to provide default data to the request methods. This
is done by providing data to the properties on a session object::
is done by providing data to the properties on a Session object::
s = requests.Session()
s.auth = ('user', 'pass')
@@ -48,13 +48,13 @@ All values that are contained within a session are directly available to you. Se
Request and Response Objects
----------------------------
Whenever a call is made to requests.*() you are doing two major things. First,
you are constructing a ``Request`` object which will be sent of to a server
to request or query some resource. Second, a ``Response`` object is generated
once ``requests`` gets a response back from the server. The response object
contains all of the information returned by the server and also contains the
``Request`` object you created originally. Here is a simple request to get some
very important information from Wikipedia's servers::
Whenever a call is made to ``requests.get()`` and friends you are doing two
major things. First, you are constructing a ``Request`` object which will be
sent off to a server to request or query some resource. Second, a ``Response``
object is generated once ``requests`` gets a response back from the server.
The Response object contains all of the information returned by the server and
also contains the ``Request`` object you created originally. Here is a simple
request to get some very important information from Wikipedia's servers::
>>> r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
@@ -120,7 +120,7 @@ Requests can verify SSL certificates for HTTPS requests, just like a web browser
>>> requests.get('https://kennethreitz.com', verify=True)
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
I don't have SSL setup on this domain, so it fails. Excellent. Github does though::
I don't have SSL setup on this domain, so it fails. Excellent. GitHub does though::
>>> requests.get('https://github.com', verify=True)
<Response [200]>
@@ -161,7 +161,7 @@ At this point only the response headers have been downloaded and the connection
content = r.content
...
You can further control the workflow by use of the :class:`Response.iter_content` and :class:`Response.iter_lines` methods, or reading from the underlying urllib3 :class:`urllib3.HTTPResponse` at :class:`Response.raw`.
You can further control the workflow by use of the :class:`Response.iter_content` and :class:`Response.iter_lines` methods. Alternatively, you can read the undecoded body from the underlying urllib3 :class:`urllib3.HTTPResponse` at :class:`Response.raw`.
Keep-Alive
@@ -217,7 +217,7 @@ argument.
::
def print_url(r):
def print_url(r, *args, **kwargs):
print(r.url)
If an error occurs while executing your callback, a warning is given.
@@ -268,18 +268,19 @@ Then, we can make a request using our Pizza Auth::
>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>
.. _streaming-requests
Streaming Requests
------------------
With ``requests.Response.iter_lines()`` you can easily iterate over streaming
APIs such as the `Twitter Streaming API <https://dev.twitter.com/docs/streaming-api>`_.
To use the Twitter Streaming API to track the keyword "requests"::
Simply set ``stream`` to ``True`` and iterate over the response with ``iter_lines()``::
import json
import requests
r = requests.post('http://httpbin.org/stream/20', stream=True)
r = requests.get('http://httpbin.org/stream/20', stream=True)
for line in r.iter_lines():
@@ -303,7 +304,8 @@ If you need to use a proxy, you can configure individual requests with the
requests.get("http://example.org", proxies=proxies)
You can also configure proxies by environment variables ``HTTP_PROXY`` and ``HTTPS_PROXY``.
You can also configure proxies by setting the environment variables
``HTTP_PROXY`` and ``HTTPS_PROXY``.
::
@@ -319,6 +321,8 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn
"http": "http://user:pass@10.10.1.10:3128/",
}
Note that proxy URLs must include the scheme.
Compliance
----------
@@ -574,3 +578,19 @@ a good start would be to subclass the ``requests.adapters.BaseAdapter`` class.
.. _`described here`: http://kennethreitz.org/exposures/the-future-of-python-http
.. _`urllib3`: https://github.com/shazow/urllib3
Blocking Or Non-Blocking?
-------------------------
With the default Transport Adapter in place, Requests does not provide any kind
of non-blocking IO. The ``Response.content`` property will block until the
entire response has been downloaded. If you require more granularity, the
streaming features of the library (see :ref:`streaming-requests`) allow you to
retrieve smaller quantities of the response at a time. However, these calls
will still block.
If you are concerned about the use of blocking IO, there are lots of projects
out there that combine Requests with one of Python's asynchronicity frameworks.
Two excellent examples are `grequests`_ and `requests-futures`_.
.. _`grequests`: https://github.com/kennethreitz/grequests
.. _`requests-futures`: https://github.com/ross/requests-futures

View File

@@ -32,6 +32,17 @@ Providing the credentials in a tuple like this is exactly the same as the
``HTTPBasicAuth`` example above.
netrc Authentication
~~~~~~~~~~~~~~~~~~~~
If no authentication method is given with the ``auth`` argument, Requests will
attempt to get the authentication credentials for the URL's hostname from the
user's netrc file.
If credentials for the hostname are found, the request is sent with HTTP Basic
Auth.
Digest Authentication
---------------------
@@ -47,7 +58,8 @@ and Requests supports this out of the box as well::
OAuth 1 Authentication
----------------------
A common form of authentication for several web APIs is OAuth. The ``requests-oauthlib`` library allows Requests users to easily make OAuth authenticated requests::
A common form of authentication for several web APIs is OAuth. The ``requests-oauthlib``
library allows Requests users to easily make OAuth authenticated requests::
>>> import requests
>>> from requests_oauthlib import OAuth1
@@ -60,7 +72,8 @@ A common form of authentication for several web APIs is OAuth. The ``requests-oa
<Response [200]>
For more information on how to OAuth flow works, please see the official `OAuth`_ website.
For examples and documentation on requests-oauthlib, please see the `requests_oauthlib`_ repository on GitHub
For examples and documentation on requests-oauthlib, please see the `requests_oauthlib`_
repository on GitHub
Other Authentication
@@ -76,7 +89,7 @@ authentication. Some of the best have been brought together under the
- NTLM_
If you want to use any of these forms of authentication, go straight to their
Github page and follow the instructions.
GitHub page and follow the instructions.
New Forms of Authentication
@@ -87,12 +100,24 @@ want, you can implement it yourself. Requests makes it easy to add your own
forms of authentication.
To do so, subclass :class:`requests.auth.AuthBase` and implement the
``__call__()`` method. When an authentication handler is attached to a request,
``__call__()`` method::
>>> import requests
>>> class MyAuth(requests.auth.AuthBase):
... def __call__(self, r):
... # Implement my authentication
... return r
...
>>> url = 'http://httpbin.org/get'
>>> requests.get(url, auth=MyAuth())
<Response [200]>
When an authentication handler is attached to a request,
it is called during request setup. The ``__call__`` method must therefore do
whatever is required to make the authentication work. Some forms of
authentication will additionally add hooks to provide further functionality.
Examples can be found under the `Requests organization`_ and in the
Further examples can be found under the `Requests organization`_ and in the
``auth.py`` file.
.. _OAuth: http://oauth.net/

View File

@@ -10,7 +10,7 @@ The first step to using any software package is getting it properly installed.
Distribute & Pip
----------------
Installing requests is simple with `pip <http://www.pip-installer.org/>`_::
Installing Requests is simple with `pip <http://www.pip-installer.org/>`_::
$ pip install requests
@@ -22,11 +22,11 @@ But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/other
Cheeseshop Mirror
-----------------
Cheeseshop (PyPI) Mirror
------------------------
If the Cheeseshop is down, you can also install Requests from one of the
mirrors. `Crate.io <http://crate.io>`_ is one of them::
If the Cheeseshop (a.k.a. PyPI) is down, you can also install Requests from one
of the mirrors. `Crate.io <http://crate.io>`_ is one of them::
$ pip install -i http://simple.crate.io/ requests

View File

@@ -20,7 +20,7 @@ All contributions to Requests should keep these important rules in mind.
.. _`apache2`:
Apache2 License
-----------
---------------
A large number of open source projects you find today are `GPL Licensed`_.
While the GPL has its time and place, it should most certainly not be your

View File

@@ -68,8 +68,11 @@ following code::
You can see that the URL has been correctly encoded by printing the URL::
>>> print r.url
u'http://httpbin.org/get?key2=value2&key1=value1'
>>> print(r.url)
http://httpbin.org/get?key2=value2&key1=value1
Note that any dictionary key whose value is ``None`` will not be added to the
URL's query string.
Response Content
@@ -81,7 +84,7 @@ again::
>>> import requests
>>> r = requests.get('https://github.com/timeline.json')
>>> r.text
'[{"repository":{"open_issues":0,"url":"https://github.com/...
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests will automatically decode content from the server. Most unicode
charsets are seamlessly decoded.
@@ -114,7 +117,7 @@ You can also access the response body as bytes, for non-text requests::
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
For example, to create an image from binary data returned by a request, you can
use the following code:
use the following code::
>>> from PIL import Image
>>> from StringIO import StringIO
@@ -149,6 +152,18 @@ server, you can access ``r.raw``. If you want to do this, make sure you set
>>> r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
In general, however, you should use a pattern like this to save what is being
streamed to a file::
with open(filename, 'wb') as fd:
for chunk in r.iter_content(chunk_size):
fd.write(chunk)
Using ``Response.iter_content`` will handle a lot of what you would otherwise
have to handle when using ``Response.raw`` directly. When streaming a
download, the above is the preferred and recommended way to retrieve the
content.
Custom Headers
--------------
@@ -170,7 +185,7 @@ More complicated POST requests
------------------------------
Typically, you want to send some form-encoded data — much like an HTML form.
To do this, simply pass a dictionary to the `data` argument. Your
To do this, simply pass a dictionary to the ``data`` argument. Your
dictionary of data will automatically be form-encoded when the request is made::
>>> payload = {'key1': 'value1', 'key2': 'value2'}
@@ -260,7 +275,7 @@ reference::
>>> r.status_code == requests.codes.ok
True
If we made a bad request (non-200 response), we can raise it with
If we made a bad request (a 4XX client error or 5XX server error response), we can raise it with
:class:`Response.raise_for_status()`::
>>> bad_r = requests.get('http://httpbin.org/status/404')
@@ -295,7 +310,7 @@ We can view the server's response headers using a Python dictionary::
'server': 'nginx/1.0.4',
'x-runtime': '148ms',
'etag': '"e1ca502697e5c9317743dc078f67693f"',
'content-type': 'application/json; charset=utf-8'
'content-type': 'application/json'
}
The dictionary is special, though: it's made just for HTTP headers. According to
@@ -305,15 +320,10 @@ Headers are case-insensitive.
So, we can access the headers using any capitalization we want::
>>> r.headers['Content-Type']
'application/json; charset=utf-8'
'application/json'
>>> r.headers.get('content-type')
'application/json; charset=utf-8'
If a header doesn't exist in the Response, its value defaults to ``None``::
>>> r.headers['X-Random']
None
'application/json'
Cookies
@@ -341,11 +351,11 @@ parameter::
Redirection and History
-----------------------
Requests will automatically perform location redirection while using the GET
and OPTIONS verbs.
Requests will automatically perform location redirection for all verbs except
HEAD.
GitHub redirects all HTTP requests to HTTPS. We can use the ``history`` method
of the Response object to track redirection. Let's see what Github does::
of the Response object to track redirection. Let's see what GitHub does::
>>> r = requests.get('http://github.com')
>>> r.url
@@ -355,11 +365,12 @@ of the Response object to track redirection. Let's see what Github does::
>>> r.history
[<Response [301]>]
The :class:`Response.history` list contains a list of the
:class:`Request` objects that were created in order to complete the request. The list is sorted from the oldest to the most recent request.
The :class:`Response.history` list contains the :class:`Request` objects that
were created in order to complete the request. The list is sorted from the
oldest to the most recent request.
If you're using GET or OPTIONS, you can disable redirection handling with the
``allow_redirects`` parameter::
If you're using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable
redirection handling with the ``allow_redirects`` parameter::
>>> r = requests.get('http://github.com', allow_redirects=False)
>>> r.status_code
@@ -367,8 +378,7 @@ If you're using GET or OPTIONS, you can disable redirection handling with the
>>> r.history
[]
If you're using POST, PUT, PATCH, DELETE or HEAD, you can enable
redirection as well::
If you're using HEAD, you can enable redirection as well::
>>> r = requests.post('http://github.com', allow_redirects=True)
>>> r.url
@@ -380,7 +390,7 @@ redirection as well::
Timeouts
--------
You can tell requests to stop waiting for a response after a given number of
You can tell Requests to stop waiting for a response after a given number of
seconds with the ``timeout`` parameter::
>>> requests.get('http://github.com', timeout=0.001)
@@ -389,25 +399,28 @@ seconds with the ``timeout`` parameter::
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
.. admonition:: Note:
.. admonition:: Note
``timeout`` only effects the connection process itself, not the
downloading of the response body.
``timeout`` is not a time limit on the entire response download;
rather, an exception is raised if the server has not issued a
response for ``timeout`` seconds (more precisely, if no bytes have been
received on the underlying socket for ``timeout`` seconds).
Errors and Exceptions
---------------------
In the event of a network problem (e.g. DNS failure, refused connection, etc),
Requests will raise a :class:`ConnectionError` exception.
Requests will raise a :class:`~requests.exceptions.ConnectionError` exception.
In the event of the rare invalid HTTP response, Requests will raise
an :class:`HTTPError` exception.
In the event of the rare invalid HTTP response, Requests will raise an
:class:`~requests.exceptions.HTTPError` exception.
If a request times out, a :class:`Timeout` exception is raised.
If a request times out, a :class:`~requests.exceptions.Timeout` exception is
raised.
If a request exceeds the configured number of maximum redirections, a
:class:`TooManyRedirects` exception is raised.
:class:`~requests.exceptions.TooManyRedirects` exception is raised.
All exceptions that Requests explicitly raises inherit from
:class:`requests.exceptions.RequestException`.

View File

@@ -1,5 +0,0 @@
from invoke import run, task
@task
def build():
print("Building!")

View File

@@ -23,7 +23,7 @@ usage:
>>> payload = dict(key1='value1', key2='value2')
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print r.text
>>> print(r.text)
{
...
"form": {
@@ -42,15 +42,15 @@ is at <http://python-requests.org>.
"""
__title__ = 'requests'
__version__ = '1.2.2'
__build__ = 0x010202
__version__ = '2.0.1'
__build__ = 0x020001
__author__ = 'Kenneth Reitz'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2013 Kenneth Reitz'
# Attempt to enable urllib3's SNI support, if possible
try:
from requests.packages.urllib3.contrib import pyopenssl
from .packages.urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
except ImportError:
pass

View File

@@ -11,18 +11,20 @@ and maintain connections.
import socket
from .models import Response
from .packages.urllib3.poolmanager import PoolManager, ProxyManager
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, unquote
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
prepend_scheme_if_needed, get_auth_from_url)
except_on_missing_scheme, get_auth_from_url)
from .structures import CaseInsensitiveDict
from .packages.urllib3.exceptions import MaxRetryError
from .packages.urllib3.exceptions import TimeoutError
from .packages.urllib3.exceptions import SSLError as _SSLError
from .packages.urllib3.exceptions import HTTPError as _HTTPError
from .packages.urllib3.exceptions import ProxyError as _ProxyError
from .cookies import extract_cookies_to_jar
from .exceptions import ConnectionError, Timeout, SSLError
from .exceptions import ConnectionError, Timeout, SSLError, ProxyError
from .auth import _basic_auth_str
DEFAULT_POOLBLOCK = False
@@ -71,6 +73,7 @@ class HTTPAdapter(BaseAdapter):
pool_block=DEFAULT_POOLBLOCK):
self.max_retries = max_retries
self.config = {}
self.proxy_manager = {}
super(HTTPAdapter, self).__init__()
@@ -118,7 +121,7 @@ class HTTPAdapter(BaseAdapter):
:param verify: Whether we should actually verify the certificate.
:param cert: The SSL certificate to verify.
"""
if url.startswith('https') and verify:
if url.lower().startswith('https') and verify:
cert_loc = None
@@ -184,19 +187,26 @@ class HTTPAdapter(BaseAdapter):
def get_connection(self, url, proxies=None):
"""Returns a urllib3 connection for the given URL. This should not be
called from user code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <reqeusts.adapters.HTTPAdapter>`.
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
: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).scheme)
proxy = proxies.get(urlparse(url.lower()).scheme)
if proxy:
proxy = prepend_scheme_if_needed(proxy, urlparse(url).scheme)
conn = ProxyManager(self.poolmanager.connection_from_url(proxy))
except_on_missing_scheme(proxy)
proxy_headers = self.proxy_headers(proxy)
if not proxy in self.proxy_manager:
self.proxy_manager[proxy] = proxy_from_url(
proxy,
proxy_headers=proxy_headers)
conn = self.proxy_manager[proxy].connection_from_url(url)
else:
conn = self.poolmanager.connection_from_url(url)
conn = self.poolmanager.connection_from_url(url.lower())
return conn
@@ -211,10 +221,10 @@ class HTTPAdapter(BaseAdapter):
def request_url(self, request, proxies):
"""Obtain the url to use when making the final request.
If the message is being sent through a proxy, the full URL has to be
used. Otherwise, we should only use the path portion of the URL.
If the message is being sent through a HTTP proxy, the full URL has to
be used. Otherwise, we should only use the path portion of the URL.
This shoudl not be called from user code, and is only exposed for use
This should not be called from user code, and is only exposed for use
when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
@@ -222,9 +232,10 @@ class HTTPAdapter(BaseAdapter):
:param proxies: A dictionary of schemes to proxy URLs.
"""
proxies = proxies or {}
proxy = proxies.get(urlparse(request.url).scheme)
scheme = urlparse(request.url).scheme.lower()
proxy = proxies.get(scheme)
if proxy:
if proxy and scheme != 'https':
url, _ = urldefrag(request.url)
else:
url = request.path_url
@@ -232,8 +243,9 @@ class HTTPAdapter(BaseAdapter):
return url
def add_headers(self, request, **kwargs):
"""Add any headers needed by the connection. Currently this adds a
Proxy-Authorization header.
"""Add any headers needed by the connection. As of v2.0 this does
nothing by default, but is left for overriding by users that subclass
the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
This should not be called from user code, and is only exposed for use
when subclassing the
@@ -242,12 +254,22 @@ class HTTPAdapter(BaseAdapter):
:param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.
:param kwargs: The keyword arguments from the call to send().
"""
proxies = kwargs.get('proxies', {})
pass
if proxies is None:
proxies = {}
def proxy_headers(self, proxy):
"""Returns a dictionary of the headers to add to any request sent
through a proxy. This works with urllib3 magic to ensure that they are
correctly sent to the proxy, rather than in a tunnelled request if
CONNECT is being used.
proxy = proxies.get(urlparse(request.url).scheme)
This should not be called from user code, and is only exposed for use
when subclassing the
: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)
if username and password:
@@ -255,8 +277,10 @@ class HTTPAdapter(BaseAdapter):
# to decode them.
username = unquote(username)
password = unquote(password)
request.headers['Proxy-Authorization'] = _basic_auth_str(username,
password)
headers['Proxy-Authorization'] = _basic_auth_str(username,
password)
return headers
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
"""Sends PreparedRequest object. Returns Response object.
@@ -273,10 +297,15 @@ class HTTPAdapter(BaseAdapter):
self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies)
self.add_headers(request, proxies=proxies)
self.add_headers(request)
chunked = not (request.body is None or 'Content-Length' in request.headers)
if stream:
timeout = TimeoutSauce(connect=timeout)
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)
try:
if not chunked:
resp = conn.urlopen(
@@ -326,6 +355,9 @@ class HTTPAdapter(BaseAdapter):
except MaxRetryError as e:
raise ConnectionError(e)
except _ProxyError as e:
raise ProxyError(e)
except (_SSLError, _HTTPError) as e:
if isinstance(e, _SSLError):
raise SSLError(e)

View File

@@ -18,7 +18,6 @@ from base64 import b64encode
from .compat import urlparse, str
from .utils import parse_dict_header
log = logging.getLogger(__name__)
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
@@ -106,7 +105,9 @@ class HTTPDigestAuth(AuthBase):
A1 = '%s:%s:%s' % (self.username, realm, self.password)
A2 = '%s:%s' % (method, path)
if qop == 'auth':
if qop is None:
respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
elif qop == 'auth' or 'auth' in qop.split(','):
if nonce == self.last_nonce:
self.nonce_count += 1
else:
@@ -121,8 +122,6 @@ class HTTPDigestAuth(AuthBase):
cnonce = (hashlib.sha1(s).hexdigest()[:16])
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, hash_utf8(A2))
respdig = KD(hash_utf8(A1), noncebit)
elif qop is None:
respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
else:
# XXX handle auth-int.
return None
@@ -159,10 +158,14 @@ class HTTPDigestAuth(AuthBase):
# to allow our new request to reuse the same one.
r.content
r.raw.release_conn()
prep = r.request.copy()
prep.prepare_cookies(r.cookies)
r.request.headers['Authorization'] = self.build_digest_header(r.request.method, r.request.url)
_r = r.connection.send(r.request, **kwargs)
prep.headers['Authorization'] = self.build_digest_header(
prep.method, prep.url)
_r = r.connection.send(prep, **kwargs)
_r.history.append(r)
_r.request = prep
return _r

File diff suppressed because it is too large Load Diff

View File

@@ -83,13 +83,14 @@ except ImportError:
# ---------
if is_py2:
from urllib import quote, unquote, quote_plus, unquote_plus, urlencode
from urllib import quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, proxy_bypass
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
from urllib2 import parse_http_list
import cookielib
from Cookie import Morsel
from StringIO import StringIO
from .packages.urllib3.packages.ordered_dict import OrderedDict
from httplib import IncompleteRead
builtin_str = str
bytes = str
@@ -100,11 +101,12 @@ if is_py2:
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
from urllib.request import parse_http_list, getproxies, proxy_bypass
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
from collections import OrderedDict
from http.client import IncompleteRead
builtin_str = str
str = str

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 time
import collections
from .compat import cookielib, urlparse, Morsel
@@ -73,6 +74,10 @@ class MockRequest(object):
def origin_req_host(self):
return self.get_origin_req_host()
@property
def host(self):
return self.get_host()
class MockResponse(object):
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
@@ -102,6 +107,9 @@ def extract_cookies_to_jar(jar, request, response):
:param request: our own requests.Request object
:param response: urllib3.HTTPResponse object
"""
if not (hasattr(response, '_original_response') and
response._original_response):
return
# the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request)
# pull out the HTTPMessage with the headers and put it in the mock:
@@ -258,6 +266,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""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):
if cookie.value.startswith('"') and cookie.value.endswith('"'):
cookie.value = cookie.value.replace('\\"', '')
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
def update(self, other):
"""Updates this jar with cookies from another CookieJar or dict-like"""
if isinstance(other, cookielib.CookieJar):
@@ -354,19 +367,23 @@ def create_cookie(name, value, **kwargs):
def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None
if morsel["max-age"]:
expires = time.time() + morsel["max-age"]
elif morsel['expires']:
expires = morsel['expires']
if type(expires) == type(""):
time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
expires = time.mktime(time.strptime(expires, time_template))
c = create_cookie(
name=morsel.key,
value=morsel.value,
version=morsel['version'] or 0,
port=None,
port_specified=False,
domain=morsel['domain'],
domain_specified=bool(morsel['domain']),
domain_initial_dot=morsel['domain'].startswith('.'),
path=morsel['path'],
path_specified=bool(morsel['path']),
secure=bool(morsel['secure']),
expires=morsel['max-age'] or morsel['expires'],
expires=expires,
discard=False,
comment=morsel['comment'],
comment_url=bool(morsel['comment']),
@@ -375,15 +392,21 @@ def morsel_to_cookie(morsel):
return c
def cookiejar_from_dict(cookie_dict, cookiejar=None):
def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
"""Returns a CookieJar from a key/value dictionary.
:param cookie_dict: Dict of key/values to insert into CookieJar.
:param cookiejar: (optional) A cookiejar to add the cookies to.
:param overwrite: (optional) If False, will not replace cookies
already in the jar with new ones.
"""
if cookiejar is None:
cookiejar = RequestsCookieJar()
if cookie_dict is not None:
names_from_jar = [cookie.name for cookie in cookiejar]
for name in cookie_dict:
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
if overwrite or (name not in names_from_jar):
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
return cookiejar

View File

@@ -9,7 +9,7 @@ This module contains the set of Requests' exceptions.
"""
class RequestException(RuntimeError):
class RequestException(IOError):
"""There was an ambiguous exception that occurred while handling your
request."""
@@ -27,6 +27,10 @@ class ConnectionError(RequestException):
"""A Connection error occurred."""
class ProxyError(ConnectionError):
"""A proxy error occurred."""
class SSLError(ConnectionError):
"""An SSL error occurred."""
@@ -53,3 +57,7 @@ class InvalidSchema(RequestException, ValueError):
class InvalidURL(RequestException, ValueError):
""" The URL provided was somehow invalid. """
class ChunkedEncodingError(RequestException):
"""The server declared chunked encoding but sent an invalid chunk."""

View File

@@ -11,22 +11,25 @@ import collections
import logging
import datetime
from io import BytesIO
from io import BytesIO, UnsupportedOperation
from .hooks import default_hooks
from .structures import CaseInsensitiveDict
from .auth import HTTPBasicAuth
from .cookies import cookiejar_from_dict, get_cookie_header
from .packages.urllib3.fields import RequestField
from .packages.urllib3.filepost import encode_multipart_formdata
from .packages.urllib3.util import parse_url
from .exceptions import HTTPError, RequestException, MissingSchema, InvalidURL
from .exceptions import (
HTTPError, RequestException, MissingSchema, InvalidURL,
ChunkedEncodingError)
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)
iter_slices, guess_json_utf, super_len, to_native_string)
from .compat import (
cookielib, urlparse, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
is_py2, chardet, json, builtin_str, basestring)
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
is_py2, chardet, json, builtin_str, basestring, IncompleteRead)
CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
@@ -88,12 +91,14 @@ class RequestEncodingMixin(object):
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
2-tuples. Order is retained if data is a list of 2-tuples but abritrary
2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
if parameters are supplied as a dict.
"""
if (not files) or isinstance(data, str):
return None
if (not files):
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
new_fields = []
fields = to_key_val_list(data or {})
@@ -104,6 +109,10 @@ class RequestEncodingMixin(object):
val = [val]
for v in val:
if v is not None:
# Don't call str() on bytestrings: in Py3 it all goes wrong.
if not isinstance(v, bytes):
v = str(v)
new_fields.append(
(field.decode('utf-8') if isinstance(field, bytes) else field,
v.encode('utf-8') if isinstance(v, str) else v))
@@ -111,11 +120,14 @@ class RequestEncodingMixin(object):
for (k, v) in files:
# support for explicit filename
ft = None
fh = None
if isinstance(v, (tuple, list)):
if len(v) == 2:
fn, fp = v
else:
elif len(v) == 3:
fn, fp, ft = v
else:
fn, fp, ft, fh = v
else:
fn = guess_filename(v) or k
fp = v
@@ -124,11 +136,10 @@ class RequestEncodingMixin(object):
if isinstance(fp, bytes):
fp = BytesIO(fp)
if ft:
new_v = (fn, fp.read(), ft)
else:
new_v = (fn, fp.read())
new_fields.append((k, new_v))
rf = RequestField(name=k, data=fp.read(),
filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
body, content_type = encode_multipart_formdata(new_fields)
@@ -139,6 +150,9 @@ class RequestHooksMixin(object):
def register_hook(self, event, hook):
"""Properly register a hook."""
if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
if isinstance(hook, collections.Callable):
self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'):
@@ -184,8 +198,8 @@ class Request(RequestHooksMixin):
url=None,
headers=None,
files=None,
data=dict(),
params=dict(),
data=None,
params=None,
auth=None,
cookies=None,
hooks=None):
@@ -209,7 +223,6 @@ class Request(RequestHooksMixin):
self.params = params
self.auth = auth
self.cookies = cookies
self.hooks = hooks
def __repr__(self):
return '<Request [%s]>' % (self.method)
@@ -217,19 +230,17 @@ class Request(RequestHooksMixin):
def prepare(self):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
p = PreparedRequest()
p.prepare_method(self.method)
p.prepare_url(self.url, self.params)
p.prepare_headers(self.headers)
p.prepare_cookies(self.cookies)
p.prepare_body(self.data, self.files)
p.prepare_auth(self.auth, self.url)
# Note that prepare_auth must be last to enable authentication schemes
# such as OAuth to work on a fully prepared request.
# This MUST go after prepare_auth. Authenticators could add a hook
p.prepare_hooks(self.hooks)
p.prepare(
method=self.method,
url=self.url,
headers=self.headers,
files=self.files,
data=self.data,
params=self.params,
auth=self.auth,
cookies=self.cookies,
hooks=self.hooks,
)
return p
@@ -264,9 +275,34 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: dictionary of callback hooks, for internal usage.
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):
"""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_auth(auth, url)
# Note that prepare_auth must be last to enable authentication schemes
# such as OAuth to work on a fully prepared request.
# This MUST go after prepare_auth. Authenticators could add a hook
self.prepare_hooks(hooks)
def __repr__(self):
return '<PreparedRequest [%s]>' % (self.method)
def copy(self):
p = PreparedRequest()
p.method = self.method
p.url = self.url
p.headers = self.headers.copy()
p.body = self.body
p.hooks = self.hooks
return p
def prepare_method(self, method):
"""Prepares the given HTTP method."""
self.method = method
@@ -337,8 +373,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
"""Prepares the given HTTP headers."""
if headers:
headers = dict((name.encode('ascii'), value) for name, value in headers.items())
self.headers = CaseInsensitiveDict(headers)
self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items())
else:
self.headers = CaseInsensitiveDict()
@@ -352,7 +387,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
body = None
content_type = None
length = None
is_stream = False
is_stream = all([
hasattr(data, '__iter__'),
@@ -363,8 +397,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
try:
length = super_len(data)
except (TypeError, AttributeError):
length = False
except (TypeError, AttributeError, UnsupportedOperation):
length = None
if is_stream:
body = data
@@ -372,13 +406,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
if length:
if length is not None:
self.headers['Content-Length'] = str(length)
else:
self.headers['Transfer-Encoding'] = 'chunked'
# Check if file, fo, generator, iterator.
# If not, run through normal process.
else:
# Multi-part file uploads.
if files:
@@ -537,11 +568,22 @@ class Response(object):
return iter_slices(self._content, chunk_size)
def generate():
while 1:
chunk = self.raw.read(chunk_size, decode_content=True)
if not chunk:
break
yield chunk
try:
# Special case for urllib3.
try:
for chunk in self.raw.stream(chunk_size,
decode_content=True):
yield chunk
except IncompleteRead as e:
raise ChunkedEncodingError(e)
except AttributeError:
# Standard file-like object.
while True:
chunk = self.raw.read(chunk_size)
if not chunk:
break
yield chunk
self._content_consumed = True
gen = generate()
@@ -648,7 +690,7 @@ class Response(object):
encoding = guess_json_utf(self.content)
if encoding is not None:
return json.loads(self.content.decode(encoding), **kwargs)
return json.loads(self.text or self.content, **kwargs)
return json.loads(self.text, **kwargs)
@property
def links(self):
@@ -683,4 +725,9 @@ class Response(object):
raise HTTPError(http_error_msg, response=self)
def close(self):
"""Closes the underlying file descriptor and releases the connection
back to the pool.
*Note: Should not normally need to be called explicitly.*
"""
return self.raw.release_conn()

View File

@@ -30,3 +30,37 @@ def detect(aBuf):
u.feed(aBuf)
u.close()
return u.result
def _description_of(path):
"""Return a string describing the probable encoding of a file."""
from charade.universaldetector import UniversalDetector
u = UniversalDetector()
for line in open(path, 'rb'):
u.feed(line)
u.close()
result = u.result
if result['encoding']:
return '%s: %s with confidence %s' % (path,
result['encoding'],
result['confidence'])
else:
return '%s: no result' % path
def charade_cli():
"""
Script which takes one or more file paths and reports on their detected
encodings
Example::
% chardetect.py somefile someotherfile
somefile: windows-1252 with confidence 0.5
someotherfile: ascii with confidence 1.0
"""
from sys import argv
for path in argv[1:]:
print(_description_of(path))

View File

@@ -0,0 +1,7 @@
'''
support ';python -m charade <file1> [file2] ...' package execution syntax (2.7+)
'''
from charade import charade_cli
charade_cli()

View File

@@ -169,7 +169,7 @@ class JapaneseContextAnalysis:
def get_confidence(self):
# This is just one way to calculate confidence. It works well for me.
if self._mTotalRel > MINIMUM_DATA_THRESHOLD:
return (self._mTotalRel - self._mRelSample[0]) / self._mTotalRel
return float(self._mTotalRel - self._mRelSample[0]) / self._mTotalRel
else:
return DONT_KNOW

View File

@@ -129,7 +129,7 @@ class Latin1Prober(CharSetProber):
if total < 0.01:
confidence = 0.0
else:
confidence = ((self._mFreqCounter[3] / total)
confidence = ((float(self._mFreqCounter[3]) / total)
- (self._mFreqCounter[1] * 20.0 / total))
if confidence < 0.0:
confidence = 0.0

View File

@@ -74,12 +74,10 @@ class UniversalDetector:
if aBuf[:3] == codecs.BOM:
# EF BB BF UTF-8 with BOM
self.result = {'encoding': "UTF-8", 'confidence': 1.0}
elif aBuf[:4] == codecs.BOM_UTF32_LE:
elif aBuf[:4] in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
# FF FE 00 00 UTF-32, little-endian BOM
self.result = {'encoding': "UTF-32LE", 'confidence': 1.0}
elif aBuf[:4] == codecs.BOM_UTF32_BE:
# 00 00 FE FF UTF-32, big-endian BOM
self.result = {'encoding': "UTF-32BE", 'confidence': 1.0}
self.result = {'encoding': "UTF-32", 'confidence': 1.0}
elif aBuf[:4] == b'\xFE\xFF\x00\x00':
# FE FF 00 00 UCS-4, unusual octet order BOM (3412)
self.result = {
@@ -92,12 +90,10 @@ class UniversalDetector:
'encoding': "X-ISO-10646-UCS-4-2143",
'confidence': 1.0
}
elif aBuf[:2] == codecs.BOM_LE:
elif aBuf[:2] == codecs.BOM_LE or aBuf[:2] == codecs.BOM_BE:
# FF FE UTF-16, little endian BOM
self.result = {'encoding': "UTF-16LE", 'confidence': 1.0}
elif aBuf[:2] == codecs.BOM_BE:
# FE FF UTF-16, big endian BOM
self.result = {'encoding': "UTF-16BE", 'confidence': 1.0}
self.result = {'encoding': "UTF-16", 'confidence': 1.0}
self._mGotData = True
if self.result['encoding'] and (self.result['confidence'] > 0.0):

View File

@@ -23,7 +23,7 @@ from . import exceptions
from .filepost import encode_multipart_formdata
from .poolmanager import PoolManager, ProxyManager, proxy_from_url
from .response import HTTPResponse
from .util import make_headers, get_host
from .util import make_headers, get_host, Timeout
# Set default logging handler to avoid "No handler found" warnings.

View File

@@ -5,7 +5,7 @@
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from collections import MutableMapping
from threading import Lock
from threading import RLock
try: # Python 2.7+
from collections import OrderedDict
@@ -40,18 +40,18 @@ class RecentlyUsedContainer(MutableMapping):
self.dispose_func = dispose_func
self._container = self.ContainerCls()
self._lock = Lock()
self.lock = RLock()
def __getitem__(self, key):
# Re-insert the item, moving it to the end of the eviction line.
with self._lock:
with self.lock:
item = self._container.pop(key)
self._container[key] = item
return item
def __setitem__(self, key, value):
evicted_value = _Null
with self._lock:
with self.lock:
# Possibly evict the existing value of 'key'
evicted_value = self._container.get(key, _Null)
self._container[key] = value
@@ -65,21 +65,21 @@ class RecentlyUsedContainer(MutableMapping):
self.dispose_func(evicted_value)
def __delitem__(self, key):
with self._lock:
with self.lock:
value = self._container.pop(key)
if self.dispose_func:
self.dispose_func(value)
def __len__(self):
with self._lock:
with self.lock:
return len(self._container)
def __iter__(self):
raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.')
def clear(self):
with self._lock:
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())
@@ -90,5 +90,5 @@ class RecentlyUsedContainer(MutableMapping):
self.dispose_func(value)
def keys(self):
with self._lock:
with self.lock:
return self._container.keys()

View File

@@ -0,0 +1,107 @@
# urllib3/connection.py
# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import socket
from socket import timeout as SocketTimeout
try: # Python 3
from http.client import HTTPConnection, HTTPException
except ImportError:
from httplib import HTTPConnection, HTTPException
class DummyConnection(object):
"Used to detect a failed ConnectionCls import."
pass
try: # Compiled with SSL?
ssl = None
HTTPSConnection = DummyConnection
class BaseSSLError(BaseException):
pass
try: # Python 3
from http.client import HTTPSConnection
except ImportError:
from httplib import HTTPSConnection
import ssl
BaseSSLError = ssl.SSLError
except (ImportError, AttributeError): # Platform-specific: No SSL.
pass
from .exceptions import (
ConnectTimeoutError,
)
from .packages.ssl_match_hostname import match_hostname
from .util import (
assert_fingerprint,
resolve_cert_reqs,
resolve_ssl_version,
ssl_wrap_socket,
)
class VerifiedHTTPSConnection(HTTPSConnection):
"""
Based on httplib.HTTPSConnection but wraps the socket with
SSL certification.
"""
cert_reqs = None
ca_certs = None
ssl_version = None
def set_cert(self, key_file=None, cert_file=None,
cert_reqs=None, ca_certs=None,
assert_hostname=None, assert_fingerprint=None):
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
def connect(self):
# Add certificate verification
try:
sock = socket.create_connection(
address=(self.host, self.port),
timeout=self.timeout,
)
except SocketTimeout:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, self.timeout))
resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
resolved_ssl_version = resolve_ssl_version(self.ssl_version)
if self._tunnel_host:
self.sock = sock
# Calls self._set_hostport(), so self.host is
# self._tunnel_host below.
self._tunnel()
# Wrap socket using verification with the root certs in
# trusted_root_certs
self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
cert_reqs=resolved_cert_reqs,
ca_certs=self.ca_certs,
server_hostname=self.host,
ssl_version=resolved_ssl_version)
if resolved_cert_reqs != ssl.CERT_NONE:
if self.assert_fingerprint:
assert_fingerprint(self.sock.getpeercert(binary_form=True),
self.assert_fingerprint)
elif self.assert_hostname is not False:
match_hostname(self.sock.getpeercert(),
self.assert_hostname or self.host)
if ssl:
HTTPSConnection = VerifiedHTTPSConnection

View File

@@ -4,57 +4,45 @@
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import logging
import socket
import errno
import logging
from socket import error as SocketError, timeout as SocketTimeout
from .util import resolve_cert_reqs, resolve_ssl_version, assert_fingerprint
try: # Python 3
from http.client import HTTPConnection, HTTPException
from http.client import HTTP_PORT, HTTPS_PORT
except ImportError:
from httplib import HTTPConnection, HTTPException
from httplib import HTTP_PORT, HTTPS_PORT
import socket
try: # Python 3
from queue import LifoQueue, Empty, Full
except ImportError:
from Queue import LifoQueue, Empty, Full
import Queue as _ # Platform-specific: Windows
try: # Compiled with SSL?
HTTPSConnection = object
BaseSSLError = None
ssl = None
try: # Python 3
from http.client import HTTPSConnection
except ImportError:
from httplib import HTTPSConnection
import ssl
BaseSSLError = ssl.SSLError
except (ImportError, AttributeError): # Platform-specific: No SSL.
pass
from .request import RequestMethods
from .response import HTTPResponse
from .util import get_host, is_connection_dropped, ssl_wrap_socket
from .exceptions import (
ClosedPoolError,
ConnectTimeoutError,
EmptyPoolError,
HostChangedError,
MaxRetryError,
SSLError,
TimeoutError,
ReadTimeoutError,
ProxyError,
)
from .packages.ssl_match_hostname import match_hostname, CertificateError
from .packages.ssl_match_hostname import CertificateError
from .packages import six
from .connection import (
DummyConnection,
HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
HTTPException, BaseSSLError,
)
from .request import RequestMethods
from .response import HTTPResponse
from .util import (
assert_fingerprint,
get_host,
is_connection_dropped,
Timeout,
)
xrange = six.moves.xrange
@@ -64,56 +52,11 @@ log = logging.getLogger(__name__)
_Default = object()
port_by_scheme = {
'http': HTTP_PORT,
'https': HTTPS_PORT,
'http': 80,
'https': 443,
}
## Connection objects (extension of httplib)
class VerifiedHTTPSConnection(HTTPSConnection):
"""
Based on httplib.HTTPSConnection but wraps the socket with
SSL certification.
"""
cert_reqs = None
ca_certs = None
ssl_version = None
def set_cert(self, key_file=None, cert_file=None,
cert_reqs=None, ca_certs=None,
assert_hostname=None, assert_fingerprint=None):
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
def connect(self):
# Add certificate verification
sock = socket.create_connection((self.host, self.port), self.timeout)
resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs)
resolved_ssl_version = resolve_ssl_version(self.ssl_version)
# Wrap socket using verification with the root certs in
# trusted_root_certs
self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file,
cert_reqs=resolved_cert_reqs,
ca_certs=self.ca_certs,
server_hostname=self.host,
ssl_version=resolved_ssl_version)
if resolved_cert_reqs != ssl.CERT_NONE:
if self.assert_fingerprint:
assert_fingerprint(self.sock.getpeercert(binary_form=True),
self.assert_fingerprint)
else:
match_hostname(self.sock.getpeercert(),
self.assert_hostname or self.host)
## Pool objects
class ConnectionPool(object):
@@ -126,6 +69,9 @@ class ConnectionPool(object):
QueueCls = LifoQueue
def __init__(self, host, port=None):
# httplib doesn't like it when we include brackets in ipv6 addresses
host = host.strip('[]')
self.host = host
self.port = port
@@ -133,6 +79,8 @@ class ConnectionPool(object):
return '%s(host=%r, port=%r)' % (type(self).__name__,
self.host, self.port)
# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK])
class HTTPConnectionPool(ConnectionPool, RequestMethods):
"""
@@ -151,9 +99,15 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
as a valid HTTP/1.0 or 1.1 status line, passed into
:class:`httplib.HTTPConnection`.
.. note::
Only works in Python 2. This parameter is ignored in Python 3.
:param timeout:
Socket timeout for each individual connection, can be a float. None
disables timeout.
Socket timeout in seconds for each individual connection. This can
be a float or integer, which sets the timeout for the HTTP request,
or an instance of :class:`urllib3.util.Timeout` which gives you more
fine-grained control over request timeouts. After the constructor has
been parsed, this is always a `urllib3.util.Timeout` object.
:param maxsize:
Number of connections to save that can be reused. More than 1 is useful
@@ -171,20 +125,40 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
:param headers:
Headers to include with all requests, unless other headers are given
explicitly.
:param _proxy:
Parsed proxy URL, should not be used directly, instead, see
:class:`urllib3.connectionpool.ProxyManager`"
:param _proxy_headers:
A dictionary with proxy headers, should not be used directly,
instead, see :class:`urllib3.connectionpool.ProxyManager`"
"""
scheme = 'http'
ConnectionCls = HTTPConnection
def __init__(self, host, port=None, strict=False, timeout=None, maxsize=1,
block=False, headers=None):
def __init__(self, host, port=None, strict=False,
timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False,
headers=None, _proxy=None, _proxy_headers=None):
ConnectionPool.__init__(self, host, port)
RequestMethods.__init__(self, headers)
self.strict = strict
# This is for backwards compatibility and can be removed once a timeout
# can only be set to a Timeout object
if not isinstance(timeout, Timeout):
timeout = Timeout.from_float(timeout)
self.timeout = timeout
self.pool = self.QueueCls(maxsize)
self.block = block
self.proxy = _proxy
self.proxy_headers = _proxy_headers or {}
# Fill the queue up so that doing get() on it will block properly
for _ in xrange(maxsize):
self.pool.put(None)
@@ -200,9 +174,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
self.num_connections += 1
log.info("Starting new HTTP connection (%d): %s" %
(self.num_connections, self.host))
return HTTPConnection(host=self.host,
port=self.port,
strict=self.strict)
extra_params = {}
if not six.PY3: # Python 2
extra_params['strict'] = self.strict
return self.ConnectionCls(host=self.host, port=self.port,
timeout=self.timeout.connect_timeout,
**extra_params)
def _get_conn(self, timeout=None):
"""
@@ -263,31 +242,100 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
% self.host)
# Connection never got put back into the pool, close it.
conn.close()
if conn:
conn.close()
def _get_timeout(self, timeout):
""" Helper that always returns a :class:`urllib3.util.Timeout` """
if timeout is _Default:
return self.timeout.clone()
if isinstance(timeout, Timeout):
return timeout.clone()
else:
# User passed us an int/float. This is for backwards compatibility,
# can be removed later
return Timeout.from_float(timeout)
def _make_request(self, conn, method, url, timeout=_Default,
**httplib_request_kw):
"""
Perform a request on a given httplib connection object taken from our
pool.
:param conn:
a connection from one of our connection pools
:param timeout:
Socket timeout in seconds for the request. This can be a
float or integer, which will set the same timeout value for
the socket connect and the socket read, or an instance of
:class:`urllib3.util.Timeout`, which gives you more fine-grained
control over your timeouts.
"""
self.num_requests += 1
if timeout is _Default:
timeout = self.timeout
timeout_obj = self._get_timeout(timeout)
conn.timeout = timeout # This only does anything in Py26+
conn.request(method, url, **httplib_request_kw)
try:
timeout_obj.start_connect()
conn.timeout = timeout_obj.connect_timeout
# conn.request() calls httplib.*.request, not the method in
# urllib3.request. It also calls makefile (recv) on the socket.
conn.request(method, url, **httplib_request_kw)
except SocketTimeout:
raise ConnectTimeoutError(
self, "Connection to %s timed out. (connect timeout=%s)" %
(self.host, timeout_obj.connect_timeout))
# Set timeout
sock = getattr(conn, 'sock', False) # AppEngine doesn't have sock attr.
if sock:
sock.settimeout(timeout)
# Reset the timeout for the recv() on the socket
read_timeout = timeout_obj.read_timeout
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()
# App Engine doesn't have a sock attr
if hasattr(conn, 'sock'):
# In Python 3 socket.py will catch EAGAIN and return None when you
# try and read into the file pointer created by http.client, which
# instead raises a BadStatusLine exception. Instead of catching
# the exception and assuming all BadStatusLine exceptions are read
# timeouts, check for a zero timeout before making the request.
if read_timeout == 0:
raise ReadTimeoutError(
self, url,
"Read timed out. (read timeout=%s)" % read_timeout)
if read_timeout is Timeout.DEFAULT_TIMEOUT:
conn.sock.settimeout(socket.getdefaulttimeout())
else: # None or a value
conn.sock.settimeout(read_timeout)
# Receive the response from the server
try:
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.")
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)
raise
# AppEngine doesn't have a version attr.
http_version = getattr(conn, '_http_vsn_str', 'HTTP/?')
@@ -367,7 +415,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
:param redirect:
If True, automatically handle redirects (status codes 301, 302,
303, 307). Each redirect counts as a retry.
303, 307, 308). Each redirect counts as a retry.
:param assert_same_host:
If ``True``, will make sure that the host of the pool requests is
@@ -375,7 +423,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
use the pool on an HTTP proxy and request foreign hosts.
:param timeout:
If specified, overrides the default timeout for this one request.
If specified, overrides the default timeout for this one
request. It may be a float (in seconds) or an instance of
:class:`urllib3.util.Timeout`.
:param pool_timeout:
If set and the pool is set to block=True, then this method will
@@ -402,18 +452,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
if retries < 0:
raise MaxRetryError(self, url)
if timeout is _Default:
timeout = self.timeout
if release_conn is None:
release_conn = response_kw.get('preload_content', True)
# Check host
if assert_same_host and not self.is_same_host(url):
host = "%s://%s" % (self.scheme, self.host)
if self.port:
host = "%s:%d" % (host, self.port)
raise HostChangedError(self, url, retries - 1)
conn = None
@@ -444,27 +487,31 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# ``response.release_conn()`` is called (implicitly by
# ``response.read()``)
except Empty as e:
except Empty:
# Timed out by queue
raise TimeoutError(self, url,
"Request timed out. (pool_timeout=%s)" %
pool_timeout)
except SocketTimeout as e:
# Timed out by socket
raise TimeoutError(self, url,
"Request timed out. (timeout=%s)" %
timeout)
raise EmptyPoolError(self, "No pool connections are available.")
except BaseSSLError as e:
# SSL certificate error
raise SSLError(e)
except CertificateError as e:
# Name mismatch
raise SSLError(e)
except TimeoutError as e:
# Connection broken, discard.
conn = None
# Save the error off for retry logic.
err = e
if retries == 0:
raise
except (HTTPException, SocketError) as e:
if isinstance(e, SocketError) and self.proxy is not None:
raise ProxyError('Cannot connect to proxy. '
'Socket error: %s.' % e)
# Connection broken, discard. It will be replaced next _get_conn().
conn = None
# This is necessary so we can access e below
@@ -513,6 +560,7 @@ class HTTPSConnectionPool(HTTPConnectionPool):
:class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``,
``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
@@ -521,17 +569,18 @@ class HTTPSConnectionPool(HTTPConnectionPool):
"""
scheme = 'https'
ConnectionCls = HTTPSConnection
def __init__(self, host, port=None,
strict=False, timeout=None, maxsize=1,
block=False, headers=None,
_proxy=None, _proxy_headers=None,
key_file=None, cert_file=None, cert_reqs=None,
ca_certs=None, ssl_version=None,
assert_hostname=None, assert_fingerprint=None):
HTTPConnectionPool.__init__(self, host, port,
strict, timeout, maxsize,
block, headers)
HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize,
block, headers, _proxy, _proxy_headers)
self.key_file = key_file
self.cert_file = cert_file
self.cert_reqs = cert_reqs
@@ -540,6 +589,34 @@ class HTTPSConnectionPool(HTTPConnectionPool):
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
def _prepare_conn(self, conn):
"""
Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket`
and establish the tunnel if proxy is used.
"""
if isinstance(conn, VerifiedHTTPSConnection):
conn.set_cert(key_file=self.key_file,
cert_file=self.cert_file,
cert_reqs=self.cert_reqs,
ca_certs=self.ca_certs,
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
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 _new_conn(self):
"""
Return a fresh :class:`httplib.HTTPSConnection`.
@@ -548,26 +625,26 @@ class HTTPSConnectionPool(HTTPConnectionPool):
log.info("Starting new HTTPS connection (%d): %s"
% (self.num_connections, self.host))
if not ssl: # Platform-specific: Python compiled without +ssl
if not HTTPSConnection or HTTPSConnection is object:
raise SSLError("Can't connect to HTTPS URL because the SSL "
"module is not available.")
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.")
return HTTPSConnection(host=self.host,
port=self.port,
strict=self.strict)
actual_host = self.host
actual_port = self.port
if self.proxy is not None:
actual_host = self.proxy.host
actual_port = self.proxy.port
connection = VerifiedHTTPSConnection(host=self.host,
port=self.port,
strict=self.strict)
connection.set_cert(key_file=self.key_file, cert_file=self.cert_file,
cert_reqs=self.cert_reqs, ca_certs=self.ca_certs,
assert_hostname=self.assert_hostname,
assert_fingerprint=self.assert_fingerprint)
extra_params = {}
if not six.PY3: # Python 2
extra_params['strict'] = self.strict
connection.ssl_version = self.ssl_version
conn = self.ConnectionCls(host=actual_host, port=actual_port,
timeout=self.timeout.connect_timeout,
**extra_params)
return connection
return self._prepare_conn(conn)
def connection_from_url(url, **kw):

View File

@@ -33,7 +33,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
def __init__(self, user, pw, authurl, *args, **kwargs):
"""
authurl is a random URL on the server that is protected by NTLM.
user is the Windows user, probably in the DOMAIN\username format.
user is the Windows user, probably in the DOMAIN\\username format.
pw is the password for the user.
"""
super(NTLMConnectionPool, self).__init__(*args, **kwargs)

View File

@@ -20,13 +20,14 @@ Now you can use :mod:`urllib3` as you normally would, and it will support SNI
when the required modules are installed.
'''
from ndg.httpsclient.ssl_peer_verification import (ServerSSLCertVerification,
SUBJ_ALT_NAME_SUPPORT)
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
from ndg.httpsclient.subj_alt_name import SubjectAltName
import OpenSSL.SSL
from pyasn1.codec.der import decoder as der_decoder
from socket import _fileobject
import ssl
import select
from cStringIO import StringIO
from .. import connectionpool
from .. import util
@@ -99,6 +100,172 @@ def get_subj_alt_name(peer_cert):
return dns_name
class fileobject(_fileobject):
def read(self, size=-1):
# Use max, disallow tiny reads in a loop as they are very inefficient.
# We never leave read() with any leftover data from a new recv() call
# in our internal buffer.
rbufsize = max(self._rbufsize, self.default_bufsize)
# Our use of StringIO rather than lists of string objects returned by
# recv() minimizes memory usage and fragmentation that occurs when
# rbufsize is large compared to the typical return value of recv().
buf = self._rbuf
buf.seek(0, 2) # seek end
if size < 0:
# Read until EOF
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True:
try:
data = self._sock.recv(rbufsize)
except OpenSSL.SSL.WantReadError:
continue
if not data:
break
buf.write(data)
return buf.getvalue()
else:
# Read until size bytes or EOF seen, whichever comes first
buf_len = buf.tell()
if buf_len >= size:
# Already have size bytes in our buffer? Extract and return.
buf.seek(0)
rv = buf.read(size)
self._rbuf = StringIO()
self._rbuf.write(buf.read())
return rv
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True:
left = size - buf_len
# recv() will malloc the amount of memory given as its
# parameter even though it often returns much less data
# than that. The returned data string is short lived
# as we copy it into a StringIO and free it. This avoids
# fragmentation issues on many platforms.
try:
data = self._sock.recv(left)
except OpenSSL.SSL.WantReadError:
continue
if not data:
break
n = len(data)
if n == size and not buf_len:
# Shortcut. Avoid buffer data copies when:
# - We have no data in our buffer.
# AND
# - Our call to recv returned exactly the
# number of bytes we were asked to read.
return data
if n == left:
buf.write(data)
del data # explicit free
break
assert n <= left, "recv(%d) returned %d bytes" % (left, n)
buf.write(data)
buf_len += n
del data # explicit free
#assert buf_len == buf.tell()
return buf.getvalue()
def readline(self, size=-1):
buf = self._rbuf
buf.seek(0, 2) # seek end
if buf.tell() > 0:
# check if we already have it in our buffer
buf.seek(0)
bline = buf.readline(size)
if bline.endswith('\n') or len(bline) == size:
self._rbuf = StringIO()
self._rbuf.write(buf.read())
return bline
del bline
if size < 0:
# Read until \n or EOF, whichever comes first
if self._rbufsize <= 1:
# Speed up unbuffered case
buf.seek(0)
buffers = [buf.read()]
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
data = None
recv = self._sock.recv
while True:
try:
while data != "\n":
data = recv(1)
if not data:
break
buffers.append(data)
except OpenSSL.SSL.WantReadError:
continue
break
return "".join(buffers)
buf.seek(0, 2) # seek end
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True:
try:
data = self._sock.recv(self._rbufsize)
except OpenSSL.SSL.WantReadError:
continue
if not data:
break
nl = data.find('\n')
if nl >= 0:
nl += 1
buf.write(data[:nl])
self._rbuf.write(data[nl:])
del data
break
buf.write(data)
return buf.getvalue()
else:
# Read until size bytes or \n or EOF seen, whichever comes first
buf.seek(0, 2) # seek end
buf_len = buf.tell()
if buf_len >= size:
buf.seek(0)
rv = buf.read(size)
self._rbuf = StringIO()
self._rbuf.write(buf.read())
return rv
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True:
try:
data = self._sock.recv(self._rbufsize)
except OpenSSL.SSL.WantReadError:
continue
if not data:
break
left = size - buf_len
# did we just receive a newline?
nl = data.find('\n', 0, left)
if nl >= 0:
nl += 1
# save the excess data to _rbuf
self._rbuf.write(data[nl:])
if buf_len:
buf.write(data[:nl])
break
else:
# Shortcut. Avoid data copy through buf when returning
# a substring of our first recv().
return data[:nl]
n = len(data)
if n == size and not buf_len:
# Shortcut. Avoid data copy through buf when
# returning exactly all of our first recv().
return data
if n >= left:
buf.write(data[:left])
self._rbuf.write(data[left:])
break
buf.write(data)
buf_len += n
#assert buf_len == buf.tell()
return buf.getvalue()
class WrappedSocket(object):
'''API-compatibility wrapper for Python OpenSSL's Connection-class.'''
@@ -106,8 +273,11 @@ class WrappedSocket(object):
self.connection = connection
self.socket = socket
def fileno(self):
return self.socket.fileno()
def makefile(self, mode, bufsize=-1):
return _fileobject(self.connection, mode, bufsize)
return fileobject(self.connection, mode, bufsize)
def settimeout(self, timeout):
return self.socket.settimeout(timeout)
@@ -115,10 +285,14 @@ class WrappedSocket(object):
def sendall(self, data):
return self.connection.sendall(data)
def close(self):
return self.connection.shutdown()
def getpeercert(self, binary_form=False):
x509 = self.connection.get_peer_certificate()
if not x509:
raise ssl.SSLError('')
return x509
if binary_form:
return OpenSSL.crypto.dump_certificate(
@@ -159,9 +333,14 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
cnx = OpenSSL.SSL.Connection(ctx, sock)
cnx.set_tlsext_host_name(server_hostname)
cnx.set_connect_state()
try:
cnx.do_handshake()
except OpenSSL.SSL.Error as e:
raise ssl.SSLError('bad handshake', e)
while True:
try:
cnx.do_handshake()
except OpenSSL.SSL.WantReadError:
select.select([sock], [], [])
continue
except OpenSSL.SSL.Error as e:
raise ssl.SSLError('bad handshake', e)
break
return WrappedSocket(cnx, sock)

View File

@@ -39,6 +39,11 @@ class SSLError(HTTPError):
pass
class ProxyError(HTTPError):
"Raised when the connection to a proxy fails."
pass
class DecodeError(HTTPError):
"Raised when automatic decoding based on Content-Type fails."
pass
@@ -70,8 +75,29 @@ class HostChangedError(RequestError):
self.retries = retries
class TimeoutError(RequestError):
"Raised when a socket timeout occurs."
class TimeoutStateError(HTTPError):
""" Raised when passing an invalid state to a timeout """
pass
class TimeoutError(HTTPError):
""" Raised when a socket timeout error occurs.
Catching this error will catch both :exc:`ReadTimeoutErrors
<ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`.
"""
pass
class ReadTimeoutError(TimeoutError, RequestError):
"Raised when a socket timeout occurs while receiving data from a server"
pass
# This timeout error does not have a URL attached and needs to inherit from the
# base HTTPError
class ConnectTimeoutError(TimeoutError):
"Raised when a socket timeout occurs while connecting to a server"
pass

View File

@@ -0,0 +1,177 @@
# urllib3/fields.py
# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import email.utils
import mimetypes
from .packages import six
def guess_content_type(filename, default='application/octet-stream'):
"""
Guess the "Content-Type" of a file.
:param filename:
The filename to guess the "Content-Type" of using :mod:`mimetimes`.
:param default:
If no "Content-Type" can be guessed, default to `default`.
"""
if filename:
return mimetypes.guess_type(filename)[0] or default
return default
def format_header_param(name, value):
"""
Helper function to format and quote a single header parameter.
Particularly useful for header parameters which might contain
non-ASCII values, like file names. This follows RFC 2231, as
suggested by RFC 2388 Section 4.4.
:param name:
The name of the parameter, a string expected to be ASCII only.
:param value:
The value of the parameter, provided as a unicode string.
"""
if not any(ch in value for ch in '"\\\r\n'):
result = '%s="%s"' % (name, value)
try:
result.encode('ascii')
except UnicodeEncodeError:
pass
else:
return result
if not six.PY3: # Python 2:
value = value.encode('utf-8')
value = email.utils.encode_rfc2231(value, 'utf-8')
value = '%s*=%s' % (name, value)
return value
class RequestField(object):
"""
A data container for request body parameters.
:param name:
The name of this request field.
:param data:
The data/value body.
:param filename:
An optional filename of the request field.
:param headers:
An optional dict-like object of headers to initially use for the field.
"""
def __init__(self, name, data, filename=None, headers=None):
self._name = name
self._filename = filename
self.data = data
self.headers = {}
if headers:
self.headers = dict(headers)
@classmethod
def from_tuples(cls, fieldname, value):
"""
A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters.
Supports constructing :class:`~urllib3.fields.RequestField` from parameter
of key/value strings AND key/filetuple. A filetuple is a (filename, data, MIME type)
tuple where the MIME type is optional. For example: ::
'foo': 'bar',
'fakefile': ('foofile.txt', 'contents of foofile'),
'realfile': ('barfile.txt', open('realfile').read()),
'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'),
'nonamefile': 'contents of nonamefile field',
Field names and filenames must be unicode.
"""
if isinstance(value, tuple):
if len(value) == 3:
filename, data, content_type = value
else:
filename, data = value
content_type = guess_content_type(filename)
else:
filename = None
content_type = None
data = value
request_param = cls(fieldname, data, filename=filename)
request_param.make_multipart(content_type=content_type)
return request_param
def _render_part(self, name, value):
"""
Overridable helper function to format a single header parameter.
:param name:
The name of the parameter, a string expected to be ASCII only.
:param value:
The value of the parameter, provided as a unicode string.
"""
return format_header_param(name, value)
def _render_parts(self, header_parts):
"""
Helper function to format and quote a single header.
Useful for single headers that are composed of multiple items. E.g.,
'Content-Disposition' fields.
:param header_parts:
A sequence of (k, v) typles or a :class:`dict` of (k, v) to format as
`k1="v1"; k2="v2"; ...`.
"""
parts = []
iterable = header_parts
if isinstance(header_parts, dict):
iterable = header_parts.items()
for name, value in iterable:
if value:
parts.append(self._render_part(name, value))
return '; '.join(parts)
def render_headers(self):
"""
Renders the headers for this request field.
"""
lines = []
sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location']
for sort_key in sort_keys:
if self.headers.get(sort_key, False):
lines.append('%s: %s' % (sort_key, self.headers[sort_key]))
for header_name, header_value in self.headers.items():
if header_name not in sort_keys:
if header_value:
lines.append('%s: %s' % (header_name, header_value))
lines.append('\r\n')
return '\r\n'.join(lines)
def make_multipart(self, content_disposition=None, content_type=None, content_location=None):
"""
Makes this request field into a multipart request field.
This method overrides "Content-Disposition", "Content-Type" and
"Content-Location" headers to the request parameter.
:param content_type:
The 'Content-Type' of the request body.
:param content_location:
The 'Content-Location' of the request body.
"""
self.headers['Content-Disposition'] = content_disposition or 'form-data'
self.headers['Content-Disposition'] += '; '.join(['', self._render_parts((('name', self._name), ('filename', self._filename)))])
self.headers['Content-Type'] = content_type
self.headers['Content-Location'] = content_location

View File

@@ -1,5 +1,5 @@
# urllib3/filepost.py
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -12,6 +12,7 @@ from io import BytesIO
from .packages import six
from .packages.six import b
from .fields import RequestField
writer = codecs.lookup('utf-8')[3]
@@ -23,15 +24,38 @@ def choose_boundary():
return uuid4().hex
def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
def iter_field_objects(fields):
"""
Iterate over fields.
Supports list of (k, v) tuples and dicts, and lists of
:class:`~urllib3.fields.RequestField`.
"""
if isinstance(fields, dict):
i = six.iteritems(fields)
else:
i = iter(fields)
for field in i:
if isinstance(field, RequestField):
yield field
else:
yield RequestField.from_tuples(*field)
def iter_fields(fields):
"""
Iterate over fields.
.. deprecated ::
The addition of `~urllib3.fields.RequestField` makes this function
obsolete. Instead, use :func:`iter_field_objects`, which returns
`~urllib3.fields.RequestField` objects, instead.
Supports list of (k, v) tuples and dicts.
"""
if isinstance(fields, dict):
return ((k, v) for k, v in six.iteritems(fields))
@@ -44,15 +68,7 @@ def encode_multipart_formdata(fields, boundary=None):
Encode a dictionary of ``fields`` using the multipart/form-data MIME format.
:param fields:
Dictionary of fields or list of (key, value) or (key, value, MIME type)
field tuples. The key is treated as the field name, and the value as
the body of the form-data bytes. If the value is a tuple of two
elements, then the first element is treated as the filename of the
form-data section and a suitable MIME type is guessed based on the
filename. If the value is a tuple of three elements, then the third
element is treated as an explicit MIME type of the form-data section.
Field names and filenames must be unicode.
Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`).
:param boundary:
If not specified, then a random boundary will be generated using
@@ -62,24 +78,11 @@ def encode_multipart_formdata(fields, boundary=None):
if boundary is None:
boundary = choose_boundary()
for fieldname, value in iter_fields(fields):
for field in iter_field_objects(fields):
body.write(b('--%s\r\n' % (boundary)))
if isinstance(value, tuple):
if len(value) == 3:
filename, data, content_type = value
else:
filename, data = value
content_type = get_content_type(filename)
writer(body).write('Content-Disposition: form-data; name="%s"; '
'filename="%s"\r\n' % (fieldname, filename))
body.write(b('Content-Type: %s\r\n\r\n' %
(content_type,)))
else:
data = value
writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
% (fieldname))
body.write(b'\r\n')
writer(body).write(field.render_headers())
data = field.data
if isinstance(data, int):
data = str(data) # Backwards compatibility

View File

@@ -7,23 +7,60 @@ __version__ = '3.2.2'
class CertificateError(ValueError):
pass
def _dnsname_to_pat(dn):
def _dnsname_match(dn, hostname, max_wildcards=1):
"""Matching according to RFC 6125, section 6.4.3
http://tools.ietf.org/html/rfc6125#section-6.4.3
"""
pats = []
for frag in dn.split(r'.'):
if frag == '*':
# When '*' is a fragment by itself, it matches a non-empty dotless
# fragment.
pats.append('[^.]+')
else:
# Otherwise, '*' matches any dotless fragment.
frag = re.escape(frag)
pats.append(frag.replace(r'\*', '[^.]*'))
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
if not dn:
return False
parts = dn.split(r'.')
leftmost = parts[0]
wildcards = leftmost.count('*')
if wildcards > max_wildcards:
# Issue #17980: avoid denials of service by refusing more
# than one wildcard per fragment. A survery of established
# policy among SSL implementations showed it to be a
# reasonable choice.
raise CertificateError(
"too many wildcards in certificate DNS name: " + repr(dn))
# speed up common case w/o wildcards
if not wildcards:
return dn.lower() == hostname.lower()
# RFC 6125, section 6.4.3, subitem 1.
# The client SHOULD NOT attempt to match a presented identifier in which
# the wildcard character comprises a label other than the left-most label.
if leftmost == '*':
# When '*' is a fragment by itself, it matches a non-empty dotless
# fragment.
pats.append('[^.]+')
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
# RFC 6125, section 6.4.3, subitem 3.
# The client SHOULD NOT attempt to match a presented identifier
# where the wildcard character is embedded within an A-label or
# U-label of an internationalized domain name.
pats.append(re.escape(leftmost))
else:
# Otherwise, '*' matches any dotless string, e.g. www*
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
# add the remaining fragments, ignore any wildcards
for frag in parts[1:]:
pats.append(re.escape(frag))
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
return pat.match(hostname)
def match_hostname(cert, hostname):
"""Verify that *cert* (in decoded format as returned by
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
are mostly followed, but IP addresses are not accepted for *hostname*.
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
rules are followed, but IP addresses are not accepted for *hostname*.
CertificateError is raised on failure. On success, the function
returns nothing.
@@ -34,7 +71,7 @@ def match_hostname(cert, hostname):
san = cert.get('subjectAltName', ())
for key, value in san:
if key == 'DNS':
if _dnsname_to_pat(value).match(hostname):
if _dnsname_match(value, hostname):
return
dnsnames.append(value)
if not dnsnames:
@@ -45,7 +82,7 @@ def match_hostname(cert, hostname):
# XXX according to RFC 2818, the most specific Common Name
# must be used.
if key == 'commonName':
if _dnsname_to_pat(value).match(hostname):
if _dnsname_match(value, hostname):
return
dnsnames.append(value)
if len(dnsnames) > 1:

View File

@@ -6,9 +6,14 @@
import logging
try: # Python 3
from urllib.parse import urljoin
except ImportError:
from urlparse import urljoin
from ._collections import RecentlyUsedContainer
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from .connectionpool import connection_from_url, port_by_scheme
from .connectionpool import port_by_scheme
from .request import RequestMethods
from .util import parse_url
@@ -55,6 +60,8 @@ class PoolManager(RequestMethods):
"""
proxy = None
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
RequestMethods.__init__(self, headers)
self.connection_pool_kw = connection_pool_kw
@@ -94,20 +101,23 @@ class PoolManager(RequestMethods):
If ``port`` isn't given, it will be derived from the ``scheme`` using
``urllib3.connectionpool.port_by_scheme``.
"""
scheme = scheme or 'http'
port = port or port_by_scheme.get(scheme, 80)
pool_key = (scheme, host, port)
# If the scheme, host, or port doesn't match existing open connections,
# open a new ConnectionPool.
pool = self.pools.get(pool_key)
if pool:
return pool
with self.pools.lock:
# If the scheme, host, or port doesn't match existing open
# connections, open a new ConnectionPool.
pool = self.pools.get(pool_key)
if pool:
return pool
# Make a fresh ConnectionPool of the desired type
pool = self._new_pool(scheme, host, port)
self.pools[pool_key] = pool
# Make a fresh ConnectionPool of the desired type
pool = self._new_pool(scheme, host, port)
self.pools[pool_key] = pool
return pool
def connection_from_url(self, url):
@@ -139,12 +149,19 @@ class PoolManager(RequestMethods):
if 'headers' not in kw:
kw['headers'] = self.headers
response = conn.urlopen(method, u.request_uri, **kw)
if self.proxy is not None and u.scheme == "http":
response = conn.urlopen(method, url, **kw)
else:
response = conn.urlopen(method, u.request_uri, **kw)
redirect_location = redirect and response.get_redirect_location()
if not redirect_location:
return response
# Support relative URLs for redirecting.
redirect_location = urljoin(url, redirect_location)
# RFC 2616, Section 10.3.4
if response.status == 303:
method = 'GET'
@@ -154,15 +171,59 @@ class PoolManager(RequestMethods):
return self.urlopen(method, redirect_location, **kw)
class ProxyManager(RequestMethods):
class ProxyManager(PoolManager):
"""
Given a ConnectionPool to a proxy, the ProxyManager's ``urlopen`` method
will make requests to any url through the defined proxy. The ProxyManager
class will automatically set the 'Host' header if it is not provided.
Behaves just like :class:`PoolManager`, but sends all requests through
the defined proxy, using the CONNECT method for HTTPS URLs.
:param poxy_url:
The URL of the proxy to be used.
:param proxy_headers:
A dictionary contaning headers that will be sent to the proxy. In case
of HTTP they are being sent with each request, while in the
HTTPS/CONNECT case they are sent only once. Could be used for proxy
authentication.
Example:
>>> proxy = urllib3.ProxyManager('http://localhost:3128/')
>>> r1 = proxy.request('GET', 'http://google.com/')
>>> r2 = proxy.request('GET', 'http://httpbin.org/')
>>> len(proxy.pools)
1
>>> r3 = proxy.request('GET', 'https://httpbin.org/')
>>> r4 = proxy.request('GET', 'https://twitter.com/')
>>> len(proxy.pools)
3
"""
def __init__(self, proxy_pool):
self.proxy_pool = proxy_pool
def __init__(self, proxy_url, num_pools=10, headers=None,
proxy_headers=None, **connection_pool_kw):
if isinstance(proxy_url, HTTPConnectionPool):
proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host,
proxy_url.port)
proxy = parse_url(proxy_url)
if not proxy.port:
port = port_by_scheme.get(proxy.scheme, 80)
proxy = proxy._replace(port=port)
self.proxy = proxy
self.proxy_headers = proxy_headers or {}
assert self.proxy.scheme in ("http", "https"), \
'Not supported proxy scheme %s' % self.proxy.scheme
connection_pool_kw['_proxy'] = self.proxy
connection_pool_kw['_proxy_headers'] = self.proxy_headers
super(ProxyManager, self).__init__(
num_pools, headers, **connection_pool_kw)
def connection_from_host(self, host, port=None, scheme='http'):
if scheme == "https":
return super(ProxyManager, self).connection_from_host(
host, port, scheme)
return super(ProxyManager, self).connection_from_host(
self.proxy.host, self.proxy.port, self.proxy.scheme)
def _set_proxy_headers(self, url, headers=None):
"""
@@ -171,22 +232,28 @@ class ProxyManager(RequestMethods):
"""
headers_ = {'Accept': '*/*'}
host = parse_url(url).host
if host:
headers_['Host'] = host
netloc = parse_url(url).netloc
if netloc:
headers_['Host'] = netloc
if headers:
headers_.update(headers)
return headers_
def urlopen(self, method, url, **kw):
def urlopen(self, method, url, redirect=True, **kw):
"Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
kw['assert_same_host'] = False
kw['headers'] = self._set_proxy_headers(url, headers=kw.get('headers'))
return self.proxy_pool.urlopen(method, url, **kw)
u = parse_url(url)
if u.scheme == "http":
# It's too late to set proxy headers on per-request basis for
# tunnelled HTTPS connections, should use
# constructor's proxy_headers instead.
kw['headers'] = self._set_proxy_headers(url, kw.get('headers',
self.headers))
kw['headers'].update(self.proxy_headers)
return super(ProxyManager, self).urlopen(method, url, redirect, **kw)
def proxy_from_url(url, **pool_kw):
proxy_pool = connection_from_url(url, **pool_kw)
return ProxyManager(proxy_pool)
def proxy_from_url(url, **kw):
return ProxyManager(proxy_url=url, **kw)

View File

@@ -30,7 +30,7 @@ class RequestMethods(object):
in the URL (such as GET, HEAD, DELETE).
:meth:`.request_encode_body` is for sending requests whose fields are
encoded in the *body* of the request using multipart or www-orm-urlencoded
encoded in the *body* of the request using multipart or www-form-urlencoded
(such as for POST, PUT, PATCH).
:meth:`.request` is for making any kind of request, it will look up the

View File

@@ -1,5 +1,5 @@
# urllib3/response.py
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
#
# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -7,9 +7,11 @@
import logging
import zlib
import io
from .exceptions import DecodeError
from .packages.six import string_types as basestring, binary_type
from .util import is_fp_closed
log = logging.getLogger(__name__)
@@ -48,7 +50,7 @@ def _get_decoder(mode):
return DeflateDecoder()
class HTTPResponse(object):
class HTTPResponse(io.IOBase):
"""
HTTP Response container.
@@ -72,6 +74,7 @@ class HTTPResponse(object):
"""
CONTENT_DECODERS = ['gzip', 'deflate']
REDIRECT_STATUSES = [301, 302, 303, 307, 308]
def __init__(self, body='', headers=None, status=0, version=0, reason=None,
strict=0, preload_content=True, decode_content=True,
@@ -105,7 +108,7 @@ class HTTPResponse(object):
code and valid location. ``None`` if redirect status and no
location. ``False`` if not a redirect status code.
"""
if self.status in [301, 302, 303, 307]:
if self.status in self.REDIRECT_STATUSES:
return self.headers.get('location')
return False
@@ -183,11 +186,13 @@ class HTTPResponse(object):
try:
if decode_content and self._decoder:
data = self._decoder.decompress(data)
except (IOError, zlib.error):
raise DecodeError("Received response with content-encoding: %s, but "
"failed to decode it." % content_encoding)
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 self._decoder:
if flush_decoder and decode_content and self._decoder:
buf = self._decoder.decompress(binary_type())
data += buf + self._decoder.flush()
@@ -200,6 +205,29 @@ class HTTPResponse(object):
if self._original_response and self._original_response.isclosed():
self.release_conn()
def stream(self, amt=2**16, decode_content=None):
"""
A generator wrapper for the read() method. A call will block until
``amt`` bytes have been read from the connection or until the
connection is closed.
:param amt:
How much of the content to read. The generator will return up to
much data per iteration, but may return less. This is particularly
likely when using compressed data. However, the empty string will
never be returned.
:param decode_content:
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 data:
yield data
@classmethod
def from_httplib(ResponseCls, r, **response_kw):
"""
@@ -239,3 +267,35 @@ class HTTPResponse(object):
def getheader(self, name, default=None):
return self.headers.get(name, default)
# Overrides from io.IOBase
def close(self):
if not self.closed:
self._fp.close()
@property
def closed(self):
if self._fp is None:
return True
elif hasattr(self._fp, 'closed'):
return self._fp.closed
elif hasattr(self._fp, 'isclosed'): # Python 2
return self._fp.isclosed()
else:
return True
def fileno(self):
if self._fp is None:
raise IOError("HTTPResponse has no file to get a fileno from")
elif hasattr(self._fp, "fileno"):
return self._fp.fileno()
else:
raise IOError("The file-like object this HTTPResponse is wrapped "
"around has no file descriptor")
def flush(self):
if self._fp is not None and hasattr(self._fp, 'flush'):
return self._fp.flush()
def readable(self):
return True

View File

@@ -6,10 +6,11 @@
from base64 import b64encode
from collections import namedtuple
from socket import error as SocketError
from hashlib import md5, sha1
from binascii import hexlify, unhexlify
from collections import namedtuple
from hashlib import md5, sha1
from socket import error as SocketError, _GLOBAL_DEFAULT_TIMEOUT
import time
try:
from select import poll, POLLIN
@@ -31,9 +32,238 @@ try: # Test for SSL features
except ImportError:
pass
from .packages import six
from .exceptions import LocationParseError, SSLError
from .exceptions import LocationParseError, SSLError, TimeoutStateError
_Default = object()
# The default timeout to use for socket connections. This is the attribute used
# by httplib to define the default timeout
def current_time():
"""
Retrieve the current time, this function is mocked out in unit testing.
"""
return time.time()
class Timeout(object):
"""
Utility object for storing timeout values.
Example usage:
.. code-block:: python
timeout = urllib3.util.Timeout(connect=2.0, read=7.0)
pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout)
pool.request(...) # Etc, etc
:param connect:
The maximum amount of time to wait for a connection attempt to a server
to succeed. Omitting the parameter will default the connect timeout to
the system default, probably `the global default timeout in socket.py
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_.
None will set an infinite timeout for connection attempts.
:type connect: integer, float, or None
:param read:
The maximum amount of time to wait between consecutive
read operations for a response from the server. Omitting
the parameter will default the read timeout to the system
default, probably `the global default timeout in socket.py
<http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_.
None will set an infinite timeout.
:type read: integer, float, or None
:param total:
This combines the connect and read timeouts into one; the read timeout
will be set to the time leftover from the connect attempt. In the
event that both a connect timeout and a total are specified, or a read
timeout and a total are specified, the shorter timeout will be applied.
Defaults to None.
:type total: integer, float, or None
.. note::
Many factors can affect the total amount of time for urllib3 to return
an HTTP response. Specifically, Python's DNS resolver does not obey the
timeout specified on the socket. Other factors that can affect total
request time include high CPU load, high swap, the program running at a
low priority level, or other behaviors. The observed running time for
urllib3 to return a response may be greater than the value passed to
`total`.
In addition, the read and total timeouts only measure the time between
read operations on the socket connecting the client and the server,
not the total amount of time for the request to return a complete
response. For most requests, the timeout is raised because the server
has not sent the first byte in the specified time. This is not always
the case; if a server streams one byte every fifteen seconds, a timeout
of 20 seconds will not ever trigger, even though the request will
take several minutes to complete.
If your goal is to cut off any request after a set amount of wall clock
time, consider having a second "watcher" thread to cut off a slow
request.
"""
#: A sentinel object representing the default timeout value
DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT
def __init__(self, total=None, connect=_Default, read=_Default):
self._connect = self._validate_timeout(connect, 'connect')
self._read = self._validate_timeout(read, 'read')
self.total = self._validate_timeout(total, 'total')
self._start_connect = None
def __str__(self):
return '%s(connect=%r, read=%r, total=%r)' % (
type(self).__name__, self._connect, self._read, self.total)
@classmethod
def _validate_timeout(cls, value, name):
""" Check that a timeout attribute is valid
:param value: The timeout value to validate
:param name: The name of the timeout attribute to validate. This is used
for clear error messages
:return: the value
:raises ValueError: if the type is not an integer or a float, or if it
is a numeric value less than zero
"""
if value is _Default:
return cls.DEFAULT_TIMEOUT
if value is None or value is cls.DEFAULT_TIMEOUT:
return value
try:
float(value)
except (TypeError, ValueError):
raise ValueError("Timeout value %s was %s, but it must be an "
"int or float." % (name, value))
try:
if value < 0:
raise ValueError("Attempted to set %s timeout to %s, but the "
"timeout cannot be set to a value less "
"than 0." % (name, value))
except TypeError: # Python 3
raise ValueError("Timeout value %s was %s, but it must be an "
"int or float." % (name, value))
return value
@classmethod
def from_float(cls, timeout):
""" Create a new Timeout from a legacy timeout value.
The timeout value used by httplib.py sets the same timeout on the
connect(), and recv() socket requests. This creates a :class:`Timeout`
object that sets the individual timeouts to the ``timeout`` value passed
to this function.
:param timeout: The legacy timeout value
:type timeout: integer, float, sentinel default object, or None
:return: a Timeout object
:rtype: :class:`Timeout`
"""
return Timeout(read=timeout, connect=timeout)
def clone(self):
""" Create a copy of the timeout object
Timeout properties are stored per-pool but each request needs a fresh
Timeout object to ensure each one has its own start/stop configured.
:return: a copy of the timeout object
:rtype: :class:`Timeout`
"""
# We can't use copy.deepcopy because that will also create a new object
# for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to
# detect the user default.
return Timeout(connect=self._connect, read=self._read,
total=self.total)
def start_connect(self):
""" Start the timeout clock, used during a connect() attempt
:raises urllib3.exceptions.TimeoutStateError: if you attempt
to start a timer that has been started already.
"""
if self._start_connect is not None:
raise TimeoutStateError("Timeout timer has already been started.")
self._start_connect = current_time()
return self._start_connect
def get_connect_duration(self):
""" Gets the time elapsed since the call to :meth:`start_connect`.
:return: the elapsed time
:rtype: float
:raises urllib3.exceptions.TimeoutStateError: if you attempt
to get duration for a timer that hasn't been started.
"""
if self._start_connect is None:
raise TimeoutStateError("Can't get connect duration for timer "
"that has not started.")
return current_time() - self._start_connect
@property
def connect_timeout(self):
""" Get the value to use when setting a connection timeout.
This will be a positive float or integer, the value None
(never timeout), or the default system timeout.
:return: the connect timeout
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
"""
if self.total is None:
return self._connect
if self._connect is None or self._connect is self.DEFAULT_TIMEOUT:
return self.total
return min(self._connect, self.total)
@property
def read_timeout(self):
""" Get the value for the read timeout.
This assumes some time has elapsed in the connection timeout and
computes the read timeout appropriately.
If self.total is set, the read timeout is dependent on the amount of
time taken by the connect timeout. If the connection time has not been
established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be
raised.
:return: the value to use for the read timeout
:rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None
:raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect`
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):
# in case the connect timeout has not yet been established.
if self._start_connect is None:
return self._read
return max(0, min(self.total - self.get_connect_duration(),
self._read))
elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT:
return max(0, self.total - self.get_connect_duration())
else:
return self._read
class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])):
@@ -61,6 +291,13 @@ class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query',
return uri
@property
def netloc(self):
"""Network location including host and port"""
if self.port:
return '%s:%d' % (self.host, self.port)
return self.host
def split_first(s, delims):
"""
@@ -114,7 +351,7 @@ def parse_url(url):
# While this code has overlap with stdlib's urlparse, it is much
# simplified for our needs and less annoying.
# Additionally, this imeplementations does silly things to be optimal
# Additionally, this implementations does silly things to be optimal
# on CPython.
scheme = None
@@ -139,11 +376,13 @@ def parse_url(url):
# Auth
if '@' in url:
auth, url = url.split('@', 1)
# Last '@' denotes end of auth part
auth, url = url.rsplit('@', 1)
# IPv6
if url and url[0] == '[':
host, url = url[1:].split(']', 1)
host, url = url.split(']', 1)
host += ']'
# Port
if ':' in url:
@@ -152,10 +391,14 @@ def parse_url(url):
if not host:
host = _host
if not port.isdigit():
raise LocationParseError("Failed to parse: %s" % url)
port = int(port)
if port:
# If given, ports must be integers.
if not port.isdigit():
raise LocationParseError("Failed to parse: %s" % url)
port = int(port)
else:
# Blank ports are cool, too. (rfc3986#section-3.2.3)
port = None
elif not host and url:
host = url
@@ -341,6 +584,20 @@ def assert_fingerprint(cert, fingerprint):
.format(hexlify(fingerprint_bytes),
hexlify(cert_digest)))
def is_fp_closed(obj):
"""
Checks whether a given file-like object is closed.
:param obj:
The file-like object to check.
"""
if hasattr(obj, 'fp'):
# Object is a container for another file-like object that gets released
# on exhaustion (e.g. HTTPResponse)
return obj.fp is None
return obj.closed
if SSLContext is not None: # Python 3.2+
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,

View File

@@ -12,7 +12,7 @@ import os
from collections import Mapping
from datetime import datetime
from .compat import cookielib, OrderedDict, urljoin, urlparse
from .compat import cookielib, OrderedDict, urljoin, urlparse, urlunparse
from .cookies import cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar
from .models import Request, PreparedRequest
from .hooks import default_hooks, dispatch_hook
@@ -71,15 +71,10 @@ class SessionRedirectMixin(object):
"""Receives a Response. Returns a generator of Responses."""
i = 0
prepared_request = PreparedRequest()
prepared_request.body = req.body
prepared_request.headers = req.headers.copy()
prepared_request.hooks = req.hooks
prepared_request.method = req.method
prepared_request.url = req.url
# ((resp.status_code is codes.see_other))
while (('location' in resp.headers and resp.status_code in REDIRECT_STATI)):
while ('location' in resp.headers and resp.status_code in REDIRECT_STATI):
prepared_request = req.copy()
resp.content # Consume socket so it can be released
@@ -90,13 +85,19 @@ class SessionRedirectMixin(object):
resp.close()
url = resp.headers['location']
method = prepared_request.method
method = req.method
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'):
parsed_rurl = urlparse(resp.url)
url = '%s:%s' % (parsed_rurl.scheme, url)
# The scheme should be lower case...
parsed = urlparse(url)
parsed = (parsed.scheme.lower(), parsed.netloc, parsed.path,
parsed.params, parsed.query, parsed.fragment)
url = urlunparse(parsed)
# Facilitate non-RFC2616-compliant 'location' headers
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
# Compliant with RFC3986, we percent encode the url.
@@ -109,12 +110,12 @@ class SessionRedirectMixin(object):
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
if (resp.status_code == codes.see_other and
prepared_request.method != 'HEAD'):
method != 'HEAD'):
method = 'GET'
# Do what the browsers do, despite standards...
if (resp.status_code in (codes.moved, codes.found) and
prepared_request.method not in ('GET', 'HEAD')):
method not in ('GET', 'HEAD')):
method = 'GET'
prepared_request.method = method
@@ -153,7 +154,7 @@ class SessionRedirectMixin(object):
class Session(SessionRedirectMixin):
"""A Requests session.
Provides cookie persistience, connection-pooling, and configuration.
Provides cookie persistence, connection-pooling, and configuration.
Basic Usage::
@@ -208,7 +209,10 @@ class Session(SessionRedirectMixin):
#: Should we trust the environment?
self.trust_env = True
# Set up a CookieJar to be used by default
#: A CookieJar containing all currently outstanding cookies set on this
#: session. By default it is a
#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
#: may be any other ``cookielib.CookieJar`` compatible object.
self.cookies = cookiejar_from_dict({})
# Default connection adapters.
@@ -222,6 +226,46 @@ class Session(SessionRedirectMixin):
def __exit__(self, *args):
self.close()
def prepare_request(self, request):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for
transmission and returns it. The :class:`PreparedRequest` has settings
merged from the :class:`Request <Request>` instance and those of the
:class:`Session`.
:param request: :class:`Request` instance to prepare with this
session's settings.
"""
cookies = request.cookies or {}
# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# Merge with session cookies
merged_cookies = RequestsCookieJar()
merged_cookies.update(self.cookies)
merged_cookies.update(cookies)
# Set environment's basic authentication if not explicitly set.
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
p = PreparedRequest()
p.prepare(
method=request.method.upper(),
url=request.url,
files=request.files,
data=request.data,
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
cookies=merged_cookies,
hooks=merge_setting(request.hooks, self.hooks),
)
return p
def request(self, method, url,
params=None,
data=None,
@@ -265,20 +309,25 @@ class Session(SessionRedirectMixin):
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
"""
# Create the Request.
req = Request(
method = method.upper(),
url = url,
headers = headers,
files = files,
data = data or {},
params = params or {},
auth = auth,
cookies = cookies,
hooks = hooks,
)
prep = self.prepare_request(req)
# Add param cookies to session cookies
self.cookies = cookiejar_from_dict(cookies, cookiejar=self.cookies, overwrite=False)
cookies = cookies or {}
proxies = proxies or {}
# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# Merge with session cookies
merged_cookies = RequestsCookieJar()
merged_cookies.update(self.cookies)
merged_cookies.update(cookies)
cookies = merged_cookies
# Gather clues from the surrounding environment.
if self.trust_env:
# Set environment's proxies.
@@ -286,10 +335,6 @@ class Session(SessionRedirectMixin):
for (k, v) in env_proxies.items():
proxies.setdefault(k, v)
# Set environment's basic authentication.
if not auth:
auth = get_netrc_auth(url)
# Look for configuration.
if not verify and verify is not False:
verify = os.environ.get('REQUESTS_CA_BUNDLE')
@@ -299,30 +344,11 @@ class Session(SessionRedirectMixin):
verify = os.environ.get('CURL_CA_BUNDLE')
# Merge all the kwargs.
params = merge_setting(params, self.params)
headers = merge_setting(headers, self.headers, dict_class=CaseInsensitiveDict)
auth = merge_setting(auth, self.auth)
proxies = merge_setting(proxies, self.proxies)
hooks = merge_setting(hooks, self.hooks)
stream = merge_setting(stream, self.stream)
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
# Create the Request.
req = Request()
req.method = method.upper()
req.url = url
req.headers = headers
req.files = files
req.data = data
req.params = params
req.auth = auth
req.cookies = cookies
req.hooks = hooks
# Prepare the Request.
prep = req.prepare()
# Send the request.
send_kwargs = {
'stream': stream,
@@ -416,7 +442,7 @@ class Session(SessionRedirectMixin):
# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
if getattr(request, 'prepare', None):
if not isinstance(request, PreparedRequest):
raise ValueError('You can only send PreparedRequests.')
# Set up variables needed for resolve_redirects and dispatching of
@@ -443,6 +469,10 @@ class Session(SessionRedirectMixin):
r = dispatch_hook('response', hooks, r, **kwargs)
# Persist cookies
if r.history:
# If the hooks create history then we want those cookies too
for resp in r.history:
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
extract_cookies_to_jar(self.cookies, request, r.raw)
# Redirect resolving generator.
@@ -467,7 +497,7 @@ class Session(SessionRedirectMixin):
"""Returns the appropriate connnection adapter for the given URL."""
for (prefix, adapter) in self.adapters.items():
if url.startswith(prefix):
if url.lower().startswith(prefix):
return adapter
# Nothing matches :-/
@@ -475,7 +505,7 @@ class Session(SessionRedirectMixin):
def close(self):
"""Closes all adapters and as such the session"""
for _, v in self.adapters.items():
for v in self.adapters.values():
v.close()
def mount(self, prefix, adapter):

View File

@@ -18,7 +18,8 @@ _codes = {
205: ('reset_content', 'reset'),
206: ('partial_content', 'partial'),
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
208: ('im_used',),
208: ('already_reported',),
226: ('im_used',),
# Redirection.
300: ('multiple_choices',),

View File

@@ -103,7 +103,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
# Copy is required
def copy(self):
return CaseInsensitiveDict(self._store.values())
return CaseInsensitiveDict(self._store.values())
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))

View File

@@ -12,6 +12,7 @@ that are also useful for external consumption.
import cgi
import codecs
import collections
import io
import os
import platform
import re
@@ -21,9 +22,11 @@ from netrc import netrc, NetrcParseError
from . import __version__
from . import certs
from .compat import parse_http_list as _parse_list_header
from .compat import quote, urlparse, bytes, str, OrderedDict, urlunparse
from .compat import (quote, urlparse, bytes, str, OrderedDict, urlunparse,
is_py2, is_py3, builtin_str, getproxies, proxy_bypass)
from .cookies import RequestsCookieJar, cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import MissingSchema, InvalidURL
_hush_pyflakes = (RequestsCookieJar,)
@@ -44,11 +47,21 @@ def dict_to_sequence(d):
def super_len(o):
if hasattr(o, '__len__'):
return len(o)
if hasattr(o, 'len'):
return o.len
if hasattr(o, 'fileno'):
return os.fstat(o.fileno()).st_size
if hasattr(o, 'fileno'):
try:
fileno = o.fileno()
except io.UnsupportedOperation:
pass
else:
return os.fstat(fileno).st_size
if hasattr(o, 'getvalue'):
# e.g. BytesIO, cStringIO.StringI
return len(o.getvalue())
def get_netrc_auth(url):
"""Returns the Requests tuple auth for a given url from netrc."""
@@ -264,8 +277,12 @@ def get_encodings_from_content(content):
"""
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
return charset_re.findall(content)
return (charset_re.findall(content) +
pragma_re.findall(content) +
xml_re.findall(content))
def get_encoding_from_headers(headers):
@@ -301,7 +318,7 @@ def stream_decode_response_unicode(iterator, r):
rv = decoder.decode(chunk)
if rv:
yield rv
rv = decoder.decode('', final=True)
rv = decoder.decode(b'', final=True)
if rv:
yield rv
@@ -361,7 +378,11 @@ def unquote_unreserved(uri):
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2 and h.isalnum():
c = chr(int(h, 16))
try:
c = chr(int(h, 16))
except ValueError:
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:]
else:
@@ -386,25 +407,17 @@ def requote_uri(uri):
def get_environ_proxies(url):
"""Return a dict of environment proxies."""
proxy_keys = [
'all',
'http',
'https',
'ftp',
'socks'
]
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
# First check whether no_proxy is defined. If it is, check that the URL
# we're getting isn't in the no_proxy list.
no_proxy = get_proxy('no_proxy')
netloc = urlparse(url).netloc
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.split(',')
netloc = urlparse(url).netloc
no_proxy = no_proxy.replace(' ', '').split(',')
for host in no_proxy:
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
@@ -412,10 +425,15 @@ def get_environ_proxies(url):
# to apply the proxies on this URL.
return {}
# If the system proxy settings indicate that this URL should be bypassed,
# don't proxy.
if proxy_bypass(netloc):
return {}
# If we get here, we either didn't have no_proxy set or we're not going
# anywhere that no_proxy applies to.
proxies = [(key, get_proxy(key + '_proxy')) for key in proxy_keys]
return dict([(key, val) for (key, val) in proxies if val])
# anywhere that no_proxy applies to, and the system settings don't require
# bypassing the proxy for the current URL.
return getproxies()
def default_user_agent():
@@ -526,18 +544,13 @@ def guess_json_utf(data):
return None
def prepend_scheme_if_needed(url, new_scheme):
'''Given a URL that may or may not have a scheme, prepend the given scheme.
Does not replace a present scheme with the one provided as an argument.'''
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
def except_on_missing_scheme(url):
"""Given a URL, raise a MissingSchema exception if the scheme is missing.
"""
scheme, netloc, path, params, query, fragment = urlparse(url)
# urlparse is a finicky beast, and sometimes decides that there isn't a
# netloc present. Assume that it's being over-cautious, and switch netloc
# and path if urlparse decided there was no netloc.
if not netloc:
netloc, path = path, netloc
return urlunparse((scheme, netloc, path, params, query, fragment))
if not scheme:
raise MissingSchema('Proxy URLs must have explicit schemes.')
def get_auth_from_url(url):
@@ -548,3 +561,22 @@ def get_auth_from_url(url):
return (parsed.username, parsed.password)
else:
return ('', '')
def to_native_string(string, encoding='ascii'):
"""
Given a string object, regardless of type, returns a representation of that
string in the native string type, encoding and decoding where necessary.
This assumes ASCII unless told otherwise.
"""
out = None
if isinstance(string, builtin_str):
out = string
else:
if is_py2:
out = string.encode(encoding)
else:
out = string.decode(encoding)
return out

View File

@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
import requests
from invoke import run, task
@task
def test():
run('py.test', pty=True)
@task
def deps():
print('Vendoring urllib3...')
run('rm -fr requests/packages/urllib3')
run('git clone https://github.com/shazow/urllib3.git')
run('mv urllib3/urllib3 requests/packages/')
run('rm -fr urllib3')
print('Vendoring Charade...')
run('rm -fr requests/packages/charade')
run('git clone https://github.com/sigmavirus24/charade.git')
run('mv charade/charade requests/packages/')
run('rm -fr charade')
@task
def certs():
print('Grabbing latest CA Bundle...')
r = requests.get('https://raw.github.com/kennethreitz/certifi/master/certifi/cacert.pem')
with open('requests/cacert.pem', 'w') as f:
f.write(r.content)

View File

@@ -10,9 +10,10 @@ import unittest
import pickle
import requests
import pytest
from requests.auth import HTTPDigestAuth
from requests.adapters import HTTPAdapter
from requests.compat import str, cookielib
from requests.compat import str, cookielib, getproxies, urljoin, urlparse
from requests.cookies import cookiejar_from_dict
from requests.exceptions import InvalidURL, MissingSchema
from requests.structures import CaseInsensitiveDict
@@ -23,11 +24,13 @@ except ImportError:
import io as StringIO
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 HTTPBIN + '/'.join(suffix)
return urljoin(HTTPBIN, '/'.join(suffix))
class RequestsTestCase(unittest.TestCase):
@@ -87,9 +90,23 @@ class RequestsTestCase(unittest.TestCase):
self.assertEqual(request.url,
"http://example.com/path?key=value&a=b#fragment")
def test_mixed_case_scheme_acceptable(self):
s = requests.Session()
s.proxies = getproxies()
parts = urlparse(httpbin('get'))
schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://',
'https://', 'HTTPS://', 'hTTps://', 'HttPs://']
for scheme in schemes:
url = scheme + parts.netloc + parts.path
r = requests.Request('GET', url)
r = s.send(r.prepare())
self.assertEqual(r.status_code, 200,
"failed for scheme %s" % scheme)
def test_HTTP_200_OK_GET_ALTERNATIVE(self):
r = requests.Request('GET', httpbin('get'))
s = requests.Session()
s.proxies = getproxies()
r = s.send(r.prepare())
@@ -142,6 +159,17 @@ class RequestsTestCase(unittest.TestCase):
)
assert 'foo' not in s.cookies
def test_cookie_quote_wrapped(self):
s = requests.session()
s.get(httpbin('cookies/set?foo="bar:baz"'))
self.assertTrue(s.cookies['foo'] == '"bar:baz"')
def test_cookie_persists_via_api(self):
s = requests.session()
r = s.get(httpbin('redirect/1'), cookies={'foo':'bar'})
self.assertTrue('foo' in r.request.headers['Cookie'])
self.assertTrue('foo' in r.history[0].request.headers['Cookie'])
def test_request_cookie_overrides_session_cookie(self):
s = requests.session()
s.cookies['foo'] = 'bar'
@@ -161,6 +189,12 @@ class RequestsTestCase(unittest.TestCase):
# Make sure the session cj is still the custom one
assert s.cookies is cj
def test_requests_in_history_are_not_overridden(self):
resp = requests.get(httpbin('redirect/3'))
urls = [r.url for r in resp.history]
req_urls = [r.request.url for r in resp.history]
self.assertEquals(urls, req_urls)
def test_user_agent_transfers(self):
heads = {
@@ -200,6 +234,34 @@ class RequestsTestCase(unittest.TestCase):
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_basicauth_with_netrc(self):
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
# Should use netrc and work.
r = requests.get(url)
self.assertEqual(r.status_code, 200)
# Given auth should override and fail.
r = requests.get(url, auth=wrong_auth)
self.assertEqual(r.status_code, 401)
s = requests.session()
# Should use netrc and work.
r = s.get(url)
self.assertEqual(r.status_code, 200)
# Given auth should override and fail.
s.auth = wrong_auth
r = s.get(url)
self.assertEqual(r.status_code, 401)
def test_DIGEST_HTTP_200_OK_GET(self):
auth = HTTPDigestAuth('user', 'pass')
@@ -212,10 +274,26 @@ class RequestsTestCase(unittest.TestCase):
self.assertEqual(r.status_code, 401)
s = requests.session()
s.auth = auth
s.auth = HTTPDigestAuth('user', 'pass')
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_DIGEST_AUTH_RETURNS_COOKIE(self):
url = httpbin('digest-auth', 'auth', 'user', 'pass')
auth = HTTPDigestAuth('user', 'pass')
r = requests.get(url)
assert r.cookies['fake'] == 'fake_value'
r = requests.get(url, auth=auth)
assert r.status_code == 200
def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self):
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):
auth = HTTPDigestAuth('user', 'pass')
@@ -284,6 +362,12 @@ class RequestsTestCase(unittest.TestCase):
except ValueError:
pass
def test_conflicting_post_params(self):
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):
r = requests.get(httpbin('status', '404'))
self.assertEqual(r.ok, False)
@@ -380,10 +464,28 @@ class RequestsTestCase(unittest.TestCase):
prep = req.prepare()
s = requests.Session()
s.proxies = getproxies()
resp = s.send(prep)
self.assertTrue(hasattr(resp, 'hook_working'))
def test_prepared_from_session(self):
class DummyAuth(requests.auth.AuthBase):
def __call__(self, r):
r.headers['Dummy-Auth-Test'] = 'dummy-auth-test-ok'
return r
req = requests.Request('GET', httpbin('headers'))
self.assertEqual(req.auth, None)
s = requests.Session()
s.auth = DummyAuth()
prep = s.prepare_request(req)
resp = s.send(prep)
self.assertTrue(resp.json()['headers']['Dummy-Auth-Test'], 'dummy-auth-test-ok')
def test_links(self):
r = requests.Response()
r.headers = {
@@ -469,6 +571,7 @@ class RequestsTestCase(unittest.TestCase):
s = requests.Session()
s = pickle.loads(pickle.dumps(s))
s.proxies = getproxies()
r = s.send(r.prepare())
self.assertEqual(r.status_code, 200)
@@ -482,20 +585,26 @@ class RequestsTestCase(unittest.TestCase):
s.headers.update({'accept': 'application/json'})
r = s.get(httpbin('get'))
headers = r.request.headers
# ASCII encode because of key comparison changes in py3
self.assertEqual(
headers['accept'.encode('ascii')],
headers['accept'],
'application/json'
)
self.assertEqual(
headers['Accept'.encode('ascii')],
headers['Accept'],
'application/json'
)
self.assertEqual(
headers['ACCEPT'.encode('ascii')],
headers['ACCEPT'],
'application/json'
)
def test_uppercase_scheme_redirect(self):
parts = urlparse(httpbin('html'))
url = "HTTP://" + parts.netloc + parts.path
r = requests.get(httpbin('redirect-to'), params={'url': url})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.url.lower(), url.lower())
def test_transport_adapter_ordering(self):
s = requests.Session()
order = ['https://', 'http://']
@@ -557,6 +666,68 @@ class RequestsTestCase(unittest.TestCase):
r = requests.Request('GET', url).prepare()
self.assertEqual(r.url, url)
def test_header_keys_are_native(self):
headers = {u'unicode': 'blah', 'byte'.encode('ascii'): 'blah'}
r = requests.Request('GET', httpbin('get'), headers=headers)
p = r.prepare()
# This is testing that they are builtin strings. A bit weird, but there
# we go.
self.assertTrue('unicode' in p.headers.keys())
self.assertTrue('byte' in p.headers.keys())
def test_can_send_nonstring_objects_with_files(self):
data = {'a': 0.0}
files = {'b': 'foo'}
r = requests.Request('POST', httpbin('post'), data=data, files=files)
p = r.prepare()
self.assertTrue('multipart/form-data' in p.headers['Content-Type'])
class TestContentEncodingDetection(unittest.TestCase):
def test_none(self):
encodings = requests.utils.get_encodings_from_content('')
self.assertEqual(len(encodings), 0)
def test_html_charset(self):
"""HTML5 meta charset attribute"""
content = '<meta charset="UTF-8">'
encodings = requests.utils.get_encodings_from_content(content)
self.assertEqual(len(encodings), 1)
self.assertEqual(encodings[0], 'UTF-8')
def test_html4_pragma(self):
"""HTML4 pragma directive"""
content = '<meta http-equiv="Content-type" content="text/html;charset=UTF-8">'
encodings = requests.utils.get_encodings_from_content(content)
self.assertEqual(len(encodings), 1)
self.assertEqual(encodings[0], 'UTF-8')
def test_xhtml_pragma(self):
"""XHTML 1.x served with text/html MIME type"""
content = '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />'
encodings = requests.utils.get_encodings_from_content(content)
self.assertEqual(len(encodings), 1)
self.assertEqual(encodings[0], 'UTF-8')
def test_xml(self):
"""XHTML 1.x served as XML"""
content = '<?xml version="1.0" encoding="UTF-8"?>'
encodings = requests.utils.get_encodings_from_content(content)
self.assertEqual(len(encodings), 1)
self.assertEqual(encodings[0], 'UTF-8')
def test_precedence(self):
content = '''
<?xml version="1.0" encoding="XML"?>
<meta charset="HTML5">
<meta http-equiv="Content-type" content="text/html;charset=HTML4" />
'''.strip()
encodings = requests.utils.get_encodings_from_content(content)
self.assertEqual(encodings, ['HTML5', 'HTML4', 'XML'])
class TestCaseInsensitiveDict(unittest.TestCase):
@@ -702,5 +873,26 @@ class TestCaseInsensitiveDict(unittest.TestCase):
self.assertEqual(frozenset(cid), keyset)
class UtilsTestCase(unittest.TestCase):
def test_super_len_io_streams(self):
""" Ensures that we properly deal with different kinds of IO streams. """
# uses StringIO or io.StringIO (see import above)
from io import BytesIO
from requests.utils import super_len
self.assertEqual(super_len(StringIO.StringIO()), 0)
self.assertEqual(super_len(StringIO.StringIO('with so much drama in the LBC')), 29)
self.assertEqual(super_len(BytesIO()), 0)
self.assertEqual(super_len(BytesIO(b"it's kinda hard bein' snoop d-o-double-g")), 40)
try:
import cStringIO
except ImportError:
pass
else:
self.assertEqual(super_len(cStringIO.StringIO('but some how, some way...')), 25)
if __name__ == '__main__':
unittest.main()