Compare commits

..

254 Commits

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
69 changed files with 3239 additions and 2337 deletions

7
.gitignore vendored
View File

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

View File

@@ -1,29 +1,27 @@
sudo: false
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
# - "3.4"
# - "3.5"
- "3.6"
# - "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 test-readme
- make ci
cache: pip
jobs:
include:
- stage: test
script:
- |
if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi
- make test-readme
- make ci
- stage: coverage
python: 3.6

View File

@@ -1,6 +1,11 @@
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.
@@ -19,6 +24,10 @@
- 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.
@@ -64,5 +73,9 @@
- ``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

17
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>`_)
@@ -186,3 +179,11 @@ Patches and Suggestions
- 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

@@ -6,10 +6,72 @@ 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)
@@ -1481,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
include README.rst LICENSE NOTICE HISTORY.rst pytest.ini requirements.txt
recursive-include tests *.py

View File

@@ -1,20 +1,23 @@
.PHONY: docs
init:
pip install -r requirements.txt
pip install pipenv --upgrade
pipenv install --dev --skip-lock
test:
# This runs all of the tests, on both Python 2 and Python 3.
detox
python setup.py test
mypy:
python setup.py mypy
ci:
py.test -n 8 --boxed --junitxml=report.xml
pipenv run py.test -n 8 --boxed --junitxml=report.xml
test-readme:
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:
flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
pipenv run flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
coverage:
py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
publish:
pip install 'twine>=1.5.0'

32
Pipfile Normal file
View File

@@ -0,0 +1,32 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
[dev-packages]
pytest = ">=2.8.0"
codecov = "*"
pytest-httpbin = "*"
pytest-mock = "*"
pytest-cov = "*"
pytest-xdist = "*"
alabaster = "*"
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]

View File

@@ -20,14 +20,10 @@ Requests: HTTP for Humans
.. 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:
@@ -81,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
-------------

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"
@@ -53,4 +33,4 @@ test_script:
- "C:\\MinGW\\bin\\mingw32-make coverage"
on_success:
- "codecov -f coverage.xml"
- "pipenv run codecov -f coverage.xml"

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

@@ -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>

View File

@@ -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

@@ -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

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:

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

@@ -129,7 +129,8 @@ html_theme_options = {
'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
@@ -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,6 +1,8 @@
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.
@@ -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,6 +1,8 @@
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.
@@ -49,9 +51,7 @@ 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

View File

@@ -28,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**::
@@ -81,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
----------------
@@ -104,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
@@ -131,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
@@ -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
@@ -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

@@ -3,18 +3,21 @@
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.

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
@@ -418,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
-------

View File

@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
# /
"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~
@@ -40,74 +38,103 @@ is at <http://python-requests.org>.
:license: Apache 2.0, see LICENSE for more details.
"""
# 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!')
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
import warnings
# 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__
from . import utils
from . import packages
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,7 +1,6 @@
# .-. .-. .-. . . .-. .-. .-. .-.
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'http://python-requests.org'

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
~~~~~~~~~~~~~~~~~
@@ -10,10 +9,6 @@ and maintain connections.
import os.path
import socket
try:
from threading import RLock
except ImportError: # threading is an optional module and may not be present.
from dummy_threading import RLock
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.response import HTTPResponse
@@ -31,36 +26,119 @@ 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, InvalidScheme)
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 InvalidScheme("Missing dependencies for SOCKS support.")
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
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.
@@ -108,46 +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._pool_kw_lock = RLock()
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 = {}
self._pool_kw_lock = RLock()
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
@@ -163,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.
@@ -190,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)
@@ -200,69 +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 _update_poolmanager_ssl_kw(self, verify, cert):
"""Update the :class:`PoolManager <urllib3.poolmanager.PoolManager>`
connection_pool_kw with the necessary SSL configuration. This method
should not be called from user code, and is only exposed for use when
subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
: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).
"""
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))
self.poolmanager.connection_pool_kw['cert_reqs'] = 'CERT_REQUIRED'
if not os.path.isdir(cert_loc):
self.poolmanager.connection_pool_kw['ca_certs'] = cert_loc
self.poolmanager.connection_pool_kw['ca_cert_dir'] = None
else:
self.poolmanager.connection_pool_kw['ca_cert_dir'] = cert_loc
self.poolmanager.connection_pool_kw['ca_certs'] = None
else:
self.poolmanager.connection_pool_kw['cert_reqs'] = 'CERT_NONE'
self.poolmanager.connection_pool_kw['ca_certs'] = None
self.poolmanager.connection_pool_kw['ca_cert_dir'] = None
if cert:
if not isinstance(cert, basestring):
self.poolmanager.connection_pool_kw['cert_file'] = cert[0]
self.poolmanager.connection_pool_kw['key_file'] = cert[1]
else:
self.poolmanager.connection_pool_kw['cert_file'] = cert
self.poolmanager.connection_pool_kw['key_file'] = None
cert_file = self.poolmanager.connection_pool_kw['cert_file']
key_file = self.poolmanager.connection_pool_kw['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))
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
@@ -274,30 +301,23 @@ 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, verify=None, cert=None):
@@ -309,22 +329,21 @@ class HTTPAdapter(BaseAdapter):
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
:rtype: urllib3.ConnectionPool
"""
with self._pool_kw_lock:
if url.lower().startswith('https'):
self._update_poolmanager_ssl_kw(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)
else:
# Only scheme should be lower case
parsed = urlparse(url)
url = parsed.geturl()
conn = self.poolmanager.connection_from_url(url)
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, pool_kwargs=pool_kwargs
)
else:
# Only scheme should be lower case
parsed = urlparse(url)
url = parsed.geturl()
conn = self.poolmanager.connection_from_url(
url, pool_kwargs=pool_kwargs
)
return conn
def close(self):
@@ -353,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):
@@ -395,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.
@@ -419,27 +442,28 @@ class HTTPAdapter(BaseAdapter):
:rtype: requests.Response
"""
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(
@@ -453,45 +477,37 @@ class HTTPAdapter(BaseAdapter):
decode_content=False,
retries=self.max_retries,
timeout=timeout,
enforce_content_length=True
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:
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,
@@ -499,7 +515,7 @@ class HTTPAdapter(BaseAdapter):
preload_content=False,
decode_content=False,
enforce_content_length=True,
request_method=request.method
request_method=request.method,
)
except:
# If we hit any problems here, clean up the connection.
@@ -522,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:
@@ -532,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,10 +9,17 @@ 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, session=None, **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.
@@ -51,18 +57,17 @@ def request(method, url, session=None, **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.
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.
@@ -71,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.
@@ -97,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.
@@ -112,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.
@@ -126,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.
@@ -140,11 +150,10 @@ def patch(url, data=None, **kwargs):
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
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.
@@ -152,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
~~~~~~~~~~~~~
@@ -15,7 +14,7 @@ import threading
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
@@ -26,25 +25,25 @@ CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
if not isinstance(username, basestring):
raise TypeError('username must be of type str or bytes, '
'instead it was %s' % type(username))
raise TypeError(
'username must be of type str or bytes, '
'instead it was %s' % type(username)
)
if not isinstance(password, basestring):
raise TypeError('password must be of type str or bytes, '
'instead it was %s' % type(password))
raise TypeError(
'password must be of type str or bytes, '
'instead it was %s' % type(password)
)
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
@@ -63,16 +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)
r.headers['Authorization'] = _basic_auth_str(
self.username, self.password
)
return r
@@ -99,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:
@@ -153,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:
@@ -182,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):
@@ -196,7 +191,6 @@ class HTTPDigestAuth(AuthBase):
:rtype: requests.Response
"""
# If response is not 4xx, do not auth
# See https://github.com/requests/requests/issues/3772
if not 400 <= r.status_code < 500:
@@ -208,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
@@ -222,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
@@ -239,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:
@@ -251,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,65 +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)
# ---------
# 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
@@ -316,6 +335,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
def __contains__(self, name):
try:
return super(RequestsCookieJar, self).__contains__(name)
except CookieConflictError:
return True
@@ -342,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"""
@@ -392,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):
@@ -426,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()
@@ -440,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'
@@ -465,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(
@@ -510,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
@@ -531,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)
@@ -108,9 +110,10 @@ class UnrewindableBodyError(RequestException):
class InvalidBodyError(RequestException, ValueError):
"""An invalid request body was specified"""
# Warnings
class RequestsWarning(Warning):
"""Base warning for Requests."""
pass
@@ -119,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

View File

@@ -6,13 +6,16 @@ import platform
import sys
import ssl
import idna
import urllib3
import chardet
from . import __version__ as requests_version
from .import types
from .import __version__ as requests_version
try:
from .packages.urllib3.contrib import pyopenssl
from . packages.urllib3.contrib import pyopenssl
except ImportError:
pyopenssl = None
OpenSSL = None
@@ -22,8 +25,8 @@ else:
import cryptography
def _implementation():
"""Return a dict with the Python implementation and verison.
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
@@ -34,71 +37,62 @@ def _implementation():
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)
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
])
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():
def info() -> types.Help:
"""Generate information for a bug report."""
try:
platform_info = {
'system': platform.system(),
'release': platform.release(),
'system': platform.system(), 'release': platform.release()
}
except IOError:
platform_info = {
'system': 'Unknown',
'release': 'Unknown',
}
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': '',
}
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__', ''),
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': {
'version': '%x' % ssl.OPENSSL_VERSION_NUMBER,
},
'system_ssl': system_ssl_info,
'using_pyopenssl': pyopenssl is not None,
'pyOpenSSL': pyopenssl_info,
'urllib3': urllib3_info,
'chardet': chardet_info,
'cryptography': cryptography_info,
'requests': {
'version': requests_version,
},
'idna': idna_info,
'requests': {'version': requests_version},
}

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__'):

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.models
~~~~~~~~~~~~~~~
@@ -15,13 +14,14 @@ import sys
# Import encoding now, to avoid implicit import later.
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
# such as in Embedded Python. See https://github.com/requests/requests/issues/3578.
import rfc3986
import encodings.idna
from urllib3.fields import RequestField
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url
from urllib3.exceptions import (
DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
DecodeError, ReadTimeoutError, ProtocolError, LocationParseError
)
from io import UnsupportedOperation
from .hooks import default_hooks
@@ -31,56 +31,74 @@ import requests
from .auth import HTTPBasicAuth
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
from .exceptions import (
HTTPError, MissingScheme, InvalidURL, ChunkedEncodingError,
ContentDecodingError, ConnectionError, StreamConsumedError,
InvalidHeader, InvalidBodyError)
HTTPError,
MissingScheme,
InvalidURL,
ChunkedEncodingError,
ContentDecodingError,
ConnectionError,
StreamConsumedError,
InvalidHeader,
InvalidBodyError,
ReadTimeout,
)
from ._internal_utils import to_native_string, unicode_is_ascii
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
stream_decode_response_unicode, to_key_val_list, parse_header_links,
iter_slices, guess_json_utf, super_len, check_header_validity,
is_stream)
from .compat import (
cookielib, urlunparse, urlsplit, urlencode, str, bytes,
is_py2, chardet, builtin_str, basestring)
guess_filename,
get_auth_from_url,
requote_uri,
stream_decode_response_unicode,
to_key_val_list,
parse_header_links,
iter_slices,
guess_json_utf,
super_len,
check_header_validity,
is_stream,
)
from .basics import (
cookielib,
urlunparse,
urlsplit,
urlencode,
str,
bytes,
chardet,
builtin_str,
basestring,
)
import json as complexjson
from .status_codes import codes
#: The set of HTTP status codes that indicate an automatically
# : The set of HTTP status codes that indicate an automatically
#: processable redirect.
REDIRECT_STATI = (
codes.moved, # 301
codes.found, # 302
codes.other, # 303
codes.temporary_redirect, # 307
codes.permanent_redirect, # 308
codes['moved'], # 301
codes['found'], # 302
codes['other'], # 303
codes['temporary_redirect'], # 307
codes['permanent_redirect'], # 308
)
DEFAULT_REDIRECT_LIMIT = 30
CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
class RequestEncodingMixin(object):
@property
def path_url(self):
"""Build the path URL to use."""
url = []
p = urlsplit(self.url)
path = p.path
if not path:
path = '/'
url.append(path)
query = p.query
if query:
url.append('?')
url.append(query)
return ''.join(url)
@staticmethod
@@ -91,11 +109,12 @@ class RequestEncodingMixin(object):
2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
if parameters are supplied as a dict.
"""
if isinstance(data, (str, bytes)):
return data
elif hasattr(data, 'read'):
return data
elif hasattr(data, '__iter__'):
result = []
for k, vs in to_key_val_list(data):
@@ -104,9 +123,13 @@ class RequestEncodingMixin(object):
for v in vs:
if v is not None:
result.append(
(k.encode('utf-8') if isinstance(k, str) else k,
v.encode('utf-8') if isinstance(v, str) else v))
(
k.encode('utf-8') if isinstance(k, str) else k,
v.encode('utf-8') if isinstance(v, str) else v,
)
)
return urlencode(result, doseq=True)
else:
return data
@@ -122,13 +145,13 @@ class RequestEncodingMixin(object):
"""
if (not files):
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
new_fields = []
fields = to_key_val_list(data or {})
files = to_key_val_list(files or {})
for field, val in fields:
if isinstance(val, basestring) or not hasattr(val, '__iter__'):
val = [val]
@@ -137,11 +160,14 @@ class RequestEncodingMixin(object):
# Don't call str() on bytestrings: in Py3 it all goes wrong.
if not isinstance(v, bytes):
v = str(v)
new_fields.append(
(field.decode('utf-8') if isinstance(field, bytes) else field,
v.encode('utf-8') if isinstance(v, str) else v))
(
field.decode('utf-8') if isinstance(
field, bytes
) else field,
v.encode('utf-8') if isinstance(v, str) else v,
)
)
for (k, v) in files:
# support for explicit filename
ft = None
@@ -156,41 +182,41 @@ class RequestEncodingMixin(object):
else:
fn = guess_filename(v) or k
fp = v
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
else:
fdata = fp.read()
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
rf.make_multipart(content_type=ft)
new_fields.append(rf)
body, content_type = encode_multipart_formdata(new_fields)
return body, content_type
class RequestHooksMixin(object):
def register_hook(self, event, hook):
"""Properly register a hook."""
if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
raise ValueError(
'Unsupported event specified, with event name "%s"' % (event)
)
if isinstance(hook, collections.Callable):
self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'):
self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable))
self.hooks[event].extend(
h for h in hook if isinstance(h, collections.Callable)
)
def deregister_hook(self, event, hook):
"""Deregister a previously registered hook.
Returns True if the hook existed, False if not.
"""
try:
self.hooks[event].remove(hook)
return True
except ValueError:
return False
@@ -218,22 +244,41 @@ class Request(RequestHooksMixin):
>>> req.prepare()
<PreparedRequest [GET]>
"""
__slots__ = (
'method',
'url',
'headers',
'files',
'data',
'params',
'auth',
'cookies',
'hooks',
'json',
)
def __init__(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
def __init__(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
# Default empty dicts for dict params.
data = [] if data is None else data
files = [] if files is None else files
headers = {} if headers is None else headers
params = {} if params is None else params
hooks = {} if hooks is None else hooks
self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
self.register_hook(event=k, hook=v)
self.method = method
self.url = url
self.headers = headers
@@ -282,44 +327,60 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
>>> s.send(r)
<Response [200]>
"""
__slots__ = (
'method',
'url',
'headers',
'_cookies',
'body',
'hooks',
'_body_position',
)
def __init__(self):
#: HTTP verb to send to the server.
# : HTTP verb to send to the server.
self.method = None
#: HTTP URL to send the request to.
# : HTTP URL to send the request to.
self.url = None
#: dictionary of HTTP headers.
# : dictionary of HTTP headers.
self.headers = None
# The `CookieJar` used to create the Cookie header will be stored here
# after prepare_cookies is called
self._cookies = None
#: request body to send to the server.
# : request body to send to the server.
self.body = None
#: dictionary of callback hooks, for internal usage.
# : dictionary of callback hooks, for internal usage.
self.hooks = default_hooks()
#: integer denoting starting position of a readable file-like body.
# : integer denoting starting position of a readable file-like body.
self._body_position = None
def prepare(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
def prepare(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
"""Prepares the entire request with the given parameters."""
self.prepare_method(method)
self.prepare_url(url, params)
self.prepare_headers(headers)
self.prepare_cookies(cookies)
self.prepare_body(data, files, json)
self.prepare_auth(auth, url)
# Note that prepare_auth must be last to enable authentication schemes
# such as OAuth to work on a fully prepared request.
# This MUST go after prepare_auth. Authenticators could add a hook
self.prepare_hooks(hooks)
def __repr__(self):
return '<PreparedRequest [%s]>' % (self.method)
return f'<PreparedRequest [{self.method}]>'
def copy(self):
p = PreparedRequest()
@@ -337,6 +398,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.method = method
if self.method is None:
raise ValueError('Request method cannot be "None"')
self.method = to_native_string(self.method.upper())
@staticmethod
@@ -347,11 +409,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
host = idna.encode(host, uts46=True).decode('utf-8')
except idna.IDNAError:
raise UnicodeError
return host
def prepare_url(self, url, params):
def prepare_url(self, url, params, validate=False):
"""Prepares the given HTTP URL."""
#: Accept objects that have string representations.
# : Accept objects that have string representations.
#: We're unable to blindly call unicode/str functions
#: as this will include the bytestring indicator (b'')
#: on python 3.x.
@@ -359,11 +422,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if isinstance(url, bytes):
url = url.decode('utf8')
else:
url = unicode(url) if is_py2 else str(url)
url = str(url)
# Ignore any leading and trailing whitespace characters.
url = url.strip()
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
@@ -373,71 +434,54 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# Support for unicode domain names and paths.
try:
scheme, auth, host, port, path, query, fragment = parse_url(url)
except LocationParseError as e:
raise InvalidURL(*e.args)
uri = rfc3986.urlparse(url)
if validate:
rfc3986.normalize_uri(url)
except rfc3986.exceptions.RFC3986Exception:
raise InvalidURL(f"Invalid URL {url!r}: URL is imporoper.")
if not scheme:
error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?")
if not uri.scheme:
error = (
"Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?"
)
error = error.format(to_native_string(url, 'utf8'))
raise MissingScheme(error)
if not host:
raise InvalidURL("Invalid URL %r: No host supplied" % url)
if not uri.host:
raise InvalidURL(f"Invalid URL {url!r}: No host supplied")
# In general, we want to try IDNA encoding the hostname if the string contains
# non-ASCII characters. This allows users to automatically get the correct IDNA
# behaviour. For strings containing only ASCII characters, we need to also verify
# it doesn't start with a wildcard (*), before allowing the unencoded hostname.
if not unicode_is_ascii(host):
if not unicode_is_ascii(uri.host):
try:
host = self._get_idna_encoded_host(host)
uri = uri.copy_with(host=self._get_idna_encoded_host(uri.host))
except UnicodeError:
raise InvalidURL('URL has an invalid label.')
elif host.startswith(u'*'):
elif uri.host.startswith(u'*'):
raise InvalidURL('URL has an invalid label.')
# Carefully reconstruct the network location
netloc = auth or ''
if netloc:
netloc += '@'
netloc += host
if port:
netloc += ':' + str(port)
# Bare domains aren't valid URLs.
if not path:
path = '/'
if is_py2:
if isinstance(scheme, str):
scheme = scheme.encode('utf-8')
if isinstance(netloc, str):
netloc = netloc.encode('utf-8')
if isinstance(path, str):
path = path.encode('utf-8')
if isinstance(query, str):
query = query.encode('utf-8')
if isinstance(fragment, str):
fragment = fragment.encode('utf-8')
if not uri.path:
uri = uri.copy_with(path='/')
if isinstance(params, (str, bytes)):
params = to_native_string(params)
enc_params = self._encode_params(params)
if enc_params:
if query:
query = '%s&%s' % (query, enc_params)
if uri.query:
uri = uri.copy_with(query=f'{uri.query}&{enc_params}')
else:
query = enc_params
url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
self.url = url
uri = uri.copy_with(query=enc_params)
# url = requote_uri(
# urlunparse([uri.scheme, uri.authority, uri.path, None, uri.query, uri.fragment])
# )
# Normalize the URI.
self.url = rfc3986.normalize_uri(uri.unsplit())
def prepare_headers(self, headers):
"""Prepares the given HTTP headers."""
self.headers = CaseInsensitiveDict()
if headers:
for header in headers.items():
@@ -448,14 +492,11 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
def prepare_body(self, data, files, json=None):
"""Prepares the given HTTP body data."""
# Check if file, fo, generator, iterator.
# If not, run through normal process.
# Nottin' on you.
body = None
content_type = None
if not data and json is not None:
# urllib3 requires a bytes-like body. Python 2's json.dumps
# provides this natively, but Python 3 gives a Unicode string.
@@ -463,10 +504,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
body = complexjson.dumps(json)
if not isinstance(body, bytes):
body = body.encode('utf-8')
if is_stream(data):
body = data
if getattr(body, 'tell', None) is not None:
# Record the current file position before reading.
# This will allow us to rewind a file in the event
@@ -477,9 +516,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# This differentiates from None, allowing us to catch
# a failed `tell()` later when trying to rewind the body
self._body_position = object()
if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
raise NotImplementedError(
'Streamed bodies and files are mutually exclusive.'
)
else:
# Multi-part file uploads.
@@ -492,11 +532,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
content_type = None
else:
content_type = 'application/x-www-form-urlencoded'
# Add content-type if it wasn't explicitly provided.
if content_type and ('content-type' not in self.headers):
self.headers['Content-Type'] = content_type
self.prepare_content_length(body)
self.body = body
@@ -511,41 +549,41 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
"""
if body is not None:
length = super_len(body)
if length:
self.headers['Content-Length'] = builtin_str(length)
elif is_stream(body):
self.headers['Transfer-Encoding'] = 'chunked'
else:
raise InvalidBodyError('Non-null body must have length or be streamable.')
elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None:
raise InvalidBodyError(
'Non-null body must have length or be streamable.'
)
elif self.method not in ('GET', 'HEAD') and self.headers.get(
'Content-Length'
) is None:
# Set Content-Length to 0 for methods that can have a body
# but don't provide one. (i.e. not GET or HEAD)
self.headers['Content-Length'] = '0'
if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers:
raise InvalidHeader('Conflicting Headers: Both Transfer-Encoding and '
'Content-Length are set.')
raise InvalidHeader(
'Conflicting Headers: Both Transfer-Encoding and '
'Content-Length are set.'
)
def prepare_auth(self, auth, url=''):
"""Prepares the given HTTP auth data."""
# If no Auth is explicitly provided, extract it from the URL first.
if auth is None:
url_auth = get_auth_from_url(self.url)
auth = url_auth if any(url_auth) else None
if auth:
if isinstance(auth, tuple) and len(auth) == 2:
# special-case basic HTTP auth
auth = HTTPBasicAuth(*auth)
# Allow auth to make its changes.
r = auth(self)
# Update self to reflect the auth changes.
self.__dict__.update(r.__dict__)
# Recompute Content-Length
self.prepare_content_length(self.body)
@@ -564,7 +602,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self._cookies = cookies
else:
self._cookies = cookiejar_from_dict(cookies)
cookie_header = get_cookie_header(self._cookies, self)
if cookie_header is not None:
self.headers['Cookie'] = cookie_header
@@ -582,7 +619,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
"""Sends the PreparedRequest to the given Session.
If none is provided, one is created for you."""
session = requests.Session() if session is None else session
with session:
return session.send(self, **send_kwargs)
@@ -591,77 +627,74 @@ class Response(object):
"""The :class:`Response <Response>` object, which contains a
server's response to an HTTP request.
"""
__attrs__ = [
'_content', 'status_code', 'headers', 'url', 'history',
'encoding', 'reason', 'cookies', 'elapsed', 'request'
'_content',
'status_code',
'headers',
'url',
'history',
'encoding',
'reason',
'cookies',
'elapsed',
'request',
]
__slots__ = __attrs__ + ['_content_consumed', 'raw', '_next', 'connection']
def __init__(self):
super(Response, self).__init__()
self._content = False
self._content_consumed = False
self._next = None
#: Integer Code of responded HTTP Status, e.g. 404 or 200.
# : Integer Code of responded HTTP Status, e.g. 404 or 200.
self.status_code = None
#: Case-insensitive Dictionary of Response Headers.
# : Case-insensitive Dictionary of Response Headers.
#: For example, ``headers['content-encoding']`` will return the
#: value of a ``'Content-Encoding'`` response header.
self.headers = CaseInsensitiveDict()
#: File-like object representation of response (for advanced usage).
# : File-like object representation of response (for advanced usage).
#: Use of ``raw`` requires that ``stream=True`` be set on the request.
# This requirement does not apply for use internally to Requests.
self.raw = None
#: Final URL location of Response.
# : Final URL location of Response.
self.url = None
#: Encoding to decode with when accessing r.text or
# : Encoding to decode with when accessing r.text or
#: r.iter_content(decode_unicode=True)
self.encoding = None
#: A list of :class:`Response <Response>` objects from
# : A list of :class:`Response <Response>` objects from
#: the history of the Request. Any redirect responses will end
#: up here. The list is sorted from the oldest to the most recent request.
self.history = []
#: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
# : Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
self.reason = None
#: A CookieJar of Cookies the server sent back.
# : A CookieJar of Cookies the server sent back.
self.cookies = cookiejar_from_dict({})
#: The amount of time elapsed between sending the request
# : The amount of time elapsed between sending the request
#: and the arrival of the response (as a timedelta).
#: This property specifically measures the time taken between sending
#: the first byte of the request and finishing parsing the headers. It
#: is therefore unaffected by consuming the response content or the
#: value of the ``stream`` keyword argument.
self.elapsed = datetime.timedelta(0)
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
# : The :class:`PreparedRequest <PreparedRequest>` object to which this
#: is a response.
self.request = None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def __getstate__(self):
# Consume everything; accessing the content attribute makes
# sure the content has been fully read.
if not self._content_consumed:
self.content
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):
for name, value in state.items():
setattr(self, name, value)
# pickled objects do not have .raw
setattr(self, '_content_consumed', True)
setattr(self, 'raw', None)
@@ -686,6 +719,7 @@ class Response(object):
self.raise_for_status()
except HTTPError:
return False
return True
@property
@@ -693,12 +727,19 @@ class Response(object):
"""True if this Response is a well-formed HTTP redirect that could have
been processed automatically (by :meth:`Session.resolve_redirects`).
"""
return ('location' in self.headers and self.status_code in REDIRECT_STATI)
return (
'location' in self.headers and self.status_code in REDIRECT_STATI
)
@property
def is_permanent_redirect(self):
"""True if this Response one of the permanent versions of redirect."""
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
return (
'location' in self.headers and
self.status_code in (
codes.moved_permanently, codes.permanent_redirect
)
)
@property
def next(self):
@@ -731,38 +772,48 @@ class Response(object):
# Special case for urllib3.
if hasattr(self.raw, 'stream'):
try:
for chunk in self.raw.stream(chunk_size, decode_content=True):
for chunk in self.raw.stream(
chunk_size, decode_content=True
):
yield chunk
except ProtocolError as e:
if self.headers.get('Transfer-Encoding') == 'chunked':
raise ChunkedEncodingError(e)
else:
raise ConnectionError(e)
except DecodeError as e:
raise ContentDecodingError(e)
except ReadTimeoutError as e:
raise ConnectionError(e)
raise ReadTimeout(e)
else:
# Standard file-like object.
while True:
chunk = self.raw.read(chunk_size)
if not chunk:
break
yield chunk
self._content_consumed = True
if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError()
elif chunk_size is not None and not isinstance(chunk_size, int):
raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size))
raise TypeError(
"chunk_size must be an int, it is instead a %s." %
type(chunk_size)
)
# simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size)
stream_chunks = generate()
chunks = reused_chunks if self._content_consumed else stream_chunks
if decode_unicode:
if self.encoding is None:
raise TypeError(
@@ -773,12 +824,12 @@ class Response(object):
# check encoding value here, don't wait for the generator to be
# consumed before raising an exception
codecs.lookup(self.encoding)
chunks = stream_decode_response_unicode(chunks, self)
return chunks
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None):
def iter_lines(
self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None
):
"""Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the
content at once into memory for large responses.
@@ -787,12 +838,11 @@ class Response(object):
"""
carriage_return = u'\r' if decode_unicode else b'\r'
line_feed = u'\n' if decode_unicode else b'\n'
pending = None
last_chunk_ends_with_cr = False
for chunk in self.iter_content(chunk_size=chunk_size,
decode_unicode=decode_unicode):
for chunk in self.iter_content(
chunk_size=chunk_size, decode_unicode=decode_unicode
):
# Skip any null responses: if there is pending data it is necessarily an
# incomplete chunk, so if we don't have more data we don't want to bother
# trying to get it. Unconsumed pending data will be yielded anyway in the
@@ -804,7 +854,6 @@ class Response(object):
if pending is not None:
chunk = pending + chunk
pending = None
# Either split on a line, or split on a specified delimiter
if delimiter:
lines = chunk.split(delimiter)
@@ -815,19 +864,20 @@ class Response(object):
# starts with '\n', they should be merged and treated as only
# *one* new line separator '\r\n' by splitlines().
# This rule only applies when splitlines() is used.
# The last chunk ends with '\r', so the '\n' at chunk[0]
# is just the second half of a '\r\n' pair rather than a
# new line break. Just skip it.
skip_first_char = last_chunk_ends_with_cr and chunk.startswith(line_feed)
skip_first_char = last_chunk_ends_with_cr and chunk.startswith(
line_feed
)
last_chunk_ends_with_cr = chunk.endswith(carriage_return)
if skip_first_char:
chunk = chunk[1:]
# it's possible that after stripping the '\n' then chunk becomes empty
if not chunk:
continue
lines = chunk.splitlines()
lines = chunk.splitlines()
# Calling `.split(delimiter)` will always end with whatever text
# remains beyond the delimiter, or '' if the delimiter is the end
# of the text. On the other hand, `.splitlines()` doesn't include
@@ -846,7 +896,6 @@ class Response(object):
incomplete_line = lines[-1] and lines[-1][-1] == chunk[-1]
if delimiter or incomplete_line:
pending = lines.pop()
for line in lines:
yield line
@@ -856,18 +905,20 @@ class Response(object):
@property
def content(self):
"""Content of the response, in bytes."""
if self._content is False:
# Read the contents.
if self._content_consumed:
raise RuntimeError(
'The content for this response was already consumed')
'The content for this response was already consumed'
)
if self.status_code == 0 or self.raw is None:
self._content = None
else:
self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
self._content = bytes().join(
self.iter_content(CONTENT_CHUNK_SIZE)
) or bytes(
)
self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
# since we exhausted the data.
@@ -885,18 +936,15 @@ class Response(object):
non-HTTP knowledge to make a better guess at the encoding, you should
set ``r.encoding`` appropriately before accessing this property.
"""
# Try charset from content-type
content = None
encoding = self.encoding
if not self.content:
return str('')
# Fallback to auto-detected encoding.
if self.encoding is None:
encoding = self.apparent_encoding
# Decode unicode from given encoding.
try:
content = str(self.content, encoding, errors='replace')
@@ -908,7 +956,6 @@ class Response(object):
#
# So we try blindly encoding.
content = str(self.content, errors='replace')
return content
def json(self, **kwargs):
@@ -917,7 +964,6 @@ class Response(object):
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises ValueError: If the response body does not contain valid json.
"""
if not self.encoding and self.content and len(self.content) > 3:
# No encoding set. JSON RFC 4627 section 3 states we should expect
# UTF-8, -16 or -32. Detect which one to use; If the detection or
@@ -929,6 +975,7 @@ class Response(object):
return complexjson.loads(
self.content.decode(encoding), **kwargs
)
except UnicodeDecodeError:
# Wrong UTF codec detected; usually because it's not UTF-8
# but some other 8-bit codec. This is an RFC violation,
@@ -940,25 +987,19 @@ class Response(object):
@property
def links(self):
"""Returns the parsed header links of the response, if any."""
header = self.headers.get('link')
# l = MultiDict()
l = {}
if header:
links = parse_header_links(header)
for link in links:
key = link.get('rel') or link.get('url')
l[key] = link
return l
def raise_for_status(self):
"""Raises stored :class:`HTTPError`, if one occurred.
Otherwise, returns the response object (self)."""
http_error_msg = ''
if isinstance(self.reason, bytes):
# We attempt to decode utf-8 first because some servers
@@ -971,13 +1012,14 @@ class Response(object):
reason = self.reason.decode('iso-8859-1')
else:
reason = self.reason
if 400 <= self.status_code < 500:
http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url)
http_error_msg = u'%s Client Error: %s for url: %s' % (
self.status_code, reason, self.url
)
elif 500 <= self.status_code < 600:
http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url)
http_error_msg = u'%s Server Error: %s for url: %s' % (
self.status_code, reason, self.url
)
if http_error_msg:
raise HTTPError(http_error_msg, response=self)
@@ -991,7 +1033,6 @@ class Response(object):
"""
if not self._content_consumed:
self.raw.close()
release_conn = getattr(self.raw, 'release_conn', None)
if release_conn is not None:
release_conn()

View File

@@ -1,14 +0,0 @@
import sys
# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)
for package in ('urllib3', 'idna', 'chardet'):
locals()[package] = __import__(package)
# This traversal is apparently necessary such that the identities are
# preserved (requests.packages.urllib3.* is urllib3.*)
for mod in list(sys.modules):
if mod == package or mod.startswith(package + '.'):
sys.modules['requests.packages.' + mod] = sys.modules[mod]
# Kinda cool, though, right?

307
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 .auth import _basic_auth_str
from .compat import cookielib, OrderedDict, urljoin, urlparse, is_py3, str
from .basics import cookielib, urljoin, urlparse, str
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar,
merge_cookies, _copy_cookie_jar)
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, InvalidScheme, ChunkedEncodingError,
ConnectionError, ContentDecodingError, InvalidHeader)
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, is_valid_location, 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
@@ -41,7 +54,7 @@ from .models import REDIRECT_STATI
# 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,7 +67,6 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
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`.
"""
if session_setting is None:
return request_setting
@@ -63,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,10 +109,18 @@ class SessionRedirectMixin(object):
def get_redirect_target(self, response):
"""Receives a Response. Returns a redirect URI or ``None``"""
# 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.')
raise InvalidHeader(
'Response contains multiple Location headers. '
'Unable to perform redirect.'
)
location = response.headers['location']
# Currently the underlying http module on py3 decode headers
@@ -111,46 +129,59 @@ class SessionRedirectMixin(object):
# 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, response, request, stream=False, timeout=None,
verify=True, cert=None, proxies=None,
yield_requests=False, **adapter_kwargs):
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
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:
response.content # Consume socket so it can be released
except (ChunkedEncodingError, ConnectionError, ContentDecodingError, RuntimeError):
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)
raise TooManyRedirects(
'Exceeded %s redirects.' % self.max_redirects,
response=response,
)
# Release the connection back into the pool.
response.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
if location_url.startswith('//'):
parsed_rurl = urlparse(response.url)
location_url = '%s:%s' % (to_native_string(parsed_rurl.scheme), location_url)
location_url = '%s:%s' % (
to_native_string(parsed_rurl.scheme), location_url
)
# The scheme should be lower case...
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.
@@ -158,37 +189,34 @@ class SessionRedirectMixin(object):
location_url = urljoin(response.url, requote_uri(location_url))
else:
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')
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, request, response.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, 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,18 +224,15 @@ 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.
request = prepared_request
if yield_requests:
yield request
else:
else:
response = self.send(
request,
stream=stream,
@@ -216,15 +241,15 @@ class SessionRedirectMixin(object):
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, response.raw
)
# extract redirect url, if any, for the next loop
location_url = self.get_redirect_target(response)
yield response
@@ -237,21 +262,17 @@ class SessionRedirectMixin(object):
"""
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):
@@ -272,27 +293,22 @@ 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):
@@ -303,19 +319,18 @@ class SessionRedirectMixin(object):
: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'
# 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':
if response.status_code in (
codes.found, codes.moved
) and method == 'POST':
method = 'GET'
prepared_request.method = method
return method != original_method
@@ -338,63 +353,62 @@ 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())
@@ -417,20 +431,16 @@ class Session(SessionRedirectMixin):
:rtype: requests.PreparedRequest
"""
cookies = request.cookies or {}
# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# Merge with session 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(),
@@ -438,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,
@@ -446,10 +458,25 @@ class Session(SessionRedirectMixin):
)
return p
def request(self, method, url,
params=None, data=None, headers=None, cookies=None, files=None,
auth=None, timeout=None, allow_redirects=True, proxies=None,
hooks=None, stream=None, verify=None, cert=None, json=None):
def request(
self,
method,
url,
params=None,
data=None,
headers=None,
cookies=None,
files=None,
auth=None,
timeout=None,
allow_redirects=True,
proxies=None,
hooks=None,
stream=None,
verify=None,
cert=None,
json=None,
):
"""Constructs a :class:`Request <Request>`, prepares it, and sends it.
Returns :class:`Response <Response>` object.
@@ -500,21 +527,14 @@ class Session(SessionRedirectMixin):
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):
@@ -524,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)
@@ -535,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)
@@ -546,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)
@@ -559,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):
@@ -570,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):
@@ -581,7 +596,6 @@ class Session(SessionRedirectMixin):
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request('PATCH', url, data=data, **kwargs)
def delete(self, url, **kwargs):
@@ -591,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):
@@ -605,7 +618,6 @@ 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):
@@ -616,52 +628,42 @@ class Session(SessionRedirectMixin):
allow_redirects = kwargs.pop('allow_redirects', True)
stream = kwargs.get('stream')
hooks = request.hooks
# 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.
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.
history = [resp for resp in gen] if allow_redirects else []
# If there is a history, replace ``r`` with the last response
if history:
r = history.pop()
# 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):
@@ -681,10 +683,11 @@ class Session(SessionRedirectMixin):
# 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)
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
@@ -693,17 +696,17 @@ class Session(SessionRedirectMixin):
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}
return {
'verify': verify,
'proxies': proxies,
'stream': stream,
'cert': cert,
}
def get_adapter(self, url):
"""
@@ -712,7 +715,6 @@ class Session(SessionRedirectMixin):
:rtype: requests.adapters.BaseAdapter
"""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix):
return adapter
@@ -727,28 +729,17 @@ class Session(SessionRedirectMixin):
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 = {attr: getattr(self, attr, None) for attr in self.__slots__}
return state
def __setstate__(self, state):
for attr, value in state.items():
setattr(self, attr, value)
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_py2, 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/requests/requests/issues/1846
return
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,12 +450,12 @@ def get_encoding_from_headers(headers):
def stream_decode_response_unicode(iterator, r):
"""Stream decodes a iterator."""
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
@@ -447,76 +467,37 @@ 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
"""
# 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_py2 and not is_bytes:
return c.decode('ascii')
elif is_py3 and is_bytes:
if is_bytes:
return c.encode('ascii')
else:
return c
# Handle both bytestrings and unicode strings.
is_bytes = isinstance(uri, bytes)
splitchar = u'%'
base = u''
if is_bytes:
splitchar = splitchar.encode('ascii')
base = base.encode('ascii')
splitchar = '%'
base = ''
parts = uri.split(splitchar)
for i in range(1, len(parts)):
h = parts[i][0:2]
@@ -527,7 +508,7 @@ def unquote_unreserved(uri):
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
if c in UNRESERVED_SET:
parts[i] = convert(is_bytes, c) + parts[i][2:]
parts[i] = convert(is_bytes=False, c=c) + parts[i][2:]
else:
parts[i] = splitchar + parts[i]
else:
@@ -535,7 +516,7 @@ def unquote_unreserved(uri):
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
@@ -550,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
@@ -557,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
@@ -567,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
@@ -583,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
"""
@@ -591,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.
@@ -613,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.
@@ -705,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
@@ -735,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.
@@ -744,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('=')
@@ -783,13 +767,11 @@ 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):
def is_valid_location(response) -> bool:
"""Verify that multiple Location headers weren't
returned from the last response.
"""
@@ -798,6 +780,7 @@ def is_valid_location(response):
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
@@ -808,7 +791,7 @@ _null2 = _null * 2
_null3 = _null * 3
def guess_json_utf(data):
def guess_json_utf(data: bytes) -> typing.Optional[str]:
"""
:rtype: str
"""
@@ -817,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
@@ -878,7 +865,9 @@ _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.
@@ -886,53 +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):
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))
is_io_type = not isinstance(
data, (basestring, list, tuple, collections.Mapping)
)
return is_iterable and is_io_type

View File

@@ -1,16 +0,0 @@
-e .[socks]
pytest>=2.8.0
codecov
pytest-httpbin==0.0.7
pytest-mock
pytest-cov
pytest-xdist
alabaster
readme_renderer
Sphinx<=1.5.5
PySocks
setuptools>=18.5
docutils
flake8
tox
detox

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,16 +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)
try:
from multiprocessing import cpu_count
self.pytest_args = ['-n', str(cpu_count()), '--boxed']
except (ImportError, NotImplementedError):
self.pytest_args = ['-n', '1', '--boxed']
self.pytest_args = ['-n', 'auto']
def finalize_options(self):
TestCommand.finalize_options(self)
@@ -33,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-httpbin==0.0.7', 'pytest-cov', 'pytest-mock', 'pytest-xdist', 'PySocks>=1.5.6, !=1.5.7', 'pytest>=2.8.0']
about = {}
with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f:
exec(f.read(), about)
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__'],
@@ -80,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():

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

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import pytest
import threading
import requests
@@ -14,23 +13,20 @@ 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."
)
"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()
@@ -47,23 +43,24 @@ def test_digestauth_401_count_reset_on_redirect():
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):
@@ -71,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)
@@ -110,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):
@@ -127,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)
@@ -157,12 +149,13 @@ def test_digestauth_only_on_4xx():
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):
@@ -170,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)
@@ -190,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}
@@ -214,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
@@ -241,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()

View File

@@ -1,13 +0,0 @@
import requests
def test_can_access_urllib3_attribute():
requests.packages.urllib3
def test_can_access_idna_attribute():
requests.packages.idna
def test_can_access_chardet_attribute():
requests.packages.chardet

1536
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,67 +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")
@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):
@@ -151,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/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)

View File

@@ -1,8 +1,8 @@
[tox]
envlist = py26,py27,py33,py34,py35,py36
envlist = py36
[testenv]
commands =
pip install -e .[socks]
python setup.py test
python setup.py test