Compare commits

...

331 Commits
v2.18.3 ... 3.0

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

Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-14 18:15:13 -04:00
Kenneth Reitz
0b96f5282a Merge branch '3.0' of github.com:requests/requests into 3.0 2018-03-14 17:29:06 -04:00
Kenneth Reitz
9cea8ce09d black
https://github.com/ambv/black
2018-03-14 17:28:51 -04:00
Kenneth Reitz
d763e8e99e Merge pull request #4543 from RatanShreshtha/3.0
Type annoted requests/__init__.py.
2018-03-14 14:16:09 -04:00
RatanShreshtha
38b29446b0 Type annoted requests/__init__.py. 2018-03-14 23:43:50 +05:30
Kenneth Reitz
2bf628be3c #4542
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-13 09:53:42 -04:00
Kenneth Reitz
9af75f01a9 Merge pull request #4542 from RatanShreshtha/3.0
Added type hints for help.py.
2018-03-13 09:52:06 -04:00
Ratan.Kulshreshtha
12671d4819 Added type hints for help.py. 2018-03-13 12:10:51 +05:30
Kenneth Reitz
272ea7db4e fix redirects
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 13:44:07 -04:00
Kenneth Reitz
f7a36c5866 catch non-keyword arguments
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 13:04:11 -04:00
Kenneth Reitz
530db6c4b9 mypy improvements
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:59:59 -04:00
Kenneth Reitz
c7d0f3ef01 better mypy compliance
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:56:09 -04:00
Kenneth Reitz
db2aecbac8 -> types.Response 2018-03-12 12:52:08 -04:00
Kenneth Reitz
fc4637032a docstring
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:36:01 -04:00
Kenneth Reitz
82c3c42193 mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:32:27 -04:00
Kenneth Reitz
b72d5379ea no python 2.7
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:31:16 -04:00
Kenneth Reitz
edf844a2db mypy command
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:31:04 -04:00
Kenneth Reitz
440dffe6e9 this should always work, i believe
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:27:10 -04:00
Kenneth Reitz
3ac6ee9ce4 make mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:25:29 -04:00
Kenneth Reitz
ccffc02547 not boxed (doesn't work on my system)
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:21:12 -04:00
Kenneth Reitz
77e2ca1a85 test
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:15:25 -04:00
Kenneth Reitz
20641583e3 for now
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:14:59 -04:00
Kenneth Reitz
582ce4d247 no cstringio
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:13:47 -04:00
Kenneth Reitz
4d902fdbaa require older version of mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:11:27 -04:00
Kenneth Reitz
9d1d3d165b no 2.7 in travis
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:08:39 -04:00
Kenneth Reitz
dba39c5285 pytest-mypy
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:05:51 -04:00
Kenneth Reitz
4239856a4a types
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 12:02:05 -04:00
Kenneth Reitz
f531629a9b remove python2–specific code
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:58:21 -04:00
Kenneth Reitz
220c368e2c gitignore
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:53:36 -04:00
Kenneth Reitz
8ba99d246b basics
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:53:22 -04:00
Kenneth Reitz
6be73f90b1 /s/compat/basics 2018-03-12 11:53:12 -04:00
Kenneth Reitz
315a5661e3 new approach
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2018-03-12 11:47:09 -04:00
Kenneth Reitz
9c6bd54b44 Merge pull request #4500 from jdufresne/remove-packages
Remove requests.packages for v3.0.0
2018-03-08 13:00:49 -05:00
Jon Dufresne
4c6d9e1954 Remove requests.packages for v3.0.0
As v3.0.0 already includes other backwards incompatible changes, it is a
good time to remove the old entry point for vendored packages. Cleans up
compatibility shims.
2018-02-04 19:07:26 -08:00
Nate Prewitt
a58b1e66cb Merge pull request #4468 from VasiliPupkin256/fix2392
Simple fix for the issue #2392
2018-01-13 17:13:30 -08:00
VasiliPupkin256
8f00695d40 updating 3.0-HISTORY for #2392 2018-01-14 01:59:29 +03:00
VasiliPupkin256
5d6eeb5a7f Map urllib3 ReadTimeoutError to an appropriate ReadTimeout requests exception, issue #2392 2018-01-13 03:28:02 +03:00
Nate Prewitt
78bf3ee6e6 Merge pull request #4328 from hugovk/rm-2.6
Remove redundant Python 2.6 stuff
2017-10-19 09:58:53 -07:00
hugovk
9146b62d31 OrderedDict no longer in compat.py as it's in collections in Py2.7+ 2017-10-17 20:29:33 +03:00
Hugo
946f136715 Update CLI example 2017-10-17 18:03:59 +03:00
Hugo
19a294a2a1 Update AUTHORS.rst 2017-10-17 16:58:22 +03:00
hugovk
006aa3de4f Drop Python 2.6: OrderedDict is in collections from 2.7 2017-10-17 16:56:56 +03:00
Nate Prewitt
37f1d23c39 Merge remote-tracking branch 'upstream/master' into proposed/3.0.0 2017-10-15 13:59:50 -07:00
Nate Prewitt
e86df6e0eb Merge pull request #4341 from hugovk/patch-1
Python 3.7 not officially supported yet
2017-10-15 13:35:52 -07:00
Hugo
ec5804c706 Python 3.3 has already been dropped (#4231) 2017-10-15 20:04:51 +03:00
Hugo
4316af7ae8 Python 3.7 not officially supported yet 2017-10-15 19:52:19 +03:00
Cory Benfield
8c4f78448a Merge pull request #4332 from PrimordialHelios/pyopenssl-warning
Warn about possible slowness on older versions of pyOpenSSL
2017-10-14 08:41:02 -07:00
Taylor Rose
15054aa390 Warn user about possible slowdown when using cryptography version <
1.3.4
2017-10-14 09:00:58 -04:00
Kenneth Reitz
9713289e74 Merge branch 'master' of github.com:requests/requests 2017-09-25 11:04:47 -04:00
Kenneth Reitz
e6afc37be5 new pipfile with new syntax
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-25 11:04:38 -04:00
Ian Stapleton Cordasco
5eb0811ef8 Merge pull request #4305 from sigmavirus24/fix-pypi-display
Fix the invalid restructured text in HISTORY file
2017-09-23 16:21:48 -05:00
Ian Stapleton Cordasco
40c8bcf70c Fix the invalid restructured text in HISTORY file
This was breaking the display of our long_description on PyPI.
2017-09-23 15:54:14 -05:00
Kenneth Reitz
0eb9a4c161 stickers
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-19 13:23:15 -04:00
Kenneth Reitz
cda429aa5d Merge pull request #4295 from requests/pipenv
pipfile (work in progress)
2017-09-17 11:58:46 -04:00
Kenneth Reitz
28ae74dbad update tests
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:46:33 -04:00
Kenneth Reitz
a9d05f06cd remove requirements.txt
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:39:53 -04:00
Kenneth Reitz
d552132464 run coverage w/ pipenv
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:36:54 -04:00
Kenneth Reitz
c3ecb825c4 don't test on 2.6
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:30:26 -04:00
Kenneth Reitz
70a4ae744c pip install pipenv
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:26:22 -04:00
Kenneth Reitz
4c300c2067 pipfile
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-09-17 11:24:05 -04:00
Allan Crooks
4f0b496d2b Update AUTHORS.rst. 2017-09-12 23:51:28 +01:00
Allan Crooks
a36247e148 Rename HTTPHeaderDict methods to getlist and setlist to be more consistent with other similar implementations. 2017-09-12 23:14:00 +01:00
Allan Crooks
6dc3003f16 Update documentation regarding response headers. 2017-09-12 23:13:03 +01:00
Allan Crooks
5aef6e7583 Use HTTPHeaderDict for response headers. 2017-09-12 22:17:23 +01:00
Allan Crooks
95b127714b Add HTTPHeaderDict data structure and tests. 2017-09-12 22:16:48 +01:00
Allan Crooks
5980d5df3e Add tests showing current behaviour of how multiple response headers with the same name are compiled into a single value. 2017-09-12 22:16:31 +01:00
Kenneth Reitz
fdb9b753b4 Update authors.rst 2017-09-05 11:31:16 -04:00
Kenneth Reitz
4bda7b66e7 Update updates.rst 2017-09-04 16:21:57 -04:00
Kenneth Reitz
068d8863d1 Update support.rst 2017-09-04 16:21:04 -04:00
Kenneth Reitz
c49f3643ae Update release-process.rst 2017-09-04 16:20:03 -04:00
Kenneth Reitz
328cff252d Update out-there.rst 2017-09-04 16:19:36 -04:00
Kenneth Reitz
c57bd07a34 Update faq.rst 2017-09-04 16:19:01 -04:00
Kenneth Reitz
0d82afa0c3 Update recommended.rst 2017-09-04 16:18:00 -04:00
Kenneth Reitz
18e37c9368 Update todo.rst 2017-09-04 16:14:48 -04:00
Kenneth Reitz
389da34fc9 Update philosophy.rst 2017-09-04 16:14:07 -04:00
Kenneth Reitz
8ef20d5c36 Update contributing.rst 2017-09-04 16:13:08 -04:00
Kenneth Reitz
e63d481a7a Update authors.rst 2017-09-04 16:12:38 -04:00
Kenneth Reitz
f3eeb38542 Update vulnerabilities.rst 2017-09-04 16:11:52 -04:00
Kenneth Reitz
afaaae185c Update authentication.rst 2017-09-04 16:10:49 -04:00
Kenneth Reitz
8af65a1eca Update quickstart.rst 2017-09-04 16:09:51 -04:00
Kenneth Reitz
c04b044b30 Update advanced.rst 2017-09-04 16:09:08 -04:00
Kenneth Reitz
530198e0c0 Update install.rst 2017-09-04 16:08:22 -04:00
Kenneth Reitz
8c57515696 Update intro.rst 2017-09-04 16:05:57 -04:00
Kenneth Reitz
175452a6b6 Update index.rst 2017-09-04 16:04:07 -04:00
Kenneth Reitz
e35c829b59 Update index.rst 2017-09-04 16:03:49 -04:00
Kenneth Reitz
1ad51b0a41 Update index.rst 2017-09-04 15:55:53 -04:00
Kenneth Reitz
8be8105c42 Update advanced.rst 2017-09-04 15:53:26 -04:00
Kenneth Reitz
49ec082a77 Update recommended.rst 2017-09-04 15:50:38 -04:00
Kenneth Reitz
0488c640e2 Update index.rst 2017-09-04 15:46:47 -04:00
Kenneth Reitz
8371b56d0f Delete extensions.rst 2017-09-04 15:46:24 -04:00
Kenneth Reitz
830c18e4f9 Update index.rst 2017-09-04 15:45:28 -04:00
Kenneth Reitz
2c438cacd1 Create extensions.rst 2017-09-04 15:44:57 -04:00
Kenneth Reitz
9c8ae76928 Update index.rst 2017-09-04 15:40:02 -04:00
Kenneth Reitz
d5a05469f1 Update README.rst 2017-09-04 15:38:13 -04:00
Kenneth Reitz
e22e3a5059 Update README.rst 2017-09-04 15:37:24 -04:00
Kenneth Reitz
054ad4aa67 Update README.rst 2017-09-04 12:59:01 -04:00
Kenneth Reitz
b9638ec7be taste 2017-09-01 13:10:52 -04:00
Kenneth Reitz
940c1246e0 taste 2017-09-01 13:10:29 -04:00
Kenneth Reitz
e0e5b2e3aa Update philosophy.rst 2017-08-29 16:16:52 -04:00
Kenneth Reitz
96b9a3f0fc Update setup.py 2017-08-28 12:25:26 -04:00
Cory Benfield
657e44ad9a Merge pull request #4265 from PascalVA/patch-2
fixed syntax for code block for prepared requests
2017-08-28 10:39:26 +01:00
Pascal Van Acker
ac9a3e5320 fixed syntax for code block for prepared requests 2017-08-28 11:14:05 +02:00
Cory Benfield
6b21682712 Merge pull request #4264 from PascalVA/patch-1
Add environment info to prepared requests docs
2017-08-28 10:08:07 +01:00
Pascal Van Acker
785c733415 Add environment info to prepared requests
When using prepared requests, the environment is not taken into account. This should be reflected in the documentation.
2017-08-28 10:58:44 +02:00
Kenneth Reitz
369be8afd0 pretty little note
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-08-28 02:01:36 -04:00
Kenneth Reitz
572d2f4534 Python 2 Death Sentence™
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-08-28 01:50:35 -04:00
Kenneth Reitz
7e20b9193a Update index.rst 2017-08-28 01:33:45 -04:00
Kenneth Reitz
ad1aff52a0 Update index.rst 2017-08-28 01:33:32 -04:00
Kenneth Reitz
3a6dec9827 Update index.rst 2017-08-28 01:29:29 -04:00
Kenneth Reitz
3cceee3459 Update install.rst 2017-08-27 23:16:32 -04:00
Kenneth Reitz
dd57439fa5 Update README.rst 2017-08-27 23:13:50 -04:00
Cory Benfield
f09e462165 Merge pull request #4261 from Lukasa/fix-broken-tests
Pin to httpbin 0.5.0.
2017-08-27 14:00:37 +01:00
Cory Benfield
3a2ea9deba Pin to httpbin 0.5.0 2017-08-27 08:54:01 +01:00
Kenneth Reitz
5dcbdb50fc docs update 2017-08-25 22:12:34 -04:00
Kenneth Reitz
8ad09b68ea coinbin.org 2017-08-25 22:02:03 -04:00
Kenneth Reitz
88dc33522d Merge branch 'master' of https://github.com/requests/requests 2017-08-20 20:56:54 -04:00
Kenneth Reitz
e5666b93cf btc
Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
2017-08-20 20:56:48 -04:00
Cory Benfield
554a210f88 Merge pull request #4245 from remram44/fix-headers-links
Fix parse_header_links on empty header
2017-08-17 20:34:06 +01:00
Remi Rampin
8e411d0f99 Add test case for empty Link: header 2017-08-17 12:56:45 -04:00
Remi Rampin
7d190ef010 Add entry in HISTORY.rst 2017-08-17 12:56:38 -04:00
Remi Rampin
965ada6f66 Fix parse_header_links on empty header 2017-08-16 22:10:20 -04:00
Cory Benfield
af9dd61fcb Prepare for new release 2017-08-15 14:24:20 +01:00
Cory Benfield
a3d7cf3f27 v2.18.4 2017-08-15 14:22:52 +01:00
Cory Benfield
3c5ecfc9f5 Merge pull request #4240 from mgasvoda/master
Adding header name to exception
2017-08-12 15:41:27 +01:00
mgasvoda
d528106876 Adding record to history 2017-08-12 10:23:45 -04:00
mgasvoda
cbc7c2d1c9 Modifying tests to include header name info 2017-08-11 17:18:58 -04:00
mgasvoda
e36083b89c Adding space after colon 2017-08-11 16:19:43 -04:00
mgasvoda
308343508b Removing trailing whitespace 2017-08-11 16:14:13 -04:00
mgasvoda
ba21d14976 Improving message formatting 2017-08-11 16:12:36 -04:00
mgasvoda
cee8fae65e Adding header name to exception
Adds the name of the header to the invalid header exception raised on TypeError.
2017-08-11 15:37:57 -04:00
Kenneth Reitz
1c2022cf86 Merge pull request #4231 from Lukasa/drop-3.3-support
Drop Python 3.3 support.
2017-08-10 14:03:36 -04:00
Kenneth Reitz
8480cf937a Merge pull request #4237 from robinske/doc-import
Adds import statement to front page of docs for clarity and quicker start up
2017-08-10 14:02:57 -04:00
Kelley Robinson
f6952a9b8d Merge branch 'master' into doc-import 2017-08-10 10:33:22 -07:00
kelley
d758f0cf97 Adds import statement to front page of docs for clarity and quicker start up 2017-08-10 10:23:43 -07:00
Cory Benfield
c31a450c55 Remove references to 3.3 from the docs 2017-08-09 19:40:52 +01:00
Cory Benfield
0a2dc74ecb Drop Python 3.3 support 2017-08-09 19:03:04 +01:00
Cory Benfield
f78a57936b Merge pull request #4229 from NikosVlagoidis/issue#3863
Fixes issue #3863
2017-08-09 18:56:40 +01:00
NikosVlagoidis
218978601f Fix issue #3863 2017-08-09 20:18:43 +03:00
NikosVlagoidis
cb9620780f Update advanced.rst 2017-08-09 18:52:26 +03:00
Nikolaos Vlagoidis
963c78853f Fixes issue #3863 2017-08-09 15:38:45 +03:00
Kenneth Reitz
b670b61d54 Update AUTHORS.rst 2017-08-08 19:16:39 -04:00
Cory Benfield
9ec2005d9f Merge pull request #4223 from Lukasa/widen-idna-pin
Allow IDNA 2.6
2017-08-08 15:42:54 +01:00
Cory Benfield
f4ddf00c36 Allow IDNA 2.6 2017-08-08 13:32:15 +01:00
Cory Benfield
248f122970 Merge pull request #4217 from nikolas/patch-1
fix typo in changelog
2017-08-02 15:38:46 +01:00
Nik Nyby
be8ce365ab fix typo in changelog 2017-08-02 10:26:41 -04:00
Cory Benfield
3393dc8221 Prepare changelog for next release 2017-08-02 14:24:02 +01:00
Kenneth Reitz
c7266a5ff4 Merge pull request #4118 from hobarrera/drop-py26
Drop python2.6 support
2017-07-26 15:59:40 -04: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
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
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
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
190a68550a This should be 3.0 2017-05-31 14:08:05 +01:00
Cory Benfield
548823be83 Clean up invalid indentation from merge 2017-05-31 10:41:47 +01:00
Cory Benfield
a6f7f5dc62 Clean up flake8 errors from merge 2017-05-31 10:39:27 +01:00
Cory Benfield
f308ea1a31 Clean up messy merge 2017-05-31 10:36:29 +01:00
Cory Benfield
c3f4a028bf Merge branch 'master' into proposed/3.0.0 2017-05-31 10:29:19 +01:00
Ian Cordasco
8e97db2dc5 Merge pull request #4042 from nateprewitt/persist_cookie_policy
Persist Session-level CookiePolicy
2017-05-23 09:39:37 -05:00
Nate Prewitt
009b80c95a persist session-level CookiePolicy 2017-05-22 09:51:40 -07:00
Nate Prewitt
a889b62c50 Merge branch 'master' into proposed/3.0.0 2017-05-21 16:25:55 -07:00
Cory Benfield
084625cb3c Merge pull request #3984 from PCMan/pcman@fix_iter_lines
Fix the additional newline generated by iter_lines() caused by a '\r\n' pair being separated in two different chunks.
2017-04-26 16:07:46 +01:00
Hong Jen-Yee (PCMan)
458df8f4f4 Fix the additional newline generated by iter_lines() caused by a '\r\n' pair being separated in two different chunks. 2017-04-26 23:02:57 +08:00
Cory Benfield
73456b0048 Merge pull request #3923 from vbarbaresi/3.0.0-iter_lines
Rebase #3745 and add some tests
2017-03-16 09:53:03 +00:00
Vincent Barbaresi
d491e9f9b2 use [-1] instead of endswith() to work with bytes or string
Also add a parametrize on decode_unicode for iter_lines() test
to check with bytestrings and str content
2017-03-15 22:29:18 +01:00
Vincent Barbaresi
052595ffbf add explanatory comment about skipping null chunks in iter_lines 2017-03-15 12:16:32 +01:00
Vincent Barbaresi
cc2ac23c0d remove useless brackets in iter_lines boolean condition 2017-03-15 01:37:55 +01:00
Vincent Barbaresi
5a8bc19384 add more tests for iter_lines()
check the case of an empty chunk somewhere in the stream
2017-03-15 01:37:55 +01:00
Vincent Barbaresi
0380ac5893 add some parametrized tests for iter_lines()
Write a list of different chunk splits and their expected results
to test against, using ianepperson's breakdown as specification:
https://github.com/kennethreitz/requests/pull/2431#issuecomment-72333964
2017-03-15 01:37:48 +01:00
Alex Chan
9881be25f3 Review markups for @Lukasa 2017-03-14 16:57:52 +01:00
Ian Epperson
9174925916 Fix bug when delimiter is split between responses 2017-03-14 16:57:52 +01:00
Ian Epperson
02031e3e14 Test to show bug when delimiter is split between reads 2017-03-14 16:57:52 +01:00
Cory Benfield
84dc6b66da Merge pull request #3898 from nateprewitt/duplicate_exception
remove redundant exception declaration
2017-03-02 14:41:17 +00:00
Cory Benfield
35fc2b387f Merge pull request #3897 from nateprewitt/prep_body
Refactor Prepare Body
2017-03-02 14:40:57 +00:00
Nate Prewitt
be2f92b9e3 updating HISTORY 2017-03-02 05:27:33 -07:00
Nate Prewitt
64b66b6409 test prepare_content_length sets expected headers 2017-03-02 05:27:22 -07:00
Nate Prewitt
5a65a0dab1 use InvalidHeader instead of ConflictingHeaderError 2017-03-01 07:32:14 -07:00
Nate Prewitt
3046a1eb54 remove redundant exception declaration 2017-02-28 16:08:48 -07:00
Nate Prewitt
a52fe6586c consolidate super_len code and cleanup docstrings 2017-02-28 14:51:08 -07:00
Casey Davidson
f239fe754d Change UnreachableCodeError to InvalidBodyError. 2017-02-28 14:49:52 -07:00
Casey Davidson
1003fdf0f2 Small fixes based on feedback in pull request. 2017-02-28 14:49:31 -07:00
Casey Davidson
033dfc165d Raise an error if body is not null, has not length and is not streamable. 2017-02-28 14:49:20 -07:00
Casey Davidson
bfb202527d Alternate fix for 3066 to refactor prepare_body to always call prepare_content_length.
This allows for the 'Content-Length' header to only be set in prepare_content_length.
2017-02-28 14:49:01 -07:00
Ian Cordasco
60339d17ee Merge pull request #3886 from jvanasco/proposed/3.0.0
proposed 3.0 - altered internal loops of resolve_redirect
2017-02-25 09:05:39 -06:00
jonathan vanasco
8e07dae0fd altered internal loops of how `SessionRedirectMixin.resolve_redirects and Session.send` handle redirect history. 3.0.0 branch 2017-02-22 13:23:21 -05:00
Kenneth Reitz
b22833ce2a Merge pull request #3874 from nateprewitt/merge_cleanup
variable name consistency
2017-02-21 14:04:24 -05:00
Nate Prewitt
b8a87e00d7 variable name consistency 2017-02-14 09:31:59 -07:00
Cory Benfield
56dd77fc52 Merge branch 'master' into proposed/3.0.0 2017-02-14 15:59:53 +00:00
Cory Benfield
250b3e5c8c Merge pull request #3868 from vmalloc/raise_for_status_return_self
Make Response.raise_for_status() return the response object if the response is successful
2017-02-12 09:58:08 +00:00
Rotem Yaari
efcbe93075 Make Response.raise_for_status() return the response object if the response is successful
This allows for chaining method calls in cases where we want to raise for bad codes but use the response otherwise,
e.g. requests.get(URL).raise_for_status().json()['value']
2017-02-12 11:11:40 +02:00
Cory Benfield
b26f4c7f58 Merge pull request #3869 from nateprewitt/auth_fix
revert removal of request param from resolve_redirects
2017-02-12 07:33:13 +00:00
Nate Prewitt
16bbc7478f revert removal of request param from resolve_redirects 2017-02-11 23:35:19 -07:00
Kenneth Reitz
85a29f1da0 Merge branch 'issue2899' of https://github.com/thoger/requests into proposed/3.0.0 2017-02-10 12:44:02 -05:00
Cory Benfield
241f2b2a6b Merge branch 'master' into proposed/3.0.0 2017-02-10 17:40:52 +00:00
Cory Benfield
195d628d31 Merge pull request #3773 from nateprewitt/2003_remove_HTTPProxyAuth
remove HTTPProxyAuth in favor of the proxies parameter
2016-12-17 09:23:32 -05:00
Nate Prewitt
7b76bd5866 remove HTTPProxyAuth in favor of the proxies parameter 2016-12-16 15:14:58 -07:00
Cory Benfield
0b4302bb2b Merge pull request #3757 from nateprewitt/2590_redirected_bodies
301/302 redirects for non-POST with body
2016-12-12 15:54:17 +00:00
Nate Prewitt
354ac7ecad squash 3xx tests into super tests 2016-12-12 08:36:22 -07:00
Nate Prewitt
3bb25a1e0b updating 3.0-HISTORY for #3757 2016-12-10 22:00:25 -07:00
Nate Prewitt
eff932627c adding return variable to rebuild_method for method change 2016-12-10 12:13:13 -07:00
Nate Prewitt
084fb05d52 fixing redirects for non-GET/HEAD/POST methods 2016-12-10 12:12:23 -07:00
Ian Cordasco
f554ec7987 Merge pull request #3762 from nateprewitt/update_30_hist
updating 3.0 history
2016-12-10 07:57:44 -06:00
Nate Prewitt
f1a707d191 updating 3.0 history 2016-12-09 12:55:58 -07:00
Cory Benfield
78fe2f738c Merge pull request #3761 from nateprewitt/3753_revert_int_exception
re-restrict params for _basic_auth_str in 3.0.0
2016-12-09 15:29:17 +00:00
Nate Prewitt
500dc75c8c remove allowance of non-string/bytes auth values 2016-12-09 08:26:13 -07:00
Cory Benfield
c7e5c0d2b7 Merge branch 'master' into proposed/3.0.0 2016-12-09 14:49:18 +00:00
Cory Benfield
4dae8de815 Merge branch 'master' into proposed/3.0.0 2016-12-09 08:45:34 +00:00
Cory Benfield
ba7c464a03 Merge branch 'master' into proposed/3.0.0 2016-12-08 09:16:51 +00:00
Cory Benfield
e237a60add Merge pull request #3563 from nateprewitt/enforce_content_length
use enforce_content_length in Requests
2016-11-16 13:29:51 +00:00
Nate Prewitt
84d99f01f5 add enforce_content_length=True default 2016-11-15 11:27:29 -07:00
Nate Prewitt
973a7b1cd0 test responses fail with incomplete body reads 2016-11-15 11:24:51 -07:00
Nate Prewitt
cfd898fb93 adding method to url tests 2016-11-15 11:19:30 -07:00
Cory Benfield
7f88aca8f7 Merge branch 'master' into proposed/3.0.0 2016-11-15 17:16:04 +00:00
Cory Benfield
e70ad25ca1 Merge pull request #3634 from jeremycline/verify-by-default
Keep ``verify`` setting when no CA_BUNDLE variable exists
2016-10-25 09:56:53 +01:00
Jeremy Cline
35bdfc78c9 Keep `verify` setting when no CA_BUNDLE variable exists
If the ``trust_env`` flag is set on a session and ``verify`` is ``True``
or ``None``, the environment is checked for ``CURL_CA_BUNDLE`` and
``REQUESTS_CA_BUNDLE``. Before this patch, if neither existed,
``verify`` would always be set to ``None`` rather than ``True`` even if
it was originally ``True``.

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

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

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

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

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

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

Fixes requests issue #2899:

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

    response = make_request(url)

    if response:
        body = response.content

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

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

Closes #2002
2015-05-04 07:58:04 -05:00
68 changed files with 3669 additions and 2346 deletions

6
.gitignore vendored
View File

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

81
3.0-HISTORY.rst Normal file
View File

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

View File

@@ -10,13 +10,6 @@ Keepers of the Four Crystals
- Nate Prewitt <nate.prewitt@gmail.com> `@nateprewitt <https://github.com/nateprewitt>`_
Urllib3
```````
- Andrey Petrov <andrey.petrov@shazow.net>
Patches and Suggestions
```````````````````````
@@ -128,7 +121,7 @@ Patches and Suggestions
- Bryce Boe <bbzbryce@gmail.com> (`@bboe <https://github.com/bboe>`_)
- Colin Dunklau <colin.dunklau@gmail.com> (`@cdunklau <https://github.com/cdunklau>`_)
- Bob Carroll <bob.carroll@alum.rit.edu> (`@rcarz <https://github.com/rcarz>`_)
- Hugo Osvaldo Barrera <hugo@osvaldobarrera.com.ar> (`@hobarrera <https://github.com/hobarrera>`_)
- Hugo Osvaldo Barrera <hugo@barrera.io> (`@hobarrera <https://github.com/hobarrera>`_)
- Łukasz Langa <lukasz@langa.pl>
- Dave Shawley <daveshawley@gmail.com>
- James Clarke (`@jam <https://github.com/jam>`_)
@@ -160,16 +153,19 @@ Patches and Suggestions
- Muhammad Yasoob Ullah Khalid <yasoob.khld@gmail.com> (`@yasoob <https://github.com/yasoob>`_)
- Paul van der Linden (`@pvanderlinden <https://github.com/pvanderlinden>`_)
- Colin Dickson (`@colindickson <https://github.com/colindickson>`_)
- Sabari Kumar Murugesan (`@neosab <https://github.com/neosab>`_)
- Smiley Barry (`@smiley <https://github.com/smiley>`_)
- Shagun Sodhani (`@shagunsodhani <https://github.com/shagunsodhani>`_)
- Robin Linderborg (`@vienno <https://github.com/vienno>`_)
- Brian Samek (`@bsamek <https://github.com/bsamek>`_)
- Dmitry Dygalo (`@Stranger6667 <https://github.com/Stranger6667>`_)
- Tomáš Heger (`@geckon <https://github.com/geckon>`_)
- piotrjurkiewicz
- Jesse Shapiro <jesse@jesseshapiro.net> (`@haikuginger <https://github.com/haikuginger>`_)
- Nate Prewitt <nate.prewitt@gmail.com> (`@nateprewitt <https://github.com/nateprewitt>`_)
- Maik Himstedt
- Michael Hunsinger
- Jeremy Cline <jcline@redhat.com> (`@jeremycline <https://github.com/jeremycline>`_)
- Brian Bamsch <bbamsch32@gmail.com> (`@bbamsch <https://github.com/bbamsch>`_)
- Om Prakash Kumar <omprakash070@gmail.com> (`@iamprakashom <https://github.com/iamprakashom>`_)
- Philipp Konrad <gardiac2002@gmail.com> (`@gardiac2002 <https://github.com/gardiac2002>`_)
@@ -183,5 +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

@@ -3,6 +3,30 @@
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)
+++++++++++++++++++
@@ -13,7 +37,7 @@ Release History
**Bugfixes**
- Fixed issue where Requests would raise ``ConnectionError`` instead of
``SSLError`` when encoutering SSL problems when using urllib3 v1.22.
``SSLError`` when encountering SSL problems when using urllib3 v1.22.
2.18.2 (2017-07-25)
+++++++++++++++++++
@@ -32,8 +56,8 @@ Release History
**Bugfixes**
- Fix an error in the packaging whereby the *.whl contained incorrect data that
regressed the fix in v2.17.3.
- 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)
+++++++++++++++++++
@@ -212,6 +236,11 @@ Or, even better::
- Updated bundled idna to v2.5.
- Updated bundled certifi to 2017.4.17.
- Altered how ``SessionRedirectMixin.resolve_redirects`` and ``Session.send``
process redirect history. Developers who subclass ``resolve_redirects`` will
find a different ``.history`` attribute - the first element now contains the
original response, and the last element now contains the active response.
2.13.0 (2017-01-24)
+++++++++++++++++++

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 --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
@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,16 +77,16 @@ 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.

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"

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

@@ -16,6 +16,7 @@
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg" id="_carbonads_js"></script>
<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>
@@ -25,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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,8 @@
Support
=======
.. image:: https://farm5.staticflickr.com/4198/34080352913_5c13ffb336_k_d.jpg
If you have questions or issues about Requests, there are several options:
StackOverflow

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
@@ -593,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
@@ -934,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
@@ -371,10 +373,14 @@ But, since our ``status_code`` for ``r`` was ``200``, when we call
``raise_for_status()`` we get::
>>> r.raise_for_status()
None
<Response [200]>
All is well.
.. note:: ``raise_for_status`` returns the response object for a successful response. This eases chaining in trivial cases, where we want bad codes to raise an exception, but use the response otherwise:
>>> value = requests.get('http://httpbin.org/ip').raise_for_status().json()['origin']
Response Headers
----------------
@@ -414,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
~~~~~~~~~~~~~~~~~~~~~
@@ -44,78 +42,99 @@ import urllib3
import chardet
import warnings
from .exceptions import RequestsDependencyWarning
from .import types
def check_compatibility(urllib3_version, chardet_version):
urllib3_version = urllib3_version.split('.')
assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.
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')
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)
major, minor, patch = int(major), int(minor), int(patch) # type: ignore
# urllib3 >= 1.21.1, <= 1.22
assert major == 1
assert minor >= 21
assert minor <= 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)
major, minor, patch = int(major), int(minor), int(patch) # type: ignore
# chardet >= 3.0.2, < 3.1.0
assert major == 3
assert minor < 1
assert patch >= 2
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)
warnings.warn(
"urllib3 ({0}) or chardet ({1}) doesn't match a supported "
"version!".format(urllib3.__version__, chardet.__version__),
RequestsDependencyWarning,
)
# Attempt to enable urllib3's SNI support, if possible
try:
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning
warnings.simplefilter('ignore', DependencyWarning)
from .__version__ import __title__, __description__, __url__, __version__
from .__version__ import __build__, __author__, __author_email__, __license__
from .__version__ import __copyright__, __cake__
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,12 +1,11 @@
# .-. .-. .-. . . .-. .-. .-. .-.
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'http://python-requests.org'
__version__ = '2.18.3'
__build__ = 0x021803
__version__ = '3.0.0'
__build__ = 0x030000
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
__license__ = 'Apache 2.0'

View File

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

View File

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

View File

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

View File

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

45
requests/basics.py Normal file
View File

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

View File

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

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.compat
~~~~~~~~~~~~~~~
This module handles import compatibility issues between Python 2 and
Python 3.
"""
import chardet
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
try:
import simplejson as json
except ImportError:
import json
# ---------
# Specifics
# ---------
if is_py2:
from urllib import (
quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
proxy_bypass, proxy_bypass_environment, getproxies_environment)
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
from urllib2 import parse_http_list
import cookielib
from Cookie import Morsel
from StringIO import StringIO
from urllib3.packages.ordered_dict import OrderedDict
builtin_str = str
bytes = str
str = unicode
basestring = basestring
numeric_types = (int, long, float)
integer_types = (int, long)
elif is_py3:
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
from collections import OrderedDict
builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.cookies
~~~~~~~~~~~~~~~~
@@ -15,12 +14,12 @@ import calendar
import collections
from ._internal_utils import to_native_string
from .compat import cookielib, urlparse, urlunparse, Morsel
from .basics import cookielib, urlparse, urlunparse, Morsel
try:
import threading
except ImportError:
import dummy_threading as threading
import dummy_threading as threading # type: ignore
class MockRequest(object):
@@ -54,14 +53,21 @@ class MockRequest(object):
# header
if not self._r.headers.get('Host'):
return self._r.url
# If they did set it, retrieve it and reconstruct the expected domain
host = to_native_string(self._r.headers['Host'], encoding='utf-8')
parsed = urlparse(self._r.url)
# Reconstruct the URL as we expect it
return urlunparse([
parsed.scheme, host, parsed.path, parsed.params, parsed.query,
parsed.fragment
])
return urlunparse(
[
parsed.scheme,
host,
parsed.path,
parsed.params,
parsed.query,
parsed.fragment,
]
)
def is_unverifiable(self):
return True
@@ -74,7 +80,9 @@ class MockRequest(object):
def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
raise NotImplementedError(
"Cookie headers should be added with add_unredirected_header()"
)
def add_unredirected_header(self, name, value):
self._new_headers[name] = value
@@ -123,9 +131,11 @@ def extract_cookies_to_jar(jar, request, response):
:param request: our own requests.Request object
:param response: urllib3.HTTPResponse object
"""
if not (hasattr(response, '_original_response') and
response._original_response):
if not (
hasattr(response, '_original_response') and response._original_response
):
return
# the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request)
# pull out the HTTPMessage with the headers and put it in the mock:
@@ -153,12 +163,14 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
for cookie in cookiejar:
if cookie.name != name:
continue
if domain is not None and domain != cookie.domain:
continue
if path is not None and path != cookie.path:
continue
clearables.append((cookie.domain, cookie.path, cookie.name))
clearables.append((cookie.domain, cookie.path, cookie.name))
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
@@ -196,6 +208,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""
try:
return self._find_no_duplicates(name, domain, path)
except KeyError:
return default
@@ -206,7 +219,12 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
"""
# support client code that unsets cookies by assignment of a None value:
if value is None:
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
remove_cookie_by_name(
self,
name,
domain=kwargs.get('domain'),
path=kwargs.get('path'),
)
return
if isinstance(value, Morsel):
@@ -294,6 +312,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
for cookie in iter(self):
if cookie.domain is not None and cookie.domain in domains:
return True
domains.append(cookie.domain)
return False # there is only one domain in jar
@@ -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):
@@ -414,7 +444,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
def copy(self):
"""Return a copy of this RequestsCookieJar."""
new_cj = RequestsCookieJar()
new_cj = RequestsCookieJar(self._policy)
new_cj.update(self)
return new_cj
@@ -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)
@@ -69,12 +71,12 @@ class TooManyRedirects(RequestException):
"""Too many redirects."""
class MissingSchema(RequestException, ValueError):
"""The URL schema (e.g. http or https) is missing."""
class MissingScheme(RequestException, ValueError):
"""The URL scheme (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
"""See defaults.py for valid schemas."""
class InvalidScheme(RequestException, ValueError):
"""See defaults.py for valid schemes."""
class InvalidURL(RequestException, ValueError):
@@ -104,9 +106,14 @@ class RetryError(RequestException):
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body"""
class InvalidBodyError(RequestException, ValueError):
"""An invalid request body was specified"""
# Warnings
class RequestsWarning(Warning):
"""Base warning for Requests."""
pass

View File

@@ -10,10 +10,12 @@ 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
@@ -23,7 +25,7 @@ else:
import cryptography
def _implementation():
def _implementation() -> types.Help:
"""Return a dict with the Python implementation and version.
Provide both the name and the version of the Python implementation
@@ -35,66 +37,51 @@ 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__', ''),
}
idna_info = {
'version': getattr(idna, '__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,
@@ -105,9 +92,7 @@ def info():
'chardet': chardet_info,
'cryptography': cryptography_info,
'idna': idna_info,
'requests': {
'version': requests_version,
},
'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__'):

File diff suppressed because it is too large Load Diff

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?

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"""
requests.session
~~~~~~~~~~~~~~~~
@@ -10,26 +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, is_py3, OrderedDict, urljoin, urlparse
from .basics import cookielib, urljoin, urlparse, str
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
cookiejar_from_dict,
extract_cookies_to_jar,
RequestsCookieJar,
merge_cookies,
_copy_cookie_jar,
)
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
from .hooks import default_hooks, dispatch_hook
from ._internal_utils import to_native_string
from .utils import to_key_val_list, default_headers
from .exceptions import (
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
TooManyRedirects,
InvalidScheme,
ChunkedEncodingError,
ConnectionError,
ContentDecodingError,
InvalidHeader,
)
from .structures import CaseInsensitiveDict
from .adapters import HTTPAdapter
from .utils import (
requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
get_auth_from_url, rewind_body
requote_uri,
get_environ_proxies,
get_netrc_auth,
should_bypass_proxies,
get_auth_from_url,
is_valid_location,
rewind_body,
)
from .status_codes import codes
@@ -39,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
@@ -50,9 +65,8 @@ else:
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
"""Determines appropriate setting for a given request, taking into account
the explicit setting on that request, and the setting in the session. If a
setting is a dictionary, they will be merged together using `dict_class`
setting is a dictionary, they will be merged together using `dict_class`.
"""
if session_setting is None:
return request_setting
@@ -61,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
@@ -95,7 +107,7 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
class SessionRedirectMixin(object):
def get_redirect_target(self, resp):
def get_redirect_target(self, response):
"""Receives a Response. Returns a redirect URI or ``None``"""
# 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
@@ -103,91 +115,108 @@ class SessionRedirectMixin(object):
# 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 resp.is_redirect:
location = resp.headers['location']
if response.is_redirect:
if not is_valid_location(response):
raise InvalidHeader(
'Response contains multiple Location headers. '
'Unable to perform redirect.'
)
location = response.headers['location']
# Currently the underlying http module on py3 decode headers
# in latin1, but empirical evidence suggests that latin1 is very
# rarely used with non-ASCII characters in HTTP headers.
# It is more likely to get UTF8 header rather than latin1.
# This causes incorrect handling of UTF8 encoded location headers.
# To solve this, we re-encode the location in latin1.
if is_py3:
location = location.encode('latin1')
location = location.encode('latin1')
return to_native_string(location, 'utf8')
return None
def resolve_redirects(self, resp, req, stream=False, timeout=None,
verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):
"""Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history
url = self.get_redirect_target(resp)
while url:
prepared_request = req.copy()
# Update history and keep track of redirects.
# resp.history must ignore the original request in this loop
hist.append(resp)
resp.history = hist[1:]
def resolve_redirects(
self,
response,
request,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
yield_requests=False,
**adapter_kwargs,
):
"""Given a Response, yields Responses until 'Location' header-based
redirection ceases, or the Session.max_redirects limit has been
reached.
"""
history = [
response
] # keep track of history; seed it with the original response
location_url = self.get_redirect_target(response)
while location_url:
prepared_request = request.copy()
try:
resp.content # Consume socket so it can be released
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects:
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
response.content # Consume socket so it can be released
except (
ChunkedEncodingError,
ConnectionError,
ContentDecodingError,
RuntimeError,
):
response.raw.read(decode_content=False)
if len(response.history) >= self.max_redirects:
raise TooManyRedirects(
'Exceeded %s redirects.' % self.max_redirects,
response=response,
)
# Release the connection back into the pool.
resp.close()
response.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'):
parsed_rurl = urlparse(resp.url)
url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
if location_url.startswith('//'):
parsed_rurl = urlparse(response.url)
location_url = '%s:%s' % (
to_native_string(parsed_rurl.scheme), location_url
)
# The scheme should be lower case...
parsed = urlparse(url)
url = parsed.geturl()
parsed = urlparse(location_url)
location_url = parsed.geturl()
# Facilitate relative 'location' headers, as allowed by RFC 7231.
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
# Compliant with RFC3986, we percent encode the url.
if not parsed.netloc:
url = urljoin(resp.url, requote_uri(url))
location_url = urljoin(response.url, requote_uri(location_url))
else:
url = requote_uri(url)
prepared_request.url = to_native_string(url)
self.rebuild_method(prepared_request, resp)
# https://github.com/requests/requests/issues/1084
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
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, req, resp.raw)
extract_cookies_to_jar(
prepared_request._cookies, request, response.raw
)
merge_cookies(prepared_request._cookies, self.cookies)
prepared_request.prepare_cookies(prepared_request._cookies)
# Rebuild auth and proxy information.
proxies = self.rebuild_proxies(prepared_request, proxies)
self.rebuild_auth(prepared_request, resp)
self.rebuild_auth(prepared_request, response)
# A failed tell() sets `_body_position` to `object()`. This non-None
# value ensures `rewindable` will be True, allowing us to raise an
# UnrewindableBodyError, instead of hanging the connection.
@@ -195,65 +224,63 @@ class SessionRedirectMixin(object):
prepared_request._body_position is not None and
('Content-Length' in headers or 'Transfer-Encoding' in headers)
)
# Attempt to rewind consumed file-like object.
if rewindable:
rewind_body(prepared_request)
# Override the original request.
req = prepared_request
request = prepared_request
if yield_requests:
yield req
else:
yield request
resp = self.send(
req,
else:
response = self.send(
request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
allow_redirects=False,
**adapter_kwargs
**adapter_kwargs,
)
# copy our history tracker into the response
response.history = history[:]
# append the new response to the history tracker for the next iteration
history.append(response)
extract_cookies_to_jar(
self.cookies, prepared_request, response.raw
)
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
# extract redirect url, if any, for the next loop
url = self.get_redirect_target(resp)
yield resp
location_url = self.get_redirect_target(response)
yield response
def rebuild_auth(self, prepared_request, response):
"""When being redirected we may want to strip authentication from the
request to avoid leaking credentials. This method intelligently removes
request to avoid leaking credentials. This method intelligently
removes
and reapplies authentication where possible to avoid credential loss.
"""
headers = prepared_request.headers
url = prepared_request.url
if 'Authorization' in headers:
# If we get redirected to a new host, we should strip out any
# authentication headers.
original_parsed = urlparse(response.request.url)
redirect_parsed = urlparse(url)
if (original_parsed.hostname != redirect_parsed.hostname):
del headers['Authorization']
# .netrc might have more auth for us on our new host.
new_auth = get_netrc_auth(url) if self.trust_env else None
if new_auth is not None:
prepared_request.prepare_auth(new_auth)
return
def rebuild_proxies(self, prepared_request, proxies):
"""This method re-evaluates the proxy configuration by considering the
environment variables. If we are redirected to a URL covered by
NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
proxy keys for this URL (in case they were stripped by a previous
redirect).
"""This method re-evaluates the proxy configuration by
considering the environment variables. If we are redirected to a
URL covered by NO_PROXY, we strip the proxy configuration.
Otherwise, we set missing proxy keys for this URL (in case they
were stripped by a previous redirect).
This method also replaces the Proxy-Authorization header where
necessary.
@@ -266,50 +293,46 @@ class SessionRedirectMixin(object):
scheme = urlparse(url).scheme
new_proxies = proxies.copy()
no_proxy = proxies.get('no_proxy')
bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
if self.trust_env and not bypass_proxy:
environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
if proxy:
new_proxies.setdefault(scheme, proxy)
if 'Proxy-Authorization' in headers:
del headers['Proxy-Authorization']
try:
username, password = get_auth_from_url(new_proxies[scheme])
except KeyError:
username, password = None, None
if username and password:
headers['Proxy-Authorization'] = _basic_auth_str(username, password)
headers['Proxy-Authorization'] = _basic_auth_str(
username, password
)
return new_proxies
def rebuild_method(self, prepared_request, response):
"""When being redirected we may want to change the method of the request
based on certain specs or browser behavior.
"""
method = prepared_request.method
:rtype bool:
:return: boolean expressing if the method changed during rebuild.
"""
method = original_method = prepared_request.method
# http://tools.ietf.org/html/rfc7231#section-6.4.4
if response.status_code == codes.see_other and method != 'HEAD':
method = 'GET'
# Do what the browsers do, despite standards...
# First, turn 302s into GETs.
if response.status_code == codes.found and method != 'HEAD':
# If a POST is responded to with a 301 or 302, turn it into a GET. This has
# become a common pattern in browsers and was introduced into later versions
# of HTTP RFCs. While some browsers transform other methods to GET, little of
# that has been standardized. For that reason, we're using curl as a model
# which only supports POST->GET.
if response.status_code in (
codes.found, codes.moved
) and method == 'POST':
method = 'GET'
# Second, if a POST is responded to with a 301, turn it into a GET.
# This bizarre behaviour is explained in Issue 1704.
if response.status_code == codes.moved and method == 'POST':
method = 'GET'
prepared_request.method = method
return method != original_method
class Session(SessionRedirectMixin):
@@ -330,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())
@@ -405,24 +427,20 @@ class Session(SessionRedirectMixin):
:class:`Session`.
:param request: :class:`Request` instance to prepare with this
session's settings.
Session's settings.
:rtype: requests.PreparedRequest
"""
cookies = request.cookies or {}
# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# Merge with session cookies
merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
session_cookies = _copy_cookie_jar(self.cookies)
merged_cookies = merge_cookies(session_cookies, cookies)
# Set environment's basic authentication if not explicitly set.
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
p = PreparedRequest()
p.prepare(
method=request.method.upper(),
@@ -430,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,
@@ -438,11 +458,26 @@ 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):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
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.
:param method: method for the new :class:`Request` object.
@@ -492,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):
@@ -516,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)
@@ -527,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)
@@ -538,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)
@@ -551,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):
@@ -562,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):
@@ -573,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):
@@ -583,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):
@@ -597,66 +618,52 @@ class Session(SessionRedirectMixin):
kwargs.setdefault('verify', self.verify)
kwargs.setdefault('cert', self.cert)
kwargs.setdefault('proxies', self.proxies)
# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
if isinstance(request, Request):
raise ValueError('You can only send PreparedRequests.')
# Set up variables needed for resolve_redirects and dispatching of hooks
# Set up variables needed for resolve_redirects and dispatching of
# hooks
allow_redirects = kwargs.pop('allow_redirects', True)
stream = kwargs.get('stream')
hooks = request.hooks
# Get the appropriate adapter to use
adapter = self.get_adapter(url=request.url)
# Start time (approximately) of the request
start = preferred_clock()
# Send the request
r = adapter.send(request, **kwargs)
# Total elapsed time of the request (approximately)
elapsed = preferred_clock() - start
r.elapsed = timedelta(seconds=elapsed)
# Response manipulation hooks
# Response manipulation hooks.
r = dispatch_hook('response', hooks, r, **kwargs)
# Persist cookies
if r.history:
# If the hooks create history then we want those cookies too
for resp in r.history:
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
extract_cookies_to_jar(self.cookies, request, r.raw)
# Redirect resolving generator.
gen = self.resolve_redirects(r, request, **kwargs)
# Resolve redirects if allowed.
# Resolve redirects, if allowed.
history = [resp for resp in gen] if allow_redirects else []
# Shuffle things around if there's history.
# If there is a history, replace ``r`` with the last response
if history:
# Insert the first (original) request at the start
history.insert(0, r)
# Get the last request made
r = history.pop()
r.history = history
# If redirects aren't being followed, store the response on the Request for Response.next().
if not allow_redirects:
try:
r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
r._next = next(
self.resolve_redirects(
r, request, yield_requests=True, **kwargs
)
)
except StopIteration:
pass
if not stream:
r.content
return r
def merge_environment_settings(self, url, proxies, stream, verify, cert):
@@ -665,28 +672,41 @@ class Session(SessionRedirectMixin):
:rtype: dict
"""
# Gather clues from the surrounding environment.
if self.trust_env:
# Set environment's proxies.
no_proxy = proxies.get('no_proxy') if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items():
proxies.setdefault(k, v)
# Look for requests environment configuration and be compatible
# with cURL.
if verify is True or verify is None:
verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
os.environ.get('CURL_CA_BUNDLE'))
# Merge all the kwargs.
proxies = merge_setting(proxies, self.proxies)
# Merge all the kwargs except for proxies.
stream = merge_setting(stream, self.stream)
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
return {'verify': verify, 'proxies': proxies, 'stream': stream,
'cert': cert}
# Gather clues from the surrounding environment.
# We do this after merging the Session values to make sure we don't
# accidentally exclude them.
if self.trust_env:
# Look for requests environment configuration and be compatible
# with cURL.
if verify is True or verify is None:
verify = (
os.environ.get('REQUESTS_CA_BUNDLE') or
os.environ.get('CURL_CA_BUNDLE') or
verify
)
# Now we handle proxies.
# Proxies need to be built up backwards. This is because None values
# can delete proxy information, which can then be re-added by a more
# specific layer. So we begin by getting the environment's proxies,
# then add the Session, then add the request.
no_proxy = proxies.get('no_proxy') if proxies is not None else None
if no_proxy is None:
no_proxy = self.proxies.get('no_proxy')
env_proxies = {}
if self.trust_env:
env_proxies = get_environ_proxies(url, no_proxy=no_proxy) or {}
new_proxies = merge_setting(self.proxies, env_proxies)
proxies = merge_setting(proxies, new_proxies)
return {
'verify': verify,
'proxies': proxies,
'stream': stream,
'cert': cert,
}
def get_adapter(self, url):
"""
@@ -695,15 +715,14 @@ class Session(SessionRedirectMixin):
:rtype: requests.adapters.BaseAdapter
"""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix):
return adapter
# Nothing matches :-/
raise InvalidSchema("No connection adapters were found for '%s'" % url)
raise InvalidScheme("No connection adapters were found for '%s'" % url)
def close(self):
"""Closes all adapters and as such the session"""
"""Closes all adapters and, as such, the Session."""
for v in self.adapters.values():
v.close()
@@ -714,24 +733,13 @@ class Session(SessionRedirectMixin):
"""
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_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,17 +450,12 @@ def get_encoding_from_headers(headers):
def stream_decode_response_unicode(iterator, r):
"""Stream decodes a iterator."""
if r.encoding is None:
for item in iterator:
yield item
return
decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace')
for chunk in iterator:
rv = decoder.decode(chunk)
if rv:
yield rv
rv = decoder.decode(b'', final=True)
if rv:
yield rv
@@ -452,58 +467,38 @@ def iter_slices(string, slice_length):
if slice_length is None or slice_length <= 0:
slice_length = len(string)
while pos < len(string):
yield string[pos:pos + slice_length]
yield string[pos: pos + slice_length]
pos += slice_length
def get_unicode_from_response(r):
"""Returns the requested content back in unicode.
:param r: Response object to get unicode content from.
Tried:
1. charset from content-type
2. fall back and replace all unicode characters
:rtype: str
"""
warnings.warn((
'In requests 3.0, get_unicode_from_response will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
tried_encodings = []
# Try charset from content-type
encoding = get_encoding_from_headers(r.headers)
if encoding:
try:
return str(r.content, encoding)
except UnicodeError:
tried_encodings.append(encoding)
# Fall back:
try:
return str(r.content, encoding, errors='replace')
except TypeError:
return r.content
# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~")
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)
def unquote_unreserved(uri):
def unquote_unreserved(uri: str) -> str:
"""Un-escape any percent-escape sequences in a URI that are unreserved
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
:rtype: str
"""
parts = uri.split('%')
# This convert function is used to optionally convert the output of `chr`.
# In Python 3, `chr` returns a unicode string, while in Python 2 it returns
# a bytestring. Here we deal with that by optionally converting.
def convert(is_bytes, c):
if is_bytes:
return c.encode('ascii')
else:
return c
# Handle both bytestrings and unicode strings.
splitchar = '%'
base = ''
parts = uri.split(splitchar)
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2 and h.isalnum():
@@ -513,15 +508,15 @@ def unquote_unreserved(uri):
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:]
parts[i] = convert(is_bytes=False, c=c) + parts[i][2:]
else:
parts[i] = '%' + parts[i]
parts[i] = splitchar + parts[i]
else:
parts[i] = '%' + parts[i]
return ''.join(parts)
parts[i] = splitchar + parts[i]
return base.join(parts)
def requote_uri(uri):
def requote_uri(uri: str) -> str:
"""Re-quote the given URI.
This function passes the given URI through an unquote/quote cycle to
@@ -536,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
@@ -543,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
@@ -553,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
@@ -569,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
"""
@@ -577,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.
@@ -599,13 +598,17 @@ 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
@@ -618,6 +621,7 @@ def set_environ(env_name, value):
os.environ[env_name] = value
try:
yield
finally:
if value_changed:
if old_value is None:
@@ -626,64 +630,53 @@ def set_environ(env_name, value):
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.
@@ -691,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
@@ -721,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.
@@ -730,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('=')
@@ -769,19 +767,31 @@ def parse_header_links(value):
break
link[key.strip(replace_chars)] = value.strip(replace_chars)
links.append(link)
return links
def is_valid_location(response) -> bool:
"""Verify that multiple Location headers weren't
returned from the last response.
"""
headers = getattr(response.raw, 'headers', None)
if headers is not None:
getlist = getattr(headers, 'getlist', None)
if getlist is not None:
return len(getlist('location')) <= 1
# If response.raw isn't urllib3-like we can't reliably check this
return True
# Null bytes; no need to recreate these on each call to guess_json_utf
_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3
_null2 = _null * 2
_null3 = _null * 3
def guess_json_utf(data):
def guess_json_utf(data: bytes) -> typing.Optional[str]:
"""
:rtype: str
"""
@@ -790,59 +800,63 @@ def guess_json_utf(data):
# determine the encoding. Also detect a BOM, if present.
sample = data[:4]
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
return 'utf-32' # BOM included
return 'utf-32' # BOM included
if sample[:3] == codecs.BOM_UTF8:
return 'utf-8-sig' # BOM included, MS style (discouraged)
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
return 'utf-16' # BOM included
return 'utf-16' # BOM included
nullcount = sample.count(_null)
if nullcount == 0:
return 'utf-8'
if nullcount == 2:
if sample[::2] == _null2: # 1st and 3rd are null
if sample[::2] == _null2: # 1st and 3rd are null
return 'utf-16-be'
if sample[1::2] == _null2: # 2nd and 4th are null
return 'utf-16-le'
# Did not detect 2 valid UTF-16 ascii-range characters
# Did not detect 2 valid UTF-16 ascii-range characters
if nullcount == 3:
if sample[:3] == _null3:
return 'utf-32-be'
if sample[1:] == _null3:
return 'utf-32-le'
# Did not detect a valid UTF-32 ascii-range character
# Did not detect a valid UTF-32 ascii-range character
return None
def prepend_scheme_if_needed(url, new_scheme):
def prepend_scheme_if_needed(url: str, new_scheme: str) -> str:
"""Given a URL that may or may not have a scheme, prepend the given scheme.
Does not replace a present scheme with the one provided as an argument.
:rtype: str
"""
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
# urlparse is a finicky beast, and sometimes decides that there isn't a
# netloc present. Assume that it's being over-cautious, and switch netloc
# and path if urlparse decided there was no netloc.
if not netloc:
netloc, path = path, netloc
return urlunparse((scheme, netloc, path, params, query, fragment))
def get_auth_from_url(url):
def get_auth_from_url(url: str) -> typing.Tuple[typing.Text, typing.Text]:
"""Given a url with authentication components, extract them into a tuple of
username,password.
:rtype: (str,str)
"""
parsed = urlparse(url)
try:
auth = (unquote(parsed.username), unquote(parsed.password))
except (AttributeError, TypeError):
auth = ('', '')
return auth
@@ -851,7 +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.
@@ -859,46 +875,61 @@ def check_header_validity(header):
:param header: tuple, in the format (name, value).
"""
name, value = header
if isinstance(value, bytes):
pat = _CLEAN_HEADER_REGEX_BYTE
else:
pat = _CLEAN_HEADER_REGEX_STR
pat = _CLEAN_HEADER_REGEX_STR
try:
if not pat.match(value):
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
raise InvalidHeader(
"Invalid return character or leading space in header: %s" %
name
)
except TypeError:
raise InvalidHeader("Header value %s must be of type str or bytes, "
"not %s" % (value, type(value)))
raise InvalidHeader(
"Value for header {%s: %s} must be of type str or "
"bytes, not %s" % (name, value, type(value))
)
def urldefragauth(url):
def urldefragauth(url: str) -> str:
"""
Given a url remove the fragment and the authentication part.
:rtype: str
"""
scheme, netloc, path, params, query, fragment = urlparse(url)
# see func:`prepend_scheme_if_needed`
if not netloc:
netloc, path = path, netloc
netloc = netloc.rsplit('@', 1)[-1]
return urlunparse((scheme, netloc, path, params, query, ''))
def rewind_body(prepared_request):
def rewind_body(prepared_request) -> None:
"""Move file pointer back to its recorded starting position
so it can be read again on redirect.
"""
body_seek = getattr(prepared_request.body, 'seek', None)
if body_seek is not None and isinstance(prepared_request._body_position, integer_types):
if body_seek is not None and isinstance(
prepared_request._body_position, integer_types
):
try:
body_seek(prepared_request._body_position)
except (IOError, OSError):
raise UnrewindableBodyError("An error occurred when rewinding request "
"body for redirect.")
raise UnrewindableBodyError(
"An error occurred when rewinding request "
"body for redirect."
)
else:
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
raise UnrewindableBodyError(
"Unable to rewind request body for redirect."
)
def is_stream(data: bytes) -> bool:
"""Given data, determines if it should be sent as a stream."""
is_iterable = getattr(data, '__iter__', False)
is_io_type = not isinstance(
data, (basestring, list, tuple, collections.Mapping)
)
return is_iterable and is_io_type

View File

@@ -1,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',
'idna>=2.5,<2.7',
'urllib3>=1.21.1,<1.23',
'certifi>=2017.4.17'
'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):

View File

@@ -1,5 +1,4 @@
# -*- encoding: utf-8
import sys
import pytest
@@ -7,7 +6,9 @@ import pytest
from requests.help import info
@pytest.mark.skipif(sys.version_info[:2] != (2,6), reason="Only run on Python 2.6")
@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.
@@ -15,13 +16,16 @@ def test_system_ssl_py26():
assert info()['system_ssl'] == {'version': ''}
@pytest.mark.skipif(sys.version_info < (2,7), reason="Only run on Python 2.7+")
@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

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():

View File

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

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

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,37 +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, set_environ)
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
@@ -46,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
@@ -58,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()
@@ -70,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')
@@ -83,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
@@ -114,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
@@ -131,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
@@ -152,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'
@@ -216,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)
@@ -239,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)
@@ -263,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
@@ -283,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
@@ -302,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
@@ -478,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
"""
@@ -605,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
@@ -642,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]
@@ -655,18 +675,19 @@ def test_should_bypass_proxies_win_registry(url, expected, override,
@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),
))
'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

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