Compare commits

...

526 Commits
v2.16.2 ... 3.0

Author SHA1 Message Date
Nate Prewitt
cd4762d5a3 Merge pull request #4551 from RatanShreshtha/3.0
Updated types in __init__.py according to types.py
2018-06-14 07:39:13 -07:00
RatanShreshtha
2bbe38fd8b Updated types in __init__.py according to types.py 2018-03-22 01:05:56 +05:30
Kenneth Reitz
8f3f022c54 attempt to fix appveyor
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 17:25:00 -04:00
Kenneth Reitz
6497cbee7e appveyor attempt
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 17:19:02 -04:00
Kenneth Reitz
3951232f8c fix tests
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 17:09:39 -04:00
Kenneth Reitz
86ab1ce916 session fixture
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 17:07:59 -04:00
Kenneth Reitz
d956dc1d70 no more session
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:53:58 -04:00
Kenneth Reitz
2aa9d23a91 session improvements
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:53:25 -04:00
Kenneth Reitz
c4a2e88436 slots on headerdict
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:49:41 -04:00
Kenneth Reitz
e407afc494 __init__.py
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:47:02 -04:00
Kenneth Reitz
f132bfbc6a f-strings
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:41:21 -04:00
Kenneth Reitz
eb37c39c66 as_dict
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:37:04 -04:00
Kenneth Reitz
b115fb7525 or none
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:34:59 -04:00
Kenneth Reitz
cac7a2843d white
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:33:33 -04:00
Kenneth Reitz
b6bc1d06aa passing tests™ 2018-03-15 16:32:16 -04:00
Kenneth Reitz
d61f6b65d3 utils typing, better
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 16:13:33 -04:00
Kenneth Reitz
f64b27bfed typing for utils.py
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 15:08:23 -04:00
Kenneth Reitz
e16fc666af white / improvements
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 14:20:53 -04:00
Kenneth Reitz
c761385c37 proper mypy-pytest setup
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 14:11:05 -04:00
Kenneth Reitz
5808cbfa7f slots
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 11:20:47 -04:00
Kenneth Reitz
f46005d771 rfc3986 2018-03-15 11:20:13 -04:00
Kenneth Reitz
68f10d0d1a pipfile updates
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 11:19:23 -04:00
Kenneth Reitz
e8ccdd6343 same
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 11:15:22 -04:00
Kenneth Reitz
aa7314e97a python 3.6 syntax
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 11:07:09 -04:00
Kenneth Reitz
0c722d9476 more __slots__ 2018-03-15 11:04:25 -04:00
Kenneth Reitz
5574076b05 __slots__
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 11:01:25 -04:00
Kenneth Reitz
631076d600 __slots__
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 10:46:30 -04:00
Kenneth Reitz
8a6bcfbe1a validate
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 10:41:26 -04:00
Kenneth Reitz
64d85b7ebf use rfc8936 2018-03-15 10:39:26 -04:00
Kenneth Reitz
6a27949e5f rfc3986 2018-03-15 09:20:32 -04:00
Kenneth Reitz
b2e99359fc merge 2018-03-15 08:59:47 -04:00
Kenneth Reitz
fa33697514 unused import
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 07:24:23 -04:00
Kenneth Reitz
8348ba8b6b white for setup.py
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-15 07:24:11 -04:00
Kenneth Reitz
8a1baaf47b white 2018-03-15 06:56:20 -04:00
Kenneth Reitz
4bd079f871 there's one solution (to a temporary problem)
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-14 18:28:18 -04:00
Kenneth Reitz
808b520724 pipfile for black
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>

Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-14 18:15:13 -04:00
Kenneth Reitz
0b96f5282a Merge branch '3.0' of github.com:requests/requests into 3.0 2018-03-14 17:29:06 -04:00
Kenneth Reitz
9cea8ce09d black
https://github.com/ambv/black
2018-03-14 17:28:51 -04:00
Kenneth Reitz
d763e8e99e Merge pull request #4543 from RatanShreshtha/3.0
Type annoted requests/__init__.py.
2018-03-14 14:16:09 -04:00
RatanShreshtha
38b29446b0 Type annoted requests/__init__.py. 2018-03-14 23:43:50 +05:30
Kenneth Reitz
2bf628be3c #4542
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-13 09:53:42 -04:00
Kenneth Reitz
9af75f01a9 Merge pull request #4542 from RatanShreshtha/3.0
Added type hints for help.py.
2018-03-13 09:52:06 -04:00
Ratan.Kulshreshtha
12671d4819 Added type hints for help.py. 2018-03-13 12:10:51 +05:30
Kenneth Reitz
272ea7db4e fix redirects
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 13:44:07 -04:00
Kenneth Reitz
f7a36c5866 catch non-keyword arguments
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 13:04:11 -04:00
Kenneth Reitz
530db6c4b9 mypy improvements
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:59:59 -04:00
Kenneth Reitz
c7d0f3ef01 better mypy compliance
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:56:09 -04:00
Kenneth Reitz
db2aecbac8 -> types.Response 2018-03-12 12:52:08 -04:00
Kenneth Reitz
fc4637032a docstring
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:36:01 -04:00
Kenneth Reitz
82c3c42193 mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:32:27 -04:00
Kenneth Reitz
b72d5379ea no python 2.7
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:31:16 -04:00
Kenneth Reitz
edf844a2db mypy command
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:31:04 -04:00
Kenneth Reitz
440dffe6e9 this should always work, i believe
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:27:10 -04:00
Kenneth Reitz
3ac6ee9ce4 make mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:25:29 -04:00
Kenneth Reitz
ccffc02547 not boxed (doesn't work on my system)
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:21:12 -04:00
Kenneth Reitz
77e2ca1a85 test
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:15:25 -04:00
Kenneth Reitz
20641583e3 for now
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:14:59 -04:00
Kenneth Reitz
582ce4d247 no cstringio
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:13:47 -04:00
Kenneth Reitz
4d902fdbaa require older version of mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:11:27 -04:00
Kenneth Reitz
9d1d3d165b no 2.7 in travis
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:08:39 -04:00
Kenneth Reitz
dba39c5285 pytest-mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:05:51 -04:00
Kenneth Reitz
4239856a4a types
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:02:05 -04:00
Kenneth Reitz
f531629a9b remove python2–specific code
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:58:21 -04:00
Kenneth Reitz
220c368e2c gitignore
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:53:36 -04:00
Kenneth Reitz
8ba99d246b basics
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:53:22 -04:00
Kenneth Reitz
6be73f90b1 /s/compat/basics 2018-03-12 11:53:12 -04:00
Kenneth Reitz
315a5661e3 new approach
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:47:09 -04:00
Kenneth Reitz
9c6bd54b44 Merge pull request #4500 from jdufresne/remove-packages
Remove requests.packages for v3.0.0
2018-03-08 13:00:49 -05:00
Jon Dufresne
4c6d9e1954 Remove requests.packages for v3.0.0
As v3.0.0 already includes other backwards incompatible changes, it is a
good time to remove the old entry point for vendored packages. Cleans up
compatibility shims.
2018-02-04 19:07:26 -08:00
Nate Prewitt
a58b1e66cb Merge pull request #4468 from VasiliPupkin256/fix2392
Simple fix for the issue #2392
2018-01-13 17:13:30 -08:00
VasiliPupkin256
8f00695d40 updating 3.0-HISTORY for #2392 2018-01-14 01:59:29 +03:00
VasiliPupkin256
5d6eeb5a7f Map urllib3 ReadTimeoutError to an appropriate ReadTimeout requests exception, issue #2392 2018-01-13 03:28:02 +03:00
Nate Prewitt
78bf3ee6e6 Merge pull request #4328 from hugovk/rm-2.6
Remove redundant Python 2.6 stuff
2017-10-19 09:58:53 -07:00
hugovk
9146b62d31 OrderedDict no longer in compat.py as it's in collections in Py2.7+ 2017-10-17 20:29:33 +03:00
Hugo
946f136715 Update CLI example 2017-10-17 18:03:59 +03:00
Hugo
19a294a2a1 Update AUTHORS.rst 2017-10-17 16:58:22 +03:00
hugovk
006aa3de4f Drop Python 2.6: OrderedDict is in collections from 2.7 2017-10-17 16:56:56 +03:00
Nate Prewitt
37f1d23c39 Merge remote-tracking branch 'upstream/master' into proposed/3.0.0 2017-10-15 13:59:50 -07:00
Nate Prewitt
e86df6e0eb Merge pull request #4341 from hugovk/patch-1
Python 3.7 not officially supported yet
2017-10-15 13:35:52 -07:00
Hugo
ec5804c706 Python 3.3 has already been dropped (#4231) 2017-10-15 20:04:51 +03:00
Hugo
4316af7ae8 Python 3.7 not officially supported yet 2017-10-15 19:52:19 +03:00
Cory Benfield
8c4f78448a Merge pull request #4332 from PrimordialHelios/pyopenssl-warning
Warn about possible slowness on older versions of pyOpenSSL
2017-10-14 08:41:02 -07:00
Taylor Rose
15054aa390 Warn user about possible slowdown when using cryptography version <
1.3.4
2017-10-14 09:00:58 -04:00
Kenneth Reitz
9713289e74 Merge branch 'master' of github.com:requests/requests 2017-09-25 11:04:47 -04:00
Kenneth Reitz
e6afc37be5 new pipfile with new syntax
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-25 11:04:38 -04:00
Ian Stapleton Cordasco
5eb0811ef8 Merge pull request #4305 from sigmavirus24/fix-pypi-display
Fix the invalid restructured text in HISTORY file
2017-09-23 16:21:48 -05:00
Ian Stapleton Cordasco
40c8bcf70c Fix the invalid restructured text in HISTORY file
This was breaking the display of our long_description on PyPI.
2017-09-23 15:54:14 -05:00
Kenneth Reitz
0eb9a4c161 stickers
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-19 13:23:15 -04:00
Kenneth Reitz
cda429aa5d Merge pull request #4295 from requests/pipenv
pipfile (work in progress)
2017-09-17 11:58:46 -04:00
Kenneth Reitz
28ae74dbad update tests
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:46:33 -04:00
Kenneth Reitz
a9d05f06cd remove requirements.txt
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:39:53 -04:00
Kenneth Reitz
d552132464 run coverage w/ pipenv
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:36:54 -04:00
Kenneth Reitz
c3ecb825c4 don't test on 2.6
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:30:26 -04:00
Kenneth Reitz
70a4ae744c pip install pipenv
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:26:22 -04:00
Kenneth Reitz
4c300c2067 pipfile
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:24:05 -04:00
Allan Crooks
4f0b496d2b Update AUTHORS.rst. 2017-09-12 23:51:28 +01:00
Allan Crooks
a36247e148 Rename HTTPHeaderDict methods to getlist and setlist to be more consistent with other similar implementations. 2017-09-12 23:14:00 +01:00
Allan Crooks
6dc3003f16 Update documentation regarding response headers. 2017-09-12 23:13:03 +01:00
Allan Crooks
5aef6e7583 Use HTTPHeaderDict for response headers. 2017-09-12 22:17:23 +01:00
Allan Crooks
95b127714b Add HTTPHeaderDict data structure and tests. 2017-09-12 22:16:48 +01:00
Allan Crooks
5980d5df3e Add tests showing current behaviour of how multiple response headers with the same name are compiled into a single value. 2017-09-12 22:16:31 +01:00
Kenneth Reitz
fdb9b753b4 Update authors.rst 2017-09-05 11:31:16 -04:00
Kenneth Reitz
4bda7b66e7 Update updates.rst 2017-09-04 16:21:57 -04:00
Kenneth Reitz
068d8863d1 Update support.rst 2017-09-04 16:21:04 -04:00
Kenneth Reitz
c49f3643ae Update release-process.rst 2017-09-04 16:20:03 -04:00
Kenneth Reitz
328cff252d Update out-there.rst 2017-09-04 16:19:36 -04:00
Kenneth Reitz
c57bd07a34 Update faq.rst 2017-09-04 16:19:01 -04:00
Kenneth Reitz
0d82afa0c3 Update recommended.rst 2017-09-04 16:18:00 -04:00
Kenneth Reitz
18e37c9368 Update todo.rst 2017-09-04 16:14:48 -04:00
Kenneth Reitz
389da34fc9 Update philosophy.rst 2017-09-04 16:14:07 -04:00
Kenneth Reitz
8ef20d5c36 Update contributing.rst 2017-09-04 16:13:08 -04:00
Kenneth Reitz
e63d481a7a Update authors.rst 2017-09-04 16:12:38 -04:00
Kenneth Reitz
f3eeb38542 Update vulnerabilities.rst 2017-09-04 16:11:52 -04:00
Kenneth Reitz
afaaae185c Update authentication.rst 2017-09-04 16:10:49 -04:00
Kenneth Reitz
8af65a1eca Update quickstart.rst 2017-09-04 16:09:51 -04:00
Kenneth Reitz
c04b044b30 Update advanced.rst 2017-09-04 16:09:08 -04:00
Kenneth Reitz
530198e0c0 Update install.rst 2017-09-04 16:08:22 -04:00
Kenneth Reitz
8c57515696 Update intro.rst 2017-09-04 16:05:57 -04:00
Kenneth Reitz
175452a6b6 Update index.rst 2017-09-04 16:04:07 -04:00
Kenneth Reitz
e35c829b59 Update index.rst 2017-09-04 16:03:49 -04:00
Kenneth Reitz
1ad51b0a41 Update index.rst 2017-09-04 15:55:53 -04:00
Kenneth Reitz
8be8105c42 Update advanced.rst 2017-09-04 15:53:26 -04:00
Kenneth Reitz
49ec082a77 Update recommended.rst 2017-09-04 15:50:38 -04:00
Kenneth Reitz
0488c640e2 Update index.rst 2017-09-04 15:46:47 -04:00
Kenneth Reitz
8371b56d0f Delete extensions.rst 2017-09-04 15:46:24 -04:00
Kenneth Reitz
830c18e4f9 Update index.rst 2017-09-04 15:45:28 -04:00
Kenneth Reitz
2c438cacd1 Create extensions.rst 2017-09-04 15:44:57 -04:00
Kenneth Reitz
9c8ae76928 Update index.rst 2017-09-04 15:40:02 -04:00
Kenneth Reitz
d5a05469f1 Update README.rst 2017-09-04 15:38:13 -04:00
Kenneth Reitz
e22e3a5059 Update README.rst 2017-09-04 15:37:24 -04:00
Kenneth Reitz
054ad4aa67 Update README.rst 2017-09-04 12:59:01 -04:00
Kenneth Reitz
b9638ec7be taste 2017-09-01 13:10:52 -04:00
Kenneth Reitz
940c1246e0 taste 2017-09-01 13:10:29 -04:00
Kenneth Reitz
e0e5b2e3aa Update philosophy.rst 2017-08-29 16:16:52 -04:00
Kenneth Reitz
96b9a3f0fc Update setup.py 2017-08-28 12:25:26 -04:00
Cory Benfield
657e44ad9a Merge pull request #4265 from PascalVA/patch-2
fixed syntax for code block for prepared requests
2017-08-28 10:39:26 +01:00
Pascal Van Acker
ac9a3e5320 fixed syntax for code block for prepared requests 2017-08-28 11:14:05 +02:00
Cory Benfield
6b21682712 Merge pull request #4264 from PascalVA/patch-1
Add environment info to prepared requests docs
2017-08-28 10:08:07 +01:00
Pascal Van Acker
785c733415 Add environment info to prepared requests
When using prepared requests, the environment is not taken into account. This should be reflected in the documentation.
2017-08-28 10:58:44 +02:00
Kenneth Reitz
369be8afd0 pretty little note
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-08-28 02:01:36 -04:00
Kenneth Reitz
572d2f4534 Python 2 Death Sentence™
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-08-28 01:50:35 -04:00
Kenneth Reitz
7e20b9193a Update index.rst 2017-08-28 01:33:45 -04:00
Kenneth Reitz
ad1aff52a0 Update index.rst 2017-08-28 01:33:32 -04:00
Kenneth Reitz
3a6dec9827 Update index.rst 2017-08-28 01:29:29 -04:00
Kenneth Reitz
3cceee3459 Update install.rst 2017-08-27 23:16:32 -04:00
Kenneth Reitz
dd57439fa5 Update README.rst 2017-08-27 23:13:50 -04:00
Cory Benfield
f09e462165 Merge pull request #4261 from Lukasa/fix-broken-tests
Pin to httpbin 0.5.0.
2017-08-27 14:00:37 +01:00
Cory Benfield
3a2ea9deba Pin to httpbin 0.5.0 2017-08-27 08:54:01 +01:00
Kenneth Reitz
5dcbdb50fc docs update 2017-08-25 22:12:34 -04:00
Kenneth Reitz
8ad09b68ea coinbin.org 2017-08-25 22:02:03 -04:00
Kenneth Reitz
88dc33522d Merge branch 'master' of https://github.com/requests/requests 2017-08-20 20:56:54 -04:00
Kenneth Reitz
e5666b93cf btc
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-08-20 20:56:48 -04:00
Cory Benfield
554a210f88 Merge pull request #4245 from remram44/fix-headers-links
Fix parse_header_links on empty header
2017-08-17 20:34:06 +01:00
Remi Rampin
8e411d0f99 Add test case for empty Link: header 2017-08-17 12:56:45 -04:00
Remi Rampin
7d190ef010 Add entry in HISTORY.rst 2017-08-17 12:56:38 -04:00
Remi Rampin
965ada6f66 Fix parse_header_links on empty header 2017-08-16 22:10:20 -04:00
Cory Benfield
af9dd61fcb Prepare for new release 2017-08-15 14:24:20 +01:00
Cory Benfield
a3d7cf3f27 v2.18.4 2017-08-15 14:22:52 +01:00
Cory Benfield
3c5ecfc9f5 Merge pull request #4240 from mgasvoda/master
Adding header name to exception
2017-08-12 15:41:27 +01:00
mgasvoda
d528106876 Adding record to history 2017-08-12 10:23:45 -04:00
mgasvoda
cbc7c2d1c9 Modifying tests to include header name info 2017-08-11 17:18:58 -04:00
mgasvoda
e36083b89c Adding space after colon 2017-08-11 16:19:43 -04:00
mgasvoda
308343508b Removing trailing whitespace 2017-08-11 16:14:13 -04:00
mgasvoda
ba21d14976 Improving message formatting 2017-08-11 16:12:36 -04:00
mgasvoda
cee8fae65e Adding header name to exception
Adds the name of the header to the invalid header exception raised on TypeError.
2017-08-11 15:37:57 -04:00
Kenneth Reitz
1c2022cf86 Merge pull request #4231 from Lukasa/drop-3.3-support
Drop Python 3.3 support.
2017-08-10 14:03:36 -04:00
Kenneth Reitz
8480cf937a Merge pull request #4237 from robinske/doc-import
Adds import statement to front page of docs for clarity and quicker start up
2017-08-10 14:02:57 -04:00
Kelley Robinson
f6952a9b8d Merge branch 'master' into doc-import 2017-08-10 10:33:22 -07:00
kelley
d758f0cf97 Adds import statement to front page of docs for clarity and quicker start up 2017-08-10 10:23:43 -07:00
Cory Benfield
c31a450c55 Remove references to 3.3 from the docs 2017-08-09 19:40:52 +01:00
Cory Benfield
0a2dc74ecb Drop Python 3.3 support 2017-08-09 19:03:04 +01:00
Cory Benfield
f78a57936b Merge pull request #4229 from NikosVlagoidis/issue#3863
Fixes issue #3863
2017-08-09 18:56:40 +01:00
NikosVlagoidis
218978601f Fix issue #3863 2017-08-09 20:18:43 +03:00
NikosVlagoidis
cb9620780f Update advanced.rst 2017-08-09 18:52:26 +03:00
Nikolaos Vlagoidis
963c78853f Fixes issue #3863 2017-08-09 15:38:45 +03:00
Kenneth Reitz
b670b61d54 Update AUTHORS.rst 2017-08-08 19:16:39 -04:00
Cory Benfield
9ec2005d9f Merge pull request #4223 from Lukasa/widen-idna-pin
Allow IDNA 2.6
2017-08-08 15:42:54 +01:00
Cory Benfield
f4ddf00c36 Allow IDNA 2.6 2017-08-08 13:32:15 +01:00
Cory Benfield
248f122970 Merge pull request #4217 from nikolas/patch-1
fix typo in changelog
2017-08-02 15:38:46 +01:00
Nik Nyby
be8ce365ab fix typo in changelog 2017-08-02 10:26:41 -04:00
Cory Benfield
3393dc8221 Prepare changelog for next release 2017-08-02 14:24:02 +01:00
Cory Benfield
7c67c4adf9 v2.18.3 2017-08-02 14:22:44 +01:00
Ian Stapleton Cordasco
2a4dd64fb4 Merge pull request #4210 from Lukasa/sslerror
Make sure we raise SSLError.
2017-07-29 06:27:17 -05:00
Cory Benfield
9e0fb37fff Changelog for #4209 2017-07-29 12:13:55 +01:00
Cory Benfield
4f49f6b3ed Correctly raise SSLError from urllib3. 2017-07-29 12:12:11 +01:00
Cory Benfield
8b3f20ca91 Add failing test for #4209 2017-07-29 12:09:04 +01:00
Nate Prewitt
fdf426125b Merge pull request #4182 from alexwlchan/idna-version
Add idna version info to requests.help
2017-07-27 14:54:09 -06:00
Alex Chan
d6b57c6fb2 Changelog entry 2017-07-27 21:33:44 +01:00
Alex Chan
39b121d791 Add idna version info to requests.help 2017-07-27 21:33:42 +01:00
Nate Prewitt
12c7456789 Merge pull request #4174 from alex/patch-2
Removed legacy fallback for python3.2
2017-07-27 09:51:33 -06:00
Alex Gaynor
2629c0906e Merge branch 'master' into patch-2 2017-07-27 11:19:32 -04:00
Cory Benfield
205755834d Merge pull request #4207 from markjszy/master
remove legacy super() call
2017-07-27 05:40:03 +01:00
Mark Szymanski
e2f5a135c3 remove legacy super() call 2017-07-26 19:21:45 -05:00
Kenneth Reitz
c7266a5ff4 Merge pull request #4118 from hobarrera/drop-py26
Drop python2.6 support
2017-07-26 15:59:40 -04:00
Cory Benfield
8de858f0de Merge pull request #4206 from jvanasco/feature-redirects
some docs on request redirect detection
2017-07-26 20:57:22 +01:00
jonathan vanasco
667896c557 some docs on request redirect detection 2017-07-26 15:32:07 -04:00
Cory Benfield
2b6f1e6236 Merge pull request #4204 from browniebroke/patch-1
Correct urllib3 compatibility comment
2017-07-26 11:03:57 +01:00
Bruno Alla
b77c1a3deb Correct urllib3 compatibility comment
So that it's in line with the code. I saw the warning raised recently and had to dive into the code for exact compatibility, but I stopped after reading the comment rather than the code. 
Maybe a better solution would be to surface the supported versions in the warning, but I see it was changed recently in https://github.com/requests/requests/pull/4144/
2017-07-26 10:23:01 +01:00
Cory Benfield
9e8d403a21 Prepare changelog for next release 2017-07-25 16:23:50 +01:00
Cory Benfield
6cc891fd32 v2.18.2 2017-07-25 16:21:48 +01:00
Cory Benfield
49c44a0c08 Merge pull request #4198 from andportnoy/patch-1
redundant comma
2017-07-21 07:05:10 +01:00
Andrey Portnoy
c6e18b04e3 redundant comma 2017-07-20 20:49:24 -07:00
Ian Stapleton Cordasco
c10b8384e4 Merge pull request #4196 from Lukasa/require-next-urllib3
Allow urllib3 1.22
2017-07-20 06:52:11 -05:00
Cory Benfield
8d1228cd19 Allow urllib3 1.22 2017-07-20 10:08:50 +01:00
Cory Benfield
8e227aeb89 Merge pull request #4195 from requests/requests/sessions-mount-docstring-clarity
Use 'prefix' instead of 'key' in session docstring
2017-07-20 09:13:06 +01:00
Alex Muller
d8c2fdf92f Use 'prefix' instead of 'key' in session docstring
This commit makes the API docs more clear. The 'key' is an
implementation detail.
2017-07-20 10:06:34 +02:00
Cory Benfield
cada19b300 Merge pull request #4192 from koobs/patch-2
Add requirements.txt to MANIFEST.in
2017-07-15 07:39:01 +01:00
Kubilay Kocak
c6a80640d1 Add requirements.txt to MANIFEST.in 2017-07-15 14:44:57 +10:00
Hugo Osvaldo Barrera
9766870807 Use comprehensions whenever possible 2017-07-14 11:42:32 -03:00
Hugo Osvaldo Barrera
e1b6fb7444 Keep an eye open on forward-compatibility 2017-07-14 11:42:32 -03:00
Hugo Osvaldo Barrera
af8ea2e809 Add myself to AUTHORS
(Or rather, just update my email)
2017-07-14 11:42:32 -03:00
Hugo Osvaldo Barrera
ed068ea0af Drop python2.6 support 2017-07-14 11:42:32 -03:00
Kenneth Reitz
649efc8c9b Merge branch 'master' of github.com:kennethreitz/requests 2017-07-06 17:57:37 -04:00
Kenneth Reitz
14176d1d63 no more stickers 2017-07-06 17:57:28 -04:00
Cory Benfield
1ea4e21958 Merge pull request #4183 from requests/fix_help_ssl_check
Fix help ssl check
2017-07-04 09:16:02 +01:00
Nate Prewitt
51feabbc27 test ssl version check functions as expected in python 2.6 2017-07-04 00:33:16 -06:00
Nate Prewitt
f6e07bb27f fix OPENSSL_VERSION_NUMBER check on Python2.6's ssl module 2017-07-03 17:21:56 -06:00
Cory Benfield
fb9abdda5e Merge pull request #4173 from jeremycline/https-proxy-issue
Pass pool_kwargs rather than updating connection_pool_kw
2017-06-27 14:54:40 +01:00
Jeremy Cline
66f5aebd35 Remove the dictionary comprehension from the tests
Signed-off-by: Jeremy Cline <jeremy@jcline.org>
2017-06-27 09:29:01 -04:00
Jeremy Cline
f58d473d4e Move _pool_kwargs out to a function
Signed-off-by: Jeremy Cline <jeremy@jcline.org>
2017-06-26 09:35:14 -04:00
Alex Gaynor
c054a722bb Removed legacy fallback for python3.2 2017-06-25 12:51:15 -04:00
Jeremy Cline
14a0bf2912 Pass pool_kwargs rather than updating connection_pool_kw
This addresses an issue where making HTTPS through proxies used the
default urllib3 connection pool settings.

fixes #3633

Signed-off-by: Jeremy Cline <jeremy@jcline.org>
2017-06-24 18:58:48 -04:00
Jeremy Cline
bacd043256 Tests to demonstrate issue #3633
Signed-off-by: Jeremy Cline <jeremy@jcline.org>
2017-06-24 18:58:20 -04:00
Kenneth Reitz
bbeb1c32d2 Merge branch 'master' of github.com:kennethreitz/requests 2017-06-16 18:31:19 -04:00
Kenneth Reitz
1263b8660d stickers 2017-06-16 18:30:39 -04:00
Cory Benfield
2fde9ee407 Setup HISTORY for next release 2017-06-14 18:52:03 +01:00
Cory Benfield
51de61f914 v2.18.1 2017-06-14 18:50:53 +01:00
Cory Benfield
dd13b13479 Prepare for next release cycle 2017-06-14 16:45:03 +01:00
Cory Benfield
d93b37cee7 Prepare v2.18.0 2017-06-14 16:43:40 +01:00
Kenneth Reitz
a5b8f8d40a Merge branch 'master' of github.com:kennethreitz/requests 2017-06-11 18:10:11 -04:00
Kenneth Reitz
a8972a5bd2 attempt new ads 2017-06-11 18:10:05 -04:00
Cory Benfield
bb4326c676 Merge pull request #4152 from felixonmars/patch-1
Fix a typo: paramters -> parameters
2017-06-11 18:34:36 +01:00
Felix Yan
a0f0258eee Fix a typo: paramters -> parameters 2017-06-12 01:27:11 +08:00
Cory Benfield
c5901ac893 Merge pull request #4151 from mayhewj/remove-exec
Remove exec permission from files that shouldn't have it
2017-06-11 10:26:51 +01:00
Justin Mayhew
c6a6347156 Remove exec permission from files that shouldn't have it 2017-06-10 20:38:45 -03:00
Cory Benfield
f3cdbcb86d Merge pull request #4120 from requests/update-3.0
Bring 3.0 up-to-date with master.
2017-06-10 07:57:23 +01:00
Cory Benfield
132040cc03 Merge pull request #4134 from mlcrazy/master
Raise exceptions in utils.set_environ
2017-06-09 19:34:56 +01:00
mlcrazy
613f61d77c Merge remote-tracking branch 'upstream/master' 2017-06-09 14:32:27 -04:00
mlcrazy
edcc894a2e Adds to AUTHORS 2017-06-09 14:29:28 -04:00
mlcrazy
761e39b443 Adds changelog entry for #4134 2017-06-09 14:16:32 -04:00
mlcrazy
1d7fd6c8b3 Fixes error swallowing in set_environ 2017-06-09 14:10:47 -04:00
Kenneth Reitz
b3d138e0fd Merge pull request #4140 from Lukasa/issue/4138
Clearer makefile for building README.
2017-06-09 13:30:56 -04:00
Kenneth Reitz
8a8b71f786 Merge branch 'master' into issue/4138 2017-06-09 13:27:02 -04:00
Cory Benfield
2872f17ab8 Merge pull request #4145 from gaojiuli/patch-1
typo
2017-06-09 10:04:40 +01:00
JiuLi Gao
22d12b0501 typo 2017-06-09 16:56:14 +08:00
Nate Prewitt
13eb907c48 Merge pull request #4144 from nateprewitt/move_compat_to_warn
Convert version compatibility checks to warnings
2017-06-08 19:40:07 -06:00
Nate Prewitt
d8e2367809 convert version compatibility checks to warning 2017-06-08 15:21:25 -06:00
Cory Benfield
f8ccc60498 Clearer makefile for building README 2017-06-07 13:50:00 +01:00
Cory Benfield
4f1b17c9c1 Merge pull request #4137 from edmorley/response-context-manager
Allow Requests.Response to be used as a context manager
2017-06-07 08:51:41 +01:00
Ed Morley
4847f5b8cd Allow Requests.Response to be used as a context manager
This saves having to wrap the call to requests with
`contextlib.closing()`, allowing it to be used directly in a
`with` statement, like so:

```
with requests.get('http://httpbin.org/get', stream=True) as r:
    # Do things with the response here.
```

Fixes #4136.
2017-06-06 23:30:47 +01:00
Kenneth Reitz
40ce8144d1 Merge pull request #4135 from edmorley/gitignore-pytest-cache
Add the pytest .cache/ directory to .gitignore
2017-06-06 18:00:38 -04:00
Ed Morley
5a616ee980 Add the pytest .cache/ directory to .gitignore
For example, when a pytest run fails, it saves failure metadata to:
`.cache/v/cache/lastfailed`
2017-06-06 22:44:22 +01:00
Nate Prewitt
b6ee4b2156 Merge pull request #4129 from felixonmars/fix-typo
Fix a typo: verison -> version
2017-06-05 22:07:28 -06:00
Felix Yan
0abb69b1cb Fix a typo: verison -> version 2017-06-06 12:01:41 +08:00
Cory Benfield
190a68550a This should be 3.0 2017-05-31 14:08:05 +01:00
Cory Benfield
548823be83 Clean up invalid indentation from merge 2017-05-31 10:41:47 +01:00
Cory Benfield
a6f7f5dc62 Clean up flake8 errors from merge 2017-05-31 10:39:27 +01:00
Cory Benfield
f308ea1a31 Clean up messy merge 2017-05-31 10:36:29 +01:00
Cory Benfield
c3f4a028bf Merge branch 'master' into proposed/3.0.0 2017-05-31 10:29:19 +01:00
Cory Benfield
56971482af Merge pull request #4114 from cyberjacob/master
Catch error if multiprocessing is not available or is unable to determine the number of CPUs
2017-05-30 13:54:31 +01:00
Jacob Mansfield
98bbf8d170 Catch error if multiprocessing is not available or is unable to determine the number of CPUs 2017-05-30 13:47:55 +01:00
Cory Benfield
d3a4d06238 Begin dev section of README 2017-05-30 11:49:05 +01:00
Kenneth Reitz
152faa2a0f change development philosophy docs to reflect reality 2017-05-29 23:46:50 -04:00
Kenneth Reitz
e41c2c2d96 remove note about cacerts.pem 2017-05-29 23:43:53 -04:00
Kenneth Reitz
3ca2722802 improve development instructions 2017-05-29 23:41:48 -04:00
Kenneth Reitz
310d7f8a9b update development documentation 2017-05-29 23:40:44 -04:00
Kenneth Reitz
01f982da50 remove "are you crazy?" section 2017-05-29 23:38:48 -04:00
Kenneth Reitz
ba509635a3 fix sidebars 2017-05-29 23:36:50 -04:00
Kenneth Reitz
008ab18a77 improve installation docs 2017-05-29 23:33:51 -04:00
Kenneth Reitz
67831abaaf improve install docs 2017-05-29 23:33:25 -04:00
Kenneth Reitz
0f57faba70 fix github stars 2017-05-29 23:26:50 -04:00
Kenneth Reitz
35f2e55308 improve installation instructions 2017-05-29 23:26:15 -04:00
Kenneth Reitz
77ae596e8b Merge branch 'master' of github.com:kennethreitz/requests 2017-05-29 23:25:31 -04:00
Kenneth Reitz
84114419a0 improve install documentation syntax 2017-05-29 23:25:21 -04:00
Kenneth Reitz
500fe3ee03 Merge pull request #4108 from requests/flake8-improvements
flake8 improvements
2017-05-29 23:08:03 -04:00
Kenneth Reitz
d78ec90e7b cleanup utils.py 2017-05-29 23:04:45 -04:00
Kenneth Reitz
04ed1e5122 cleanup sessions.py 2017-05-29 23:02:23 -04:00
Kenneth Reitz
33619268f9 cleanup models.py 2017-05-29 23:00:09 -04:00
Kenneth Reitz
e5894e99b7 improve flake8 help.py 2017-05-29 22:58:01 -04:00
Kenneth Reitz
6495190b00 cleanup help.py (flake8) 2017-05-29 22:55:08 -04:00
Kenneth Reitz
5b363d1a57 improve line continuations for flake8 2017-05-29 22:54:23 -04:00
Kenneth Reitz
f6a4ab02da fix empty lines for flake8 2017-05-29 22:44:33 -04:00
Kenneth Reitz
5fa4e74d61 fix flake8 indent error 2017-05-29 22:43:12 -04:00
Kenneth Reitz
7fc48b099e ignore undefined names in flake8 2017-05-29 22:41:51 -04:00
Kenneth Reitz
3fa02a1336 flake8 ignore lambda expressions 2017-05-29 22:41:07 -04:00
Kenneth Reitz
e55d3d0658 fix spaces in api.py 2017-05-29 22:40:37 -04:00
Kenneth Reitz
7c3861a322 fix flake8 for EOF in __init__.py 2017-05-29 22:40:09 -04:00
Kenneth Reitz
7c2618eb82 flake8: import statement improvements 2017-05-29 22:37:43 -04:00
Kenneth Reitz
4e1bbe1288 ignore line continuations for flake8 2017-05-29 22:35:33 -04:00
Kenneth Reitz
cfc3db06cf ignore import statements for flake8 2017-05-29 22:34:32 -04:00
Kenneth Reitz
5091c15bd7 v2.17.3 2017-05-29 17:54:15 -04:00
Kenneth Reitz
dcb8277975 v2.17.2 2017-05-29 17:52:03 -04:00
Kenneth Reitz
412e3a20a9 2.17.1 2017-05-29 17:52:03 -04:00
Kenneth Reitz
17084cd6d8 Merge pull request #4106 from silverlogic/master
Fix requests.packages not having package attributes
2017-05-29 17:31:49 -04:00
Ryan Pineo
1278ecdf71 Fix requests.packages not having package attributes
Fixes #4104
2017-05-29 17:27:10 -04:00
Kenneth Reitz
2d763c90ae v2.17.1 2017-05-29 15:59:00 -04:00
Kenneth Reitz
db076c098e Merge pull request #4103 from untitaker/issue4102
Preserve identity of unbundled deps
2017-05-29 15:45:39 -04:00
Markus Unterwaditzer
588e8f7f64 Preserve identity of unbundled deps
Fix #4102
2017-05-29 21:41:59 +02:00
Kenneth Reitz
593cd18202 Merge branch 'master' of github.com:kennethreitz/requests 2017-05-29 14:28:23 -04:00
Kenneth Reitz
2daf1cbad9 fix manifest file 2017-05-29 14:28:17 -04:00
Kenneth Reitz
71d8f289b0 Update index.rst 2017-05-29 13:28:54 -04:00
Kenneth Reitz
44c9b7e93b Update README.rst 2017-05-29 13:28:18 -04:00
Kenneth Reitz
916d171f2b Merge branch 'master' of github.com:kennethreitz/requests 2017-05-29 13:17:57 -04:00
Kenneth Reitz
3857431159 v2.17.0 2017-05-29 13:17:49 -04:00
Kenneth Reitz
4ad4670583 Merge pull request #4099 from requests/requests-namespace
Requests namespace
2017-05-29 13:15:20 -04:00
Kenneth Reitz
68076a7f81 Merge pull request #4100 from bcongdon/remove-redirect-cache
Remove session redirect cache
2017-05-29 13:14:42 -04:00
Benjamin Congdon
86f8cb8a35 Remove session redirect cache 2017-05-29 09:53:24 -07:00
Kenneth Reitz
7f14db17c8 new requests namespace 2017-05-29 12:11:43 -04:00
Kenneth Reitz
14c1102e0f new installation location 2017-05-29 12:08:28 -04:00
Kenneth Reitz
c77911ca0f Merge pull request #4094 from kennethreitz/flake8
Flake8
2017-05-29 01:53:26 -04:00
Kenneth Reitz
5a2e5af3a4 try junit 2017-05-29 01:51:06 -04:00
Kenneth Reitz
31fc69ec0d empty commit 2017-05-29 01:39:42 -04:00
Kenneth Reitz
88b435e7e8 only flake8 the right directory 2017-05-29 01:36:00 -04:00
Kenneth Reitz
60226cc25c empty commit 2017-05-29 01:32:26 -04:00
Kenneth Reitz
8ddba4662d no piping to a file 2017-05-28 22:06:04 -04:00
Kenneth Reitz
cd2beaf04a flake8report.txt 2017-05-28 21:54:13 -04:00
Kenneth Reitz
58501d1ec7 docutils 2017-05-28 21:43:29 -04:00
Kenneth Reitz
beccd21146 bunk change 2017-05-28 21:42:09 -04:00
Kenneth Reitz
0622b5c54e flake8 2017-05-28 21:38:27 -04:00
Kenneth Reitz
821774cf47 Update requirements.txt 2017-05-28 21:29:35 -04:00
Kenneth Reitz
78349914c8 Merge pull request #4086 from kennethreitz/travis2
try this travis file
2017-05-28 11:38:51 -07:00
Kenneth Reitz
2ccecf6dbd skip for now 2017-05-28 13:05:09 -04:00
Kenneth Reitz
8af1d9331f fix 2017-05-28 13:03:26 -04:00
Kenneth Reitz
ef34a85378 better ci file 2017-05-28 12:55:59 -04:00
Kenneth Reitz
55014bcc48 try this travis file 2017-05-28 12:51:27 -04:00
Kenneth Reitz
303eca03f3 no more pipenv 2017-05-28 12:37:34 -04:00
Kenneth Reitz
e02eb69fcc that doesn't work on windows 2017-05-28 12:33:56 -04:00
Kenneth Reitz
2936a77678 speed up appveyor 2017-05-28 12:30:58 -04:00
Kenneth Reitz
e6e3589c36 number of cores for travis 2017-05-28 12:14:27 -04:00
Kenneth Reitz
c3a00771e3 todo 2017-05-28 00:43:54 -04:00
Kenneth Reitz
16c8241a81 v2.16.5 2017-05-28 00:35:36 -04:00
Kenneth Reitz
1dfa631dae updates to travis ci 2017-05-27 23:54:53 -04:00
Kenneth Reitz
1fa168eea4 tox.ini 2017-05-27 23:52:44 -04:00
Kenneth Reitz
8b5fa606ec improvements to help utility 2017-05-27 23:51:22 -04:00
Kenneth Reitz
148ab30d70 Merge pull request #4083 from kennethreitz/faster-ci
faster ci (test)
2017-05-27 20:37:37 -07:00
Kenneth Reitz
8d0c2537ee better comments 2017-05-27 23:34:56 -04:00
Kenneth Reitz
458008653c detox 2017-05-27 23:33:13 -04:00
Kenneth Reitz
679d368e3e setup.py improvements 2017-05-27 23:27:38 -04:00
Kenneth Reitz
7384fe71d6 let's see what this breaks 2017-05-27 23:22:49 -04:00
Kenneth Reitz
94331723d1 try again 2017-05-27 23:19:59 -04:00
Kenneth Reitz
b565b79858 skip that 2017-05-27 23:14:06 -04:00
Kenneth Reitz
a00d14a61f pytest 2017-05-27 23:11:01 -04:00
Kenneth Reitz
dc70edf17d try this 2017-05-27 23:09:38 -04:00
Kenneth Reitz
68fda6bcf2 see if that fixes things 2017-05-27 23:07:24 -04:00
Kenneth Reitz
141ce93445 test 2017-05-27 23:04:12 -04:00
Kenneth Reitz
6abe1ff933 update 2017-05-27 23:02:45 -04:00
Kenneth Reitz
3d425ac191 try this 2017-05-27 23:00:30 -04:00
Kenneth Reitz
0ff28d7421 faster 2017-05-27 22:57:45 -04:00
Kenneth Reitz
2758b8b0f0 improve speed of travis-ci 2017-05-27 22:56:43 -04:00
Kenneth Reitz
6746b2540d faster 2017-05-27 22:55:22 -04:00
Kenneth Reitz
03944f4cd6 restore old behavior 2017-05-27 20:33:32 -04:00
Kenneth Reitz
4a28ffcfe9 try this 2017-05-27 20:28:30 -04:00
Kenneth Reitz
b6d51d9f2b change test configuration 2017-05-27 20:25:11 -04:00
Kenneth Reitz
2edb553036 so frustrating 2017-05-27 20:23:02 -04:00
Kenneth Reitz
cffa0effe0 remove pytest-mock 2017-05-27 20:21:29 -04:00
Kenneth Reitz
76a66c41d6 final attempt 2017-05-27 20:19:42 -04:00
Kenneth Reitz
19a1379ff5 >=2.7 2017-05-27 20:17:33 -04:00
Kenneth Reitz
057689bb8a grr 2017-05-27 20:16:32 -04:00
Kenneth Reitz
88f28325c9 pytest 2017-05-27 20:15:17 -04:00
Kenneth Reitz
62afdabb30 blah 2017-05-27 20:11:04 -04:00
Kenneth Reitz
2468058fea better requirements 2017-05-27 20:09:58 -04:00
Kenneth Reitz
e4d8571080 concurrency in coverage report 2017-05-27 20:08:08 -04:00
Kenneth Reitz
048997daa0 better requirements 2017-05-27 20:07:35 -04:00
Kenneth Reitz
c166c4f5d4 remove circle.yml 2017-05-27 20:01:08 -04:00
Kenneth Reitz
f4b488b7f8 try removing that 2017-05-27 19:59:19 -04:00
Kenneth Reitz
cd4c701186 xdist 2017-05-27 19:57:40 -04:00
Kenneth Reitz
8e93b100da lets give this a try 2017-05-27 19:44:54 -04:00
Kenneth Reitz
276024b170 fix tests 2017-05-27 19:33:40 -04:00
Kenneth Reitz
6af910610f omg 2017-05-27 19:31:31 -04:00
Kenneth Reitz
1d76cb3572 cleanup tests (make them faster) 2017-05-27 19:30:13 -04:00
Kenneth Reitz
03e5f5daf3 socks needed for tests 2017-05-27 19:16:16 -04:00
Kenneth Reitz
b9a004a22b improvements 2017-05-27 17:25:44 -04:00
Kenneth Reitz
2e67c86075 merge 2017-05-27 17:24:06 -04:00
Kenneth Reitz
f0fb5034fc better headings 2017-05-27 17:14:10 -04:00
Kenneth Reitz
b0c19226c6 simplify 2017-05-27 17:12:50 -04:00
Kenneth Reitz
4e06345988 issue template 2017-05-27 17:09:52 -04:00
Kenneth Reitz
892fbe09f6 issue template 2017-05-27 17:08:50 -04:00
Kenneth Reitz
46a4e731da issue template 2017-05-27 17:07:30 -04:00
Kenneth Reitz
46a930941a issue template 2017-05-27 17:06:37 -04:00
Kenneth Reitz
c6827de846 better template 2017-05-27 17:03:17 -04:00
Kenneth Reitz
5c65f6d01e issue template 2017-05-27 17:01:09 -04:00
Kenneth Reitz
7ce5ca853d Merge pull request #4079 from kennethreitz/help
$ python -m requests.help
2017-05-27 13:58:19 -07:00
Kenneth Reitz
0ae8524e3b v2.16.4 2017-05-27 16:57:50 -04:00
Kenneth Reitz
bf4a813304 rename information/info 2017-05-27 16:50:51 -04:00
Kenneth Reitz
6d082ea972 add info about chardet and urllib3 2017-05-27 16:49:48 -04:00
Kenneth Reitz
f180980b28 Merge branch 'master' into bug-report-info 2017-05-27 13:41:54 -07:00
Kenneth Reitz
a44284c045 cleanup version.py 2017-05-27 12:41:11 -04:00
Kenneth Reitz
c1f32f68f6 v2.16.3 2017-05-27 12:20:34 -04:00
Kenneth Reitz
4c155de9fd test entrypoints 2017-05-27 11:06:53 -04:00
Kenneth Reitz
03dd91093b pep8 fix 2017-05-27 10:57:46 -04:00
Ian Cordasco
8e97db2dc5 Merge pull request #4042 from nateprewitt/persist_cookie_policy
Persist Session-level CookiePolicy
2017-05-23 09:39:37 -05:00
Nate Prewitt
009b80c95a persist session-level CookiePolicy 2017-05-22 09:51:40 -07:00
Nate Prewitt
a889b62c50 Merge branch 'master' into proposed/3.0.0 2017-05-21 16:25:55 -07:00
Cory Benfield
084625cb3c Merge pull request #3984 from PCMan/pcman@fix_iter_lines
Fix the additional newline generated by iter_lines() caused by a '\r\n' pair being separated in two different chunks.
2017-04-26 16:07:46 +01:00
Hong Jen-Yee (PCMan)
458df8f4f4 Fix the additional newline generated by iter_lines() caused by a '\r\n' pair being separated in two different chunks. 2017-04-26 23:02:57 +08:00
Ian Cordasco
567e12bfb8 Add debugging submodule for bug reporters
The suggested usage in a bug report would be

    python -c 'from requests import _bug; _bug.print_information()'

This should generate most of the information we tend to ask for
repeatedly from bug reporters.
2017-03-23 07:22:55 -05:00
Cory Benfield
73456b0048 Merge pull request #3923 from vbarbaresi/3.0.0-iter_lines
Rebase #3745 and add some tests
2017-03-16 09:53:03 +00:00
Vincent Barbaresi
d491e9f9b2 use [-1] instead of endswith() to work with bytes or string
Also add a parametrize on decode_unicode for iter_lines() test
to check with bytestrings and str content
2017-03-15 22:29:18 +01:00
Vincent Barbaresi
052595ffbf add explanatory comment about skipping null chunks in iter_lines 2017-03-15 12:16:32 +01:00
Vincent Barbaresi
cc2ac23c0d remove useless brackets in iter_lines boolean condition 2017-03-15 01:37:55 +01:00
Vincent Barbaresi
5a8bc19384 add more tests for iter_lines()
check the case of an empty chunk somewhere in the stream
2017-03-15 01:37:55 +01:00
Vincent Barbaresi
0380ac5893 add some parametrized tests for iter_lines()
Write a list of different chunk splits and their expected results
to test against, using ianepperson's breakdown as specification:
https://github.com/kennethreitz/requests/pull/2431#issuecomment-72333964
2017-03-15 01:37:48 +01:00
Alex Chan
9881be25f3 Review markups for @Lukasa 2017-03-14 16:57:52 +01:00
Ian Epperson
9174925916 Fix bug when delimiter is split between responses 2017-03-14 16:57:52 +01:00
Ian Epperson
02031e3e14 Test to show bug when delimiter is split between reads 2017-03-14 16:57:52 +01:00
Cory Benfield
84dc6b66da Merge pull request #3898 from nateprewitt/duplicate_exception
remove redundant exception declaration
2017-03-02 14:41:17 +00:00
Cory Benfield
35fc2b387f Merge pull request #3897 from nateprewitt/prep_body
Refactor Prepare Body
2017-03-02 14:40:57 +00:00
Nate Prewitt
be2f92b9e3 updating HISTORY 2017-03-02 05:27:33 -07:00
Nate Prewitt
64b66b6409 test prepare_content_length sets expected headers 2017-03-02 05:27:22 -07:00
Gary Wu
21972c4601 fix #3902 Add proposed documentation change for post data 2017-03-01 13:04:11 -06:00
Nate Prewitt
5a65a0dab1 use InvalidHeader instead of ConflictingHeaderError 2017-03-01 07:32:14 -07:00
Nate Prewitt
3046a1eb54 remove redundant exception declaration 2017-02-28 16:08:48 -07:00
Nate Prewitt
a52fe6586c consolidate super_len code and cleanup docstrings 2017-02-28 14:51:08 -07:00
Casey Davidson
f239fe754d Change UnreachableCodeError to InvalidBodyError. 2017-02-28 14:49:52 -07:00
Casey Davidson
1003fdf0f2 Small fixes based on feedback in pull request. 2017-02-28 14:49:31 -07:00
Casey Davidson
033dfc165d Raise an error if body is not null, has not length and is not streamable. 2017-02-28 14:49:20 -07:00
Casey Davidson
bfb202527d Alternate fix for 3066 to refactor prepare_body to always call prepare_content_length.
This allows for the 'Content-Length' header to only be set in prepare_content_length.
2017-02-28 14:49:01 -07:00
Ian Cordasco
60339d17ee Merge pull request #3886 from jvanasco/proposed/3.0.0
proposed 3.0 - altered internal loops of resolve_redirect
2017-02-25 09:05:39 -06:00
jonathan vanasco
8e07dae0fd altered internal loops of how `SessionRedirectMixin.resolve_redirects and Session.send` handle redirect history. 3.0.0 branch 2017-02-22 13:23:21 -05:00
Kenneth Reitz
b22833ce2a Merge pull request #3874 from nateprewitt/merge_cleanup
variable name consistency
2017-02-21 14:04:24 -05:00
Nate Prewitt
b8a87e00d7 variable name consistency 2017-02-14 09:31:59 -07:00
Cory Benfield
56dd77fc52 Merge branch 'master' into proposed/3.0.0 2017-02-14 15:59:53 +00:00
Cory Benfield
250b3e5c8c Merge pull request #3868 from vmalloc/raise_for_status_return_self
Make Response.raise_for_status() return the response object if the response is successful
2017-02-12 09:58:08 +00:00
Rotem Yaari
efcbe93075 Make Response.raise_for_status() return the response object if the response is successful
This allows for chaining method calls in cases where we want to raise for bad codes but use the response otherwise,
e.g. requests.get(URL).raise_for_status().json()['value']
2017-02-12 11:11:40 +02:00
Cory Benfield
b26f4c7f58 Merge pull request #3869 from nateprewitt/auth_fix
revert removal of request param from resolve_redirects
2017-02-12 07:33:13 +00:00
Nate Prewitt
16bbc7478f revert removal of request param from resolve_redirects 2017-02-11 23:35:19 -07:00
Kenneth Reitz
85a29f1da0 Merge branch 'issue2899' of https://github.com/thoger/requests into proposed/3.0.0 2017-02-10 12:44:02 -05:00
Cory Benfield
241f2b2a6b Merge branch 'master' into proposed/3.0.0 2017-02-10 17:40:52 +00:00
Cory Benfield
195d628d31 Merge pull request #3773 from nateprewitt/2003_remove_HTTPProxyAuth
remove HTTPProxyAuth in favor of the proxies parameter
2016-12-17 09:23:32 -05:00
Nate Prewitt
7b76bd5866 remove HTTPProxyAuth in favor of the proxies parameter 2016-12-16 15:14:58 -07:00
Cory Benfield
0b4302bb2b Merge pull request #3757 from nateprewitt/2590_redirected_bodies
301/302 redirects for non-POST with body
2016-12-12 15:54:17 +00:00
Nate Prewitt
354ac7ecad squash 3xx tests into super tests 2016-12-12 08:36:22 -07:00
Nate Prewitt
3bb25a1e0b updating 3.0-HISTORY for #3757 2016-12-10 22:00:25 -07:00
Nate Prewitt
eff932627c adding return variable to rebuild_method for method change 2016-12-10 12:13:13 -07:00
Nate Prewitt
084fb05d52 fixing redirects for non-GET/HEAD/POST methods 2016-12-10 12:12:23 -07:00
Ian Cordasco
f554ec7987 Merge pull request #3762 from nateprewitt/update_30_hist
updating 3.0 history
2016-12-10 07:57:44 -06:00
Nate Prewitt
f1a707d191 updating 3.0 history 2016-12-09 12:55:58 -07:00
Cory Benfield
78fe2f738c Merge pull request #3761 from nateprewitt/3753_revert_int_exception
re-restrict params for _basic_auth_str in 3.0.0
2016-12-09 15:29:17 +00:00
Nate Prewitt
500dc75c8c remove allowance of non-string/bytes auth values 2016-12-09 08:26:13 -07:00
Cory Benfield
c7e5c0d2b7 Merge branch 'master' into proposed/3.0.0 2016-12-09 14:49:18 +00:00
Cory Benfield
4dae8de815 Merge branch 'master' into proposed/3.0.0 2016-12-09 08:45:34 +00:00
Cory Benfield
ba7c464a03 Merge branch 'master' into proposed/3.0.0 2016-12-08 09:16:51 +00:00
Cory Benfield
e237a60add Merge pull request #3563 from nateprewitt/enforce_content_length
use enforce_content_length in Requests
2016-11-16 13:29:51 +00:00
Nate Prewitt
84d99f01f5 add enforce_content_length=True default 2016-11-15 11:27:29 -07:00
Nate Prewitt
973a7b1cd0 test responses fail with incomplete body reads 2016-11-15 11:24:51 -07:00
Nate Prewitt
cfd898fb93 adding method to url tests 2016-11-15 11:19:30 -07:00
Cory Benfield
7f88aca8f7 Merge branch 'master' into proposed/3.0.0 2016-11-15 17:16:04 +00:00
Cory Benfield
e70ad25ca1 Merge pull request #3634 from jeremycline/verify-by-default
Keep ``verify`` setting when no CA_BUNDLE variable exists
2016-10-25 09:56:53 +01:00
Jeremy Cline
35bdfc78c9 Keep `verify` setting when no CA_BUNDLE variable exists
If the ``trust_env`` flag is set on a session and ``verify`` is ``True``
or ``None``, the environment is checked for ``CURL_CA_BUNDLE`` and
``REQUESTS_CA_BUNDLE``. Before this patch, if neither existed,
``verify`` would always be set to ``None`` rather than ``True`` even if
it was originally ``True``.

Signed-off-by: Jeremy Cline <jeremy@jcline.org>
2016-10-24 20:32:12 -04:00
Ian Cordasco
5a41febce2 Merge pull request #3109 from jeremycline/use-pool-keys-by-scheme
HTTPAdapter now updates its PoolManager connection_pool_kw
2016-09-29 15:30:31 -05:00
Cory Benfield
233a5da664 Merge pull request #3594 from nateprewitt/move_3576_to_Proposed300
#3576 for Proposed/3.0.0
2016-09-21 10:41:00 +01:00
Nate Prewitt
563d6572f3 reverting 3357 and comparing properly encoded strings 2016-09-20 10:21:14 -06:00
Cory Benfield
5f941b3583 Merge pull request #3574 from shellhead/stream-encoding-3.0.0
Streaming responses require encoding to be set
2016-09-16 11:36:03 +01:00
Michael Hunsinger
55e511dd10 Parametrized decode response tests 2016-09-15 21:25:23 -06:00
Michael Hunsinger
713f56ea53 Decode response requires encoding to be set 2016-09-14 20:58:36 -06:00
Jeremy Cline
3d2b337906 HTTPAdapter now updates its PoolManager connection_pool_kw
With the addition of https://github.com/shazow/urllib3/pull/830 requests
should update the connection_pool_kw on the PoolManager so that new
ConnectionPools get created when TLS/SSL settings change. This ensures
that users can update the CA certificates used to verify servers as well
as the client certificate and key it uses to authenticate with servers.

This fixes issue #2863
2016-09-06 11:11:59 -04:00
Ian Cordasco
b2f6173a38 Merge remote-tracking branch 'upstream/master' into proposed/3.0.0
Conflicts:
   requests/__init__.py
   requests/sessions.py
   tests/test_requests.py
2016-09-06 09:46:02 -05:00
Ian Cordasco
2b2a07e3b7 Merge pull request #3417 from nateprewitt/2939_multiple_location_headers
raise InvalidHeader on multiple Location values
2016-08-22 11:49:36 -05:00
Nate Prewitt
715830fe21 removing incorrect param from resolve_redirects call 2016-08-22 10:47:17 -06:00
Cory Benfield
adb577d5b8 Merge pull request #3442 from nateprewitt/resolve_redirects_param_fix
removing incorrect param from resolve_redirects call
2016-07-26 22:16:38 +02:00
Nate Prewitt
569601e233 removing incorrect param from resolve_redirects call 2016-07-26 11:35:56 -06:00
Nate Prewitt
c418c4c4aa moving implementation details into util func 2016-07-16 12:37:42 -06:00
Nate Prewitt
fd4332916f raise InvalidHeader on multiple Location values 2016-07-16 10:03:19 -06:00
Cory Benfield
deada71526 Merge pull request #3383 from davidsoncasey/proposed/3.0.0
Change exception and variable names so that tests will run.
2016-07-02 08:33:31 +01:00
Casey Davidson
ed07583f7e Change exception and variable names so that tests will run (currently one failing test). 2016-07-01 16:38:46 -07:00
Ian Cordasco
ecfb85f85e Merge remote-tracking branch 'upstream/master' into proposed/3.0.0
Conflicts:
      AUTHORS.rst
      requests/__init__.py
      requests/sessions.py
      tests/test_requests.py
2016-06-03 08:42:01 -05:00
Ian Cordasco
9fc765638f Merge pull request #2754 from Lukasa/python3-redirect-3.0.0
Fix unicode redirects on Python 3.
2016-04-15 16:39:18 -05:00
Ian Cordasco
41fc9ebff7 Merge pull request #2839 from Lukasa/issue/2836
Make sure we build environment settings properly.
2016-04-15 16:38:50 -05:00
Cory Benfield
e988e22d0f Fix test failures. 2016-04-07 08:46:39 +01:00
Cory Benfield
eab12fa029 Fixup Python 3 test failures. 2016-04-07 08:43:38 +01:00
Cory Benfield
8f33e56c0d Remove unneeded functools import. 2016-04-07 08:37:25 +01:00
Cory Benfield
8000def20c Refactor unquote_unreserved to be simpler. 2016-04-07 08:36:35 +01:00
Cory Benfield
c26e82ed87 Add test for Issue 2653. 2016-04-07 08:36:35 +01:00
Cory Benfield
e68dd5dca0 Get tests passing on Python 3. 2016-04-07 08:35:30 +01:00
Cory Benfield
a3532632af Unicode/bytes tests for unquote_unreserved 2016-04-07 08:35:30 +01:00
Cory Benfield
5530091b86 Enhance unquote_unreserved to handle all strings 2016-04-07 08:35:30 +01:00
Cory Benfield
d185a40aaf Split on bytestrings. 2016-04-07 08:35:30 +01:00
Cory Benfield
aedc0e515d Handle complex redirect URIs on Python 3 2016-04-07 08:35:30 +01:00
Cory Benfield
b644af0ec7 Make sure we build environment settings properly. 2016-04-07 08:34:12 +01:00
Ian Cordasco
0acbf2b91d Merge pull request #3056 from digitaldavenyc/native_json
Import native Python JSON only
2016-03-16 11:51:41 -05:00
Dave Padovano
09ecb718ae remove simplejson 2016-03-16 12:24:50 -04:00
Kenneth Reitz
e668a09490 changelog for #3021 2016-02-18 02:04:42 -05:00
Kenneth Reitz
7977cd9808 Merge pull request #3021 from geckon/proposed/3.0.0
Fix #3017: Whitespace characters surrounding a URL should be ignored
2016-02-18 02:01:41 -05:00
Tomáš Heger
7a2b20cb53 Fix #3017: Whitepace characters surrounding a URL should be ignored 2016-02-18 07:44:21 +01:00
Kenneth Reitz
4e880b5bbe Update 3.0-HISTORY.rst 2016-02-13 08:35:41 -05:00
Kenneth Reitz
a94b3e4513 update version and (c) 2016-02-03 03:57:41 -05:00
Kenneth Reitz
d9b1cac867 cleanup test_requests from merge 2016-02-03 03:56:08 -05:00
Kenneth Reitz
14b6f6fad8 Merge branch 'master' into proposed/3.0.0
# Conflicts:
#	test_requests.py
2016-02-03 03:54:44 -05:00
Kenneth Reitz
20ca527751 Merge branch 'master' into proposed/3.0.0 2016-02-02 02:35:16 -05:00
Kenneth Reitz
c270845a46 Update 3.0-HISTORY.rst 2016-02-02 02:31:45 -05:00
Kenneth Reitz
d6538d7034 requests.get(..., session=Session()) 2016-02-02 02:23:55 -05:00
Kenneth Reitz
0e51e48473 PreparedRequest.send() 2016-02-02 02:12:38 -05:00
Kenneth Reitz
789227f5a9 notes on Session.resolve_redirects's response arg 2016-02-02 02:09:38 -05:00
Kenneth Reitz
be83be2457 lots of docstrings 2016-02-02 01:42:48 -05:00
Kenneth Reitz
0d8f38a403 cleaned up Session.resolve_redirects() code 2016-02-02 00:56:53 -05:00
Kenneth Reitz
66eedec782 Session.resolve_redirects code cleanup 2016-02-02 00:48:09 -05:00
Kenneth Reitz
4dfe7a4885 Remove req argument from Session.resolve_redirects 2016-02-02 00:31:08 -05:00
Kenneth Reitz
0f930d99f2 resolve merge errors causing test failures 2016-02-02 00:10:06 -05:00
Kenneth Reitz
a0b7bdd187 Merge commit '1881851' into v3.0.0 2016-02-01 23:57:29 -05:00
Kenneth Reitz
ad4d87437a Merge commit 'ea1cbb3' into v3.0.0 2016-02-01 23:49:08 -05:00
Kenneth Reitz
2ce700f9a2 Merge commit 'aa8c8f3' into v3.0.0 2016-02-01 23:48:55 -05:00
Kenneth Reitz
d4eb2e9ce1 Merge commit '9c5ce48' into v3.0.0 2016-02-01 23:47:34 -05:00
Kenneth Reitz
4b60bd4d7b Merge commit 'f351c1b' into v3.0.0 2016-02-01 23:47:26 -05:00
Kenneth Reitz
c44fb99633 Merge commit '91eebb1' into v3.0.0 2016-02-01 23:47:05 -05:00
Kenneth Reitz
5da8a34277 Merge commit '42944007338bdaa570a3ab25aafc279ef150d169' into v3.0.0 2016-02-01 23:46:41 -05:00
Kenneth Reitz
6dff2bb71c Merge commit '8c4d4f1af3a501ae0beec5e270f3206cda5c4842' into v3.0.0 2016-02-01 23:46:26 -05:00
Kenneth Reitz
26cf9abfd6 Merge commit '4aa4f82b37aeb272637fafeff498014af1c11c6b' into v3.0.0 2016-02-01 23:39:17 -05:00
Kenneth Reitz
d1871fabe9 Merge commit 'ff8153d9c56bfa62782fb99743219d0b02b512bb' into v3.0.0 2016-02-01 23:28:55 -05:00
Kenneth Reitz
45f110b52d Merge commit '636b946af5eac8ba4cffa63a727523cd8c2c01ab' into v3.0.0 2016-02-01 23:28:26 -05:00
Cory Benfield
1881851e50 Merge pull request #2961 from whit537/schema-scheme
Rename {Missing,Invalid}Schema to *Scheme
2016-01-05 17:23:21 +00:00
Chad Whitacre
f8d2fb83a3 Rename {Missing,Invalid}Schema to *Scheme
Schemes are what they're called, not schemas.

Conflicts:
	requests/models.py
2016-01-05 12:20:20 -05:00
Tomas Hoger
0c14e84c82 Support SSL_CERT_FILE and SSL_CERT_DIR env vars
Python PEP 476 (Enabling certificate verification by default for stdlib http
clients) recommends the use of SSL_CERT_FILE and SSL_CERT_DIR environment
variables to point the OpenSSL library used by Python to use specific
non-default bundle of trusted CA certificates.

https://www.python.org/dev/peps/pep-0476/#trust-database

These variables could not have been used to point scripts using requests to a
different CA bundle.  A different variable, REQUESTS_CA_BUNDLE, is read by
requests.  CURL_CA_BUNDLE is also used for compatibility with cURL.

This commit makes requests also look at SSL_CERT_FILE and SSL_CERT_DIR.  They
are handled as equivalent to REQUESTS_CA_BUNDLE.  As REQUESTS_CA_BUNDLE can
point to either certificate file or certificate directory, SSL_CERT_* can also
point to a file or directory.  There's no attempt to ensure SSL_CERT_FILE can
only point to a file and SSL_CERT_DIR to a directory.  This is similar to how
CURL_CA_BUNDLE is handled - requests allows it to specify certificate
directory, while cURL only allows it to specify certificate file.

Fixes requests issue #2899:

https://github.com/kennethreitz/requests/issues/2899
2015-11-26 23:09:28 +01:00
Cory Benfield
ea1cbb3a1d Merge pull request #2617 from Lukasa/move_native_str_30
Move native string conversion to Request
2015-07-18 16:43:50 +01:00
Cory Benfield
aa8c8f3983 Merge pull request #2647 from neosab/patch-2
Update AUTHORS.rst
2015-06-22 08:08:48 +01:00
Sabari Kumar Murugesan
1024441aee Update AUTHORS.rst 2015-06-21 23:09:24 -07:00
Cory Benfield
9c5ce48ca5 Merge pull request #2646 from sigmavirus24/release-notes-2631
Add release notes for PR 2631
2015-06-21 16:15:17 +01:00
Ian Cordasco
f351c1b687 Add release notes for PR 2631 2015-06-21 09:49:46 -05:00
Ian Cordasco
91eebb1fb0 Merge pull request #2631 from neosab/handle_empty_chunk
Handle empty chunks
2015-06-21 09:22:55 -05:00
Sabari Kumar Murugesan
038b61477c Handle empty chunks
Empty chunk in request body could prematurely signal end of chunked
transmission. As a result, the terminating zero-size chunk sent by
'requests' can be interpretted as bad request by the recepient. We
ignore such empty chunks.
2015-06-10 14:24:09 -07:00
Ian Cordasco
4294400733 Qualify classname in tests 2015-06-02 20:13:22 -05:00
Ian Cordasco
8c4d4f1af3 Move handling of method to PreparedRequest 2015-06-02 14:02:46 -05:00
Ian Cordasco
4aa4f82b37 Make the tests pass the method to Request 2015-05-30 09:32:31 -05:00
Cory Benfield
ff8153d9c5 Better default for request method 2015-05-28 18:33:24 +01:00
Cory Benfield
50a65a7415 Convert method to native str in request 2015-05-28 18:08:28 +01:00
Cory Benfield
636b946af5 Merge pull request #2587 from sigmavirus24/deprecate-boolean-responses
Remove the __bool__ and __nonzero__ response methods
2015-05-04 14:05:36 +01:00
Ian Cordasco
52bc2e57c0 Remove the __bool__ and __nonzero__ response methods
Many people expect to be able to say:

    response = make_request(url)

    if response:
        body = response.content

Where the first part should test for whether or not response is None.
Instead, the __bool__ and __nonzero__ methods return response.ok, so if
the response is actually a 4xx or 5xx response, then the user would
expect to get the body of the response.

By removing these methods, we restore the functionality that most users
expect.

Closes #2002
2015-05-04 07:58:04 -05:00
71 changed files with 4156 additions and 2570 deletions

28
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,28 @@
Summary.
## Expected Result
What you expected.
## Actual Result
What happened instead.
## Reproduction Steps
```python
import requests
```
## System Information
$ python -m requests.help
```
<paste here>
```
This command is only available on Requests v2.16.4 and greater. Otherwise,
please provide some basic information about your system (Python version,
operating system, &c).

8
.gitignore vendored
View File

@@ -5,7 +5,7 @@ nosetests.xml
junit-report.xml
pylint.txt
toy.py
tox.ini
.cache/
cover/
build/
docs/_build
@@ -21,3 +21,9 @@ t.py
t2.py
dist
/.mypy_cache/
/.pytest_cache/
/.tox/

View File

@@ -1,22 +1,30 @@
sudo: false
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
# - "3.4"
# - "3.5"
- "3.6"
- "3.7-dev"
# - "3.7-dev"
# - "pypy" -- appears to hang
# - "pypy3"
matrix:
allow_failures:
- python: 3.7-dev
# command to install dependencies
install: "make"
# command to run tests
script:
- |
if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi
- make coverage
after_success:
- pipenv run codecov
- make test-readme
- make ci
cache: pip
jobs:
include:
- stage: test
script:
- make test-readme
- make ci
- stage: coverage
python: 3.6
script: codecov

81
3.0-HISTORY.rst Normal file
View File

@@ -0,0 +1,81 @@
3.0.0 (2017-xx-xx)
++++++++++++++++++
- Support for Python 2.6 has been dropped.
- The ``OrderedDict`` import no longer exists in compat.py because it is part
of ``collections`` in Python 2.7 and newer.
- Simplified logic for determining Content-Length and Transfer-Encoding.
Requests will now avoid setting both headers on the same request, and
raise an exception if this is done manually by a user.
- Remove the HTTPProxyAuth class in favor of supporting proxy auth via
the proxies parameter.
- Relax how Requests strips bodies from redirects. 3.0.0 only supports body
removal on 301/302 POST redirects and all 303 redirects.
- Remove support for non-string/bytes parameters in ``_basic_auth_str``.
- Prevent ``Session.merge_environment`` from erroneously setting the
``verify`` parameter to ``None`` instead of ``True``.
- Streaming responses with ``Response.iter_lines`` or ``Response.iter_content``
now requires an encoding to be set if one isn't provided by the server.
- Exception raised during read timeout for ``Response.iter_content`` and
``Response.iter_lines`` changed from ``ConnectionError`` to more
specific ``ReadTimeout``.
- Raise exception if multiple locations are returned during a redirect.
- Update ConnectionPool connections when TLS/SSL settings change.
- Remove simplejson import and only use standard json module.
- Strip surrounding whitespace from urls.
- MissingSchema and InvalidSchema renamed to MissingScheme and InvalidScheme
respectively.
- Change merge order for environment settings to avoid excluding Session-level
settings.
- Encode redirect URIs as latin-1 before performing redirects in Python 3 to
avoid mangling during the requoting process.
- Remove the ``__bool__`` and ``__nonzero__`` methods from a ``Response``
object.
This has been a planned feature for over a year. The behaviour is surprising
to most people and breaks most of the assumptions that people have about
Response objects. This resolves issue `#2002`_
- Skip over empty chunks in iterators. Empty chunks could prematurely signal
the end of a request body's transmission, skipping them allows all of the
data through. See `#2631`_ for more details.
- Rename the ``req`` argument from ``Session.resolve_redirects`` method
to ``request``.
- Rename the ``resp`` argument from ``Session.resolve_redirects`` to
``response``.
- New ``PreparedRequest.send`` method. Now, you can
``Request().prepare().send()``.
- All porcelain API functions (e.g. ``requests.get``, etc) now accept an
optional ``session`` parameter. If provided, the session given will be used
for the request, in place of one being created for you.
- URLs are now automatically stripped of leading/trailing whitespace.
- ``Response.raise_for_status()`` now returns the response object for good responses
- Use ``HTTPHeaderDict`` for response headers, allowing easier access to
individual values when multiple response headers are sent using the same
header name.
.. _#2002: https://github.com/kennethreitz/requests/issues/2002
.. _#2631: https://github.com/kennethreitz/requests/issues/2631

22
AUTHORS.rst Executable file → Normal file
View File

@@ -10,13 +10,6 @@ Keepers of the Four Crystals
- Nate Prewitt <nate.prewitt@gmail.com> `@nateprewitt <https://github.com/nateprewitt>`_
Urllib3
```````
- Andrey Petrov <andrey.petrov@shazow.net>
Patches and Suggestions
```````````````````````
@@ -128,7 +121,7 @@ Patches and Suggestions
- Bryce Boe <bbzbryce@gmail.com> (`@bboe <https://github.com/bboe>`_)
- Colin Dunklau <colin.dunklau@gmail.com> (`@cdunklau <https://github.com/cdunklau>`_)
- Bob Carroll <bob.carroll@alum.rit.edu> (`@rcarz <https://github.com/rcarz>`_)
- Hugo Osvaldo Barrera <hugo@osvaldobarrera.com.ar> (`@hobarrera <https://github.com/hobarrera>`_)
- Hugo Osvaldo Barrera <hugo@barrera.io> (`@hobarrera <https://github.com/hobarrera>`_)
- Łukasz Langa <lukasz@langa.pl>
- Dave Shawley <daveshawley@gmail.com>
- James Clarke (`@jam <https://github.com/jam>`_)
@@ -160,16 +153,19 @@ Patches and Suggestions
- Muhammad Yasoob Ullah Khalid <yasoob.khld@gmail.com> (`@yasoob <https://github.com/yasoob>`_)
- Paul van der Linden (`@pvanderlinden <https://github.com/pvanderlinden>`_)
- Colin Dickson (`@colindickson <https://github.com/colindickson>`_)
- Sabari Kumar Murugesan (`@neosab <https://github.com/neosab>`_)
- Smiley Barry (`@smiley <https://github.com/smiley>`_)
- Shagun Sodhani (`@shagunsodhani <https://github.com/shagunsodhani>`_)
- Robin Linderborg (`@vienno <https://github.com/vienno>`_)
- Brian Samek (`@bsamek <https://github.com/bsamek>`_)
- Dmitry Dygalo (`@Stranger6667 <https://github.com/Stranger6667>`_)
- Tomáš Heger (`@geckon <https://github.com/geckon>`_)
- piotrjurkiewicz
- Jesse Shapiro <jesse@jesseshapiro.net> (`@haikuginger <https://github.com/haikuginger>`_)
- Nate Prewitt <nate.prewitt@gmail.com> (`@nateprewitt <https://github.com/nateprewitt>`_)
- Maik Himstedt
- Michael Hunsinger
- Jeremy Cline <jcline@redhat.com> (`@jeremycline <https://github.com/jeremycline>`_)
- Brian Bamsch <bbamsch32@gmail.com> (`@bbamsch <https://github.com/bbamsch>`_)
- Om Prakash Kumar <omprakash070@gmail.com> (`@iamprakashom <https://github.com/iamprakashom>`_)
- Philipp Konrad <gardiac2002@gmail.com> (`@gardiac2002 <https://github.com/gardiac2002>`_)
@@ -181,3 +177,13 @@ Patches and Suggestions
- Jonathan Vanasco (`@jvanasco <https://github.com/jvanasco>`_)
- David Fontenot (`@davidfontenot <https://github.com/davidfontenot>`_)
- Shmuel Amar (`@shmuelamar <https://github.com/shmuelamar>`_)
- Gary Wu (`@garywu <https://github.com/garywu>`_)
- Ryan Pineo (`@ryanpineo <https://github.com/ryanpineo>`_)
<<<<<<< HEAD
- Ed Morley (`@edmorley <https://github.com/edmorley>`_)
- Matt Liu <liumatt@gmail.com> (`@mlcrazy <https://github.com/mlcrazy>`_)
- Taylor Hoff <primdevs@protonmail.com> (`@PrimordialHelios <https://github.com/PrimordialHelios>`_)
- Hugo van Kemenade (`@hugovk <https://github.com/hugovk>`_)
=======
- Allan Crooks (`@the-allanc <https://github.com/the-allanc>`_)
>>>>>>> 4f0b496d2b3c91d72a951bebdd6be9cec33c17d6

View File

@@ -3,6 +3,124 @@
Release History
---------------
dev
+++
**Improvements**
- Warn user about possible slowdown when using cryptography version < 1.3.4
- Use rfc3986 for URL parsing.
- Use __slots__ for oftencalled classes.
**Bugfixes**
- Parsing empty ``Link`` headers with ``parse_header_links()`` no longer return one bogus entry
2.18.4 (2017-08-15)
+++++++++++++++++++
**Improvements**
- Error messages for invalid headers now include the header name for easier debugging
**Dependencies**
- We now support idna v2.6.
2.18.3 (2017-08-02)
+++++++++++++++++++
**Improvements**
- Running ``$ python -m requests.help`` now includes the installed version of idna.
**Bugfixes**
- Fixed issue where Requests would raise ``ConnectionError`` instead of
``SSLError`` when encountering SSL problems when using urllib3 v1.22.
2.18.2 (2017-07-25)
+++++++++++++++++++
**Bugfixes**
- ``requests.help`` no longer fails on Python 2.6 due to the absence of
``ssl.OPENSSL_VERSION_NUMBER``.
**Dependencies**
- We now support urllib3 v1.22.
2.18.1 (2017-06-14)
+++++++++++++++++++
**Bugfixes**
- Fix an error in the packaging whereby the ``*.whl`` contained incorrect data
that regressed the fix in v2.17.3.
2.18.0 (2017-06-14)
+++++++++++++++++++
**Improvements**
- ``Response`` is now a context manager, so can be used directly in a ``with`` statement
without first having to be wrapped by ``contextlib.closing()``.
**Bugfixes**
- Resolve installation failure if multiprocessing is not available
- Resolve tests crash if multiprocessing is not able to determine the number of CPU cores
- Resolve error swallowing in utils set_environ generator
2.17.3 (2017-05-29)
+++++++++++++++++++
**Improvements**
- Improved ``packages`` namespace identity support, for monkeypatching libraries.
2.17.2 (2017-05-29)
+++++++++++++++++++
**Improvements**
- Improved ``packages`` namespace identity support, for monkeypatching libraries.
2.17.1 (2017-05-29)
+++++++++++++++++++
**Improvements**
- Improved ``packages`` namespace identity support, for monkeypatching libraries.
2.17.0 (2017-05-29)
+++++++++++++++++++
**Improvements**
- Removal of the 301 redirect cache. This improves thread-safety.
2.16.5 (2017-05-28)
+++++++++++++++++++
- Improvements to ``$ python -m requests.help``.
2.16.4 (2017-05-27)
+++++++++++++++++++
- Introduction of the ``$ python -m requests.help`` command, for debugging with maintainers!
2.16.3 (2017-05-27)
+++++++++++++++++++
- Further restored the ``requests.packages`` namespace for compatibility reasons.
2.16.2 (2017-05-27)
+++++++++++++++++++
@@ -118,6 +236,11 @@ Or, even better::
- Updated bundled idna to v2.5.
- Updated bundled certifi to 2017.4.17.
- Altered how ``SessionRedirectMixin.resolve_redirects`` and ``Session.send``
process redirect history. Developers who subclass ``resolve_redirects`` will
find a different ``.history`` attribute - the first element now contains the
original response, and the last element now contains the active response.
2.13.0 (2017-01-24)
+++++++++++++++++++
@@ -577,7 +700,7 @@ This is the first release that follows our new release process. For more, see
- Unicode URL improvements for Python 2.
- Re-order JSON param for backwards compat.
- Automatically defrag authentication schemes from host/pass URIs. (`#2249 <https://github.com/kennethreitz/requests/issues/2249>`_)
- Automatically defrag authentication schemes from host/pass URIs. (`#2249 <https://github.com/requests/requests/issues/2249>`_)
2.4.2 (2014-10-05)
@@ -585,19 +708,19 @@ This is the first release that follows our new release process. For more, see
**Improvements**
- FINALLY! Add json parameter for uploads! (`#2258 <https://github.com/kennethreitz/requests/pull/2258>`_)
- Support for bytestring URLs on Python 3.x (`#2238 <https://github.com/kennethreitz/requests/pull/2238>`_)
- FINALLY! Add json parameter for uploads! (`#2258 <https://github.com/requests/requests/pull/2258>`_)
- Support for bytestring URLs on Python 3.x (`#2238 <https://github.com/requests/requests/pull/2238>`_)
**Bugfixes**
- Avoid getting stuck in a loop (`#2244 <https://github.com/kennethreitz/requests/pull/2244>`_)
- Multiple calls to iter* fail with unhelpful error. (`#2240 <https://github.com/kennethreitz/requests/issues/2240>`_, `#2241 <https://github.com/kennethreitz/requests/issues/2241>`_)
- Avoid getting stuck in a loop (`#2244 <https://github.com/requests/requests/pull/2244>`_)
- Multiple calls to iter* fail with unhelpful error. (`#2240 <https://github.com/requests/requests/issues/2240>`_, `#2241 <https://github.com/requests/requests/issues/2241>`_)
**Documentation**
- Correct redirection introduction (`#2245 <https://github.com/kennethreitz/requests/pull/2245/>`_)
- Added example of how to send multiple files in one request. (`#2227 <https://github.com/kennethreitz/requests/pull/2227/>`_)
- Clarify how to pass a custom set of CAs (`#2248 <https://github.com/kennethreitz/requests/pull/2248/>`_)
- Correct redirection introduction (`#2245 <https://github.com/requests/requests/pull/2245/>`_)
- Added example of how to send multiple files in one request. (`#2227 <https://github.com/requests/requests/pull/2227/>`_)
- Clarify how to pass a custom set of CAs (`#2248 <https://github.com/requests/requests/pull/2248/>`_)
@@ -1420,4 +1543,3 @@ This is not a backwards compatible change.
* Frustration
* Conception

View File

@@ -1,2 +1,2 @@
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini requests/cacert.pem
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini requirements.txt
recursive-include tests *.py

View File

@@ -1,24 +1,24 @@
.PHONY: docs
init:
pip install pipenv
pipenv install --dev
pipenv run pip install -e .[socks]
pip install pipenv --upgrade
pipenv install --dev --skip-lock
test:
# This runs all of the tests. To run an individual test, run py.test with
# the -k flag, like "py.test -k test_path_is_not_double_encoded"
pipenv run py.test tests
# This runs all of the tests, on both Python 2 and Python 3.
python setup.py test
mypy:
python setup.py mypy
ci:
pipenv run py.test -n 8 --boxed --junitxml=report.xml
test-readme:
pipenv run python setup.py check -r -s
@pipenv run python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
flake8:
pipenv run flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
coverage:
pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
certs:
curl http://ci.kennethreitz.org/job/ca-bundle/lastSuccessfulBuild/artifact/cacerts.pem -o requests/cacert.pem
publish:
pip install 'twine>=1.5.0'
python setup.py sdist bdist_wheel
@@ -27,4 +27,4 @@ publish:
docs:
cd docs && make html
@echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m"
@echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m"

26
Pipfile
View File

@@ -1,14 +1,32 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
[dev-packages]
pytest = ">=2.8.0"
codecov = "*"
pytest-httpbin = "==0.0.7"
pytest-httpbin = "*"
pytest-mock = "*"
pytest-cov = "*"
pytest-xdist = "*"
alabaster = "*"
readme_renderer = "*"
Sphinx = "<=1.5.5"
PySocks = "*"
readme-renderer = "*"
sphinx = "<=1.5.5"
pysocks = "*"
docutils = "*"
"flake8" = "*"
tox = "*"
detox = "*"
httpbin = "==0.5.0"
pytest-mypy = {git = "https://github.com/petr-muller/pytest-mypy", editable = true, ref = "flush-errors"}
white = {version="*"}
"e1839a8" = {path = ".", editable = true, extras=["socks"]}
mypy = "*"
"rfc3986" = "*"
[packages]

116
Pipfile.lock generated
View File

@@ -1,116 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "a26ef57f42fa3da7d70d1c1decb4e6445a6ff66e47c9094ddc9ab8e1f4c0a5b6"
},
"requires": {},
"sources": [
{
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {},
"develop": {
"alabaster": {
"version": "==0.7.10"
},
"appdirs": {
"version": "==1.4.3"
},
"babel": {
"version": "==2.4.0"
},
"click": {
"version": "==6.7"
},
"codecov": {
"version": "==2.0.9"
},
"coverage": {
"version": "==4.4.1"
},
"decorator": {
"version": "==4.0.11"
},
"docutils": {
"version": "==0.13.1"
},
"flask": {
"version": "==0.12.2"
},
"funcsigs": {
"version": "==1.0.2"
},
"httpbin": {
"version": "==0.5.0"
},
"imagesize": {
"version": "==0.7.1"
},
"itsdangerous": {
"version": "==0.24"
},
"jinja2": {
"version": "==2.9.6"
},
"markupsafe": {
"version": "==1.0"
},
"mock": {
"version": "==2.0.0"
},
"packaging": {
"version": "==16.8"
},
"pbr": {
"version": "==3.0.0"
},
"py": {
"version": "==1.4.33"
},
"pygments": {
"version": "==2.2.0"
},
"pyparsing": {
"version": "==2.2.0"
},
"pysocks": {
"version": "==1.6.7"
},
"pytest": {
"version": "==3.0.7"
},
"pytest-cov": {
"version": "==2.5.1"
},
"pytest-httpbin": {
"version": "==0.0.7"
},
"pytest-mock": {
"version": "==1.6.0"
},
"pytz": {
"version": "==2017.2"
},
"requests": {
"version": "==2.14.2"
},
"setuptools": {
"version": "==35.0.2"
},
"six": {
"version": "==1.10.0"
},
"snowballstemmer": {
"version": "==1.2.1"
},
"sphinx": {
"version": "==1.5.5"
},
"werkzeug": {
"version": "==0.12.2"
}
}
}

View File

@@ -10,27 +10,20 @@ Requests: HTTP for Humans
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
:target: https://pypi.python.org/pypi/requests
.. image:: https://travis-ci.org/kennethreitz/requests.svg?branch=master
:target: https://travis-ci.org/kennethreitz/requests
.. image:: https://codecov.io/github/kennethreitz/requests/coverage.svg?branch=master
:target: https://codecov.io/github/kennethreitz/requests
.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master
:target: https://codecov.io/github/requests/requests
:alt: codecov.io
.. image:: https://img.shields.io/github/contributors/kennethreitz/requests.svg
:target: https://github.com/kennethreitz/requests/graphs/contributors
.. image:: https://img.shields.io/github/contributors/requests/requests.svg
:target: https://github.com/requests/requests/graphs/contributors
.. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz
Requests is the only *Non-GMO* HTTP library for Python, safe for human
consumption.
**Warning:** Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
including: security vulnerabilities, verbose code, reinventing the wheel,
constantly reading documentation, depression, headaches, or even death.
.. image:: https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg
Behold, the power of Requests:
@@ -50,7 +43,7 @@ Behold, the power of Requests:
See `the similar code, sans Requests <https://gist.github.com/973705>`_.
.. image:: https://raw.githubusercontent.com/kennethreitz/requests/master/docs/_static/requests-logo-small.png
.. image:: https://raw.githubusercontent.com/requests/requests/master/docs/_static/requests-logo-small.png
:target: http://docs.python-requests.org/
@@ -84,19 +77,19 @@ Requests is ready for today's web.
- ``.netrc`` Support
- Chunked Requests
Requests officially supports Python 2.62.7 & 3.33.7, and runs great on PyPy.
Requests officially supports Python 2.7 & 3.43.7, and runs great on PyPy.
Installation
------------
To install Requests, simply:
To install Requests, simply use `pipenv <http://pipenv.org/>`_ (or pip, of course):
.. code-block:: bash
$ pip install requests
$ pipenv install requests
✨🍰✨
Satisfaction, guaranteed.
Satisfaction guaranteed.
Documentation
-------------
@@ -112,6 +105,6 @@ How to Contribute
#. 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
.. _`the repository`: http://github.com/requests/requests
.. _AUTHORS: https://github.com/requests/requests/blob/master/AUTHORS.rst
.. _Contributor Friendly: https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open

View File

@@ -5,26 +5,6 @@ build: off
environment:
matrix:
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x"
PYTHON_ARCH: "64"
TOXENV: "py27"
- PYTHON: "C:\\Python33-x64"
PYTHON_VERSION: "3.3.x"
PYTHON_ARCH: "64"
TOXENV: "py33"
- PYTHON: "C:\\Python34-x64"
PYTHON_VERSION: "3.4.x"
PYTHON_ARCH: "64"
TOXENV: "py34"
- PYTHON: "C:\\Python35-x64"
PYTHON_VERSION: "3.5.x"
PYTHON_ARCH: "64"
TOXENV: "py35"
- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
PYTHON_ARCH: "64"

39
docs/_static/custom.css vendored Normal file
View File

@@ -0,0 +1,39 @@
#carbonads {
display: block;
overflow: hidden;
padding: 1em;
background-color: #eeeeee;
text-align: center;
border: solid 1px #cccccc;
margin: 1.5em 0 2em;
border-radius: 2px;
line-height: 1.5;
}
#carbonads a {
border-bottom: 0;
}
#carbonads span {
display: block;
overflow: hidden;
}
.carbon-img {
display: block;
margin: 0 auto 1em;
text-align: center;
}
.carbon-text {
display: block;
margin-bottom: 1em;
}
.carbon-poweredby {
display: block;
text-transform: uppercase;
letter-spacing: 1px;
line-height: 1;
font-size: 10px;
}

View File

@@ -1,4 +1,34 @@
<!-- Alabaster (krTheme++) Hacks -->
<aside id="python27">
Python 3, the new best practice, is here to stay.
Python 2 will retire in only <time></time> months!
</aside>
<!-- Python 2 Death Clock™ -->
<style type="text/css">
body { margin-top: 4em; }
#python27 {
position: fixed;
top: 0;
left: 0; right: 0;
height: auto;
text-align: center;
color: white;
background-color: black;
font-size: larger;
line-height: 3;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script>
var death = new Date('2020-04-12');
var diff = moment.duration(death - moment());
document.querySelector('#python27 time').innerText = (diff.years() * 12) + diff.months();
</script>
<!-- CSS Adjustments (I'm very picky.) -->
<style type="text/css">
@@ -26,6 +56,7 @@
</style>
<!-- Analytics tracking for Kenneth. -->
<script type="text/javascript">
var _gauges = _gauges || [];
@@ -42,6 +73,7 @@
})();
</script>
<!-- There are no more hacks. -->
<!-- இڿڰۣ-ڰۣ— -->
<!-- Love, Kenneth Reitz -->

View File

@@ -5,7 +5,7 @@
</p>
<p>
<iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
<iframe src="http://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
@@ -14,17 +14,9 @@
human beings.
</p>
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg" id="_carbonads_js"></script>
<h3>The Hitchhiker's Guide to Python</h3>
<p>This guide is now available in tangible book form!</p>
<a href="https://www.amazon.com/Hitchhikers-Guide-Python-Practices-Development/dp/1491933178/ref=as_li_ss_il?ie=UTF8&linkCode=li2&tag=bookforkind-20&linkId=804806ebdacaf3b56567347f3afbdbca" target="_blank"><img border="0" src="https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=1491933178&Format=_SL160_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=bookforkind-20" ></a><img src="//ir-na.amazon-adsystem.com/e/ir?t=bookforkind-20&l=li2&o=1&a=1491933178" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
<p>All proceeds are being directly donated to the <a href="https://djangogirls.org">DjangoGirls</a> organization.</p>
<h3><a href="http://www.unixstickers.com/stickers/coding_stickers/requests-shaped-sticker">Stickers!</a></h3>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
@@ -34,8 +26,6 @@
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow @kennethreitz</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
<p><a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>
<p><a href="http://tinyletter.com/kennethreitz">Join Mailing List</a>.</p>
<h3>Other Projects</h3>
<p>More <a href="http://kennethreitz.org/">Kenneth Reitz</a> projects:</p>
@@ -58,9 +48,9 @@
<p></p>
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
<li><a href="http://github.com/requests/requests">Requests @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
<li><a href="http://github.com/requests/requests/issues">Issue Tracker</a></li>
<li><a href="http://docs.python-requests.org/en/latest/community/updates/#software-updates">Release History</a></li>
</ul>

View File

@@ -4,7 +4,7 @@
</a>
</p>
<p>
<iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
<iframe src="http://ghbtns.com/github-btn.html?user=requests&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
@@ -13,7 +13,7 @@
human beings. You are currently looking at the documentation of the
development release.
</p>
<h3><a href="http://www.unixstickers.com/stickers/coding_stickers/requests-shaped-sticker">Stickers!</a></h3>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
@@ -21,14 +21,7 @@
<hr/>
<h3>The Hitchhiker's Guide to Python</h3>
<p>This guide is now available in tangible book form!</p>
<a href="https://www.amazon.com/Hitchhikers-Guide-Python-Practices-Development/dp/1491933178/ref=as_li_ss_il?ie=UTF8&linkCode=li2&tag=bookforkind-20&linkId=804806ebdacaf3b56567347f3afbdbca" target="_blank"><img border="0" src="https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=1491933178&Format=_SL160_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=bookforkind-20" ></a><img src="//ir-na.amazon-adsystem.com/e/ir?t=bookforkind-20&l=li2&o=1&a=1491933178" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
<p>All proceeds are being directly donated to the <a href="https://djangogirls.org">DjangoGirls</a> organization.</p>
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg" id="_carbonads_js"></script>
<p>If you enjoy using this project, <a href="https://saythanks.io/to/kennethreitz">Say Thanks!</a></p>

View File

@@ -1,86 +1,76 @@
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
from pygments.token import Keyword, Name, Comment, String, Error, Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000",
# class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}

View File

@@ -74,7 +74,6 @@ Authentication
.. autoclass:: requests.auth.AuthBase
.. autoclass:: requests.auth.HTTPBasicAuth
.. autoclass:: requests.auth.HTTPProxyAuth
.. autoclass:: requests.auth.HTTPDigestAuth
@@ -252,7 +251,7 @@ API Changes
}
# In requests 1.x, this was legal, in requests 2.x,
# this raises requests.exceptions.MissingSchema
# this raises requests.exceptions.MissingScheme
requests.get("http://example.org", proxies=proxies)

View File

@@ -3,6 +3,8 @@
Frequently Asked Questions
==========================
.. image:: https://farm5.staticflickr.com/4290/35294660055_42c02b2316_k_d.jpg
This part of the documentation answers common questions about Requests.
Encoded Data?
@@ -54,9 +56,7 @@ Python 3 Support?
Yes! Here's a list of Python platforms that are officially
supported:
* Python 2.6
* Python 2.7
* Python 3.3
* Python 3.4
* Python 3.5
* Python 3.6
@@ -69,7 +69,7 @@ These errors occur when :ref:`SSL certificate verification <verification>`
fails to match the certificate the server responds with to the hostname
Requests thinks it's contacting. If you're certain the server's SSL setup is
correct (for example, because you can visit the site with your browser) and
you're using Python 2.6 or 2.7, a possible explanation is that you need
you're using Python 2.7, a possible explanation is that you need
Server-Name-Indication.
`Server-Name-Indication`_, or SNI, is an official extension to SSL where the

View File

@@ -1,6 +1,8 @@
Integrations
============
.. image:: https://farm5.staticflickr.com/4239/34450900674_15863ddea0_k_d.jpg
Python for iOS
--------------

View File

@@ -3,6 +3,8 @@
Recommended Packages and Extensions
===================================
.. image:: https://farm5.staticflickr.com/4218/35224319272_cfc0e621fb_k_d.jpg
Requests has a great variety of powerful and useful third-party extensions.
This page provides an overview of some of the best of them.
@@ -34,6 +36,14 @@ requested by users within the community.
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/en/latest/index.html
Requests-Threads
----------------
`Requests-Threads` is a Requests session that returns the amazing Twisted's awaitable Deferreds instead of Response objects. This allows the use of ``async``/``await`` keyword usage on Python 3, or Twisted's style of programming, if desired.
.. _Requests-Threads: https://github.com/requests/requests-threads
Requests-OAuthlib
-----------------

View File

@@ -1,6 +1,8 @@
Release Process and Rules
=========================
.. image:: https://farm5.staticflickr.com/4215/34450901614_b74ae720db_k_d.jpg
.. versionadded:: v2.6.2
Starting with the version to be released after ``v2.6.2``, the following rules

View File

@@ -3,6 +3,8 @@
Support
=======
.. image:: https://farm5.staticflickr.com/4198/34080352913_5c13ffb336_k_d.jpg
If you have questions or issues about Requests, there are several options:
StackOverflow
@@ -26,7 +28,7 @@ File an Issue
If you notice some unexpected behaviour in Requests, or want to see support
for a new feature,
`file an issue on GitHub <https://github.com/kennethreitz/requests/issues>`_.
`file an issue on GitHub <https://github.com/requests/requests/issues>`_.
E-mail

View File

@@ -4,6 +4,8 @@
Community Updates
=================
.. image:: https://farm5.staticflickr.com/4244/34080354873_516c283ad0_k_d.jpg
If you'd like to stay up to date on the community and development of Requests,
there are several options:
@@ -12,7 +14,7 @@ GitHub
------
The best way to track the development of Requests is through
`the GitHub repo <https://github.com/kennethreitz/requests>`_.
`the GitHub repo <https://github.com/requests/requests>`_.
Twitter
-------

View File

@@ -1,6 +1,8 @@
Vulnerability Disclosure
========================
.. image:: https://farm5.staticflickr.com/4211/34709353644_b041e9e1c2_k_d.jpg
If you think you have found a potential security vulnerability in requests,
please email `sigmavirus24 <mailto:graffatcolmingov@gmail.com>`_ and
`Lukasa <mailto:cory@lukasa.co.uk>`_ directly. **Do not file a public issue.**

View File

@@ -126,10 +126,11 @@ html_theme = 'alabaster'
# documentation.
html_theme_options = {
'show_powered_by': False,
'github_user': 'kennethreitz',
'github_user': 'requests',
'github_repo': 'requests',
'github_banner': True,
'show_related': False
'show_related': False,
'note_bg': '#FFF59C'
}
# Add any paths that contain custom themes here, relative to this directory.

View File

@@ -1,5 +1,6 @@
Authors
=======
.. image:: https://static1.squarespace.com/static/533ad9bde4b098d084a846b1/t/534f6e1ce4b09b70f38ee6c1/1432265542589/DSCF3147.jpg?format=2500w
.. include:: ../../AUTHORS.rst

View File

@@ -3,6 +3,8 @@
Contributor's Guide
===================
.. image:: https://farm5.staticflickr.com/4237/35550408335_7671fde302_k_d.jpg
If you're reading this, you're probably interested in contributing to Requests.
Thank you very much! Open source projects live-and-die based on the support
they receive from others, and the fact that you're even considering
@@ -187,7 +189,7 @@ through the `GitHub issues`_, **both open and closed**, to confirm that the bug
hasn't been reported before. Duplicate bug reports are a huge drain on the time
of other contributors, and should be avoided as much as possible.
.. _GitHub issues: https://github.com/kennethreitz/requests/issues
.. _GitHub issues: https://github.com/requests/requests/issues
Feature Requests
@@ -203,4 +205,4 @@ while keeping an open ear and mind.
If you believe there is a feature missing, feel free to raise a feature
request, but please do be aware that the overwhelming likelihood is that your
feature request will not be accepted.
feature request will not be accepted.

View File

@@ -1,15 +1,17 @@
Development Philosophy
======================
.. image:: https://farm5.staticflickr.com/4231/34484831073_636008a23d_k_d.jpg
Requests is an open but opinionated library, created by an open but opinionated developer.
Management Style
~~~~~~~~~~~~~~~~
`Kenneth Reitz <http://kennethreitz.org>`_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests. Only Kenneth may merge code into Requests.
`Kenneth Reitz <http://kennethreitz.org>`_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library, as well as its presentation. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests.
`Ian Cordasco <http://www.coglib.com/~icordasc/>`_ and `Cory Benfield <https://lukasa.co.uk/about/>`_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. They do not have the authority to change code or merge code changes, though they may change documentation. Their word is not final.
`Ian Cordasco <http://www.coglib.com/~icordasc/>`_ and `Cory Benfield <https://lukasa.co.uk/about/>`_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. Their word is, in some ways, more final than Kenneth's.
Values
~~~~~~
@@ -31,6 +33,10 @@ Standard Library?
Requests has no *active* plans to be included in the standard library. This decision has been discussed at length with Guido as well as numerous core developers.
.. raw:: html
<script async class="speakerdeck-embed" data-id="68f22f0841734d848315c618111b13ea" data-ratio="1.33333333333333" src="//speakerdeck.com/assets/embed.js"></script>
Essentially, the standard library is where a library goes to die. It is appropriate for a module to be included when active development is no longer necessary.
Linux Distro Packages

View File

@@ -1,16 +1,18 @@
How to Help
===========
.. image:: https://farm5.staticflickr.com/4290/34450900104_bc1d424213_k_d.jpg
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/requests/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. :)
Make sure to add yourself to `AUTHORS <https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst>`_.
Make sure to add yourself to `AUTHORS <https://github.com/requests/requests/blob/master/AUTHORS.rst>`_.
Feature Freeze
--------------
@@ -23,10 +25,11 @@ Development Dependencies
You'll need to install py.test in order to run the Requests' test suite::
$ pip install pipenv
$ pipenv lock
$ pipenv install --dev
$ pipenv run py.test tests
$ venv .venv
$ source .venv/bin/activate
$ make
$ python setup.py test
============================= test session starts ==============================
platform darwin -- Python 3.4.4, pytest-3.0.6, py-1.4.32, pluggy-0.4.0
...
@@ -41,14 +44,14 @@ You'll need to install py.test in order to run the Requests' test suite::
============== 442 passed, 1 skipped, 2 xpassed in 46.48 seconds ===============
You can also run ``$ make tests`` to run against all supported Python versions, using tox/detox.
Runtime Environments
--------------------
Requests currently supports the following versions of Python:
- Python 2.6
- Python 2.7
- Python 3.3
- Python 3.4
- Python 3.5
- Python 3.6
@@ -59,13 +62,3 @@ with the `Requests-Toolbelt`_.
.. _Requests-Toolbelt: http://toolbelt.readthedocs.io/
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

@@ -17,11 +17,8 @@ Release v\ |version|. (:ref:`Installation <install>`)
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
:target: https://pypi.python.org/pypi/requests
.. image:: https://travis-ci.org/kennethreitz/requests.svg?branch=master
:target: https://travis-ci.org/kennethreitz/requests
.. image:: https://codecov.io/github/kennethreitz/requests/coverage.svg?branch=master
:target: https://codecov.io/github/kennethreitz/requests
.. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master
:target: https://codecov.io/github/requests/requests
:alt: codecov.io
.. image:: https://img.shields.io/badge/Say%20Thanks!-🦉-1EAEDB.svg
@@ -31,10 +28,10 @@ Release v\ |version|. (:ref:`Installation <install>`)
**Requests** is the only *Non-GMO* HTTP library for Python, safe for human
consumption.
*Warning: Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
including: security vulnerabilities, verbose code, reinventing the wheel,
constantly reading documentation, depression, headaches, or even death.*
.. note:: The use of **Python 3** is *highly* preferred over Python 2. Consider upgrading your applications and infrastructure if you find yourself *still* using Python 2 in production today. If you are using Python 3, congratulations — you are indeed a person of excellent taste.
*Kenneth Reitz*
-------------------
**Behold, the power of Requests**::
@@ -84,7 +81,7 @@ Institutions that prefer to be unnamed claim to use Requests internally.
simple, Pythonic.*
Requests is one of the most downloaded Python packages of all time, pulling in
over 11,000,000 downloads every month. All the cool kids are doing it!
over 13,000,000 downloads every month. All the cool kids are doing it!
Beloved Features
----------------
@@ -107,7 +104,7 @@ Requests is ready for today's web.
- Chunked Requests
- ``.netrc`` Support
Requests officially supports Python 2.62.7 & 3.33.7, and runs great on PyPy.
Requests officially supports Python 2.7 & 3.43.7, and runs great on PyPy.
The User Guide
@@ -134,10 +131,10 @@ This part of the documentation, which is mostly prose, details the
Requests ecosystem and community.
.. toctree::
:maxdepth: 1
:maxdepth: 2
community/faq
community/recommended
community/faq
community/out-there
community/support
community/vulnerabilities

View File

@@ -3,6 +3,8 @@
Advanced Usage
==============
.. image:: https://farm5.staticflickr.com/4263/35163665790_d182d84f5e_k_d.jpg
This document covers some of Requests more advanced features.
.. _session-objects:
@@ -187,6 +189,25 @@ applied, replace the call to :meth:`Request.prepare()
print(resp.status_code)
When you are using the prepared request flow, keep in mind that it does not take into account the environment.
This can cause problems if you are using environment variables to change the behaviour of requests.
For example: Self-signed SSL certificates specified in ``REQUESTS_CA_BUNDLE`` will not be taken into account.
As a result an ``SSL: CERTIFICATE_VERIFY_FAILED`` is thrown.
You can get around this behaviour by explicity merging the environment settings into your session::
from requests import Request, Session
s = Session()
req = Request('GET', url)
prepped = s.prepare_request(req)
# Merge environment settings into session
settings = s.merge_environment_settings(prepped.url, None, None, None, None)
resp = s.send(prepped, **settings)
print(resp.status_code)
.. _verification:
SSL Cert Verification
@@ -280,7 +301,7 @@ immediately. You can override this behaviour and defer downloading the response
body until you access the :attr:`Response.content <requests.Response.content>`
attribute with the ``stream`` parameter::
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
tarball_url = 'https://github.com/requests/requests/tarball/master'
r = requests.get(tarball_url, stream=True)
At this point only the response headers have been downloaded and the connection
@@ -301,15 +322,11 @@ release the connection back to the pool unless you consume all the data or call
:meth:`Response.close <requests.Response.close>`. This can lead to
inefficiency with connections. If you find yourself partially reading request
bodies (or not reading them at all) while using ``stream=True``, you should
consider using ``contextlib.closing`` (`documented here`_), like this::
make the request within a ``with`` statement to ensure it's always closed::
from contextlib import closing
with closing(requests.get('http://httpbin.org/get', stream=True)) as r:
with requests.get('http://httpbin.org/get', stream=True) as r:
# Do things with the response here.
.. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing
.. _keep-alive:
Keep-Alive
@@ -597,6 +614,8 @@ as using a HTTP one::
'https': 'socks5://user:pass@host:port'
}
Using the scheme ``socks5`` causes the DNS resolution to happen on the client, rather than on the proxy server. This is in line with curl, which uses the scheme to decide whether to do the DNS resolution on the client or proxy. If you want to resolve the domains on the proxy server, use ``socks5h`` as the scheme.
.. _compliance:
Compliance
@@ -642,7 +661,7 @@ from GitHub. Suppose we wanted commit ``a050faf`` on Requests. We would get it
like so::
>>> import requests
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
>>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
We should confirm that GitHub responded correctly. If it has, we want to work
out what type of content it is. Do this like so::
@@ -697,12 +716,12 @@ we should probably avoid making ham-handed POSTS to it. Instead, let's play
with the Issues feature of GitHub.
This documentation was added in response to
`Issue #482 <https://github.com/kennethreitz/requests/issues/482>`_. Given that
`Issue #482 <https://github.com/requests/requests/issues/482>`_. Given that
this issue already exists, we will use it as an example. Let's start by getting it.
::
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
>>> r = requests.get('https://api.github.com/repos/requests/requests/issues/482')
>>> r.status_code
200
@@ -745,7 +764,7 @@ is to POST to the thread. Let's do it.
::
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
>>> url = u"https://api.github.com/repos/requests/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
@@ -779,7 +798,7 @@ that.
5804413
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
>>> url = u"https://api.github.com/repos/requests/requests/issues/comments/5804413"
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
@@ -938,8 +957,9 @@ 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`_.
Some excellent examples are `requests-threads`_, `grequests`_, and `requests-futures`_.
.. _`requests-threads`: https://github.com/requests/requests-threads
.. _`grequests`: https://github.com/kennethreitz/grequests
.. _`requests-futures`: https://github.com/ross/requests-futures

View File

@@ -3,6 +3,8 @@
Authentication
==============
.. image:: https://farm5.staticflickr.com/4258/35550409215_3b08d49d22_k_d.jpg
This document discusses using various kinds of authentication with Requests.
Many web services require authentication, and there are many different types.

View File

@@ -1,20 +1,23 @@
.. _install:
Installation
============
Installation of Requests
========================
.. image:: https://farm5.staticflickr.com/4230/35550376215_da1bf77a8c_k_d.jpg
This part of the documentation covers the installation of Requests.
The first step to using any software package is getting it properly installed.
Pip Install Requests
--------------------
$ pipenv install requests
-------------------------
To install Requests, simply run this simple command in your terminal of choice::
$ pip install requests
$ pipenv install requests
If you don't have `pip <https://pip.pypa.io>`_ installed (tisk tisk!),
If you don't have `pipenv <http://pipenv.org/>`_ installed (tisk tisk!), head over to the Pipenv website for installation instructions. Or, if you prefer to just use pip and don't have it installed,
`this Python installation guide <http://docs.python-guide.org/en/latest/starting/installation/>`_
can guide you through the process.
@@ -22,18 +25,19 @@ Get the Source Code
-------------------
Requests is actively developed on GitHub, where the code is
`always available <https://github.com/kennethreitz/requests>`_.
`always available <https://github.com/requests/requests>`_.
You can either clone the public repository::
$ git clone git://github.com/kennethreitz/requests.git
$ git clone git://github.com/requests/requests.git
Or, download the `tarball <https://github.com/kennethreitz/requests/tarball/master>`_::
Or, download the `tarball <https://github.com/requests/requests/tarball/master>`_::
$ curl -OL https://github.com/kennethreitz/requests/tarball/master
# optionally, zipball is also available (for Windows users).
$ curl -OL https://github.com/requests/requests/tarball/master
# optionally, zipball is also available (for Windows users).
Once you have a copy of the source, you can embed it in your own Python
package, or install it into your site-packages easily::
$ python setup.py install
$ cd requests
$ pip install .

View File

@@ -3,6 +3,8 @@
Introduction
============
.. image:: https://farm5.staticflickr.com/4317/35198386374_1939af3de6_k_d.jpg
Philosophy
----------

View File

@@ -3,6 +3,8 @@
Quickstart
==========
.. image:: https://farm5.staticflickr.com/4259/35163667010_8bfcaef274_k_d.jpg
.. module:: requests.models
Eager to get started? This page gives a good introduction in how to get started
@@ -234,7 +236,24 @@ dictionary of data will automatically be form-encoded when the request is made::
...
}
There are many times that you want to send data that is not form-encoded. If
You can also pass a list of tuples to the ``data`` argument. This is particularly
useful when the form has multiple elements that use the same key::
>>> payload = (('key1', 'value1'), ('key1', 'value2'))
>>> r = requests.post('http://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
"key1": [
"value1",
"value2"
]
},
...
}
There are times that you may want to send data that is not form-encoded. If
you pass in a ``string`` instead of a ``dict``, that data will be posted directly.
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
@@ -354,10 +373,14 @@ But, since our ``status_code`` for ``r`` was ``200``, when we call
``raise_for_status()`` we get::
>>> r.raise_for_status()
None
<Response [200]>
All is well.
.. note:: ``raise_for_status`` returns the response object for a successful response. This eases chaining in trivial cases, where we want bad codes to raise an exception, but use the response otherwise:
>>> value = requests.get('http://httpbin.org/ip').raise_for_status().json()['origin']
Response Headers
----------------
@@ -397,6 +420,10 @@ represented in the dictionary within a single mapping, as per
of the message, by appending each subsequent field value to the combined
field value in order, separated by a comma.
If you do need to access each individual value sent with the same header, then
you can use the ``getlist`` method to get a sequence of all the values returned
for a particular header.
Cookies
-------
@@ -485,7 +512,7 @@ Timeouts
--------
You can tell Requests to stop waiting for a response after a given number of
seconds with the ``timeout`` parameter. Nearly all production code should use
seconds with the ``timeout`` parameter. Nearly all production code should use
this parameter in nearly all requests. Failure to do so can cause your program
to hang indefinitely::

View File

@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
# /
"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~
@@ -40,73 +38,103 @@ is at <http://python-requests.org>.
:license: Apache 2.0, see LICENSE for more details.
"""
import urllib3
import chardet
import warnings
from .exceptions import RequestsDependencyWarning
from .import types
def check_compatibility(urllib3_version: types.String, chardet_version: types.String) -> None:
urllib3_version = urllib3_version.split('.') # type: ignore
assert urllib3_version != [
'dev'
] # Verify urllib3 isn't installed from git.
# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
urllib3_version.append('0') # type: ignore
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch) # type: ignore
# urllib3 >= 1.21.1, <= 1.22
assert major == 1 # type: ignore
assert minor >= 21 # type: ignore
assert minor <= 22 # type: ignore
# Check chardet for compatibility.
major, minor, patch = chardet_version.split('.')[:3]
major, minor, patch = int(major), int(minor), int(patch) # type: ignore
# chardet >= 3.0.2, < 3.1.0
assert major == 3 # type: ignore
assert minor < 1 # type: ignore
assert patch >= 2 # type: ignore
def _check_cryptography(cryptography_version: types.String) -> None:
# cryptography < 1.3.4
try:
cryptography_version = list(
map(int, cryptography_version.split('.'))
) # type: ignore
except ValueError:
return
if cryptography_version < [1, 3, 4]: # type: ignore
warning = 'Old version of cryptography ({0}) may cause slowdown.'.format(
cryptography_version
)
warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility.
try:
check_compatibility(urllib3.__version__, chardet.__version__)
except (AssertionError, ValueError):
warnings.warn(
"urllib3 ({0}) or chardet ({1}) doesn't match a supported "
"version!".format(urllib3.__version__, chardet.__version__),
RequestsDependencyWarning,
)
# Attempt to enable urllib3's SNI support, if possible
try:
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning
warnings.simplefilter('ignore', DependencyWarning)
from .__version__ import __title__, __description__, __url__, __version__
from .__version__ import __build__, __author__, __author_email__, __license__
from .__version__ import __copyright__, __cake__
# Check urllib3 for compatibility.
import urllib3
urllib3_version = urllib3.__version__.split('.')
# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
urllib3_version.append('0')
major, minor, patch= urllib3_version
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1, < 1.22
try:
assert major == 1
assert minor >= 21
assert minor <= 22
except AssertionError:
raise RuntimeError('Requests dependency \'urllib3\' must be version >= 1.21.1, < 1.22!')
# Check chardet for compatibility.
import chardet
major, minor, patch = chardet.__version__.split('.')[:3]
major, minor, patch = int(major), int(minor), int(patch)
# chardet >= 3.0.2, < 3.1.0
try:
assert major == 3
assert minor < 1
assert patch >= 2
except AssertionError:
raise RuntimeError('Requests dependency \'chardet\' must be version >= 3.0.2, < 3.1.0!')
# Attempt to enable urllib3's SNI support, if possible
try:
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
except ImportError:
pass
import warnings
# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning
warnings.simplefilter('ignore', DependencyWarning)
from . import utils
from .import utils
from .models import Request, Response, PreparedRequest
from .api import request, get, head, post, patch, put, delete, options
from .sessions import session, Session
from .sessions import Session
from .status_codes import codes
from .exceptions import (
RequestException, Timeout, URLRequired,
TooManyRedirects, HTTPError, ConnectionError,
FileModeWarning, ConnectTimeout, ReadTimeout
RequestException,
Timeout,
URLRequired,
TooManyRedirects,
HTTPError,
ConnectionError,
FileModeWarning,
ConnectTimeout,
ReadTimeout,
)
# Set default logging handler to avoid "No handler found" warnings.
import logging
try: # Python 2.7+
from logging import NullHandler
except ImportError:
class NullHandler(logging.Handler):
def emit(self, record):
pass
from logging import NullHandler
logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default.
warnings.simplefilter('default', FileModeWarning, append=True)

View File

@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
# .-. .-. .-. . . .-. .-. .-. .-.
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'http://python-requests.org'
__version__ = '2.16.2'
__build__ = 0x021602
__version__ = '3.0.0'
__build__ = 0x030000
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2017 Kenneth Reitz'
__cake__ = u'✨ 🍰 ✨'
__cake__ = u'\u2728 \U0001f370 \u2728'

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests._internal_utils
~~~~~~~~~~~~~~
@@ -8,7 +7,7 @@ Provides utility functions that are consumed internally by Requests
which depend on extremely few external helpers (such as compat)
"""
from .compat import is_py2, builtin_str, str
from .basics import builtin_str, str
def to_native_string(string, encoding='ascii'):
@@ -19,11 +18,7 @@ def to_native_string(string, encoding='ascii'):
if isinstance(string, builtin_str):
out = string
else:
if is_py2:
out = string.encode(encoding)
else:
out = string.decode(encoding)
out = string.decode(encoding)
return out
@@ -34,9 +29,12 @@ def unicode_is_ascii(u_string):
and not Python 2 `str`.
:rtype: bool
"""
assert isinstance(u_string, str)
if not isinstance(u_string, str):
return None
try:
u_string.encode('ascii')
return True
except UnicodeEncodeError:
return False

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.adapters
~~~~~~~~~~~~~~~~~
@@ -27,21 +26,35 @@ from urllib3.exceptions import SSLError as _SSLError
from urllib3.exceptions import ResponseError
from .models import Response
from .compat import urlparse, basestring
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
select_proxy)
from .structures import CaseInsensitiveDict
from .basics import urlparse, basestring
from .utils import (
DEFAULT_CA_BUNDLE_PATH,
get_encoding_from_headers,
prepend_scheme_if_needed,
get_auth_from_url,
urldefragauth,
select_proxy,
)
from .structures import HTTPHeaderDict
from .cookies import extract_cookies_to_jar
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
ProxyError, RetryError, InvalidSchema)
from .exceptions import (
ConnectionError,
ConnectTimeout,
ReadTimeout,
SSLError,
ProxyError,
RetryError,
InvalidScheme,
)
from .auth import _basic_auth_str
try:
from urllib3.contrib.socks import SOCKSProxyManager
except ImportError:
def SOCKSProxyManager(*args, **kwargs):
raise InvalidSchema("Missing dependencies for SOCKS support.")
raise InvalidScheme("Missing dependencies for SOCKS support.")
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
@@ -49,14 +62,83 @@ DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
def _pool_kwargs(verify, cert):
"""Create a dictionary of keyword arguments to pass to a
:class:`PoolManager <urllib3.poolmanager.PoolManager>` with the
necessary SSL configuration.
:param verify: Whether we should actually verify the certificate;
optionally a path to a CA certificate bundle or
directory of CA certificates.
:param cert: The path to the client certificate and key, if any.
This can either be the path to the certificate and
key concatenated in a single file, or as a tuple of
(cert_file, key_file).
"""
pool_kwargs = {}
if verify:
cert_loc = None
# Allow self-specified cert location.
if verify is not True:
cert_loc = verify
if not cert_loc:
cert_loc = DEFAULT_CA_BUNDLE_PATH
if not cert_loc or not os.path.exists(cert_loc):
raise IOError(
"Could not find a suitable TLS CA certificate bundle, "
"invalid path: {0}".format(cert_loc)
)
pool_kwargs['cert_reqs'] = 'CERT_REQUIRED'
if not os.path.isdir(cert_loc):
pool_kwargs['ca_certs'] = cert_loc
pool_kwargs['ca_cert_dir'] = None
else:
pool_kwargs['ca_cert_dir'] = cert_loc
pool_kwargs['ca_certs'] = None
else:
pool_kwargs['cert_reqs'] = 'CERT_NONE'
pool_kwargs['ca_certs'] = None
pool_kwargs['ca_cert_dir'] = None
if cert:
if not isinstance(cert, basestring):
pool_kwargs['cert_file'] = cert[0]
pool_kwargs['key_file'] = cert[1]
else:
pool_kwargs['cert_file'] = cert
pool_kwargs['key_file'] = None
cert_file = pool_kwargs['cert_file']
key_file = pool_kwargs['key_file']
if cert_file and not os.path.exists(cert_file):
raise IOError(
"Could not find the TLS certificate file, "
"invalid path: {0}".format(cert_file)
)
if key_file and not os.path.exists(key_file):
raise IOError(
"Could not find the TLS key file, "
"invalid path: {0}".format(key_file)
)
return pool_kwargs
class BaseAdapter(object):
"""The Base Transport Adapter"""
def __init__(self):
super(BaseAdapter, self).__init__()
def send(self, request, stream=False, timeout=None, verify=True,
cert=None, proxies=None):
def send(
self,
request,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -104,44 +186,50 @@ class HTTPAdapter(BaseAdapter):
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
>>> s.mount('http://', a)
"""
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
'_pool_block']
__attrs__ = [
'max_retries',
'config',
'_pool_connections',
'_pool_maxsize',
'_pool_block',
]
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK):
def __init__(
self,
pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK,
):
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
else:
self.max_retries = Retry.from_int(max_retries)
self.config = {}
self.proxy_manager = {}
super(HTTPAdapter, self).__init__()
self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize
self._pool_block = pool_block
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
def __getstate__(self):
return dict((attr, getattr(self, attr, None)) for attr in
self.__attrs__)
return {attr: getattr(self, attr, None) for attr in self.__attrs__}
def __setstate__(self, state):
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
# self.poolmanager uses a lambda function, which isn't pickleable.
self.proxy_manager = {}
self.config = {}
for attr, value in state.items():
setattr(self, attr, value)
self.init_poolmanager(
self._pool_connections, self._pool_maxsize, block=self._pool_block
)
self.init_poolmanager(self._pool_connections, self._pool_maxsize,
block=self._pool_block)
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
def init_poolmanager(
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
):
"""Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only
@@ -157,9 +245,13 @@ class HTTPAdapter(BaseAdapter):
self._pool_connections = connections
self._pool_maxsize = maxsize
self._pool_block = block
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
block=block, strict=True, **pool_kwargs)
self.poolmanager = PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
strict=True,
**pool_kwargs,
)
def proxy_manager_for(self, proxy, **proxy_kwargs):
"""Return urllib3 ProxyManager for the given proxy.
@@ -184,7 +276,7 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs
**proxy_kwargs,
)
else:
proxy_headers = self.proxy_headers(proxy)
@@ -194,62 +286,10 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs)
**proxy_kwargs,
)
return manager
def cert_verify(self, conn, url, verify, cert):
"""Verify a SSL certificate. This method should not be called from user
code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param conn: The urllib3 connection object associated with the cert.
:param url: The requested URL.
:param verify: Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use
:param cert: The SSL certificate to verify.
"""
if url.lower().startswith('https') and verify:
cert_loc = None
# Allow self-specified cert location.
if verify is not True:
cert_loc = verify
if not cert_loc:
cert_loc = DEFAULT_CA_BUNDLE_PATH
if not cert_loc or not os.path.exists(cert_loc):
raise IOError("Could not find a suitable TLS CA certificate bundle, "
"invalid path: {0}".format(cert_loc))
conn.cert_reqs = 'CERT_REQUIRED'
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
else:
conn.cert_reqs = 'CERT_NONE'
conn.ca_certs = None
conn.ca_cert_dir = None
if cert:
if not isinstance(cert, basestring):
conn.cert_file = cert[0]
conn.key_file = cert[1]
else:
conn.cert_file = cert
conn.key_file = None
if conn.cert_file and not os.path.exists(conn.cert_file):
raise IOError("Could not find the TLS certificate file, "
"invalid path: {0}".format(conn.cert_file))
if conn.key_file and not os.path.exists(conn.key_file):
raise IOError("Could not find the TLS key file, "
"invalid path: {0}".format(conn.key_file))
def build_response(self, req, resp):
"""Builds a :class:`Response <requests.Response>` object from a urllib3
response. This should not be called from user code, and is only exposed
@@ -261,33 +301,26 @@ class HTTPAdapter(BaseAdapter):
:rtype: requests.Response
"""
response = Response()
# Fallback to None if there's no status_code, for whatever reason.
response.status_code = getattr(resp, 'status', None)
# Make headers case-insensitive.
response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {}))
response.headers = HTTPHeaderDict(getattr(resp, 'headers', {}))
# Set encoding.
response.encoding = get_encoding_from_headers(response.headers)
response.raw = resp
response.reason = response.raw.reason
if isinstance(req.url, bytes):
response.url = req.url.decode('utf-8')
else:
response.url = req.url
# Add new cookies from the server.
extract_cookies_to_jar(response.cookies, req, resp)
# Give the Response some context.
response.request = req
response.connection = self
return response
def get_connection(self, url, proxies=None):
def get_connection(self, url, proxies=None, verify=None, cert=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 <requests.adapters.HTTPAdapter>`.
@@ -296,18 +329,21 @@ class HTTPAdapter(BaseAdapter):
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
:rtype: urllib3.ConnectionPool
"""
pool_kwargs = _pool_kwargs(verify, cert)
proxy = select_proxy(url, proxies)
if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http')
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url)
conn = proxy_manager.connection_from_url(
url, pool_kwargs=pool_kwargs
)
else:
# Only scheme should be lower case
parsed = urlparse(url)
url = parsed.geturl()
conn = self.poolmanager.connection_from_url(url)
conn = self.poolmanager.connection_from_url(
url, pool_kwargs=pool_kwargs
)
return conn
def close(self):
@@ -336,17 +372,14 @@ class HTTPAdapter(BaseAdapter):
"""
proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme
is_proxied_http_request = (proxy and scheme != 'https')
using_socks_proxy = False
if proxy:
proxy_scheme = urlparse(proxy).scheme.lower()
using_socks_proxy = proxy_scheme.startswith('socks')
url = request.path_url
if is_proxied_http_request and not using_socks_proxy:
url = urldefragauth(request.url)
return url
def add_headers(self, request, **kwargs):
@@ -378,14 +411,21 @@ class HTTPAdapter(BaseAdapter):
"""
headers = {}
username, password = get_auth_from_url(proxy)
if username:
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):
def send(
self,
request,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -401,30 +441,29 @@ class HTTPAdapter(BaseAdapter):
:param proxies: (optional) The proxies dictionary to apply to the request.
:rtype: requests.Response
"""
conn = self.get_connection(request.url, proxies)
self.cert_verify(conn, request.url, verify, cert)
conn = self.get_connection(request.url, proxies, verify, cert)
url = self.request_url(request, proxies)
self.add_headers(request)
chunked = not (request.body is None or 'Content-Length' in request.headers)
chunked = not (
request.body is None or 'Content-Length' in request.headers
)
if isinstance(timeout, tuple):
try:
connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read)
except ValueError as e:
# this may raise a string formatting error.
err = ("Invalid timeout {0}. Pass a (connect, read) "
"timeout tuple, or a single float to set "
"both timeouts to the same value".format(timeout))
err = (
"Invalid timeout {0}. Pass a (connect, read) "
"timeout tuple, or a single float to set "
"both timeouts to the same value".format(timeout)
)
raise ValueError(err)
elif isinstance(timeout, TimeoutSauce):
pass
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)
try:
if not chunked:
resp = conn.urlopen(
@@ -437,48 +476,46 @@ class HTTPAdapter(BaseAdapter):
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout
timeout=timeout,
enforce_content_length=True,
)
# Send the request.
else:
if hasattr(conn, 'proxy_pool'):
conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try:
low_conn.putrequest(request.method,
url,
skip_accept_encoding=True)
low_conn.putrequest(
request.method, url, skip_accept_encoding=True
)
for header, value in request.headers.items():
low_conn.putheader(header, value)
low_conn.endheaders()
for i in request.body:
low_conn.send(hex(len(i))[2:].encode('utf-8'))
chunk_size = len(i)
if chunk_size == 0:
continue
low_conn.send(hex(chunk_size)[2:].encode('utf-8'))
low_conn.send(b'\r\n')
low_conn.send(i)
low_conn.send(b'\r\n')
low_conn.send(b'0\r\n\r\n')
# Receive the response from the server
try:
# For Python 2.7+ versions, use buffering of HTTP
# responses
# For Python 2.7, use buffering of HTTP responses
r = low_conn.getresponse(buffering=True)
except TypeError:
# For compatibility with Python 2.6 versions and back
# For Python 3.3+ versions, this is the default
r = low_conn.getresponse()
resp = HTTPResponse.from_httplib(
r,
pool=conn,
connection=low_conn,
preload_content=False,
decode_content=False
decode_content=False,
enforce_content_length=True,
request_method=request.method,
)
except:
# If we hit any problems here, clean up the connection.
@@ -501,6 +538,10 @@ class HTTPAdapter(BaseAdapter):
if isinstance(e.reason, _ProxyError):
raise ProxyError(e, request=request)
if isinstance(e.reason, _SSLError):
# This branch is for urllib3 v1.22 and later.
raise SSLError(e, request=request)
raise ConnectionError(e, request=request)
except ClosedPoolError as e:
@@ -511,9 +552,12 @@ class HTTPAdapter(BaseAdapter):
except (_SSLError, _HTTPError) as e:
if isinstance(e, _SSLError):
# This branch is for urllib3 versions earlier than v1.22
raise SSLError(e, request=request)
elif isinstance(e, ReadTimeoutError):
raise ReadTimeout(e, request=request)
else:
raise

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.api
~~~~~~~~~~~~
@@ -10,14 +9,22 @@ This module implements the Requests API.
:license: Apache2, see LICENSE for more details.
"""
from . import sessions
from .import sessions
from .import types
def request(method, url, **kwargs):
def request(
method: types.Method,
url: types.URL,
*,
session: types.Session = None,
**kwargs,
) -> types.Response:
"""Constructs and sends a :class:`Request <Request>`.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param session: :class:`Session` object to use for this request. If none is given, one will be provided.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
@@ -50,15 +57,17 @@ def request(method, url, **kwargs):
>>> req = requests.request('GET', 'http://httpbin.org/get')
<Response [200]>
"""
# By using the 'with' statement we are sure the session is closed, thus we
# avoid leaving sockets open which can trigger a ResourceWarning in some
# cases, and look like a memory leak in others.
with sessions.Session() as session:
session = sessions.Session() if session is None else session
with session:
return session.request(method=method, url=url, **kwargs)
def get(url, params=None, **kwargs):
def get(
url: types.URL, *, params: types.Params = None, **kwargs
) -> types.Response:
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
@@ -67,25 +76,23 @@ def get(url, params=None, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
def options(url, **kwargs):
r"""Sends a OPTIONS request.
def options(url: types.URL, **kwargs) -> types.Response:
r"""Sends an OPTIONS request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('options', url, **kwargs)
def head(url, **kwargs):
def head(url: types.URL, **kwargs) -> types.Response:
r"""Sends a HEAD request.
:param url: URL for the new :class:`Request` object.
@@ -93,12 +100,17 @@ def head(url, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', False)
return request('head', url, **kwargs)
def post(url, data=None, json=None, **kwargs):
def post(
url: types.URL,
*,
data: types.Data = None,
json: types.JSON = None,
**kwargs,
) -> types.Response:
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
@@ -108,11 +120,12 @@ def post(url, data=None, json=None, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('post', url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs):
def put(
url: types.URL, *, data: types.Data = None, **kwargs
) -> types.Response:
r"""Sends a PUT request.
:param url: URL for the new :class:`Request` object.
@@ -122,11 +135,12 @@ def put(url, data=None, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('put', url, data=data, **kwargs)
def patch(url, data=None, **kwargs):
def patch(
url: types.URL, *, data: types.Data = None, **kwargs
) -> types.Response:
r"""Sends a PATCH request.
:param url: URL for the new :class:`Request` object.
@@ -136,11 +150,10 @@ def patch(url, data=None, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('patch', url, data=data, **kwargs)
return request('patch', url, data=data, **kwargs)
def delete(url, **kwargs):
def delete(url: types.URL, **kwargs) -> types.Response:
r"""Sends a DELETE request.
:param url: URL for the new :class:`Request` object.
@@ -148,5 +161,4 @@ def delete(url, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('delete', url, **kwargs)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.auth
~~~~~~~~~~~~~
@@ -12,11 +11,10 @@ import re
import time
import hashlib
import threading
import warnings
from base64 import b64encode
from .compat import urlparse, str, basestring
from .basics import urlparse, str, basestring
from .cookies import extract_cookies_to_jar
from ._internal_utils import to_native_string
from .utils import parse_dict_header
@@ -27,45 +25,25 @@ CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
# "I want us to put a big-ol' comment on top of it that
# says that this behaviour is dumb but we need to preserve
# it because people are relying on it."
# - Lukasa
#
# These are here solely to maintain backwards compatibility
# for things like ints. This will be removed in 3.0.0.
if not isinstance(username, basestring):
warnings.warn(
"Non-string usernames will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({0!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(username),
category=DeprecationWarning,
raise TypeError(
'username must be of type str or bytes, '
'instead it was %s' % type(username)
)
username = str(username)
if not isinstance(password, basestring):
warnings.warn(
"Non-string passwords will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({0!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(password),
category=DeprecationWarning,
raise TypeError(
'password must be of type str or bytes, '
'instead it was %s' % type(password)
)
password = str(password)
# -- End Removal --
if isinstance(username, str):
username = username.encode('latin1')
if isinstance(password, str):
password = password.encode('latin1')
authstr = 'Basic ' + to_native_string(
b64encode(b':'.join((username, password))).strip()
)
return authstr
@@ -84,24 +62,20 @@ class HTTPBasicAuth(AuthBase):
self.password = password
def __eq__(self, other):
return all([
self.username == getattr(other, 'username', None),
self.password == getattr(other, 'password', None)
])
return all(
[
self.username == getattr(other, 'username', None),
self.password == getattr(other, 'password', None),
]
)
def __ne__(self, other):
return not self == other
def __call__(self, r):
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
return r
class HTTPProxyAuth(HTTPBasicAuth):
"""Attaches HTTP Proxy Authentication to a given Request object."""
def __call__(self, r):
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
r.headers['Authorization'] = _basic_auth_str(
self.username, self.password
)
return r
@@ -128,51 +102,48 @@ class HTTPDigestAuth(AuthBase):
"""
:rtype: str
"""
realm = self._thread_local.chal['realm']
nonce = self._thread_local.chal['nonce']
qop = self._thread_local.chal.get('qop')
algorithm = self._thread_local.chal.get('algorithm')
opaque = self._thread_local.chal.get('opaque')
hash_utf8 = None
if algorithm is None:
_algorithm = 'MD5'
else:
_algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level
if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
def md5_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.md5(x).hexdigest()
hash_utf8 = md5_utf8
elif _algorithm == 'SHA':
def sha_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
return hashlib.sha1(x).hexdigest()
hash_utf8 = sha_utf8
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
if hash_utf8 is None:
return None
# XXX not implemented yet
entdig = None
p_parsed = urlparse(url)
#: path is request-uri defined in RFC 2616 which should not be empty
# : path is request-uri defined in RFC 2616 which should not be empty
path = p_parsed.path or "/"
if p_parsed.query:
path += '?' + p_parsed.query
A1 = '%s:%s:%s' % (self.username, realm, self.password)
A2 = '%s:%s' % (method, path)
HA1 = hash_utf8(A1)
HA2 = hash_utf8(A2)
if nonce == self._thread_local.last_nonce:
self._thread_local.nonce_count += 1
else:
@@ -182,27 +153,23 @@ class HTTPDigestAuth(AuthBase):
s += nonce.encode('utf-8')
s += time.ctime().encode('utf-8')
s += os.urandom(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16])
if _algorithm == 'MD5-SESS':
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
elif qop == 'auth' or 'auth' in qop.split(','):
noncebit = "%s:%s:%s:%s:%s" % (
nonce, ncvalue, cnonce, 'auth', HA2
)
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, 'auth', HA2)
respdig = KD(HA1, noncebit)
else:
# XXX handle auth-int.
return None
self._thread_local.last_nonce = nonce
# XXX should the partial digests be encoded too?
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
'response="%s"' % (self.username, realm, nonce, path, respdig)
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' 'response="%s"' % (
self.username, realm, nonce, path, respdig
)
if opaque:
base += ', opaque="%s"' % opaque
if algorithm:
@@ -211,7 +178,6 @@ class HTTPDigestAuth(AuthBase):
base += ', digest="%s"' % entdig
if qop:
base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
return 'Digest %s' % (base)
def handle_redirect(self, r, **kwargs):
@@ -225,9 +191,8 @@ class HTTPDigestAuth(AuthBase):
:rtype: requests.Response
"""
# If response is not 4xx, do not auth
# See https://github.com/kennethreitz/requests/issues/3772
# See https://github.com/requests/requests/issues/3772
if not 400 <= r.status_code < 500:
self._thread_local.num_401_calls = 1
return r
@@ -237,13 +202,12 @@ class HTTPDigestAuth(AuthBase):
# it was to resend the request.
r.request.body.seek(self._thread_local.pos)
s_auth = r.headers.get('www-authenticate', '')
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
self._thread_local.num_401_calls += 1
pat = re.compile(r'digest ', flags=re.IGNORECASE)
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
self._thread_local.chal = parse_dict_header(
pat.sub('', s_auth, count=1)
)
# Consume content and release the original connection
# to allow our new request to reuse the same one.
r.content
@@ -251,13 +215,12 @@ class HTTPDigestAuth(AuthBase):
prep = r.request.copy()
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies)
prep.headers['Authorization'] = self.build_digest_header(
prep.method, prep.url)
prep.method, prep.url
)
_r = r.connection.send(prep, **kwargs)
_r.history.append(r)
_r.request = prep
return _r
self._thread_local.num_401_calls = 1
@@ -268,7 +231,9 @@ class HTTPDigestAuth(AuthBase):
self.init_per_thread_state()
# If we have a saved nonce, skip the 401
if self._thread_local.last_nonce:
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
r.headers['Authorization'] = self.build_digest_header(
r.method, r.url
)
try:
self._thread_local.pos = r.body.tell()
except AttributeError:
@@ -280,14 +245,15 @@ class HTTPDigestAuth(AuthBase):
r.register_hook('response', self.handle_401)
r.register_hook('response', self.handle_redirect)
self._thread_local.num_401_calls = 1
return r
def __eq__(self, other):
return all([
self.username == getattr(other, 'username', None),
self.password == getattr(other, 'password', None)
])
return all(
[
self.username == getattr(other, 'username', None),
self.password == getattr(other, 'password', None),
]
)
def __ne__(self, other):
return not self == other

45
requests/basics.py Normal file
View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
"""
requests.basics
~~~~~~~~~~~~~~~
This modules covers the basics.
"""
import chardet
import sys
# ---------
# Specifics
# ---------
from urllib.parse import (
urlparse,
urlunparse,
urljoin,
urlsplit,
urlencode,
quote,
unquote,
quote_plus,
unquote_plus,
urldefrag,
)
from urllib.request import (
parse_http_list,
getproxies,
proxy_bypass,
proxy_bypass_environment,
getproxies_environment,
)
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
from collections import OrderedDict
builtin_str = str # type: ignore
str = str # type: ignore
bytes = bytes # type: ignore
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
requests.certs
~~~~~~~~~~~~~~

View File

@@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.compat
~~~~~~~~~~~~~~~
This module handles import compatibility issues between Python 2 and
Python 3.
"""
import chardet
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
try:
import simplejson as json
except (ImportError, SyntaxError):
# simplejson does not support Python 3.2, it throws a SyntaxError
# because of u'...' Unicode literals.
import json
# ---------
# Specifics
# ---------
if is_py2:
from urllib import (
quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
proxy_bypass, proxy_bypass_environment, getproxies_environment)
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 urllib3.packages.ordered_dict import OrderedDict
builtin_str = str
bytes = str
str = unicode
basestring = basestring
numeric_types = (int, long, float)
integer_types = (int, long)
elif is_py3:
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
from collections import OrderedDict
builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.cookies
~~~~~~~~~~~~~~~~
@@ -15,12 +14,12 @@ import calendar
import collections
from ._internal_utils import to_native_string
from .compat import cookielib, urlparse, urlunparse, Morsel
from .basics import cookielib, urlparse, urlunparse, Morsel
try:
import threading
except ImportError:
import dummy_threading as threading
import dummy_threading as threading # type: ignore
class MockRequest(object):
@@ -54,14 +53,21 @@ class MockRequest(object):
# header
if not self._r.headers.get('Host'):
return self._r.url
# If they did set it, retrieve it and reconstruct the expected domain
host = to_native_string(self._r.headers['Host'], encoding='utf-8')
parsed = urlparse(self._r.url)
# Reconstruct the URL as we expect it
return urlunparse([
parsed.scheme, host, parsed.path, parsed.params, parsed.query,
parsed.fragment
])
return urlunparse(
[
parsed.scheme,
host,
parsed.path,
parsed.params,
parsed.query,
parsed.fragment,
]
)
def is_unverifiable(self):
return True
@@ -74,7 +80,9 @@ class MockRequest(object):
def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
raise NotImplementedError(
"Cookie headers should be added with add_unredirected_header()"
)
def add_unredirected_header(self, name, value):
self._new_headers[name] = value
@@ -123,9 +131,11 @@ 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):
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:
@@ -153,12 +163,14 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
for cookie in cookiejar:
if cookie.name != name:
continue
if domain is not None and domain != cookie.domain:
continue
if path is not None and path != cookie.path:
continue
clearables.append((cookie.domain, cookie.path, cookie.name))
clearables.append((cookie.domain, cookie.path, cookie.name))
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
@@ -196,6 +208,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""
try:
return self._find_no_duplicates(name, domain, path)
except KeyError:
return default
@@ -206,7 +219,12 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""
# support client code that unsets cookies by assignment of a None value:
if value is None:
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
remove_cookie_by_name(
self,
name,
domain=kwargs.get('domain'),
path=kwargs.get('path'),
)
return
if isinstance(value, Morsel):
@@ -294,6 +312,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
for cookie in iter(self):
if cookie.domain is not None and cookie.domain in domains:
return True
domains.append(cookie.domain)
return False # there is only one domain in jar
@@ -306,14 +325,17 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""
dictionary = {}
for cookie in iter(self):
if (domain is None or cookie.domain == domain) and (path is None
or cookie.path == path):
if (
(domain is None or cookie.domain == domain) and
(path is None or cookie.path == path)
):
dictionary[cookie.name] = cookie.value
return dictionary
def __contains__(self, name):
try:
return super(RequestsCookieJar, self).__contains__(name)
except CookieConflictError:
return True
@@ -340,9 +362,15 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
remove_cookie_by_name(self, name)
def set_cookie(self, cookie, *args, **kwargs):
if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
if hasattr(cookie.value, 'startswith') and cookie.value.startswith(
'"'
) and cookie.value.endswith(
'"'
):
cookie.value = cookie.value.replace('\\"', '')
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
return super(RequestsCookieJar, self).set_cookie(
cookie, *args, **kwargs
)
def update(self, other):
"""Updates this jar with cookies from another CookieJar or dict-like"""
@@ -390,11 +418,15 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
if toReturn is not None: # if there are multiple cookies that meet passed in criteria
raise CookieConflictError('There are multiple cookies with name, %r' % (name))
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
raise CookieConflictError(
'There are multiple cookies with name, %r' %
(name)
)
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
if toReturn:
return toReturn
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
def __getstate__(self):
@@ -412,7 +444,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
def copy(self):
"""Return a copy of this RequestsCookieJar."""
new_cj = RequestsCookieJar()
new_cj = RequestsCookieJar(self._policy)
new_cj.update(self)
return new_cj
@@ -424,6 +456,7 @@ def _copy_cookie_jar(jar):
if hasattr(jar, 'copy'):
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
new_jar = copy.copy(jar)
new_jar.clear()
@@ -438,21 +471,21 @@ def create_cookie(name, value, **kwargs):
By default, the pair of `name` and `value` will be set for the domain ''
and sent on every request (this is sometimes called a "supercookie").
"""
result = dict(
version=0,
name=name,
value=value,
port=None,
domain='',
path='/',
secure=False,
expires=None,
discard=True,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False,)
result = {
'version': 0,
'name': name,
'value': value,
'port': None,
'domain': '',
'path': '/',
'secure': False,
'expires': None,
'discard': True,
'comment': None,
'comment_url': None,
'rest': {'HttpOnly': None},
'rfc2109': False,
}
badargs = set(kwargs) - set(result)
if badargs:
err = 'create_cookie() got unexpected keyword arguments: %s'
@@ -463,19 +496,18 @@ def create_cookie(name, value, **kwargs):
result['domain_specified'] = bool(result['domain'])
result['domain_initial_dot'] = result['domain'].startswith('.')
result['path_specified'] = bool(result['path'])
return cookielib.Cookie(**result)
def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None
if morsel['max-age']:
try:
expires = int(time.time() + int(morsel['max-age']))
except ValueError:
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
elif morsel['expires']:
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
expires = calendar.timegm(
@@ -508,13 +540,11 @@ def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
"""
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:
if overwrite or (name not in names_from_jar):
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
return cookiejar
@@ -529,12 +559,12 @@ def merge_cookies(cookiejar, cookies):
if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict(
cookies, cookiejar=cookiejar, overwrite=False)
cookies, cookiejar=cookiejar, overwrite=False
)
elif isinstance(cookies, cookielib.CookieJar):
try:
cookiejar.update(cookies)
except AttributeError:
for cookie_in_jar in cookies:
cookiejar.set_cookie(cookie_in_jar)
return cookiejar

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~
@@ -19,8 +18,11 @@ class RequestException(IOError):
response = kwargs.pop('response', None)
self.response = response
self.request = kwargs.pop('request', None)
if (response is not None and not self.request and
hasattr(response, 'request')):
if (
response is not None and
not self.request and
hasattr(response, 'request')
):
self.request = self.response.request
super(RequestException, self).__init__(*args, **kwargs)
@@ -69,12 +71,12 @@ class TooManyRedirects(RequestException):
"""Too many redirects."""
class MissingSchema(RequestException, ValueError):
"""The URL schema (e.g. http or https) is missing."""
class MissingScheme(RequestException, ValueError):
"""The URL scheme (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
"""See defaults.py for valid schemas."""
class InvalidScheme(RequestException, ValueError):
"""See defaults.py for valid schemes."""
class InvalidURL(RequestException, ValueError):
@@ -100,12 +102,18 @@ class StreamConsumedError(RequestException, TypeError):
class RetryError(RequestException):
"""Custom retries logic failed"""
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body"""
class InvalidBodyError(RequestException, ValueError):
"""An invalid request body was specified"""
# Warnings
class RequestsWarning(Warning):
"""Base warning for Requests."""
pass
@@ -114,3 +122,8 @@ class RequestsWarning(Warning):
class FileModeWarning(RequestsWarning, DeprecationWarning):
"""A file was opened in text mode, but Requests determined its binary length."""
pass
class RequestsDependencyWarning(RequestsWarning):
"""An imported dependency doesn't match the expected version range."""
pass

105
requests/help.py Normal file
View File

@@ -0,0 +1,105 @@
"""Module containing bug report helper(s)."""
from __future__ import print_function
import json
import platform
import sys
import ssl
import idna
import urllib3
import chardet
from .import types
from .import __version__ as requests_version
try:
from . packages.urllib3.contrib import pyopenssl
except ImportError:
pyopenssl = None
OpenSSL = None
cryptography = None
else:
import OpenSSL
import cryptography
def _implementation() -> types.Help:
"""Return a dict with the Python implementation and version.
Provide both the name and the version of the Python implementation
currently running. For example, on CPython 2.7.5 it will return
{'name': 'CPython', 'version': '2.7.5'}.
This function works best on CPython and PyPy: in particular, it probably
doesn't work for Jython or IronPython. Future investigation should be done
to work out the correct shape of the code for those platforms.
"""
implementation = platform.python_implementation()
if implementation == 'CPython':
implementation_version = platform.python_version()
elif implementation == 'PyPy':
implementation_version = '%s.%s.%s' % (
sys.pypy_version_info.major,
sys.pypy_version_info.minor,
sys.pypy_version_info.micro,
)
if sys.pypy_version_info.releaselevel != 'final':
implementation_version = ''.join(
[implementation_version, sys.pypy_version_info.releaselevel]
)
elif implementation == 'Jython':
implementation_version = platform.python_version() # Complete Guess
elif implementation == 'IronPython':
implementation_version = platform.python_version() # Complete Guess
else:
implementation_version = 'Unknown'
return {'name': implementation, 'version': implementation_version}
def info() -> types.Help:
"""Generate information for a bug report."""
try:
platform_info = {
'system': platform.system(), 'release': platform.release()
}
except IOError:
platform_info = {'system': 'Unknown', 'release': 'Unknown'}
implementation_info = _implementation()
urllib3_info = {'version': urllib3.__version__}
chardet_info = {'version': chardet.__version__}
pyopenssl_info = {'version': None, 'openssl_version': ''}
if OpenSSL:
pyopenssl_info = {
'version': OpenSSL.__version__,
'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER,
}
cryptography_info = {'version': getattr(cryptography, '__version__', '')}
idna_info = {'version': getattr(idna, '__version__', '')}
# OPENSSL_VERSION_NUMBER doesn't exist in the Python 2.6 ssl module.
system_ssl = getattr(ssl, 'OPENSSL_VERSION_NUMBER', None)
system_ssl_info = {
'version': '%x' % system_ssl if system_ssl is not None else ''
}
return {
'platform': platform_info,
'implementation': implementation_info,
'system_ssl': system_ssl_info,
'using_pyopenssl': pyopenssl is not None,
'pyOpenSSL': pyopenssl_info,
'urllib3': urllib3_info,
'chardet': chardet_info,
'cryptography': cryptography_info,
'idna': idna_info,
'requests': {'version': requests_version},
}
def main():
"""Pretty-print the bug information as JSON."""
print(json.dumps(info(), sort_keys=True, indent=2))
if __name__ == '__main__':
main()

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.hooks
~~~~~~~~~~~~~~
@@ -15,14 +14,15 @@ HOOKS = ['response']
def default_hooks():
return dict((event, []) for event in HOOKS)
return {event: [] for event in HOOKS}
# TODO: response is the only one
def dispatch_hook(key, hooks, hook_data, **kwargs):
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or dict()
hooks = hooks or {}
hooks = hooks.get(key)
if hooks:
if hasattr(hooks, '__call__'):

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
import sys
# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)
import urllib3
sys.modules['requests.packages.urllib3'] = urllib3
import idna
sys.modules['requests.packages.idna'] = idna
import chardet
sys.modules['requests.packages.chardet'] = chardet
# Kinda cool, though, right?

495
requests/sessions.py Executable file → Normal file
View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.session
~~~~~~~~~~~~~~~~
@@ -10,28 +9,42 @@ requests (cookies, auth, proxies).
import os
import platform
import time
from collections import Mapping
from collections import Mapping, OrderedDict
from datetime import timedelta
from urllib3._collections import RecentlyUsedContainer
from .auth import _basic_auth_str
from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse
from .basics import cookielib, urljoin, urlparse, str
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
cookiejar_from_dict,
extract_cookies_to_jar,
RequestsCookieJar,
merge_cookies,
_copy_cookie_jar,
)
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
from .hooks import default_hooks, dispatch_hook
from ._internal_utils import to_native_string
from .utils import to_key_val_list, default_headers
from .exceptions import (
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
TooManyRedirects,
InvalidScheme,
ChunkedEncodingError,
ConnectionError,
ContentDecodingError,
InvalidHeader,
)
from .structures import CaseInsensitiveDict
from .adapters import HTTPAdapter
from .utils import (
requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
get_auth_from_url, rewind_body
requote_uri,
get_environ_proxies,
get_netrc_auth,
should_bypass_proxies,
get_auth_from_url,
is_valid_location,
rewind_body,
)
from .status_codes import codes
@@ -39,11 +52,9 @@ from .status_codes import codes
# formerly defined here, reexposed here for backward compatibility
from .models import REDIRECT_STATI
REDIRECT_CACHE_SIZE = 1000
# Preferred clock, based on which one is more accurate on a given system.
if platform.system() == 'Windows':
try: # Python 3.3+
try: # Python 3.4+
preferred_clock = time.perf_counter
except AttributeError: # Earlier than Python 3.
preferred_clock = time.clock
@@ -54,9 +65,8 @@ else:
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
"""Determines appropriate setting for a given request, taking into account
the explicit setting on that request, and the setting in the session. If a
setting is a dictionary, they will be merged together using `dict_class`
setting is a dictionary, they will be merged together using `dict_class`.
"""
if session_setting is None:
return request_setting
@@ -65,20 +75,18 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
# Bypass if not a dictionary (e.g. verify)
if not (
isinstance(session_setting, Mapping) and
isinstance(request_setting, Mapping)
isinstance(session_setting, Mapping) and
isinstance(request_setting, Mapping)
):
return request_setting
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
# Remove keys that are set to None. Extract keys first to avoid altering
# the dictionary during iteration.
none_keys = [k for (k, v) in merged_setting.items() if v is None]
for key in none_keys:
del merged_setting[key]
return merged_setting
@@ -99,96 +107,116 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
class SessionRedirectMixin(object):
def get_redirect_target(self, resp):
def get_redirect_target(self, response):
"""Receives a Response. Returns a redirect URI or ``None``"""
if resp.is_redirect:
location = resp.headers['location']
# Due to the nature of how requests processes redirects this method will
# be called at least once upon the original response and at least twice
# on each subsequent redirect response (if any).
# If a custom mixin is used to handle this logic, it may be advantageous
# to cache the redirect location onto the response object as a private
# attribute.
if response.is_redirect:
if not is_valid_location(response):
raise InvalidHeader(
'Response contains multiple Location headers. '
'Unable to perform redirect.'
)
location = response.headers['location']
# Currently the underlying http module on py3 decode headers
# in latin1, but empirical evidence suggests that latin1 is very
# rarely used with non-ASCII characters in HTTP headers.
# It is more likely to get UTF8 header rather than latin1.
# This causes incorrect handling of UTF8 encoded location headers.
# To solve this, we re-encode the location in latin1.
if is_py3:
location = location.encode('latin1')
location = location.encode('latin1')
return to_native_string(location, 'utf8')
return None
def resolve_redirects(self, resp, req, stream=False, timeout=None,
verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):
"""Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history
url = self.get_redirect_target(resp)
while url:
prepared_request = req.copy()
# Update history and keep track of redirects.
# resp.history must ignore the original request in this loop
hist.append(resp)
resp.history = hist[1:]
def resolve_redirects(
self,
response,
request,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
yield_requests=False,
**adapter_kwargs,
):
"""Given a Response, yields Responses until 'Location' header-based
redirection ceases, or the Session.max_redirects limit has been
reached.
"""
history = [
response
] # keep track of history; seed it with the original response
location_url = self.get_redirect_target(response)
while location_url:
prepared_request = request.copy()
try:
resp.content # Consume socket so it can be released
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects:
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
response.content # Consume socket so it can be released
except (
ChunkedEncodingError,
ConnectionError,
ContentDecodingError,
RuntimeError,
):
response.raw.read(decode_content=False)
if len(response.history) >= self.max_redirects:
raise TooManyRedirects(
'Exceeded %s redirects.' % self.max_redirects,
response=response,
)
# Release the connection back into the pool.
resp.close()
response.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'):
parsed_rurl = urlparse(resp.url)
url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
if location_url.startswith('//'):
parsed_rurl = urlparse(response.url)
location_url = '%s:%s' % (
to_native_string(parsed_rurl.scheme), location_url
)
# The scheme should be lower case...
parsed = urlparse(url)
url = parsed.geturl()
parsed = urlparse(location_url)
location_url = parsed.geturl()
# Facilitate relative 'location' headers, as allowed by RFC 7231.
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
# Compliant with RFC3986, we percent encode the url.
if not parsed.netloc:
url = urljoin(resp.url, requote_uri(url))
location_url = urljoin(response.url, requote_uri(location_url))
else:
url = requote_uri(url)
prepared_request.url = to_native_string(url)
# Cache the url, unless it redirects to itself.
if resp.is_permanent_redirect and req.url != prepared_request.url:
self.redirect_cache[req.url] = prepared_request.url
self.rebuild_method(prepared_request, resp)
# https://github.com/kennethreitz/requests/issues/1084
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
# https://github.com/kennethreitz/requests/issues/3490
purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')
location_url = requote_uri(location_url)
prepared_request.url = to_native_string(location_url)
method_changed = self.rebuild_method(prepared_request, response)
# https://github.com/kennethreitz/requests/issues/2590
# If method is changed to GET we need to remove body and associated headers.
if method_changed and prepared_request.method == 'GET':
# https://github.com/requests/requests/issues/3490
purged_headers = (
'Content-Length', 'Content-Type', 'Transfer-Encoding'
)
for header in purged_headers:
prepared_request.headers.pop(header, None)
prepared_request.body = None
headers = prepared_request.headers
try:
del headers['Cookie']
except KeyError:
pass
# Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared
# request, use the old one that we haven't yet touched.
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
extract_cookies_to_jar(
prepared_request._cookies, request, response.raw
)
merge_cookies(prepared_request._cookies, self.cookies)
prepared_request.prepare_cookies(prepared_request._cookies)
# Rebuild auth and proxy information.
proxies = self.rebuild_proxies(prepared_request, proxies)
self.rebuild_auth(prepared_request, resp)
self.rebuild_auth(prepared_request, response)
# A failed tell() sets `_body_position` to `object()`. This non-None
# value ensures `rewindable` will be True, allowing us to raise an
# UnrewindableBodyError, instead of hanging the connection.
@@ -196,65 +224,63 @@ class SessionRedirectMixin(object):
prepared_request._body_position is not None and
('Content-Length' in headers or 'Transfer-Encoding' in headers)
)
# Attempt to rewind consumed file-like object.
if rewindable:
rewind_body(prepared_request)
# Override the original request.
req = prepared_request
request = prepared_request
if yield_requests:
yield req
else:
yield request
resp = self.send(
req,
else:
response = self.send(
request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
allow_redirects=False,
**adapter_kwargs
**adapter_kwargs,
)
# copy our history tracker into the response
response.history = history[:]
# append the new response to the history tracker for the next iteration
history.append(response)
extract_cookies_to_jar(
self.cookies, prepared_request, response.raw
)
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
# extract redirect url, if any, for the next loop
url = self.get_redirect_target(resp)
yield resp
location_url = self.get_redirect_target(response)
yield response
def rebuild_auth(self, prepared_request, response):
"""When being redirected we may want to strip authentication from the
request to avoid leaking credentials. This method intelligently removes
request to avoid leaking credentials. This method intelligently
removes
and reapplies authentication where possible to avoid credential loss.
"""
headers = prepared_request.headers
url = prepared_request.url
if 'Authorization' in headers:
# If we get redirected to a new host, we should strip out any
# authentication headers.
original_parsed = urlparse(response.request.url)
redirect_parsed = urlparse(url)
if (original_parsed.hostname != redirect_parsed.hostname):
del headers['Authorization']
# .netrc might have more auth for us on our new host.
new_auth = get_netrc_auth(url) if self.trust_env else None
if new_auth is not None:
prepared_request.prepare_auth(new_auth)
return
def rebuild_proxies(self, prepared_request, proxies):
"""This method re-evaluates the proxy configuration by considering the
environment variables. If we are redirected to a URL covered by
NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
proxy keys for this URL (in case they were stripped by a previous
redirect).
"""This method re-evaluates the proxy configuration by
considering the environment variables. If we are redirected to a
URL covered by NO_PROXY, we strip the proxy configuration.
Otherwise, we set missing proxy keys for this URL (in case they
were stripped by a previous redirect).
This method also replaces the Proxy-Authorization header where
necessary.
@@ -267,50 +293,46 @@ class SessionRedirectMixin(object):
scheme = urlparse(url).scheme
new_proxies = proxies.copy()
no_proxy = proxies.get('no_proxy')
bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
if self.trust_env and not bypass_proxy:
environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
if proxy:
new_proxies.setdefault(scheme, proxy)
if 'Proxy-Authorization' in headers:
del headers['Proxy-Authorization']
try:
username, password = get_auth_from_url(new_proxies[scheme])
except KeyError:
username, password = None, None
if username and password:
headers['Proxy-Authorization'] = _basic_auth_str(username, password)
headers['Proxy-Authorization'] = _basic_auth_str(
username, password
)
return new_proxies
def rebuild_method(self, prepared_request, response):
"""When being redirected we may want to change the method of the request
based on certain specs or browser behavior.
"""
method = prepared_request.method
:rtype bool:
:return: boolean expressing if the method changed during rebuild.
"""
method = original_method = prepared_request.method
# http://tools.ietf.org/html/rfc7231#section-6.4.4
if response.status_code == codes.see_other and method != 'HEAD':
method = 'GET'
# Do what the browsers do, despite standards...
# First, turn 302s into GETs.
if response.status_code == codes.found and method != 'HEAD':
# If a POST is responded to with a 301 or 302, turn it into a GET. This has
# become a common pattern in browsers and was introduced into later versions
# of HTTP RFCs. While some browsers transform other methods to GET, little of
# that has been standardized. For that reason, we're using curl as a model
# which only supports POST->GET.
if response.status_code in (
codes.found, codes.moved
) and method == 'POST':
method = 'GET'
# Second, if a POST is responded to with a 301, turn it into a GET.
# This bizarre behaviour is explained in Issue 1704.
if response.status_code == codes.moved and method == 'POST':
method = 'GET'
prepared_request.method = method
return method != original_method
class Session(SessionRedirectMixin):
@@ -331,71 +353,67 @@ class Session(SessionRedirectMixin):
>>> s.get('http://httpbin.org/get')
<Response [200]>
"""
__attrs__ = [
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
__slots__ = [
'headers',
'cookies',
'auth',
'proxies',
'hooks',
'params',
'verify',
'cert',
'prefetch',
'adapters',
'stream',
'trust_env',
'max_redirects',
]
def __init__(self):
__slots__
#: A case-insensitive dictionary of headers to be sent on each
def __init__(self):
# : A case-insensitive dictionary of headers to be sent on each
#: :class:`Request <Request>` sent from this
#: :class:`Session <Session>`.
self.headers = default_headers()
#: Default Authentication tuple or object to attach to
# : Default Authentication tuple or object to attach to
#: :class:`Request <Request>`.
self.auth = None
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
# : Dictionary mapping protocol or protocol and host to the URL of the proxy
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
#: be used on each :class:`Request <Request>`.
self.proxies = {}
#: Event-handling hooks.
# : Event-handling hooks.
self.hooks = default_hooks()
#: Dictionary of querystring data to attach to each
# : Dictionary of querystring data to attach to each
#: :class:`Request <Request>`. The dictionary values may be lists for
#: representing multivalued query parameters.
self.params = {}
#: Stream response content default.
# : Stream response content default.
self.stream = False
#: SSL Verification default.
# : SSL Verification default.
self.verify = True
#: SSL client certificate default, if String, path to ssl client
# : SSL client certificate default, if String, path to ssl client
#: cert file (.pem). If Tuple, ('cert', 'key') pair.
self.cert = None
#: Maximum number of redirects allowed. If the request exceeds this
# : Maximum number of redirects allowed. If the request exceeds this
#: limit, a :class:`TooManyRedirects` exception is raised.
#: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
#: 30.
self.max_redirects = DEFAULT_REDIRECT_LIMIT
#: Trust environment settings for proxy configuration, default
# : Trust environment settings for proxy configuration, default
#: authentication and similar.
self.trust_env = True
#: A CookieJar containing all currently outstanding cookies set on this
# : 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.
self.adapters = OrderedDict()
self.mount('https://', HTTPAdapter())
self.mount('http://', HTTPAdapter())
# Only store 1000 redirects to prevent using infinite memory
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
def __enter__(self):
return self
@@ -409,24 +427,20 @@ class Session(SessionRedirectMixin):
:class:`Session`.
:param request: :class:`Request` instance to prepare with this
session's settings.
Session's settings.
:rtype: requests.PreparedRequest
"""
cookies = request.cookies or {}
# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# Merge with session cookies
merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
session_cookies = _copy_cookie_jar(self.cookies)
merged_cookies = merge_cookies(session_cookies, 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(),
@@ -434,7 +448,9 @@ class Session(SessionRedirectMixin):
files=request.files,
data=request.data,
json=request.json,
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
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,
@@ -442,7 +458,10 @@ class Session(SessionRedirectMixin):
)
return p
def request(self, method, url,
def request(
self,
method,
url,
params=None,
data=None,
headers=None,
@@ -456,8 +475,9 @@ class Session(SessionRedirectMixin):
stream=None,
verify=None,
cert=None,
json=None):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
json=None,
):
"""Constructs a :class:`Request <Request>`, prepares it, and sends it.
Returns :class:`Response <Response>` object.
:param method: method for the new :class:`Request` object.
@@ -495,33 +515,26 @@ class Session(SessionRedirectMixin):
"""
# Create the Request.
req = Request(
method = method.upper(),
url = url,
headers = headers,
files = files,
data = data or {},
json = json,
params = params or {},
auth = auth,
cookies = cookies,
hooks = hooks,
method=method.upper(),
url=url,
headers=headers,
files=files,
data=data or {},
json=json,
params=params or {},
auth=auth,
cookies=cookies,
hooks=hooks,
)
prep = self.prepare_request(req)
proxies = proxies or {}
settings = self.merge_environment_settings(
prep.url, proxies, stream, verify, cert
)
# Send the request.
send_kwargs = {
'timeout': timeout,
'allow_redirects': allow_redirects,
}
send_kwargs = {'timeout': timeout, 'allow_redirects': allow_redirects}
send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs)
return resp
def get(self, url, **kwargs):
@@ -531,7 +544,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return self.request('GET', url, **kwargs)
@@ -542,7 +554,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return self.request('OPTIONS', url, **kwargs)
@@ -553,7 +564,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', False)
return self.request('HEAD', url, **kwargs)
@@ -566,7 +576,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request('POST', url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs):
@@ -577,7 +586,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request('PUT', url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs):
@@ -588,8 +596,7 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request('PATCH', url, data=data, **kwargs)
return self.request('PATCH', url, data=data, **kwargs)
def delete(self, url, **kwargs):
r"""Sends a DELETE request. Returns :class:`Response` object.
@@ -598,7 +605,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request('DELETE', url, **kwargs)
def send(self, request, **kwargs):
@@ -612,76 +618,52 @@ class Session(SessionRedirectMixin):
kwargs.setdefault('verify', self.verify)
kwargs.setdefault('cert', self.cert)
kwargs.setdefault('proxies', self.proxies)
# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
if isinstance(request, Request):
raise ValueError('You can only send PreparedRequests.')
# Set up variables needed for resolve_redirects and dispatching of hooks
# Set up variables needed for resolve_redirects and dispatching of
# hooks
allow_redirects = kwargs.pop('allow_redirects', True)
stream = kwargs.get('stream')
hooks = request.hooks
# Resolve URL in redirect cache, if available.
if allow_redirects:
checked_urls = set()
while request.url in self.redirect_cache:
checked_urls.add(request.url)
new_url = self.redirect_cache.get(request.url)
if new_url in checked_urls:
break
request.url = new_url
# Get the appropriate adapter to use
adapter = self.get_adapter(url=request.url)
# Start time (approximately) of the request
start = preferred_clock()
# Send the request
r = adapter.send(request, **kwargs)
# Total elapsed time of the request (approximately)
elapsed = preferred_clock() - start
r.elapsed = timedelta(seconds=elapsed)
# Response manipulation hooks
# Response manipulation hooks.
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.
gen = self.resolve_redirects(r, request, **kwargs)
# Resolve redirects if allowed.
# Resolve redirects, if allowed.
history = [resp for resp in gen] if allow_redirects else []
# Shuffle things around if there's history.
# If there is a history, replace ``r`` with the last response
if history:
# Insert the first (original) request at the start
history.insert(0, r)
# Get the last request made
r = history.pop()
r.history = history
# If redirects aren't being followed, store the response on the Request for Response.next().
if not allow_redirects:
try:
r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
r._next = next(
self.resolve_redirects(
r, request, yield_requests=True, **kwargs
)
)
except StopIteration:
pass
if not stream:
r.content
return r
def merge_environment_settings(self, url, proxies, stream, verify, cert):
@@ -690,28 +672,41 @@ class Session(SessionRedirectMixin):
:rtype: dict
"""
# Gather clues from the surrounding environment.
if self.trust_env:
# Set environment's proxies.
no_proxy = proxies.get('no_proxy') if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items():
proxies.setdefault(k, v)
# Look for requests environment configuration and be compatible
# with cURL.
if verify is True or verify is None:
verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
os.environ.get('CURL_CA_BUNDLE'))
# Merge all the kwargs.
proxies = merge_setting(proxies, self.proxies)
# Merge all the kwargs except for proxies.
stream = merge_setting(stream, self.stream)
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
return {'verify': verify, 'proxies': proxies, 'stream': stream,
'cert': cert}
# Gather clues from the surrounding environment.
# We do this after merging the Session values to make sure we don't
# accidentally exclude them.
if self.trust_env:
# Look for requests environment configuration and be compatible
# with cURL.
if verify is True or verify is None:
verify = (
os.environ.get('REQUESTS_CA_BUNDLE') or
os.environ.get('CURL_CA_BUNDLE') or
verify
)
# Now we handle proxies.
# Proxies need to be built up backwards. This is because None values
# can delete proxy information, which can then be re-added by a more
# specific layer. So we begin by getting the environment's proxies,
# then add the Session, then add the request.
no_proxy = proxies.get('no_proxy') if proxies is not None else None
if no_proxy is None:
no_proxy = self.proxies.get('no_proxy')
env_proxies = {}
if self.trust_env:
env_proxies = get_environ_proxies(url, no_proxy=no_proxy) or {}
new_proxies = merge_setting(self.proxies, env_proxies)
proxies = merge_setting(proxies, new_proxies)
return {
'verify': verify,
'proxies': proxies,
'stream': stream,
'cert': cert,
}
def get_adapter(self, url):
"""
@@ -720,49 +715,31 @@ class Session(SessionRedirectMixin):
:rtype: requests.adapters.BaseAdapter
"""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix):
return adapter
# Nothing matches :-/
raise InvalidSchema("No connection adapters were found for '%s'" % url)
raise InvalidScheme("No connection adapters were found for '%s'" % url)
def close(self):
"""Closes all adapters and as such the session"""
"""Closes all adapters and, as such, the Session."""
for v in self.adapters.values():
v.close()
def mount(self, prefix, adapter):
"""Registers a connection adapter to a prefix.
Adapters are sorted in descending order by key length.
Adapters are sorted in descending order by prefix length.
"""
self.adapters[prefix] = adapter
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
for key in keys_to_move:
self.adapters[key] = self.adapters.pop(key)
def __getstate__(self):
state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
state['redirect_cache'] = dict(self.redirect_cache)
state = {attr: getattr(self, attr, None) for attr in self.__slots__}
return state
def __setstate__(self, state):
redirect_cache = state.pop('redirect_cache', {})
for attr, value in state.items():
setattr(self, attr, value)
self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
for redirect, to in redirect_cache.items():
self.redirect_cache[redirect] = to
def session():
"""
Returns a :class:`Session` for context-management.
:rtype: Session
"""
return Session()

View File

@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
from .structures import LookupDict
_codes = {
# Informational.
100: ('continue',),
101: ('switching_protocols',),
@@ -20,7 +18,6 @@ _codes = {
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
208: ('already_reported',),
226: ('im_used',),
# Redirection.
300: ('multiple_choices',),
301: ('moved_permanently', 'moved', '\\o-'),
@@ -30,9 +27,8 @@ _codes = {
305: ('use_proxy',),
306: ('switch_proxy',),
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
308: ('permanent_redirect',
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
308: ('permanent_redirect', 'resume_incomplete', 'resume'),
# These 2 to be removed in 3.0
# Client Error.
400: ('bad_request', 'bad'),
401: ('unauthorized',),
@@ -41,7 +37,9 @@ _codes = {
404: ('not_found', '-o-'),
405: ('method_not_allowed', 'not_allowed'),
406: ('not_acceptable',),
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
407: (
'proxy_authentication_required', 'proxy_auth', 'proxy_authentication'
),
408: ('request_timeout', 'timeout'),
409: ('conflict',),
410: ('gone',),
@@ -50,7 +48,11 @@ _codes = {
413: ('request_entity_too_large',),
414: ('request_uri_too_large',),
415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
416: (
'requested_range_not_satisfiable',
'requested_range',
'range_not_satisfiable',
),
417: ('expectation_failed',),
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
421: ('misdirected_request',),
@@ -67,7 +69,6 @@ _codes = {
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
451: ('unavailable_for_legal_reasons', 'legal_reasons'),
499: ('client_closed_request',),
# Server Error.
500: ('internal_server_error', 'server_error', '/o\\', ''),
501: ('not_implemented',),
@@ -79,13 +80,15 @@ _codes = {
507: ('insufficient_storage',),
509: ('bandwidth_limit_exceeded', 'bandwidth'),
510: ('not_extended',),
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
511: (
'network_authentication_required',
'network_auth',
'network_authentication',
),
}
codes = LookupDict(name='status_codes')
for code, titles in _codes.items():
for title in titles:
for title in titles: # type: ignore
setattr(codes, title, code)
if not title.startswith(('\\', '/')):
setattr(codes, title.upper(), code)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.structures
~~~~~~~~~~~~~~~~~~~
@@ -9,7 +8,7 @@ Data structures that power Requests.
import collections
from .compat import OrderedDict
from .basics import basestring, OrderedDict
class CaseInsensitiveDict(collections.MutableMapping):
@@ -38,9 +37,10 @@ class CaseInsensitiveDict(collections.MutableMapping):
operations are given keys that have equal ``.lower()``s, the
behavior is undefined.
"""
__slots__ = ('_store')
def __init__(self, data=None, **kwargs):
self._store = OrderedDict()
self._store = collections.OrderedDict()
if data is None:
data = {}
self.update(data, **kwargs)
@@ -65,9 +65,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
def lower_items(self):
"""Like iteritems(), but with all lowercase keys."""
return (
(lowerkey, keyval[1])
for (lowerkey, keyval)
in self._store.items()
(lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()
)
def __eq__(self, other):
@@ -75,9 +73,11 @@ class CaseInsensitiveDict(collections.MutableMapping):
other = CaseInsensitiveDict(other)
else:
return NotImplemented
# Compare insensitively
return dict(self.lower_items()) == dict(other.lower_items())
# Copy is required
def copy(self):
return CaseInsensitiveDict(self._store.values())
@@ -86,6 +86,128 @@ class CaseInsensitiveDict(collections.MutableMapping):
return str(dict(self.items()))
class HTTPHeaderDict(CaseInsensitiveDict):
"""A case-insensitive ``dict``-like object suitable for HTTP headers that
supports multiple values with the same key, via the ``add``, ``extend``,
``multiget`` and ``multiset`` methods.
"""
def __init__(self, data=None, **kwargs):
super(HTTPHeaderDict, self).__init__()
self.extend({} if data is None else data, **kwargs)
# We'll store tuples in the internal dictionary, but present them as a
# concatenated string when we use item access methods.
#
def __setitem__(self, key, val):
# Specialcase null values.
if (not isinstance(val, basestring)) and (val is not None):
raise ValueError('only string-type values (or None) are allowed')
super(HTTPHeaderDict, self).__setitem__(key, (val,))
def __getitem__(self, key):
val = super(HTTPHeaderDict, self).__getitem__(key)
# Specialcase null values.
if len(val) == 1 and val[0] is None:
return val[0]
return ', '.join(val)
def lower_items(self):
return (
(lk, ', '.join(vals)) for (lk, (k, vals)) in self._store.items()
)
def copy(self):
return type(self)(self)
def getlist(self, key):
"""Returns a list of all the values for the named field. Returns an
empty list if the key isn't present in the dictionary."""
return list(self._store.get(key.lower(), (None, []))[1])
def setlist(self, key, values):
"""Set a sequence of strings to the associated key - this will overwrite
any previously stored value."""
if not isinstance(values, (list, tuple)):
raise ValueError('argument is not sequence')
if any(not isinstance(v, basestring) for v in values):
raise ValueError('non-string items in sequence')
if not values:
self.pop(key, None)
return
super(HTTPHeaderDict, self).__setitem__(key, tuple(values))
def _extend(self, key, values):
new_value_tpl = key, values
# Inspired by urllib3's implementation - use one call which should be
# suitable for the common case.
old_value_tpl = self._store.setdefault(key.lower(), new_value_tpl)
if old_value_tpl is not new_value_tpl:
old_key, old_values = old_value_tpl
self._store[key.lower()] = (old_key, old_values + values)
def add(self, key, val):
"""Adds a key, value pair to this dictionary - if there is already a
value for this key, then the value will be appended to those values.
"""
if not isinstance(val, basestring):
raise ValueError('value must be a string-type object')
self._extend(key, (val,))
def extend(self, *args, **kwargs):
"""Like update, but will add values to existing sequences rather than
replacing them. You can pass a mapping object or a sequence of two
tuples - values in these objects can be strings or sequence of strings.
"""
if len(args) > 1:
raise TypeError(
f"extend() takes at most 1 positional "
"arguments ({len(args)} given)"
)
for other in args + (kwargs,):
if isinstance(other, collections.Mapping):
# See if looks like a HTTPHeaderDict (either urllib3's
# implementation or ours). If so, then we have to add values
# in one go for each key.
multiget = getattr(other, 'getlist', None)
if multiget:
for key in other:
self._extend(key, tuple(multiget(key)))
continue
# Otherwise, just walk over items to get them.
item_seq = other.items()
else:
item_seq = other
for ik, iv in item_seq:
if isinstance(iv, basestring):
self._extend(ik, (iv,))
elif any(not isinstance(v, basestring) for v in iv):
raise ValueError('non-string items in sequence')
else:
self._extend(ik, tuple(iv))
@property
def _as_dict(self):
"""A dictionary representation of the HTTPHeaderDict."""
d = {}
for k, vals in self._store.values():
d[k] = vals[0] if len(vals) == 1 else vals
return d
def __repr__(self):
return repr(self._as_dict)
class LookupDict(dict):
"""Dictionary lookup object."""
@@ -94,12 +216,14 @@ class LookupDict(dict):
super(LookupDict, self).__init__()
def __repr__(self):
return '<lookup \'%s\'>' % (self.name)
return f'<lookup \'{self.name}\'>'
def __getitem__(self, key):
# We allow fall-through here, so values default to None
return self.__dict__.get(key, None)
def __iter__(self):
return super(LookupDict, self).__dir__()
def get(self, key, default=None):
return self.__dict__.get(key, default)

72
requests/types.py Normal file
View File

@@ -0,0 +1,72 @@
from typing import (
Callable,
Optional,
Union,
Any,
Iterable,
List,
Mapping,
MutableMapping,
Tuple,
IO,
Text,
Type,
Dict,
)
from .import auth
from .models import Response, PreparedRequest
from .cookies import RequestsCookieJar
from .sessions import Session
_ParamsMappingValueType = Union[
str, bytes, int, float, Iterable[Union[str, bytes, int, float]]
]
Params = Optional[
Union[
Mapping[Union[str, bytes, int, float], _ParamsMappingValueType],
Union[str, bytes],
Tuple[Union[str, bytes, int, float], _ParamsMappingValueType],
Mapping[str, _ParamsMappingValueType],
Mapping[bytes, _ParamsMappingValueType],
Mapping[int, _ParamsMappingValueType],
Mapping[float, _ParamsMappingValueType],
]
]
Data = Union[
None,
bytes,
MutableMapping[str, str],
MutableMapping[str, Text],
MutableMapping[Text, str],
MutableMapping[Text, Text],
Iterable[Tuple[str, str]],
IO,
]
_Hook = Callable[[Response], Any]
Method = str
URL = str
Headers = Optional[Union[None, MutableMapping[Text, Text]]]
Cookies = Optional[Union[None, RequestsCookieJar, MutableMapping[Text, Text]]]
Files = Optional[MutableMapping[Text, IO]]
Auth = Union[
None,
Tuple[Text, Text],
auth.AuthBase,
Callable[[PreparedRequest], PreparedRequest],
]
Timeout = Union[None, float, Tuple[float, float]]
AllowRedirects = Optional[bool]
Proxies = Optional[MutableMapping[Text, Text]]
Hooks = Optional[MutableMapping[Text, Union[Iterable[_Hook], _Hook]]]
Stream = Optional[bool]
Verify = Union[None, bool, Text]
Cert = Union[Text, Tuple[Text, Text]]
JSON = Optional[MutableMapping]
Help = Dict
Host = str
Sequence = List
Filename = str
KeyValueList = List[Tuple[Text, Text]]
String = str
Boolean = Union[bool, None]

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.utils
~~~~~~~~~~~~~~
@@ -19,43 +18,57 @@ import re
import socket
import struct
import warnings
import typing
from .__version__ import __version__
from . import certs
# to_native_string is unused here, but imported here for backwards compatibility
from ._internal_utils import to_native_string
from .compat import parse_http_list as _parse_list_header
from .compat import (
quote, urlparse, bytes, str, OrderedDict, unquote, getproxies,
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
proxy_bypass_environment, getproxies_environment)
from .import certs
from .basics import parse_http_list as _parse_list_header
from .basics import (
quote,
urlparse,
bytes,
str,
unquote,
getproxies,
proxy_bypass,
urlunparse,
basestring,
integer_types,
proxy_bypass_environment,
getproxies_environment,
)
from .cookies import cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .structures import HTTPHeaderDict
from .cookies import RequestsCookieJar
from .exceptions import (
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError)
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError
)
NETRC_FILES = ('.netrc', '_netrc')
DEFAULT_CA_BUNDLE_PATH = certs.where()
if platform.system() == 'Windows':
# provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host):
if is_py3:
import winreg
else:
import _winreg as winreg
# provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host: str) -> bool:
import winreg # typing: ignore
try:
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
proxyEnable = winreg.QueryValueEx(internetSettings,
'ProxyEnable')[0]
proxyOverride = winreg.QueryValueEx(internetSettings,
'ProxyOverride')[0]
internetSettings = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings',
)
proxyEnable = winreg.QueryValueEx(internetSettings, 'ProxyEnable')[
0
]
proxyOverride = winreg.QueryValueEx(
internetSettings, 'ProxyOverride'
)[
0
]
except OSError:
return False
if not proxyEnable or not proxyOverride:
return False
@@ -68,14 +81,16 @@ if platform.system() == 'Windows':
if test == '<local>':
if '.' not in host:
return True
test = test.replace(".", r"\.") # mask dots
test = test.replace("*", r".*") # change glob sequence
test = test.replace("?", r".") # change glob char
test = test.replace(".", r"\.") # mask dots
test = test.replace("*", r".*") # change glob sequence
test = test.replace("?", r".") # change glob char
if re.match(test, host, re.I):
return True
return False
def proxy_bypass(host): # noqa
def proxy_bypass(host: str) -> bool: # noqa
"""Return True, if the host should be bypassed.
Checks proxy settings gathered from the environment, if specified,
@@ -83,29 +98,30 @@ if platform.system() == 'Windows':
"""
if getproxies_environment():
return proxy_bypass_environment(host)
else:
return proxy_bypass_registry(host)
def dict_to_sequence(d):
def dict_to_sequence(
d: dict
) -> typing.Union[
typing.Optional[typing.ItemsView[typing.Any, typing.Any]], dict
]:
"""Returns an internal sequence dictionary update."""
if hasattr(d, 'items'):
d = d.items()
return d.items()
return d
def super_len(o):
def super_len(o) -> int:
total_length = None
current_position = 0
if hasattr(o, '__len__'):
total_length = len(o)
elif hasattr(o, 'len'):
total_length = o.len
elif hasattr(o, 'fileno'):
try:
fileno = o.fileno()
@@ -113,20 +129,20 @@ def super_len(o):
pass
else:
total_length = os.fstat(fileno).st_size
# Having used fstat to determine the file length, we need to
# confirm that this file was opened up in binary mode.
if 'b' not in o.mode:
warnings.warn((
"Requests has determined the content-length for this "
"request using the binary size of the file: however, the "
"file has been opened in text mode (i.e. without the 'b' "
"flag in the mode). This may lead to an incorrect "
"content-length. In Requests 3.0, support will be removed "
"for files in text mode."),
FileModeWarning
warnings.warn(
(
"Requests has determined the content-length for this "
"request using the binary size of the file: however, the "
"file has been opened in typing.Text mode (i.e. without the 'b' "
"flag in the mode). This may lead to an incorrect "
"content-length. In Requests 3.0, support will be removed "
"for files in typing.Text mode."
),
FileModeWarning,
)
if hasattr(o, 'tell'):
try:
current_position = o.tell()
@@ -144,35 +160,32 @@ def super_len(o):
# seek to end of file
o.seek(0, 2)
total_length = o.tell()
# seek back to current position to support
# partially read file-like objects
o.seek(current_position or 0)
except (OSError, IOError):
total_length = 0
if total_length is None:
total_length = 0
return max(0, total_length - current_position)
def get_netrc_auth(url, raise_errors=False):
def get_netrc_auth(
url: str, raise_errors: bool = False
) -> typing.Optional[typing.Tuple[typing.Text, typing.Text]]:
"""Returns the Requests tuple auth for a given url from netrc."""
try:
from netrc import netrc, NetrcParseError
netrc_path = None
for f in NETRC_FILES:
try:
loc = os.path.expanduser('~/{0}'.format(f))
loc = os.path.expanduser(f'~/{f}')
except KeyError:
# os.path.expanduser can fail when $HOME is undefined and
# getpwuid fails. See http://bugs.python.org/issue20164 &
# https://github.com/kennethreitz/requests/issues/1846
return
# https://github.com/requests/requests/issues/1846
return None
if os.path.exists(loc):
netrc_path = loc
@@ -180,23 +193,17 @@ def get_netrc_auth(url, raise_errors=False):
# Abort early if there isn't one.
if netrc_path is None:
return
return None
ri = urlparse(url)
# Strip port numbers from netloc. This weird `if...encode`` dance is
# used for Python 3.2, which doesn't support unicode literals.
splitstr = b':'
if isinstance(url, str):
splitstr = splitstr.decode('ascii')
host = ri.netloc.split(splitstr)[0]
host = ri.netloc.split(':')[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
if _netrc:
# Return with login / password
login_i = (0 if _netrc[0] else 1)
return (_netrc[login_i], _netrc[2])
except (NetrcParseError, IOError):
# If there was a parsing error or a permissions issue reading the file,
# we'll just skip netrc auth unless explicitly asked to raise errors.
@@ -206,13 +213,18 @@ def get_netrc_auth(url, raise_errors=False):
# AppEngine hackiness.
except (ImportError, AttributeError):
pass
return None
def guess_filename(obj):
def guess_filename(obj) -> str:
"""Tries to guess the filename of the given object."""
name = getattr(obj, 'name', None)
if (name and isinstance(name, basestring) and name[0] != '<' and
name[-1] != '>'):
if (
name and
isinstance(name, basestring) and
name[0] != '<' and
name[-1] != '>'
):
return os.path.basename(name)
@@ -238,10 +250,12 @@ def from_key_val_list(value):
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
return OrderedDict(value)
return collections.OrderedDict(value)
def to_key_val_list(value):
def to_key_val_list(
value
) -> typing.List[typing.Tuple[typing.Text, typing.Text]]:
"""Take an object and test to see if it can be represented as a
dictionary. If it can be, return a list of tuples, e.g.,
@@ -264,12 +278,13 @@ def to_key_val_list(value):
if isinstance(value, collections.Mapping):
value = value.items()
return list(value)
# From mitsuhiko/werkzeug (used with permission).
def parse_list_header(value):
def parse_list_header(value: str) -> typing.List[typing.Text]:
"""Parse lists as described by RFC 2068 Section 2.
In particular, parse comma-separated lists where the elements of
@@ -300,8 +315,10 @@ def parse_list_header(value):
return result
# From mitsuhiko/werkzeug (used with permission).
def parse_dict_header(value):
def parse_dict_header(value) -> dict:
"""Parse lists of key, value pairs as described by RFC 2068 Section 2 and
convert them into a python dict:
@@ -323,11 +340,12 @@ def parse_dict_header(value):
:return: :class:`dict`
:rtype: dict
"""
result = {}
result = {} # type: dict
for item in _parse_list_header(value):
if '=' not in item:
result[item] = None
continue
name, value = item.split('=', 1)
if value[:1] == value[-1:] == '"':
value = unquote_header_value(value[1:-1])
@@ -335,8 +353,10 @@ def parse_dict_header(value):
return result
# From mitsuhiko/werkzeug (used with permission).
def unquote_header_value(value, is_filename=False):
def unquote_header_value(value: str, is_filename: bool = False):
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
This does not use the real unquoting but what browsers are actually
using for quoting.
@@ -350,7 +370,6 @@ def unquote_header_value(value, is_filename=False):
# probably some other browsers as well. IE for example is
# uploading files with "C:\foo\bar.txt" as filename
value = value[1:-1]
# if this is a filename and the starting characters look like
# a UNC path, then just return the value without quotes. Using the
# replace sequence below on a UNC path has the effect of turning
@@ -358,69 +377,70 @@ def unquote_header_value(value, is_filename=False):
# _fix_ie_filename() doesn't work correctly. See #458.
if not is_filename or value[:2] != '\\\\':
return value.replace('\\\\', '\\').replace('\\"', '"')
return value
def dict_from_cookiejar(cj):
def dict_from_cookiejar(cj: RequestsCookieJar) -> dict:
"""Returns a key/value dictionary from a CookieJar.
:param cj: CookieJar object to extract cookies from.
:rtype: dict
"""
cookie_dict = {}
for cookie in cj:
cookie_dict[cookie.name] = cookie.value
return cookie_dict
def add_dict_to_cookiejar(cj, cookie_dict):
def add_dict_to_cookiejar(
cj: RequestsCookieJar, cookie_dict: dict
) -> RequestsCookieJar:
"""Returns a CookieJar from a key/value dictionary.
:param cj: CookieJar to insert cookies into.
:param cookie_dict: Dict of key/values to insert into CookieJar.
:rtype: CookieJar
"""
return cookiejar_from_dict(cookie_dict, cj)
def get_encodings_from_content(content):
def get_encodings_from_content(content: str) -> typing.List[str]:
"""Returns encodings from given content string.
:param content: bytestring to extract encodings from.
"""
warnings.warn((
'In requests 3.0, get_encodings_from_content will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
warnings.warn(
(
'In requests 3.0, get_encodings_from_content will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'
),
DeprecationWarning,
)
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(
r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I
)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
return (charset_re.findall(content) +
pragma_re.findall(content) +
xml_re.findall(content))
return (
charset_re.findall(content) +
pragma_re.findall(content) +
xml_re.findall(content)
)
def get_encoding_from_headers(headers):
def get_encoding_from_headers(headers: typing.MutableMapping) -> str:
"""Returns encodings from given HTTP Header Dict.
:param headers: dictionary to extract encoding from.
:rtype: str
"""
content_type = headers.get('content-type')
content_type = headers.get('Content-Type')
if not content_type:
return None
content_type, params = cgi.parse_header(content_type)
if 'charset' in params:
return params['charset'].strip("'\"")
@@ -430,17 +450,12 @@ def get_encoding_from_headers(headers):
def stream_decode_response_unicode(iterator, r):
"""Stream decodes a iterator."""
if r.encoding is None:
for item in iterator:
yield item
return
decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace')
for chunk in iterator:
rv = decoder.decode(chunk)
if rv:
yield rv
rv = decoder.decode(b'', final=True)
if rv:
yield rv
@@ -452,59 +467,38 @@ def iter_slices(string, slice_length):
if slice_length is None or slice_length <= 0:
slice_length = len(string)
while pos < len(string):
yield string[pos:pos + slice_length]
yield string[pos: pos + slice_length]
pos += slice_length
def get_unicode_from_response(r):
"""Returns the requested content back in unicode.
:param r: Response object to get unicode content from.
Tried:
1. charset from content-type
2. fall back and replace all unicode characters
:rtype: str
"""
warnings.warn((
'In requests 3.0, get_unicode_from_response will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
tried_encodings = []
# Try charset from content-type
encoding = get_encoding_from_headers(r.headers)
if encoding:
try:
return str(r.content, encoding)
except UnicodeError:
tried_encodings.append(encoding)
# Fall back:
try:
return str(r.content, encoding, errors='replace')
except TypeError:
return r.content
# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789-._~")
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)
def unquote_unreserved(uri):
def unquote_unreserved(uri: str) -> str:
"""Un-escape any percent-escape sequences in a URI that are unreserved
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
:rtype: str
"""
parts = uri.split('%')
# This convert function is used to optionally convert the output of `chr`.
# In Python 3, `chr` returns a unicode string, while in Python 2 it returns
# a bytestring. Here we deal with that by optionally converting.
def convert(is_bytes, c):
if is_bytes:
return c.encode('ascii')
else:
return c
# Handle both bytestrings and unicode strings.
splitchar = '%'
base = ''
parts = uri.split(splitchar)
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2 and h.isalnum():
@@ -514,15 +508,15 @@ def unquote_unreserved(uri):
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:]
parts[i] = convert(is_bytes=False, c=c) + parts[i][2:]
else:
parts[i] = '%' + parts[i]
parts[i] = splitchar + parts[i]
else:
parts[i] = '%' + parts[i]
return ''.join(parts)
parts[i] = splitchar + parts[i]
return base.join(parts)
def requote_uri(uri):
def requote_uri(uri: str) -> str:
"""Re-quote the given URI.
This function passes the given URI through an unquote/quote cycle to
@@ -537,6 +531,7 @@ def requote_uri(uri):
# Then quote only illegal characters (do not quote reserved,
# unreserved, or '%')
return quote(unquote_unreserved(uri), safe=safe_with_percent)
except InvalidURL:
# We couldn't unquote the given URI, so let's try quoting it, but
# there may be unquoted '%'s in the URI. We need to make sure they're
@@ -544,7 +539,7 @@ def requote_uri(uri):
return quote(uri, safe=safe_without_percent)
def address_in_network(ip, net):
def address_in_network(ip: str, net: str) -> bool:
"""This function allows you to check if an IP belongs to a network subnet
Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24
@@ -554,12 +549,14 @@ def address_in_network(ip, net):
"""
ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0]
netaddr, bits = net.split('/')
netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0]
netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[
0
]
network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask
return (ipaddr & netmask) == (network & netmask)
return ( ipaddr & netmask) == ( network & netmask)
def dotted_netmask(mask):
def dotted_netmask(mask: str) -> str:
"""Converts mask from /xx format to xxx.xxx.xxx.xxx
Example: if mask is 24 function returns 255.255.255.0
@@ -570,7 +567,7 @@ def dotted_netmask(mask):
return socket.inet_ntoa(struct.pack('>I', bits))
def is_ipv4_address(string_ip):
def is_ipv4_address(string_ip: str) -> bool:
"""
:rtype: bool
"""
@@ -578,10 +575,11 @@ def is_ipv4_address(string_ip):
socket.inet_aton(string_ip)
except socket.error:
return False
return True
def is_valid_cidr(string_network):
def is_valid_cidr(string_network: str) -> bool:
"""
Very simple check of the cidr format in no_proxy variable.
@@ -600,91 +598,85 @@ def is_valid_cidr(string_network):
socket.inet_aton(string_network.split('/')[0])
except socket.error:
return False
else:
return False
return True
@contextlib.contextmanager
def set_environ(env_name, value):
def set_environ(
env_name: str, value: typing.Optional[str]
) -> typing.Generator:
"""Set the environment variable 'env_name' to 'value'
Save previous value, yield, and then restore the previous value stored in
the environment variable 'env_name'.
If 'value' is None, do nothing"""
if value is not None:
value_changed = value is not None
if value_changed:
old_value = os.environ.get(env_name)
os.environ[env_name] = value
try:
yield
finally:
if value is None:
return
if old_value is None:
del os.environ[env_name]
else:
os.environ[env_name] = old_value
if value_changed:
if old_value is None:
del os.environ[env_name]
else:
os.environ[env_name] = old_value
def should_bypass_proxies(url, no_proxy):
def should_bypass_proxies(url: str, no_proxy: typing.Optional[str]) -> bool:
"""
Returns whether we should bypass proxies or not.
:rtype: bool
"""
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_arg = no_proxy
if no_proxy is None:
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 = (
host for host in no_proxy.replace(' ', '').split(',') if host
)
ip = netloc.split(':')[0]
if is_ipv4_address(ip):
for proxy_ip in no_proxy:
if is_valid_cidr(proxy_ip):
if address_in_network(ip, proxy_ip):
return True
elif ip == proxy_ip:
# If no_proxy ip was defined in plain IP notation instead of cidr notation &
# matches the IP of the index
return True
else:
for host in no_proxy:
if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
if netloc.endswith(host) or netloc.split(':')[0].endswith(
host
):
# The URL does match something in no_proxy, so we don't want
# to apply the proxies on this URL.
return True
# If the system proxy settings indicate that this URL should be bypassed,
# don't proxy.
# The proxy_bypass function is incredibly buggy on OS X in early versions
# of Python 2.6, so allow this call to fail. Only catch the specific
# exceptions we've seen, though: this call failing in other ways can reveal
# legitimate problems.
with set_environ('no_proxy', no_proxy_arg):
try:
bypass = proxy_bypass(netloc)
except (TypeError, socket.gaierror):
bypass = False
if bypass:
return True
return False
return bool(proxy_bypass(netloc))
def get_environ_proxies(url, no_proxy=None):
def get_environ_proxies(
url: str, no_proxy: typing.Optional[bool] = None
) -> dict:
"""
Return a dict of environment proxies.
@@ -692,11 +684,15 @@ def get_environ_proxies(url, no_proxy=None):
"""
if should_bypass_proxies(url, no_proxy=no_proxy):
return {}
else:
return getproxies()
def select_proxy(url, proxies):
def select_proxy(
url: str,
proxies: typing.Optional[typing.MutableMapping[typing.Text, typing.Text]],
):
"""Select a proxy for the url, if applicable.
:param url: The url being for the request
@@ -722,7 +718,7 @@ def select_proxy(url, proxies):
return proxy
def default_user_agent(name="python-requests"):
def default_user_agent(name: str = "python-requests") -> str:
"""
Return a string representing the default user agent.
@@ -731,38 +727,39 @@ def default_user_agent(name="python-requests"):
return '%s/%s' % (name, __version__)
def default_headers():
def default_headers() -> HTTPHeaderDict:
"""
:rtype: requests.structures.CaseInsensitiveDict
:rtype: requests.structures.HTTPHeaderDict
"""
return CaseInsensitiveDict({
'User-Agent': default_user_agent(),
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
'Accept': '*/*',
'Connection': 'keep-alive',
})
return HTTPHeaderDict(
{
'User-Agent': default_user_agent(),
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
'Accept': '*/*',
'Connection': 'keep-alive',
}
)
def parse_header_links(value):
"""Return a dict of parsed link headers proxies.
def parse_header_links(value: str) -> typing.List[typing.MutableMapping]:
"""Return a list of parsed link headers proxies.
i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"
:rtype: list
"""
links = []
links = [] # type: typing.List
replace_chars = ' \'"'
value = value.strip(replace_chars)
if not value:
return links
for val in re.split(', *<', value):
try:
url, params = val.split(';', 1)
except ValueError:
url, params = val, ''
link = {'url': url.strip('<> \'"')}
for param in params.split(';'):
try:
key, value = param.split('=')
@@ -770,19 +767,31 @@ def parse_header_links(value):
break
link[key.strip(replace_chars)] = value.strip(replace_chars)
links.append(link)
return links
def is_valid_location(response) -> bool:
"""Verify that multiple Location headers weren't
returned from the last response.
"""
headers = getattr(response.raw, 'headers', None)
if headers is not None:
getlist = getattr(headers, 'getlist', None)
if getlist is not None:
return len(getlist('location')) <= 1
# If response.raw isn't urllib3-like we can't reliably check this
return True
# Null bytes; no need to recreate these on each call to guess_json_utf
_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3
_null2 = _null * 2
_null3 = _null * 3
def guess_json_utf(data):
def guess_json_utf(data: bytes) -> typing.Optional[str]:
"""
:rtype: str
"""
@@ -791,59 +800,63 @@ def guess_json_utf(data):
# determine the encoding. Also detect a BOM, if present.
sample = data[:4]
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
return 'utf-32' # BOM included
return 'utf-32' # BOM included
if sample[:3] == codecs.BOM_UTF8:
return 'utf-8-sig' # BOM included, MS style (discouraged)
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
return 'utf-16' # BOM included
return 'utf-16' # BOM included
nullcount = sample.count(_null)
if nullcount == 0:
return 'utf-8'
if nullcount == 2:
if sample[::2] == _null2: # 1st and 3rd are null
if sample[::2] == _null2: # 1st and 3rd are null
return 'utf-16-be'
if sample[1::2] == _null2: # 2nd and 4th are null
return 'utf-16-le'
# Did not detect 2 valid UTF-16 ascii-range characters
# Did not detect 2 valid UTF-16 ascii-range characters
if nullcount == 3:
if sample[:3] == _null3:
return 'utf-32-be'
if sample[1:] == _null3:
return 'utf-32-le'
# Did not detect a valid UTF-32 ascii-range character
# Did not detect a valid UTF-32 ascii-range character
return None
def prepend_scheme_if_needed(url, new_scheme):
def prepend_scheme_if_needed(url: str, new_scheme: str) -> str:
"""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.
:rtype: str
"""
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
# 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))
def get_auth_from_url(url):
def get_auth_from_url(url: str) -> typing.Tuple[typing.Text, typing.Text]:
"""Given a url with authentication components, extract them into a tuple of
username,password.
:rtype: (str,str)
"""
parsed = urlparse(url)
try:
auth = (unquote(parsed.username), unquote(parsed.password))
except (AttributeError, TypeError):
auth = ('', '')
return auth
@@ -851,7 +864,10 @@ def get_auth_from_url(url):
_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$')
_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$')
def check_header_validity(header):
def check_header_validity(
header: typing.Tuple[typing.Text, typing.Text]
) -> None:
"""Verifies that header value is a string which doesn't contain
leading whitespace or return characters. This prevents unintended
header injection.
@@ -859,45 +875,61 @@ def check_header_validity(header):
:param header: tuple, in the format (name, value).
"""
name, value = header
if isinstance(value, bytes):
pat = _CLEAN_HEADER_REGEX_BYTE
else:
pat = _CLEAN_HEADER_REGEX_STR
pat = _CLEAN_HEADER_REGEX_STR
try:
if not pat.match(value):
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
raise InvalidHeader(
"Invalid return character or leading space in header: %s" %
name
)
except TypeError:
raise InvalidHeader("Header value %s must be of type str or bytes, "
"not %s" % (value, type(value)))
raise InvalidHeader(
"Value for header {%s: %s} must be of type str or "
"bytes, not %s" % (name, value, type(value))
)
def urldefragauth(url):
def urldefragauth(url: str) -> str:
"""
Given a url remove the fragment and the authentication part.
:rtype: str
"""
scheme, netloc, path, params, query, fragment = urlparse(url)
# see func:`prepend_scheme_if_needed`
if not netloc:
netloc, path = path, netloc
netloc = netloc.rsplit('@', 1)[-1]
return urlunparse((scheme, netloc, path, params, query, ''))
def rewind_body(prepared_request):
def rewind_body(prepared_request) -> None:
"""Move file pointer back to its recorded starting position
so it can be read again on redirect.
"""
body_seek = getattr(prepared_request.body, 'seek', None)
if body_seek is not None and isinstance(prepared_request._body_position, integer_types):
if body_seek is not None and isinstance(
prepared_request._body_position, integer_types
):
try:
body_seek(prepared_request._body_position)
except (IOError, OSError):
raise UnrewindableBodyError("An error occurred when rewinding request "
"body for redirect.")
raise UnrewindableBodyError(
"An error occurred when rewinding request "
"body for redirect."
)
else:
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
raise UnrewindableBodyError(
"Unable to rewind request body for redirect."
)
def is_stream(data: bytes) -> bool:
"""Given data, determines if it should be sent as a stream."""
is_iterable = getattr(data, '__iter__', False)
is_io_type = not isinstance(
data, (basestring, list, tuple, collections.Mapping)
)
return is_iterable and is_io_type

View File

@@ -1 +0,0 @@
-e .

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python
# Learn more: https://github.com/kennethreitz/setup.py
import os
import re
import sys
from codecs import open
@@ -11,12 +10,27 @@ from setuptools.command.test import test as TestCommand
here = os.path.abspath(os.path.dirname(__file__))
class Format(TestCommand):
user_options = []
def initialize_options(self):
TestCommand.initialize_options(self)
pass
def finalize_options(self):
pass
def run_tests(self):
os.system('white requests')
class PyTest(TestCommand):
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = []
self.pytest_args = ['-n', 'auto']
def finalize_options(self):
TestCommand.finalize_options(self)
@@ -29,32 +43,57 @@ class PyTest(TestCommand):
errno = pytest.main(self.pytest_args)
sys.exit(errno)
class MyPyTest(TestCommand):
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ['-n', 'auto', '--mypy', 'tests']
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
import pytest
errno = pytest.main(self.pytest_args)
sys.exit(errno)
# 'setup.py publish' shortcut.
if sys.argv[-1] == 'publish':
os.system('python setup.py sdist bdist_wheel')
os.system('twine upload dist/*')
sys.exit()
packages = ['requests']
requires = [
'chardet>=3.0.2,<3.1.0',
'idna>=2.5,<2.6',
'urllib3>=1.21.1,<1.22',
'certifi>=2017.4.17'
'idna>=2.5,<2.7',
'urllib3>=1.21.1,<1.23',
'certifi>=2017.4.17',
'rfc3986>=1.1.0<2',
]
test_requirements = [
'pytest-httpbin==0.0.7',
'pytest-cov',
'pytest-mock',
'pytest-xdist',
'PySocks>=1.5.6, !=1.5.7',
'pytest>=2.8.0',
'pytest-mypy',
'mypy',
'white',
]
test_requirements = ['pytest>=2.8.0', 'pytest-httpbin==0.0.7', 'pytest-cov', 'pytest-mock']
about = {}
with open(os.path.join(here, 'requests', '__version__.py')) as f:
exec(f.read(), about)
with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f:
exec (f.read(), about)
with open('README.rst', 'r', 'utf-8') as f:
readme = f.read()
with open('HISTORY.rst', 'r', 'utf-8') as f:
history = f.read()
setup(
name=about['__title__'],
version=about['__version__'],
@@ -76,22 +115,20 @@ setup(
'Natural Language :: English',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy'
'Programming Language :: Python :: Implementation :: PyPy',
),
cmdclass={'test': PyTest},
cmdclass={'test': PyTest, 'mypy': MyPyTest, 'format': Format},
tests_require=test_requirements,
extras_require={
'security': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'],
'socks': ['PySocks>=1.5.6, !=1.5.7'],
'socks:sys_platform == "win32" and (python_version == "2.7" or python_version == "2.6")': ['win_inet_pton'],
'socks:sys_platform == "win32" and python_version == "2.7"': [
'win_inet_pton'
],
},
)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""Requests test package initialisation."""
import warnings

View File

@@ -1,21 +1,6 @@
# -*- coding: utf-8 -*-
from requests.compat import is_py3
import io as StringIO
try:
import StringIO
except ImportError:
import io as StringIO
try:
from cStringIO import StringIO as cStringIO
except ImportError:
cStringIO = None
if is_py3:
def u(s):
return s
else:
def u(s):
return s.decode('unicode-escape')
def u(s):
return s

View File

@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import pytest
from requests.compat import urljoin
from urllib.parse import urljoin
def prepare_url(value):

44
tests/test_help.py Normal file
View File

@@ -0,0 +1,44 @@
# -*- encoding: utf-8
import sys
import pytest
from requests.help import info
@pytest.mark.skipif(
sys.version_info[:2] != (2, 6), reason="Only run on Python 2.6"
)
def test_system_ssl_py26():
"""OPENSSL_VERSION_NUMBER isn't provided in Python 2.6, verify we don't
blow up in this case.
"""
assert info()['system_ssl'] == {'version': ''}
@pytest.mark.skipif(
sys.version_info < (2, 7), reason="Only run on Python 2.7+"
)
def test_system_ssl():
"""Verify we're actually setting system_ssl when it should be available."""
assert info()['system_ssl']['version'] != ''
class VersionedPackage(object):
def __init__(self, version):
self.__version__ = version
def test_idna_without_version_attribute(mocker):
"""Older versions of IDNA don't provide a __version__ attribute, verify
that if we have such a package, we don't blow up.
"""
mocker.patch('requests.help.idna', new=None)
assert info()['idna'] == {'version': ''}
def test_idna_with_version_attribute(mocker):
"""Verify we're actually setting idna version when it should be available."""
mocker.patch('requests.help.idna', new=VersionedPackage('2.6'))
assert info()['idna'] == {'version': '2.6'}

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import pytest
from requests import hooks
@@ -10,13 +9,12 @@ def hook(value):
@pytest.mark.parametrize(
'hooks_list, result', (
(hook, 'ata'),
([hook, lambda x: None, hook], 'ta'),
)
'hooks_list, result', ((hook, 'ata'), ([hook, lambda x: None, hook], 'ta'))
)
def test_hooks(hooks_list, result):
assert hooks.dispatch_hook('response', {'response': hooks_list}, 'Data') == result
assert hooks.dispatch_hook(
'response', {'response': hooks_list}, 'Data'
) == result
def test_default_hooks():

126
tests/test_lowlevel.py Executable file → Normal file
View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import pytest
import threading
import requests
@@ -14,39 +13,54 @@ def test_chunked_upload():
close_server = threading.Event()
server = Server.basic_response_server(wait_to_close_event=close_server)
data = iter([b'a', b'b', b'c'])
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.post(url, data=data, stream=True)
close_server.set() # release server block
assert r.status_code == 200
assert r.request.headers['Transfer-Encoding'] == 'chunked'
def test_incorrect_content_length():
"""Test ConnectionError raised for incomplete responses"""
close_server = threading.Event()
server = Server.text_response_server(
"HTTP/1.1 200 OK\r\n" + "Content-Length: 50\r\n\r\n" + "Hello World."
)
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.Request('GET', url).prepare()
s = requests.Session()
with pytest.raises(requests.exceptions.ConnectionError) as e:
resp = s.send(r)
assert "12 bytes read, 38 more expected" in str(e)
close_server.set() # release server block
def test_digestauth_401_count_reset_on_redirect():
"""Ensure we correctly reset num_401_calls after a successful digest auth,
followed by a 302 redirect to another digest auth prompt.
See https://github.com/kennethreitz/requests/issues/1979.
See https://github.com/requests/requests/issues/1979.
"""
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
text_302 = (b'HTTP/1.1 302 FOUND\r\n'
b'Content-Length: 0\r\n'
b'Location: /\r\n\r\n')
text_200 = (b'HTTP/1.1 200 OK\r\n'
b'Content-Length: 0\r\n\r\n')
expected_digest = (b'Authorization: Digest username="user", '
b'realm="me@kennethreitz.com", '
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
text_401 = (
b'HTTP/1.1 401 UNAUTHORIZED\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n'
)
text_302 = (
b'HTTP/1.1 302 FOUND\r\n'
b'Content-Length: 0\r\n'
b'Location: /\r\n\r\n'
)
text_200 = (b'HTTP/1.1 200 OK\r\n' b'Content-Length: 0\r\n\r\n')
expected_digest = (
b'Authorization: Digest username="user", '
b'realm="me@kennethreitz.com", '
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"'
)
auth = requests.auth.HTTPDigestAuth('user', 'pass')
def digest_response_handler(sock):
@@ -54,28 +68,23 @@ def test_digestauth_401_count_reset_on_redirect():
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content.startswith(b"GET / HTTP/1.1")
sock.send(text_401)
# Verify we receive an Authorization header in response, then redirect.
request_content = consume_socket_content(sock, timeout=0.5)
assert expected_digest in request_content
sock.send(text_302)
# Verify Authorization isn't sent to the redirected host,
# then send another challenge.
request_content = consume_socket_content(sock, timeout=0.5)
assert b'Authorization:' not in request_content
sock.send(text_401)
# Verify Authorization is sent correctly again, and return 200 OK.
request_content = consume_socket_content(sock, timeout=0.5)
assert expected_digest in request_content
sock.send(text_200)
return request_content
close_server = threading.Event()
server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.get(url, auth=auth)
@@ -93,16 +102,18 @@ def test_digestauth_401_only_sent_once():
"""Ensure we correctly respond to a 401 challenge once, and then
stop responding if challenged again.
"""
text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
expected_digest = (b'Authorization: Digest username="user", '
b'realm="me@kennethreitz.com", '
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"')
text_401 = (
b'HTTP/1.1 401 UNAUTHORIZED\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n'
)
expected_digest = (
b'Authorization: Digest username="user", '
b'realm="me@kennethreitz.com", '
b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"'
)
auth = requests.auth.HTTPDigestAuth('user', 'pass')
def digest_failed_response_handler(sock):
@@ -110,22 +121,20 @@ def test_digestauth_401_only_sent_once():
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content.startswith(b"GET / HTTP/1.1")
sock.send(text_401)
# Verify we receive an Authorization header in response, then
# challenge again.
request_content = consume_socket_content(sock, timeout=0.5)
assert expected_digest in request_content
sock.send(text_401)
# Verify the client didn't respond to second challenge.
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content == b''
return request_content
close_server = threading.Event()
server = Server(digest_failed_response_handler, wait_to_close_event=close_server)
server = Server(
digest_failed_response_handler, wait_to_close_event=close_server
)
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.get(url, auth=auth)
@@ -138,14 +147,15 @@ def test_digestauth_401_only_sent_once():
def test_digestauth_only_on_4xx():
"""Ensure we only send digestauth on 4xx challenges.
See https://github.com/kennethreitz/requests/issues/3772.
See https://github.com/requests/requests/issues/3772.
"""
text_200_chal = (b'HTTP/1.1 200 OK\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n')
text_200_chal = (
b'HTTP/1.1 200 OK\r\n'
b'Content-Length: 0\r\n'
b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"'
b', opaque="372825293d1c26955496c80ed6426e9e", '
b'realm="me@kennethreitz.com", qop=auth\r\n\r\n'
)
auth = requests.auth.HTTPDigestAuth('user', 'pass')
def digest_response_handler(sock):
@@ -153,16 +163,13 @@ def test_digestauth_only_on_4xx():
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content.startswith(b"GET / HTTP/1.1")
sock.send(text_200_chal)
# Verify the client didn't respond with auth.
request_content = consume_socket_content(sock, timeout=0.5)
assert request_content == b''
return request_content
close_server = threading.Event()
server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{0}:{1}/'.format(host, port)
r = requests.get(url, auth=auth)
@@ -173,23 +180,20 @@ def test_digestauth_only_on_4xx():
_schemes_by_var_prefix = [
('http', ['http']),
('https', ['https']),
('all', ['http', 'https']),
('http', ['http']), ('https', ['https']), ('all', ['http', 'https'])
]
_proxy_combos = []
for prefix, schemes in _schemes_by_var_prefix:
for scheme in schemes:
_proxy_combos.append(("{0}_proxy".format(prefix), scheme))
_proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos]
@pytest.mark.parametrize("var,scheme", _proxy_combos)
def test_use_proxy_from_environment(httpbin, var, scheme):
url = "{0}://httpbin.org".format(scheme)
fake_proxy = Server() # do nothing with the requests; just close the socket
fake_proxy = Server(
) # do nothing with the requests; just close the socket
with fake_proxy as (host, port):
proxy_url = "socks5://{0}:{1}".format(host, port)
kwargs = {var: proxy_url}
@@ -197,10 +201,8 @@ def test_use_proxy_from_environment(httpbin, var, scheme):
# fake proxy's lack of response will cause a ConnectionError
with pytest.raises(requests.exceptions.ConnectionError):
requests.get(url)
# the fake proxy received a request
assert len(fake_proxy.handler_results) == 1
# it had actual content (not checking for SOCKS protocol for now)
assert len(fake_proxy.handler_results[0]) > 0
@@ -224,14 +226,14 @@ def test_redirect_rfc1808_to_non_ascii_location():
close_server = threading.Event()
server = Server(redirect_resp_handler, wait_to_close_event=close_server)
with server as (host, port):
url = u'http://{0}:{1}'.format(host, port)
r = requests.get(url=url, allow_redirects=True)
assert r.status_code == 200
assert len(r.history) == 1
assert r.history[0].status_code == 301
assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1')
assert redirect_request[0].startswith(
b'GET /' + expected_path + b' HTTP/1.1'
)
assert r.url == u'{0}/{1}'.format(url, expected_path.decode('ascii'))
close_server.set()

1777
tests/test_requests.py Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
import pytest
from requests.structures import CaseInsensitiveDict, LookupDict
from requests.structures import CaseInsensitiveDict, LookupDict, HTTPHeaderDict
from urllib3._collections import HTTPHeaderDict as U3HeaderDict
class TestCaseInsensitiveDict:
@@ -16,7 +16,9 @@ class TestCaseInsensitiveDict:
def test_list(self):
assert list(self.case_insensitive_dict) == ['Accept']
possible_keys = pytest.mark.parametrize('key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept'))
possible_keys = pytest.mark.parametrize(
'key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept')
)
@possible_keys
def test_getitem(self, key):
@@ -28,10 +30,14 @@ class TestCaseInsensitiveDict:
assert key not in self.case_insensitive_dict
def test_lower_items(self):
assert list(self.case_insensitive_dict.lower_items()) == [('accept', 'application/json')]
assert list(self.case_insensitive_dict.lower_items()) == [
('accept', 'application/json')
]
def test_repr(self):
assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}"
assert repr(
self.case_insensitive_dict
) == "{'Accept': 'application/json'}"
def test_copy(self):
copy = self.case_insensitive_dict.copy()
@@ -39,16 +45,186 @@ class TestCaseInsensitiveDict:
assert copy == self.case_insensitive_dict
@pytest.mark.parametrize(
'other, result', (
({'AccePT': 'application/json'}, True),
({}, False),
(None, False)
)
'other, result',
(({'AccePT': 'application/json'}, True), ({}, False), (None, False)),
)
def test_instance_equality(self, other, result):
assert (self.case_insensitive_dict == other) is result
class TestHTTPHeaderDictCompatibility(TestCaseInsensitiveDict):
"""HTTPHeaderDict should be completely compatible with CaseInsensitiveDict
when used for headers, so ensure that all the tests for the base class
also pass for this one."""
@pytest.fixture(autouse=True)
def setup(self):
self.case_insensitive_dict = HTTPHeaderDict()
self.case_insensitive_dict['Accept'] = 'application/json'
class TestHTTPHeaderDict:
@pytest.fixture(autouse=True)
def setup(self):
self.kvs = [
('animal', 'chicken'),
('AnimaL', 'Cow'),
('CAKE', 'Cheese!'),
('Sauce', 'Bread'),
('Sauce', 'Cherry, or Plum Tomato'),
]
# HTTPHeaderDict from urllib3.
self.u3dict = ud = U3HeaderDict()
[ud.add(*tpl) for tpl in self.kvs]
# Regular dictionary.
self.ddict = dict(self.kvs)
self.ddict['Sauce'] = ['Bread!', 'Cherry, or Plum Tomato']
# Used by test_extend. All of these "extra" values are mostly
# equivalent to each other.
self.extra_hd = hd2 = HTTPHeaderDict(ANIMAL=['Dog', 'elephant'])
hd2['cake'] = 'Babka'
hd2.setlist('sound', ['quiet', 'LOUD'])
hd2['CUTLERY'] = 'fork'
self.extra_tuple_pairs = tuple_pairs = [
('ANIMAL', 'Dog'),
('Animal', 'elephant'),
('cake', ['Babka']),
('sound', 'quiet'),
('sound', 'LOUD'),
('CUTLERY', 'fork'),
]
self.extra_simple_dict = dict(tuple_pairs)
self.extra_simple_dict['sound'] = ('quiet', 'LOUD')
self.extra_u3 = U3HeaderDict()
for k, v in tuple_pairs:
if isinstance(v, (tuple, list)):
for vi in v:
self.extra_u3.add(k, vi)
else:
self.extra_u3.add(k, v)
def test_item_access(self):
hd = HTTPHeaderDict(self.kvs)
# Test that values are combined.
assert hd['Sauce'] == 'Bread, Cherry, or Plum Tomato'
assert hd['ANIMAL'] == 'chicken, Cow'
# Test we can overwrite values.
hd['animal'] = 'Goat!'
assert hd['anIMal'] == 'Goat!'
# Test deletion works.
del hd['sauce']
pytest.raises(KeyError, hd.__getitem__, 'sauce')
# Only string types allowed.
pytest.raises(ValueError, hd.__setitem__, 'cake', ['Cheese', 'sponge'])
def test_equality(self):
hd = HTTPHeaderDict(self.u3dict)
assert hd == self.u3dict
assert hd == HTTPHeaderDict(hd)
# Test that we still work even if we are comparing to a
# CaseInsensitiveDict instance.
cid = CaseInsensitiveDict(hd)
assert hd == cid
assert cid == hd
def test_lower_items(self):
hd = HTTPHeaderDict(self.kvs, cutlery='fork')
assert list(hd.lower_items()) == [
('animal', 'chicken, Cow'),
('cake', 'Cheese!'),
('sauce', 'Bread, Cherry, or Plum Tomato'),
('cutlery', 'fork'),
]
def test_copy(self):
hd = HTTPHeaderDict(self.u3dict)
hd2 = hd.copy()
assert hd is not hd2
assert hd == hd2
def test_get_and_set_list(self):
hd = HTTPHeaderDict(self.kvs)
assert hd.getlist('SAUCE') == ['Bread', 'Cherry, or Plum Tomato']
assert hd.getlist('CAKE') == ['Cheese!']
assert hd.getlist('DRINK') == []
# Needs to be a regular sequence type containing just strings.
pytest.raises(ValueError, hd.setlist, 'Drink', 'Water')
pytest.raises(ValueError, hd.setlist, 'Drink', ['H', 2, 'O'])
# Test multi-setting.
hd.setlist('Drink', ['Water', 'Juice'])
assert hd.getlist('DRINK') == ['Water', 'Juice']
# Setting to an empty sequence should remove the entry.
hd.setlist('DRInk', [])
pytest.raises(KeyError, hd.__getitem__, 'DrinK')
assert hd.getlist('DRiNK') == []
def test_add(self):
hd = HTTPHeaderDict()
hd.add('sound', 'quiet')
hd.add('SOUND', 'LOUD')
assert hd.getlist('Sound') == ['quiet', 'LOUD']
# Enforce type-checking in the add method.
pytest.raises(ValueError, hd.add, 'Sound', 5)
@pytest.mark.parametrize(
'attr,as_arg,animal_arg_is_ordered',
[('extra_hd', True, True), ('extra_tuple_pairs', True, True), ('extra_simple_dict', True, False), ('extra_u3', True, False), ('extra_simple_dict', False, False)],
# These types will have the "animal" arguments in our preferred order.
# And these types will lose the ordering, so we can't make assertions
# about the final order of those values.
)
def test_extend(self, attr, as_arg, animal_arg_is_ordered):
item = getattr(self, attr)
# Call extend with the associated values - we should see all of the
# merged data in the HTTPHeaderDict instance.
extras = {'cutlery': 'knife'}
hd = HTTPHeaderDict(self.kvs)
if as_arg:
hd.extend(item, **extras)
else:
hd.extend(extras, **item)
# Test all the stored values are what we expect.
mget = hd.getlist
# Depending on the item we merged in, we might be able to make
# assumptions what the overall order of the structure is.
animal_seq = mget('animal')
if animal_arg_is_ordered:
assert animal_seq == ['chicken', 'Cow', 'Dog', 'elephant']
else:
# The existing order in HTTPHeadersDict of the first two values
# should be preserved - no guarantees in which order the other
# two values are added.
assert animal_seq in [
['chicken', 'Cow', 'Dog', 'elephant'],
['chicken', 'Cow', 'elephant', 'Dog'],
]
assert mget('cake') == ['Cheese!', 'Babka']
assert mget('sound') == ['quiet', 'LOUD']
# We don't mandate the order in which these dictionaries are
# processed, so it's fine whichever order it is.
assert mget('cutlery') in [['fork', 'knife'], ['knife', 'fork']]
def test_extend_type_checking(self):
hd = HTTPHeaderDict()
pytest.raises(ValueError, hd.extend, dict(type=['xml', None, 'html']))
def test_repr(self):
hd = HTTPHeaderDict()
assert repr(hd) == '{}'
hd.add('type', 'xml')
assert repr(hd) == "{'type': 'xml'}"
hd.add('type', 'html')
assert repr(hd) == "{'type': ('xml', 'html')}"
# We can't guarantee order once we have more than one key.
hd.add('Accept', 'text/html')
assert repr(hd) in [
"{'type': ('xml', 'html'), 'Accept': 'text/html'}",
"{'Accept': 'text/html', 'type': ('xml', 'html')}",
]
assert str(hd) == repr(hd)
class TestLookupDict:
@pytest.fixture(autouse=True)
@@ -61,10 +237,7 @@ class TestLookupDict:
assert repr(self.lookup_dict) == "<lookup 'test'>"
get_item_parameters = pytest.mark.parametrize(
'key, value', (
('bad_gateway', 502),
('not_a_key', None)
)
'key, value', (('bad_gateway', 502), ('not_a_key', None))
)
@get_item_parameters

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import threading
import socket
import time
@@ -34,9 +33,7 @@ class TestTestServer:
with Server.basic_response_server() as (host, port):
sock = socket.socket()
sock.connect((host, port))
sock.close()
with pytest.raises(socket.error):
new_sock = socket.socket()
new_sock.connect((host, port))
@@ -44,14 +41,10 @@ class TestTestServer:
def test_text_response(self):
"""the text_response_server sends the given text"""
server = Server.text_response_server(
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 6\r\n" +
"\r\nroflol"
"HTTP/1.1 200 OK\r\n" + "Content-Length: 6\r\n" + "\r\nroflol"
)
with server as (host, port):
r = requests.get('http://{0}:{1}'.format(host, port))
assert r.status_code == 200
assert r.text == u'roflol'
assert r.headers['Content-Length'] == '6'
@@ -67,8 +60,11 @@ class TestTestServer:
def test_basic_waiting_server(self):
"""the server waits for the block_server event to be set before closing"""
block_server = threading.Event()
with Server.basic_response_server(wait_to_close_event=block_server) as (host, port):
with Server.basic_response_server(
wait_to_close_event=block_server
) as (
host, port
):
sock = socket.socket()
sock.connect((host, port))
sock.sendall(b'send something')
@@ -79,65 +75,60 @@ class TestTestServer:
def test_multiple_requests(self):
"""multiple requests can be served"""
requests_to_handle = 5
server = Server.basic_response_server(requests_to_handle=requests_to_handle)
server = Server.basic_response_server(
requests_to_handle=requests_to_handle
)
with server as (host, port):
server_url = 'http://{0}:{1}'.format(host, port)
for _ in range(requests_to_handle):
r = requests.get(server_url)
assert r.status_code == 200
# the (n+1)th request fails
with pytest.raises(requests.exceptions.ConnectionError):
r = requests.get(server_url)
@pytest.mark.skip(
reason="this fails non-deterministically under pytest-xdist"
)
def test_request_recovery(self):
"""can check the requests content"""
# TODO: figure out why this sometimes fails when using pytest-xdist.
server = Server.basic_response_server(requests_to_handle=2)
first_request = b'put your hands up in the air'
second_request = b'put your hand down in the floor'
with server as address:
sock1 = socket.socket()
sock2 = socket.socket()
sock1.connect(address)
sock1.sendall(first_request)
sock1.close()
sock2.connect(address)
sock2.sendall(second_request)
sock2.close()
assert server.handler_results[0] == first_request
assert server.handler_results[1] == second_request
def test_requests_after_timeout_are_not_received(self):
"""the basic response handler times out when receiving requests"""
server = Server.basic_response_server(request_timeout=1)
with server as address:
sock = socket.socket()
sock.connect(address)
time.sleep(1.5)
sock.sendall(b'hehehe, not received')
sock.close()
assert server.handler_results[0] == b''
def test_request_recovery_with_bigger_timeout(self):
"""a biggest timeout can be specified"""
server = Server.basic_response_server(request_timeout=3)
data = b'bananadine'
with server as address:
sock = socket.socket()
sock.connect(address)
time.sleep(1.5)
sock.sendall(data)
sock.close()
assert server.handler_results[0] == data
def test_server_finishes_on_error(self):
@@ -149,16 +140,16 @@ class TestTestServer:
assert len(server.handler_results) == 0
# if the server thread fails to finish, the test suite will hang
# and get killed by the jenkins timeout.
# if the server thread fails to finish, the test suite will hang
# and get killed by the jenkins timeout.
def test_server_finishes_when_no_connections(self):
"""the server thread exits even if there are no connections"""
server = Server.basic_response_server()
with server:
pass
assert len(server.handler_results) == 0
# if the server thread fails to finish, the test suite will hang
# and get killed by the jenkins timeout.
# if the server thread fails to finish, the test suite will hang
# and get killed by the jenkins timeout.

View File

@@ -1,36 +1,48 @@
# -*- coding: utf-8 -*-
import os
import copy
from io import BytesIO
import pytest
from requests import compat
from requests import basics
from requests.cookies import RequestsCookieJar
from requests.structures import CaseInsensitiveDict
from requests.utils import (
address_in_network, dotted_netmask,
get_auth_from_url, get_encoding_from_headers,
get_encodings_from_content, get_environ_proxies,
guess_filename, guess_json_utf, is_ipv4_address,
is_valid_cidr, iter_slices, parse_dict_header,
parse_header_links, prepend_scheme_if_needed,
requote_uri, select_proxy, should_bypass_proxies, super_len,
to_key_val_list, to_native_string,
unquote_header_value, unquote_unreserved,
urldefragauth, add_dict_to_cookiejar)
address_in_network,
dotted_netmask,
get_auth_from_url,
get_encoding_from_headers,
get_encodings_from_content,
get_environ_proxies,
guess_filename,
guess_json_utf,
is_ipv4_address,
is_valid_cidr,
iter_slices,
parse_dict_header,
parse_header_links,
prepend_scheme_if_needed,
requote_uri,
select_proxy,
should_bypass_proxies,
super_len,
to_key_val_list,
unquote_header_value,
unquote_unreserved,
urldefragauth,
add_dict_to_cookiejar,
set_environ,
)
from requests._internal_utils import unicode_is_ascii
from .compat import StringIO, cStringIO
from .compat import StringIO
class TestSuperLen:
@pytest.mark.parametrize(
'stream, value', (
(StringIO.StringIO, 'Test'),
(BytesIO, b'Test'),
pytest.mark.skipif('cStringIO is None')((cStringIO, 'Test')),
))
'stream, value', ((StringIO.StringIO, 'Test'), (BytesIO, b'Test'))
)
def test_io_streams(self, stream, value):
"""Ensures that we properly deal with different kinds of IO streams."""
assert super_len(stream()) == 0
@@ -45,7 +57,9 @@ class TestSuperLen:
@pytest.mark.parametrize('error', [IOError, OSError])
def test_super_len_handles_files_raising_weird_errors_in_tell(self, error):
"""If tell() raises errors, assume the cursor is at position zero."""
class BoomFile(object):
def __len__(self):
return 5
@@ -57,7 +71,9 @@ class TestSuperLen:
@pytest.mark.parametrize('error', [IOError, OSError])
def test_super_len_tell_ioerror(self, error):
"""Ensure that if tell gives an IOError super_len doesn't fail"""
class NoLenBoomFile(object):
def tell(self):
raise error()
@@ -69,11 +85,7 @@ class TestSuperLen:
def test_string(self):
assert super_len('Test') == 4
@pytest.mark.parametrize(
'mode, warnings_num', (
('r', 1),
('rb', 0),
))
@pytest.mark.parametrize('mode, warnings_num', (('r', 1), ('rb', 0)))
def test_file(self, tmpdir, mode, warnings_num, recwarn):
file_obj = tmpdir.join('test.txt')
file_obj.write('Test')
@@ -82,12 +94,14 @@ class TestSuperLen:
assert len(recwarn) == warnings_num
def test_super_len_with__len__(self):
foo = [1,2,3,4]
foo = [1, 2, 3, 4]
len_foo = super_len(foo)
assert len_foo == 4
def test_super_len_with_no__len__(self):
class LenFile(object):
def __init__(self):
self.len = 5
@@ -113,12 +127,14 @@ class TestSuperLen:
class TestToKeyValList:
@pytest.mark.parametrize(
'value, expected', (
'value, expected',
(
([('key', 'val')], [('key', 'val')]),
((('key', 'val'), ), [('key', 'val')]),
((('key', 'val'),), [('key', 'val')]),
({'key': 'val'}, [('key', 'val')]),
(None, None)
))
(None, None),
),
)
def test_valid(self, value, expected):
assert to_key_val_list(value) == expected
@@ -130,13 +146,15 @@ class TestToKeyValList:
class TestUnquoteHeaderValue:
@pytest.mark.parametrize(
'value, expected', (
'value, expected',
(
(None, None),
('Test', 'Test'),
('"Test"', 'Test'),
('"Test\\\\"', 'Test\\'),
('"\\\\Comp\\Res"', '\\Comp\\Res'),
))
),
)
def test_valid(self, value, expected):
assert unquote_header_value(value) == expected
@@ -151,46 +169,57 @@ class TestGetEnvironProxies:
@pytest.fixture(autouse=True, params=['no_proxy', 'NO_PROXY'])
def no_proxy(self, request, monkeypatch):
monkeypatch.setenv(request.param, '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
monkeypatch.setenv(
request.param,
'192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1',
)
@pytest.mark.parametrize(
'url', (
'url',
(
'http://192.168.0.1:5000/',
'http://192.168.0.1/',
'http://172.16.1.1/',
'http://172.16.1.1:5000/',
'http://localhost.localdomain:5000/v1.0/',
))
),
)
def test_bypass(self, url):
assert get_environ_proxies(url, no_proxy=None) == {}
@pytest.mark.parametrize(
'url', (
'url',
(
'http://192.168.1.1:5000/',
'http://192.168.1.1/',
'http://www.requests.com/',
))
),
)
def test_not_bypass(self, url):
assert get_environ_proxies(url, no_proxy=None) != {}
@pytest.mark.parametrize(
'url', (
'url',
(
'http://192.168.1.1:5000/',
'http://192.168.1.1/',
'http://www.requests.com/',
))
),
)
def test_bypass_no_proxy_keyword(self, url):
no_proxy = '192.168.1.1,requests.com'
assert get_environ_proxies(url, no_proxy=no_proxy) == {}
@pytest.mark.parametrize(
'url', (
'url',
(
'http://192.168.0.1:5000/',
'http://192.168.0.1/',
'http://172.16.1.1/',
'http://172.16.1.1:5000/',
'http://localhost.localdomain:5000/v1.0/',
))
),
)
def test_not_bypass_no_proxy_keyword(self, url, monkeypatch):
# This is testing that the 'no_proxy' argument overrides the
# environment variable 'no_proxy'
@@ -215,13 +244,15 @@ class TestIsValidCIDR:
assert is_valid_cidr('192.168.1.0/24')
@pytest.mark.parametrize(
'value', (
'value',
(
'8.8.8.8',
'192.168.1.0/a',
'192.168.1.0/128',
'192.168.1.0/-1',
'192.168.1.999/24',
))
),
)
def test_invalid(self, value):
assert not is_valid_cidr(value)
@@ -238,16 +269,15 @@ class TestAddressInNetwork:
class TestGuessFilename:
@pytest.mark.parametrize(
'value', (1, type('Fake', (object,), {'name': 1})()),
'value', (1, type('Fake', (object,), {'name': 1})())
)
def test_guess_filename_invalid(self, value):
assert guess_filename(value) is None
@pytest.mark.parametrize(
'value, expected_type', (
(b'value', compat.bytes),
(b'value'.decode('utf-8'), compat.str)
))
'value, expected_type',
((b'value', basics.bytes), (b'value'.decode('utf-8'), basics.str)),
)
def test_guess_filename_valid(self, value, expected_type):
obj = type('Fake', (object,), {'name': value})()
result = guess_filename(obj)
@@ -262,16 +292,13 @@ class TestContentEncodingDetection:
assert not len(encodings)
@pytest.mark.parametrize(
'content', (
# HTML5 meta charset attribute
'<meta charset="UTF-8">',
# HTML4 pragma directive
'<meta http-equiv="Content-type" content="text/html;charset=UTF-8">',
# XHTML 1.x served with text/html MIME type
'<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />',
# XHTML 1.x served as XML
'<?xml version="1.0" encoding="UTF-8"?>',
))
'content',
('<meta charset="UTF-8">', '<meta http-equiv="Content-type" content="text/html;charset=UTF-8">', '<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />', '<?xml version="1.0" encoding="UTF-8"?>'),
# HTML5 meta charset attribute
# HTML4 pragma directive
# XHTML 1.x served with text/html MIME type
# XHTML 1.x served as XML
)
def test_pragmas(self, content):
encodings = get_encodings_from_content(content)
assert len(encodings) == 1
@@ -282,17 +309,26 @@ class TestContentEncodingDetection:
<?xml version="1.0" encoding="XML"?>
<meta charset="HTML5">
<meta http-equiv="Content-type" content="text/html;charset=HTML4" />
'''.strip()
'''.strip(
)
assert get_encodings_from_content(content) == ['HTML5', 'HTML4', 'XML']
class TestGuessJSONUTF:
@pytest.mark.parametrize(
'encoding', (
'utf-32', 'utf-8-sig', 'utf-16', 'utf-8', 'utf-16-be', 'utf-16-le',
'utf-32-be', 'utf-32-le'
))
'encoding',
(
'utf-32',
'utf-8-sig',
'utf-16',
'utf-8',
'utf-16-be',
'utf-16-le',
'utf-32-be',
'utf-32-le',
),
)
def test_encoded(self, encoding):
data = '{}'.encode(encoding)
assert guess_json_utf(data) == encoding
@@ -301,173 +337,154 @@ class TestGuessJSONUTF:
assert guess_json_utf(b'\x00\x00\x00\x00') is None
@pytest.mark.parametrize(
('encoding', 'expected'), (
('encoding', 'expected'),
(
('utf-16-be', 'utf-16'),
('utf-16-le', 'utf-16'),
('utf-32-be', 'utf-32'),
('utf-32-le', 'utf-32')
))
('utf-32-le', 'utf-32'),
),
)
def test_guess_by_bom(self, encoding, expected):
data = u'\ufeff{}'.encode(encoding)
assert guess_json_utf(data) == expected
USER = PASSWORD = "%!*'();:@&=+$,/?#[] "
ENCODED_USER = compat.quote(USER, '')
ENCODED_PASSWORD = compat.quote(PASSWORD, '')
ENCODED_USER = basics.quote(USER, '')
ENCODED_PASSWORD = basics.quote(PASSWORD, '')
@pytest.mark.parametrize(
'url, auth', (
'url, auth',
(
(
'http://' + ENCODED_USER + ':' + ENCODED_PASSWORD + '@' +
'http://' +
ENCODED_USER +
':' +
ENCODED_PASSWORD +
'@' +
'request.com/url.html#test',
(USER, PASSWORD)
),
(
'http://user:pass@complex.url.com/path?query=yes',
('user', 'pass')
(USER, PASSWORD),
),
('http://user:pass@complex.url.com/path?query=yes', ('user', 'pass')),
(
'http://user:pass%20pass@complex.url.com/path?query=yes',
('user', 'pass pass')
('user', 'pass pass'),
),
(
'http://user:pass pass@complex.url.com/path?query=yes',
('user', 'pass pass')
('user', 'pass pass'),
),
(
'http://user%25user:pass@complex.url.com/path?query=yes',
('user%user', 'pass')
('user%user', 'pass'),
),
(
'http://user:pass%23pass@complex.url.com/path?query=yes',
('user', 'pass#pass')
('user', 'pass#pass'),
),
(
'http://complex.url.com/path?query=yes',
('', '')
),
))
('http://complex.url.com/path?query=yes', ('', '')),
),
)
def test_get_auth_from_url(url, auth):
assert get_auth_from_url(url) == auth
@pytest.mark.parametrize(
'uri, expected', (
(
# Ensure requoting doesn't break expectations
'http://example.com/fiz?buz=%25ppicture',
'http://example.com/fiz?buz=%25ppicture',
),
(
# Ensure we handle unquoted percent signs in redirects
'http://example.com/fiz?buz=%ppicture',
'http://example.com/fiz?buz=%25ppicture',
),
))
'uri, expected',
(('http://example.com/fiz?buz=%25ppicture', 'http://example.com/fiz?buz=%25ppicture'), ('http://example.com/fiz?buz=%ppicture', 'http://example.com/fiz?buz=%25ppicture')),
# Ensure requoting doesn't break expectations
# Ensure we handle unquoted percent signs in redirects
)
def test_requote_uri_with_unquoted_percents(uri, expected):
"""See: https://github.com/kennethreitz/requests/issues/2356"""
"""See: https://github.com/requests/requests/issues/2356"""
assert requote_uri(uri) == expected
@pytest.mark.parametrize(
'uri, expected', (
(
# Illegal bytes
'http://example.com/?a=%--',
'http://example.com/?a=%--',
),
(
# Reserved characters
'http://example.com/?a=%300',
'http://example.com/?a=00',
)
))
'uri, expected',
(('http://example.com/?a=%--', 'http://example.com/?a=%--'), ('http://example.com/?a=%300', 'http://example.com/?a=00')),
# Illegal bytes
# Reserved characters
)
def test_unquote_unreserved(uri, expected):
assert unquote_unreserved(uri) == expected
@pytest.mark.parametrize(
'mask, expected', (
(8, '255.0.0.0'),
(24, '255.255.255.0'),
(25, '255.255.255.128'),
))
'mask, expected',
((8, '255.0.0.0'), (24, '255.255.255.0'), (25, '255.255.255.128')),
)
def test_dotted_netmask(mask, expected):
assert dotted_netmask(mask) == expected
http_proxies = {'http': 'http://http.proxy',
'http://some.host': 'http://some.host.proxy'}
all_proxies = {'all': 'socks5://http.proxy',
'all://some.host': 'socks5://some.host.proxy'}
mixed_proxies = {'http': 'http://http.proxy',
'http://some.host': 'http://some.host.proxy',
'all': 'socks5://http.proxy'}
http_proxies = {
'http': 'http://http.proxy', 'http://some.host': 'http://some.host.proxy'
}
all_proxies = {
'all': 'socks5://http.proxy', 'all://some.host': 'socks5://some.host.proxy'
}
mixed_proxies = {
'http': 'http://http.proxy',
'http://some.host': 'http://some.host.proxy',
'all': 'socks5://http.proxy',
}
@pytest.mark.parametrize(
'url, expected, proxies', (
('hTTp://u:p@Some.Host/path', 'http://some.host.proxy', http_proxies),
('hTTp://u:p@Other.Host/path', 'http://http.proxy', http_proxies),
('hTTp:///path', 'http://http.proxy', http_proxies),
('hTTps://Other.Host', None, http_proxies),
('file:///etc/motd', None, http_proxies),
('hTTp://u:p@Some.Host/path', 'socks5://some.host.proxy', all_proxies),
('hTTp://u:p@Other.Host/path', 'socks5://http.proxy', all_proxies),
('hTTp:///path', 'socks5://http.proxy', all_proxies),
('hTTps://Other.Host', 'socks5://http.proxy', all_proxies),
('http://u:p@other.host/path', 'http://http.proxy', mixed_proxies),
('http://u:p@some.host/path', 'http://some.host.proxy', mixed_proxies),
('https://u:p@other.host/path', 'socks5://http.proxy', mixed_proxies),
('https://u:p@some.host/path', 'socks5://http.proxy', mixed_proxies),
('https://', 'socks5://http.proxy', mixed_proxies),
# XXX: unsure whether this is reasonable behavior
('file:///etc/motd', 'socks5://http.proxy', all_proxies),
))
'url, expected, proxies',
(('hTTp://u:p@Some.Host/path', 'http://some.host.proxy', http_proxies), ('hTTp://u:p@Other.Host/path', 'http://http.proxy', http_proxies), ('hTTp:///path', 'http://http.proxy', http_proxies), ('hTTps://Other.Host', None, http_proxies), ('file:///etc/motd', None, http_proxies), ('hTTp://u:p@Some.Host/path', 'socks5://some.host.proxy', all_proxies), ('hTTp://u:p@Other.Host/path', 'socks5://http.proxy', all_proxies), ('hTTp:///path', 'socks5://http.proxy', all_proxies), ('hTTps://Other.Host', 'socks5://http.proxy', all_proxies), ('http://u:p@other.host/path', 'http://http.proxy', mixed_proxies), ('http://u:p@some.host/path', 'http://some.host.proxy', mixed_proxies), ('https://u:p@other.host/path', 'socks5://http.proxy', mixed_proxies), ('https://u:p@some.host/path', 'socks5://http.proxy', mixed_proxies), ('https://', 'socks5://http.proxy', mixed_proxies), ('file:///etc/motd', 'socks5://http.proxy', all_proxies)),
# XXX: unsure whether this is reasonable behavior
)
def test_select_proxies(url, expected, proxies):
"""Make sure we can select per-host proxies correctly."""
assert select_proxy(url, proxies) == expected
@pytest.mark.parametrize(
'value, expected', (
('foo="is a fish", bar="as well"', {'foo': 'is a fish', 'bar': 'as well'}),
('key_without_value', {'key_without_value': None})
))
'value, expected',
(
(
'foo="is a fish", bar="as well"',
{'foo': 'is a fish', 'bar': 'as well'},
),
('key_without_value', {'key_without_value': None}),
),
)
def test_parse_dict_header(value, expected):
assert parse_dict_header(value) == expected
@pytest.mark.parametrize(
'value, expected', (
'value, expected',
(
(CaseInsensitiveDict(), None),
(
CaseInsensitiveDict(),
None
CaseInsensitiveDict(
{'content-type': 'application/json; charset=utf-8'}
),
'utf-8',
),
(
CaseInsensitiveDict({'content-type': 'application/json; charset=utf-8'}),
'utf-8'
),
(
CaseInsensitiveDict({'content-type': 'text/plain'}),
'ISO-8859-1'
),
))
(CaseInsensitiveDict({'content-type': 'text/plain'}), 'ISO-8859-1'),
),
)
def test_get_encoding_from_headers(value, expected):
assert get_encoding_from_headers(value) == expected
@pytest.mark.parametrize(
'value, length', (
'value, length',
(
('', 0),
('T', 1),
('Test', 4),
('Cont', 0),
('Other', -5),
('Content', None),
))
),
)
def test_iter_slices(value, length):
if length is None or (length <= 0 and len(value) > 0):
# Reads all content at once
@@ -477,123 +494,124 @@ def test_iter_slices(value, length):
@pytest.mark.parametrize(
'value, expected', (
'value, expected',
(
(
'<http:/.../front.jpeg>; rel=front; type="image/jpeg"',
[{'url': 'http:/.../front.jpeg', 'rel': 'front', 'type': 'image/jpeg'}]
),
(
'<http:/.../front.jpeg>',
[{'url': 'http:/.../front.jpeg'}]
),
(
'<http:/.../front.jpeg>;',
[{'url': 'http:/.../front.jpeg'}]
[
{
'url': 'http:/.../front.jpeg',
'rel': 'front',
'type': 'image/jpeg',
}
],
),
('<http:/.../front.jpeg>', [{'url': 'http:/.../front.jpeg'}]),
('<http:/.../front.jpeg>;', [{'url': 'http:/.../front.jpeg'}]),
(
'<http:/.../front.jpeg>; type="image/jpeg",<http://.../back.jpeg>;',
[
{'url': 'http:/.../front.jpeg', 'type': 'image/jpeg'},
{'url': 'http://.../back.jpeg'}
]
{'url': 'http://.../back.jpeg'},
],
),
))
('', []),
),
)
def test_parse_header_links(value, expected):
assert parse_header_links(value) == expected
@pytest.mark.parametrize(
'value, expected', (
'value, expected',
(
('example.com/path', 'http://example.com/path'),
('//example.com/path', 'http://example.com/path'),
))
),
)
def test_prepend_scheme_if_needed(value, expected):
assert prepend_scheme_if_needed(value, 'http') == expected
@pytest.mark.parametrize(
'value, expected', (
('T', 'T'),
(b'T', 'T'),
(u'T', 'T'),
))
def test_to_native_string(value, expected):
assert to_native_string(value) == expected
@pytest.mark.parametrize(
'url, expected', (
('http://u:p@example.com/path?a=1#test', 'http://example.com/path?a=1'),
'url, expected',
(
(
'http://u:p@example.com/path?a=1#test',
'http://example.com/path?a=1',
),
('http://example.com/path', 'http://example.com/path'),
('//u:p@example.com/path', '//example.com/path'),
('//example.com/path', '//example.com/path'),
('example.com/path', '//example.com/path'),
('scheme:u:p@example.com/path', 'scheme://example.com/path'),
))
),
)
def test_urldefragauth(url, expected):
assert urldefragauth(url) == expected
@pytest.mark.parametrize(
'url, expected', (
('http://192.168.0.1:5000/', True),
('http://192.168.0.1/', True),
('http://172.16.1.1/', True),
('http://172.16.1.1:5000/', True),
('http://localhost.localdomain:5000/v1.0/', True),
('http://172.16.1.12/', False),
('http://172.16.1.12:5000/', False),
('http://google.com:5000/v1.0/', False),
))
'url, expected',
(
('http://192.168.0.1:5000/', True),
('http://192.168.0.1/', True),
('http://172.16.1.1/', True),
('http://172.16.1.1:5000/', True),
('http://localhost.localdomain:5000/v1.0/', True),
('http://172.16.1.12/', False),
('http://172.16.1.12:5000/', False),
('http://google.com:5000/v1.0/', False),
),
)
def test_should_bypass_proxies(url, expected, monkeypatch):
"""Tests for function should_bypass_proxies to check if proxy
can be bypassed or not
"""
monkeypatch.setenv('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
monkeypatch.setenv('NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1')
monkeypatch.setenv(
'no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1'
)
monkeypatch.setenv(
'NO_PROXY', '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1'
)
assert should_bypass_proxies(url, no_proxy=None) == expected
@pytest.mark.parametrize(
'cookiejar', (
compat.cookielib.CookieJar(),
RequestsCookieJar()
))
'cookiejar', (basics.cookielib.CookieJar(), RequestsCookieJar())
)
def test_add_dict_to_cookiejar(cookiejar):
"""Ensure add_dict_to_cookiejar works for
non-RequestsCookieJar CookieJars
"""
cookiedict = {'test': 'cookies',
'good': 'cookies'}
cookiedict = {'test': 'cookies', 'good': 'cookies'}
cj = add_dict_to_cookiejar(cookiejar, cookiedict)
cookies = dict((cookie.name, cookie.value) for cookie in cj)
cookies = {cookie.name: cookie.value for cookie in cj}
assert cookiedict == cookies
@pytest.mark.parametrize(
'value, expected', (
(u'test', True),
(u'æíöû', False),
(u'ジェーピーニック', False),
)
'value, expected',
((u'test', True), (u'æíöû', False), (u'ジェーピーニック', False)),
)
def test_unicode_is_ascii(value, expected):
assert unicode_is_ascii(value) is expected
@pytest.mark.parametrize(
'url, expected', (
('http://192.168.0.1:5000/', True),
('http://192.168.0.1/', True),
('http://172.16.1.1/', True),
('http://172.16.1.1:5000/', True),
('http://localhost.localdomain:5000/v1.0/', True),
('http://172.16.1.12/', False),
('http://172.16.1.12:5000/', False),
('http://google.com:5000/v1.0/', False),
))
def test_should_bypass_proxies_no_proxy(
url, expected, monkeypatch):
'url, expected',
(
('http://192.168.0.1:5000/', True),
('http://192.168.0.1/', True),
('http://172.16.1.1/', True),
('http://172.16.1.1:5000/', True),
('http://localhost.localdomain:5000/v1.0/', True),
('http://172.16.1.12/', False),
('http://172.16.1.12:5000/', False),
('http://google.com:5000/v1.0/', False),
),
)
def test_should_bypass_proxies_no_proxy(url, expected, monkeypatch):
"""Tests for function should_bypass_proxies to check if proxy
can be bypassed or not using the 'no_proxy' argument
"""
@@ -604,31 +622,33 @@ def test_should_bypass_proxies_no_proxy(
@pytest.mark.skipif(os.name != 'nt', reason='Test only on Windows')
@pytest.mark.parametrize(
'url, expected, override', (
('http://192.168.0.1:5000/', True, None),
('http://192.168.0.1/', True, None),
('http://172.16.1.1/', True, None),
('http://172.16.1.1:5000/', True, None),
('http://localhost.localdomain:5000/v1.0/', True, None),
('http://172.16.1.22/', False, None),
('http://172.16.1.22:5000/', False, None),
('http://google.com:5000/v1.0/', False, None),
('http://mylocalhostname:5000/v1.0/', True, '<local>'),
('http://192.168.0.1/', False, ''),
))
def test_should_bypass_proxies_win_registry(url, expected, override,
monkeypatch):
'url, expected, override',
(
('http://192.168.0.1:5000/', True, None),
('http://192.168.0.1/', True, None),
('http://172.16.1.1/', True, None),
('http://172.16.1.1:5000/', True, None),
('http://localhost.localdomain:5000/v1.0/', True, None),
('http://172.16.1.22/', False, None),
('http://172.16.1.22:5000/', False, None),
('http://google.com:5000/v1.0/', False, None),
('http://mylocalhostname:5000/v1.0/', True, '<local>'),
('http://192.168.0.1/', False, ''),
),
)
def test_should_bypass_proxies_win_registry(
url, expected, override, monkeypatch
):
"""Tests for function should_bypass_proxies to check if proxy
can be bypassed or not with Windows registry settings
"""
if override is None:
override = '192.168.*;127.0.0.1;localhost.localdomain;172.16.1.1'
if compat.is_py3:
import winreg
else:
import _winreg as winreg
import winreg
class RegHandle:
def Close(self):
pass
@@ -641,6 +661,7 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
if key is ie_settings:
if value_name == 'ProxyEnable':
return [1]
elif value_name == 'ProxyOverride':
return [override]
@@ -651,4 +672,30 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
monkeypatch.setenv('NO_PROXY', '')
monkeypatch.setattr(winreg, 'OpenKey', OpenKey)
monkeypatch.setattr(winreg, 'QueryValueEx', QueryValueEx)
assert should_bypass_proxies(url, no_proxy=None) == expected
@pytest.mark.parametrize(
'env_name, value',
(
('no_proxy', '192.168.0.0/24,127.0.0.1,localhost.localdomain'),
('no_proxy', None),
('a_new_key', '192.168.0.0/24,127.0.0.1,localhost.localdomain'),
('a_new_key', None),
),
)
def test_set_environ(env_name, value):
"""Tests set_environ will set environ values and will restore the environ."""
environ_copy = copy.deepcopy(os.environ)
with set_environ(env_name, value):
assert os.environ.get(env_name) == value
assert os.environ == environ_copy
def test_set_environ_raises_exception():
"""Tests set_environ will raise exceptions in context when the
value parameter is None."""
with pytest.raises(Exception) as exception:
with set_environ('test1', None):
raise Exception('Expected exception')
assert 'Expected exception' in str(exception.value)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import threading
import socket
import select
@@ -8,7 +7,6 @@ import select
def consume_socket_content(sock, timeout=0.5):
chunks = 65536
content = b''
while True:
more_to_read = select.select([sock], [], [], timeout)[0]
if not more_to_read:
@@ -19,7 +17,6 @@ def consume_socket_content(sock, timeout=0.5):
break
content += new_content
return content
@@ -27,37 +24,40 @@ class Server(threading.Thread):
"""Dummy server using for unit testing"""
WAIT_EVENT_TIMEOUT = 5
def __init__(self, handler=None, host='localhost', port=0, requests_to_handle=1, wait_to_close_event=None):
def __init__(
self,
handler=None,
host='localhost',
port=0,
requests_to_handle=1,
wait_to_close_event=None,
):
super(Server, self).__init__()
self.handler = handler or consume_socket_content
self.handler_results = []
self.host = host
self.port = port
self.requests_to_handle = requests_to_handle
self.wait_to_close_event = wait_to_close_event
self.ready_event = threading.Event()
self.stop_event = threading.Event()
@classmethod
def text_response_server(cls, text, request_timeout=0.5, **kwargs):
def text_response_handler(sock):
request_content = consume_socket_content(sock, timeout=request_timeout)
request_content = consume_socket_content(
sock, timeout=request_timeout
)
sock.send(text.encode('utf-8'))
return request_content
return Server(text_response_handler, **kwargs)
@classmethod
def basic_response_server(cls, **kwargs):
return cls.text_response_server(
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n\r\n",
**kwargs
"HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n\r\n", **kwargs
)
def run(self):
@@ -67,11 +67,10 @@ class Server(threading.Thread):
self.port = self.server_sock.getsockname()[1]
self.ready_event.set()
self._handle_requests()
if self.wait_to_close_event:
self.wait_to_close_event.wait(self.WAIT_EVENT_TIMEOUT)
finally:
self.ready_event.set() # just in case of exception
self.ready_event.set() # just in case of exception
self._close_server_sock_ignore_errors()
self.stop_event.set()
@@ -94,16 +93,18 @@ class Server(threading.Thread):
break
handler_result = self.handler(sock)
self.handler_results.append(handler_result)
def _accept_connection(self):
try:
ready, _, _ = select.select([self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT)
ready, _, _ = select.select(
[self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT
)
if not ready:
return None
return self.server_sock.accept()[0]
except (select.error, socket.error):
return None
@@ -120,8 +121,7 @@ class Server(threading.Thread):
# avoid server from waiting for event timeouts
# if an exception is found in the main thread
self.wait_to_close_event.set()
# ensure server thread doesn't get stuck waiting for connections
self._close_server_sock_ignore_errors()
self.join()
return False # allow exceptions to propagate
return False # allow exceptions to propagate

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import contextlib
import os
@@ -14,6 +13,7 @@ def override_environ(**kwargs):
os.environ[key] = value
try:
yield
finally:
os.environ.clear()
os.environ.update(save_env)

8
tox.ini Normal file
View File

@@ -0,0 +1,8 @@
[tox]
envlist = py36
[testenv]
commands =
pip install -e .[socks]
python setup.py test