Compare commits
313 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e2696b385 | ||
|
|
b5212ddc3d | ||
|
|
dacaae520a | ||
|
|
e663c77e70 | ||
|
|
ec109c9ac8 | ||
|
|
0b9a2a1581 | ||
|
|
2759060559 | ||
|
|
00b13673e3 | ||
|
|
91ac5ba110 | ||
|
|
0d8c08cd22 | ||
|
|
ab4c03adc3 | ||
|
|
75b0c4be1b | ||
|
|
4a8e6415c7 | ||
|
|
58bf158840 | ||
|
|
c0f066626e | ||
|
|
69c7a8638e | ||
|
|
1b6f1b091c | ||
|
|
d39cdc45d1 | ||
|
|
7f761f3adf | ||
|
|
5e76943f02 | ||
|
|
b887ea3b60 | ||
|
|
690426ac86 | ||
|
|
b8298b0710 | ||
|
|
3bd5406c06 | ||
|
|
3ae0ff3685 | ||
|
|
80e29bfcab | ||
|
|
d7107c2cdc | ||
|
|
658da765ec | ||
|
|
9e2f5dd0ed | ||
|
|
1ffce4f7dc | ||
|
|
fcac1c3746 | ||
|
|
c0763bb8d5 | ||
|
|
75bd9d0e94 | ||
|
|
08bc1198d3 | ||
|
|
cf54f60367 | ||
|
|
329efcae6e | ||
|
|
5a2a78f7df | ||
|
|
f72c13ffda | ||
|
|
0a17badb21 | ||
|
|
12f9aa3669 | ||
|
|
afa2e86a4d | ||
|
|
b7842ab34b | ||
|
|
f8b3141204 | ||
|
|
c40d6d0509 | ||
|
|
a74481a1d4 | ||
|
|
b898415d0d | ||
|
|
b47effe5d8 | ||
|
|
4f10605389 | ||
|
|
120e62900b | ||
|
|
986704dc62 | ||
|
|
2f13956357 | ||
|
|
8d700dd1e5 | ||
|
|
5be412db13 | ||
|
|
a61a861cfb | ||
|
|
c363ca476f | ||
|
|
319b1ed551 | ||
|
|
67f50a0fe5 | ||
|
|
b98cc8b4e9 | ||
|
|
495646bb6a | ||
|
|
4516bd45b2 | ||
|
|
730c0e2e2b | ||
|
|
3020d0e56a | ||
|
|
49bed18a83 | ||
|
|
e6959412aa | ||
|
|
04c0bcc9b4 | ||
|
|
ec4edae977 | ||
|
|
f478c34671 | ||
|
|
a0ef4f2358 | ||
|
|
ca57e93ebf | ||
|
|
5ca5b7ec65 | ||
|
|
2b202aabd7 | ||
|
|
cb64d31171 | ||
|
|
80eafe3b63 | ||
|
|
16aa444261 | ||
|
|
0bbdbac17f | ||
|
|
c430b60e38 | ||
|
|
02f6555536 | ||
|
|
4293e3fdb2 | ||
|
|
50298bbf97 | ||
|
|
ac82fa47a0 | ||
|
|
976bfbf687 | ||
|
|
1bc377b188 | ||
|
|
a89530aef5 | ||
|
|
c32b1cd6d6 | ||
|
|
90f5c8bd85 | ||
|
|
878bb61d24 | ||
|
|
8493d8329e | ||
|
|
57aeafa115 | ||
|
|
a29e862adf | ||
|
|
5ed62e1137 | ||
|
|
f99a2534c9 | ||
|
|
a939423006 | ||
|
|
4493acc5ee | ||
|
|
c4eb9301bb | ||
|
|
57ba337e79 | ||
|
|
3d542693bc | ||
|
|
5f2607ffca | ||
|
|
ab916a8a8d | ||
|
|
1fb4bd82f7 | ||
|
|
d892e93589 | ||
|
|
38c2532c5f | ||
|
|
18b5ee7d5b | ||
|
|
9fd2d07c1d | ||
|
|
e7bcc3b332 | ||
|
|
b001fcf968 | ||
|
|
125619c783 | ||
|
|
fe6882623c | ||
|
|
11c4315da3 | ||
|
|
672e66ba5f | ||
|
|
bcfddb3d65 | ||
|
|
9cae98c167 | ||
|
|
b0e2e4f284 | ||
|
|
36842db26f | ||
|
|
e84a7c7e6f | ||
|
|
2109afc144 | ||
|
|
786dfdd7e5 | ||
|
|
62d05f842d | ||
|
|
fc3b03a5f5 | ||
|
|
f4f2bd9fab | ||
|
|
b0555b68e7 | ||
|
|
497695864b | ||
|
|
128b49a1fb | ||
|
|
0dbda6665d | ||
|
|
464279f48d | ||
|
|
b14a244ab4 | ||
|
|
2c7e920298 | ||
|
|
b725bfd3fb | ||
|
|
47e96b8040 | ||
|
|
9a1348707c | ||
|
|
ada98899f8 | ||
|
|
16a7a26452 | ||
|
|
2c04f9d199 | ||
|
|
70e98e47cf | ||
|
|
b525dc29f7 | ||
|
|
a3fb5ac326 | ||
|
|
8e6f98f44e | ||
|
|
9ba93b885c | ||
|
|
5f44d79729 | ||
|
|
a3b26c7e5b | ||
|
|
e9be7c1a58 | ||
|
|
6c359b2457 | ||
|
|
7647e52943 | ||
|
|
a0ae2e6c7b | ||
|
|
efd2b57b92 | ||
|
|
47aba0cc20 | ||
|
|
f5a2b75924 | ||
|
|
49a38ac89a | ||
|
|
6d071f4331 | ||
|
|
3873121c01 | ||
|
|
5bff8e362f | ||
|
|
62733146be | ||
|
|
99f6581770 | ||
|
|
b37fbf30a3 | ||
|
|
e65d0ac9e3 | ||
|
|
61a0e85672 | ||
|
|
c18e6459a8 | ||
|
|
6db255d17d | ||
|
|
49d79546e8 | ||
|
|
f3ba022f46 | ||
|
|
17f2002839 | ||
|
|
a6ee9757a7 | ||
|
|
d9cb18ad2a | ||
|
|
557480cfe8 | ||
|
|
5dae0bb9bf | ||
|
|
e38046e6c6 | ||
|
|
97be9ee4cd | ||
|
|
c658b363e3 | ||
|
|
7f9cef0114 | ||
|
|
8efb6cc870 | ||
|
|
1c8a141711 | ||
|
|
4d38790b5b | ||
|
|
5cfbb0aaf5 | ||
|
|
29271a4100 | ||
|
|
df0dc2b67a | ||
|
|
ff286f351c | ||
|
|
430125b396 | ||
|
|
0536e5b708 | ||
|
|
e6990162e5 | ||
|
|
d9b304b906 | ||
|
|
79f5d532c6 | ||
|
|
6af6a08b7a | ||
|
|
96d5044722 | ||
|
|
4258201900 | ||
|
|
11e5126454 | ||
|
|
9e953f3320 | ||
|
|
bbbeedb0a2 | ||
|
|
de17f4e5a4 | ||
|
|
86eba21b8a | ||
|
|
53d679edba | ||
|
|
89c90add2c | ||
|
|
a869be1e5a | ||
|
|
68619b8088 | ||
|
|
534cdd7587 | ||
|
|
e185aec920 | ||
|
|
14e8eea0ee | ||
|
|
2b849545ea | ||
|
|
15396fb2f9 | ||
|
|
1acef81ed3 | ||
|
|
a25141f9f4 | ||
|
|
d339ffe9d9 | ||
|
|
c5ba5888b5 | ||
|
|
3415e6de0b | ||
|
|
638532dd9a | ||
|
|
c6fc7d99b5 | ||
|
|
281e0598bc | ||
|
|
aa39fb0c80 | ||
|
|
459a91c54f | ||
|
|
115b51e68c | ||
|
|
4441697e49 | ||
|
|
5174ab4325 | ||
|
|
95911f059a | ||
|
|
e3288d5e33 | ||
|
|
49140d6880 | ||
|
|
9c6ffc5303 | ||
|
|
f80984f377 | ||
|
|
9c0a0fbd01 | ||
|
|
57dbab2932 | ||
|
|
7fdb541e2d | ||
|
|
082f4d8948 | ||
|
|
8aa015ffc9 | ||
|
|
cfb1a478ac | ||
|
|
28db6c3f53 | ||
|
|
5f768b191d | ||
|
|
d9a9a981bc | ||
|
|
9fcfec18da | ||
|
|
a83268b01b | ||
|
|
a1afbfbaa3 | ||
|
|
4114b04521 | ||
|
|
f7e67c01ea | ||
|
|
544a5fea59 | ||
|
|
0a8bb2e1b3 | ||
|
|
5184f0c5a9 | ||
|
|
9a1a4136ac | ||
|
|
ca26c1a253 | ||
|
|
a91ac242f3 | ||
|
|
31d495fb02 | ||
|
|
bdda99c0c1 | ||
|
|
9ed93044e0 | ||
|
|
0bfc029b5e | ||
|
|
4eb2759400 | ||
|
|
0001f5b651 | ||
|
|
e1615e0f1e | ||
|
|
3483bae24f | ||
|
|
716d29c49c | ||
|
|
a7223f86a9 | ||
|
|
2c0ff95d4d | ||
|
|
91766548d1 | ||
|
|
12f3c65e5a | ||
|
|
f5e421e880 | ||
|
|
9d88ac7b50 | ||
|
|
75dc012f41 | ||
|
|
f214b91d77 | ||
|
|
f80fbad4ff | ||
|
|
2e38f54b39 | ||
|
|
2c153c1556 | ||
|
|
70fbd27d5c | ||
|
|
c910b958f0 | ||
|
|
6ce4ece8aa | ||
|
|
5be6c21b14 | ||
|
|
8207ed074e | ||
|
|
f8cf713f41 | ||
|
|
8f58c095c5 | ||
|
|
5937282618 | ||
|
|
a3b8ef15db | ||
|
|
4e2ecd6987 | ||
|
|
814e0520ad | ||
|
|
19a2a6c6d1 | ||
|
|
0e711f2f9d | ||
|
|
5e60279a19 | ||
|
|
6fb2674446 | ||
|
|
5a3aae73ba | ||
|
|
47510486ec | ||
|
|
c3bba1543b | ||
|
|
cef1811415 | ||
|
|
21acc6b732 | ||
|
|
ba71e2a3cb | ||
|
|
baf3dcb6c7 | ||
|
|
8b92b6513c | ||
|
|
03ddf26596 | ||
|
|
8c1fd5db95 | ||
|
|
10eed6344c | ||
|
|
06eee390dd | ||
|
|
fad9abfd77 | ||
|
|
a5f79712bb | ||
|
|
c71ac7917a | ||
|
|
4e59f9ec93 | ||
|
|
a6412b02ba | ||
|
|
961f78ab5f | ||
|
|
463ca76cc4 | ||
|
|
f58ab8bc03 | ||
|
|
3a06c35da3 | ||
|
|
37070ac30e | ||
|
|
e56d05d230 | ||
|
|
f1fa8f0064 | ||
|
|
56d6d703f9 | ||
|
|
7fbbdeb324 | ||
|
|
f7e4832472 | ||
|
|
4ed16d99c2 | ||
|
|
02f422d132 | ||
|
|
a76f4925b5 | ||
|
|
c2e942f230 | ||
|
|
c7b99f7819 | ||
|
|
6bb4c4ebe6 | ||
|
|
d1dfa02070 | ||
|
|
42b33ae1a1 | ||
|
|
1165bec773 | ||
|
|
8f20058d0d | ||
|
|
6e9cb09fff | ||
|
|
b40128219f | ||
|
|
72943d0247 | ||
|
|
d0b96f8e3b | ||
|
|
3326030862 | ||
|
|
c60618ae01 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
.idea
|
||||
MANIFEST
|
||||
coverage.xml
|
||||
nosetests.xml
|
||||
|
||||
21
AUTHORS.rst
21
AUTHORS.rst
@@ -62,4 +62,23 @@ Patches and Suggestions
|
||||
- Ryan Kelly
|
||||
- Rolando Espinoza La fuente
|
||||
- Robert Gieseke
|
||||
- Idan Gazit
|
||||
- Idan Gazit
|
||||
- Ed Summers
|
||||
- Chris Van Horne
|
||||
- Christopher Davis
|
||||
- Ori Livneh
|
||||
- Jason Emerick
|
||||
- Bryan Helmig
|
||||
- Jonas Obrist
|
||||
- Lucian Ursu
|
||||
- Tom Moertel
|
||||
- Frank Kumro Jr
|
||||
- Chase Sterling
|
||||
- Marty Alchin
|
||||
- takluyver
|
||||
- Ben Toews (mastahyeti)
|
||||
- David Kemp
|
||||
- Brendon Crawford
|
||||
- Denis (Telofy)
|
||||
- Cory Benfield (Lukasa)
|
||||
- Matt Giuca
|
||||
|
||||
105
HISTORY.rst
105
HISTORY.rst
@@ -1,6 +1,106 @@
|
||||
History
|
||||
-------
|
||||
|
||||
0.10.2
|
||||
++++++
|
||||
|
||||
* Vastly improved URL quoting.
|
||||
* Additional allowed cookie key values.
|
||||
* Attempted fix for "Too many open files" Error
|
||||
* Replace unicode errors on first pass, no need for second pass.
|
||||
* Append '/' to bare-domain urls before query insertion.
|
||||
* Exceptions now inherit from RuntimeError.
|
||||
* Binary uploads + auth fix.
|
||||
* Bugfixes.
|
||||
|
||||
|
||||
0.10.1 (2012-01-23)
|
||||
+++++++++++++++++++
|
||||
|
||||
* PYTHON 3 SUPPORT!
|
||||
* Dropped 2.5 Support. (*Backwards Incompatible*)
|
||||
|
||||
0.10.0 (2012-01-21)
|
||||
+++++++++++++++++++
|
||||
|
||||
* ``Response.content`` is now bytes-only. (*Backwards Incompatible*)
|
||||
* New ``Response.text`` is unicode-only.
|
||||
* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Respoonse.text`` will guess an encoding.
|
||||
* Default to ISO-8859-1 (Western) encoding for "text" subtypes.
|
||||
* Removal of `decode_unicode`. (*Backwards Incompatible*)
|
||||
* New multiple-hooks system.
|
||||
* New ``Response.register_hook`` for registering hooks within the pipeline.
|
||||
* ``Response.url`` is now Unicode.
|
||||
|
||||
0.9.3 (2012-01-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* SSL verify=False bugfix (apparent on windows machines).
|
||||
|
||||
0.9.2 (2012-01-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Asynchronous async.send method.
|
||||
* Support for proper chunk streams with boundaries.
|
||||
* session argument for Session classes.
|
||||
* Print entire hook tracebacks, not just exception instance.
|
||||
* Fix response.iter_lines from pending next line.
|
||||
* Fix but in HTTP-digest auth w/ URI having query strings.
|
||||
* Fix in Event Hooks section.
|
||||
* Urllib3 update.
|
||||
|
||||
|
||||
0.9.1 (2012-01-06)
|
||||
++++++++++++++++++
|
||||
|
||||
* danger_mode for automatic Response.raise_for_status()
|
||||
* Response.iter_lines refactor
|
||||
|
||||
0.9.0 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* verify ssl is default.
|
||||
|
||||
|
||||
0.8.9 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* Packaging fix.
|
||||
|
||||
|
||||
0.8.8 (2011-12-28)
|
||||
++++++++++++++++++
|
||||
|
||||
* SSL CERT VERIFICATION!
|
||||
* Release of Cerifi: Mozilla's cert list.
|
||||
* New 'verify' argument for SSL requests.
|
||||
* Urllib3 update.
|
||||
|
||||
0.8.7 (2011-12-24)
|
||||
++++++++++++++++++
|
||||
|
||||
* iter_lines last-line truncation fix
|
||||
* Force safe_mode for async requests
|
||||
* Handle safe_mode exceptions more consistently
|
||||
* Fix iteration on null responses in safe_mode
|
||||
|
||||
0.8.6 (2011-12-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Socket timeout fixes.
|
||||
* Proxy Authorization support.
|
||||
|
||||
0.8.5 (2011-12-14)
|
||||
++++++++++++++++++
|
||||
|
||||
* Response.iter_lines!
|
||||
|
||||
0.8.4 (2011-12-11)
|
||||
++++++++++++++++++
|
||||
|
||||
* Prefetch bugfix.
|
||||
* Added license to installed version.
|
||||
|
||||
0.8.3 (2011-11-27)
|
||||
++++++++++++++++++
|
||||
|
||||
@@ -11,7 +111,7 @@ History
|
||||
0.8.2 (2011-11-19)
|
||||
++++++++++++++++++
|
||||
|
||||
* New unicode decoding system, based on overridable `Response.encoding`.
|
||||
* New Unicode decoding system, based on over-ridable `Response.encoding`.
|
||||
* Proper URL slash-quote handling.
|
||||
* Cookies with ``[``, ``]``, and ``_`` allowed.
|
||||
|
||||
@@ -35,6 +135,7 @@ History
|
||||
* OPTION method
|
||||
* Async pool size throttling
|
||||
* File uploads send real names
|
||||
* Vendored in urllib3
|
||||
|
||||
0.7.6 (2011-11-07)
|
||||
++++++++++++++++++
|
||||
@@ -50,7 +151,7 @@ History
|
||||
0.7.4 (2011-10-26)
|
||||
++++++++++++++++++
|
||||
|
||||
* Sesion Hooks fix.
|
||||
* Session Hooks fix.
|
||||
|
||||
0.7.3 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2011 Kenneth Reitz.
|
||||
Copyright (c) 2012 Kenneth Reitz.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
@@ -1 +1 @@
|
||||
include README.rst LICENSE HISTORY.rst test_requests.py
|
||||
include README.rst LICENSE NOTICE HISTORY.rst test_requests.py
|
||||
|
||||
25
Makefile
25
Makefile
@@ -1,11 +1,20 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
init:
|
||||
pip install -r reqs.txt
|
||||
python setup.py develop
|
||||
pip install -r requirements.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py
|
||||
nosetests --with-color ./tests/*
|
||||
|
||||
lazy:
|
||||
nosetests --with-color tests/test_requests.py
|
||||
|
||||
server:
|
||||
gunicorn httpbin:app --bind=0.0.0.0:7077 &
|
||||
|
||||
ci: init
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
nosetests --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
stats:
|
||||
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
|
||||
@@ -13,4 +22,14 @@ stats:
|
||||
site:
|
||||
cd docs; make dirhtml
|
||||
|
||||
pyc:
|
||||
find . -name "*.pyc" -exec rm '{}' ';'
|
||||
|
||||
deps:
|
||||
rm -fr requests/packages/urllib3
|
||||
git clone https://github.com/shazow/urllib3.git
|
||||
cd urllib3 && git checkout python3 && cd ..
|
||||
mv urllib3/urllib3 requests/packages/
|
||||
rm -fr urllib3
|
||||
|
||||
docs: site
|
||||
|
||||
25
NOTICE
25
NOTICE
@@ -1,12 +1,25 @@
|
||||
Request includes some vendorized python libraries to ease installation.
|
||||
|
||||
Poster License
|
||||
==============
|
||||
Urllib3 License
|
||||
===============
|
||||
|
||||
Copyright (c) 2010 Chris AtLee
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt),
|
||||
Modifications copyright 2022 Kenneth Reitz.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
56
README.rst
56
README.rst
@@ -19,7 +19,7 @@ Things shouldn't be this way. Not in Python.
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
...
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
@@ -27,52 +27,24 @@ See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
Requests allow you to send **HEAD**, **GET**, **POST**, **PUT**,
|
||||
**PATCH**, and **DELETE** HTTP requests. You can add headers, form data,
|
||||
multipart files, and parameters with simple Python dictionaries, and access the
|
||||
response data in the same way. It's powered by httplib, but it does
|
||||
all the hard work and crazy hacks for you.
|
||||
response data in the same way. It's powered by httplib and `urllib3
|
||||
<https://github.com/shazow/urllib3>`_, but it does all the hard work and crazy
|
||||
hacks for you.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests.
|
||||
- Gevent support for Asyncronous Requests.
|
||||
- Sessions with cookie persistience.
|
||||
- Basic, Digest, and Custom Authentication support.
|
||||
- Automatic form-encoding of dictionaries
|
||||
- A simple dictionary interface for request/response cookies.
|
||||
- Multipart file uploads.
|
||||
- Automatc decoding of Unicode, gzip, and deflate responses.
|
||||
- Full support for unicode URLs and domain names.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
It couldn't be simpler. ::
|
||||
|
||||
>>> import requests
|
||||
>>> r = requests.get('http://google.com')
|
||||
|
||||
|
||||
HTTPS? Basic Authentication? ::
|
||||
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass')
|
||||
>>> r.status_code
|
||||
401
|
||||
|
||||
|
||||
Uh oh, we're not authorized! Let's add authentication. ::
|
||||
|
||||
>>> r = requests.get('https://httpbin.ep.io/basic-auth/user/pass', auth=('user', 'pass'))
|
||||
|
||||
>>> r.status_code
|
||||
200
|
||||
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
|
||||
>>> r.content
|
||||
'{"authenticated": true, "user": "user"}'
|
||||
- International Domains and URLs
|
||||
- Keep-Alive & Connection Pooling
|
||||
- Sessions with Cookie Persistence
|
||||
- Browser-style SSL Verification
|
||||
- Basic/Digest Authentication
|
||||
- Elegant Key/Value Cookies
|
||||
- Automatic Decompression
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
|
||||
|
||||
Installation
|
||||
|
||||
2
docs/_themes/README.rst
vendored
2
docs/_themes/README.rst
vendored
@@ -2,7 +2,7 @@ krTheme Sphinx Style
|
||||
====================
|
||||
|
||||
This repository contains sphinx styles Kenneth Reitz uses in most of
|
||||
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
|
||||
his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
|
||||
10
docs/_themes/kr/layout.html
vendored
10
docs/_themes/kr/layout.html
vendored
@@ -25,6 +25,14 @@
|
||||
})();
|
||||
/* ]]> */
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="//www.hellobar.com/hellobar.js"></script>
|
||||
<script type="text/javascript">
|
||||
new HelloBar(36402,48802);
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
@@ -54,4 +62,6 @@
|
||||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
@@ -51,23 +51,17 @@ Chris Adams gave an excellent summary on
|
||||
Python 3 Support?
|
||||
-----------------
|
||||
|
||||
It's on the way. Here's a list of Python platforms that are officially
|
||||
Yes! Here's a list of Python platforms that are officially
|
||||
supported:
|
||||
|
||||
* cPython 2.5
|
||||
* cPython 2.5.5
|
||||
* cPython 2.5.6
|
||||
* cPython 2.6
|
||||
* cPython 2.6.6
|
||||
* cPython 2.6.7
|
||||
* cPython 2.7
|
||||
* cPython 2.7.1
|
||||
* cPython 2.7.2
|
||||
* cPython 3.1
|
||||
* cPython 3.2
|
||||
* PyPy-c 1.4
|
||||
* PyPy-c 1.5
|
||||
|
||||
|
||||
Support for Python 3.x is coming *very* soon.
|
||||
* PyPy-c 1.6
|
||||
* PyPy-c 1.7
|
||||
|
||||
|
||||
Keep-alive Support?
|
||||
@@ -81,3 +75,9 @@ Proxy Support?
|
||||
|
||||
You bet!
|
||||
|
||||
|
||||
SSL Verification?
|
||||
-----------------
|
||||
|
||||
Absolutely.
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Requests'
|
||||
copyright = u'2011. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
copyright = u'2012. A <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> Project'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
@@ -3,6 +3,24 @@ How to Help
|
||||
|
||||
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 feature idea or a bug.
|
||||
There is a Contributor Friendly tag for issues that should be ideal for people who are not very
|
||||
familiar with the codebase yet.
|
||||
#. Fork `the repository`_ on Github to start making your changes to the **develop**
|
||||
branch (or branch off of it).
|
||||
#. Write a test which shows that the bug was fixed or that the feature works as expected.
|
||||
#. Send a pull request and bug the maintainer until it gets merged and published. :)
|
||||
Make sure to add yourself to AUTHORS_.
|
||||
|
||||
Development dependencies
|
||||
------------------------
|
||||
|
||||
You'll need to install ``gunicorn`` and ``httpbin`` in order to run requests' test suite::
|
||||
|
||||
$ pip install -r requirements.txt
|
||||
|
||||
|
||||
|
||||
|
||||
What Needs to be Done
|
||||
---------------------
|
||||
|
||||
@@ -23,22 +23,22 @@ Things shouldn’t be this way. Not in Python.
|
||||
204
|
||||
>>> r.headers['content-type']
|
||||
'application/json'
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
...
|
||||
|
||||
See `the same code, without Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
Requests takes all of the work out of Python HTTP — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data.
|
||||
Requests takes all of the work out of Python HTTP — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by `urllib3 <https://github.com/shazow/urllib3>`_, which is embedded within Requests.
|
||||
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`The Washington Post <http://www.washingtonpost.com/>`_,
|
||||
`Native Instruments <http://www.native-instruments.com/>`_, `The Washington Post <http://www.washingtonpost.com/>`_,
|
||||
`Twitter, Inc <http://twitter.com>`_,
|
||||
`Readability <http://readability.com>`_, and
|
||||
Federal US Institutions
|
||||
use Requests internally. It has been installed over 45,000 times from PyPi.
|
||||
use Requests internally. It has been installed over 80,000 times from PyPI.
|
||||
|
||||
**Armin Ronacher**
|
||||
Requests is the perfect example how beautiful an API can be with the
|
||||
@@ -65,14 +65,13 @@ Requests is ready for today's web.
|
||||
- International Domains and URLs
|
||||
- Keep-Alive & Connection Pooling
|
||||
- Sessions with Cookie Persistence
|
||||
- Browser-style SSL Verification
|
||||
- Basic/Digest Authentication
|
||||
- Elegant Key/Value Cookies
|
||||
- Automatic Decompression
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- Connection Timeouts
|
||||
- Zero Dependencies
|
||||
|
||||
|
||||
|
||||
User Guide
|
||||
@@ -98,7 +97,7 @@ This part of the documentation, which is mostly prose, details the
|
||||
Requests ecosystem and community.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
community/faq
|
||||
community/out-there.rst
|
||||
@@ -124,7 +123,7 @@ If you want to contribute to the project, this part of the documentation is for
|
||||
you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
dev/internals
|
||||
dev/todo
|
||||
|
||||
@@ -10,7 +10,7 @@ Session Objects
|
||||
---------------
|
||||
|
||||
The Session object allows you to persist certain parameters across
|
||||
requests. It also perstists cookies across all requests made from the
|
||||
requests. It also persists cookies across all requests made from the
|
||||
Session instance.
|
||||
|
||||
A session object has all the methods of the main Requests API.
|
||||
@@ -22,7 +22,7 @@ Let's persist some cookies across requests::
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
|
||||
print r.content
|
||||
print r.text
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
|
||||
|
||||
@@ -45,11 +45,26 @@ Any dictionaries that you pass to a request method will be merged with the sessi
|
||||
|
||||
All values that are contained within a session are directly available to you. See the :ref:`Session API Docs <sessionapi>` to learn more.
|
||||
|
||||
SSL Cert Verification
|
||||
---------------------
|
||||
|
||||
Requests can verify SSL certificates for HTTPS requests, just like a web browser. To check a host's SSL certificate, you can use the ``verify`` argument::
|
||||
|
||||
>>> requests.get('https://kennethreitz.com', verify=True)
|
||||
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
|
||||
|
||||
I don't have SSL setup on this domain, so it fails. Excellent. Github does though::
|
||||
|
||||
>>> requests.get('https://github.com', verify=True)
|
||||
<Response [200]>
|
||||
|
||||
You can also pass ``verify`` the path to a CA_BUNDLE file for private certs. You can also set the ``REQUESTS_CA_BUNDLE`` environment variable.
|
||||
|
||||
|
||||
Body Content Workflow
|
||||
----------------------
|
||||
---------------------
|
||||
|
||||
By default, When you make a request, the body of the response isn't downloaded immediately. The response headers are downloaded when you make a request, but the content isn't downloaded until you access the :class:`Response.content` attribute.
|
||||
By default, when you make a request, the body of the response isn't downloaded immediately. The response headers are downloaded when you make a request, but the content isn't downloaded until you access the :class:`Response.content` attribute.
|
||||
|
||||
Let's walk through it::
|
||||
|
||||
@@ -80,6 +95,8 @@ Keep-Alive
|
||||
|
||||
Excellent news — thanks to urllib3, keep-alive is 100% automatic within a session! Any requests that you make within a session will automatically reuse the appropriate connection!
|
||||
|
||||
Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``prefetch`` to ``True`` or read the ``content`` property of the ``Response`` object.
|
||||
|
||||
If you'd like to disable keep-alive, you can simply set the ``keep_alive`` configuration to ``False``::
|
||||
|
||||
s = requests.session()
|
||||
@@ -89,8 +106,8 @@ If you'd like to disable keep-alive, you can simply set the ``keep_alive`` confi
|
||||
Asynchronous Requests
|
||||
----------------------
|
||||
|
||||
Requests has first-class support for concurrent requests, powered
|
||||
by gevent. This allows you to send a bunch of HTTP requests at the same
|
||||
Requests has first-class support for concurrent requests, powered by gevent.
|
||||
This allows you to send a bunch of HTTP requests at the same time.
|
||||
|
||||
First, let's import the async module. Heads up — if you don't have
|
||||
`gevent <http://pypi.python.org/pypi/gevent>`_ this will fail::
|
||||
@@ -122,7 +139,7 @@ will also guarantee execution of the ``response`` hook, described below. ::
|
||||
|
||||
.. admonition:: Throttling
|
||||
|
||||
The ``map`` function also takes a ``size`` parameter, that specifies the nubmer of connections to make at a time::
|
||||
The ``map`` function also takes a ``size`` parameter, that specifies the number of connections to make at a time::
|
||||
|
||||
async.map(rs, size=5)
|
||||
|
||||
@@ -170,14 +187,14 @@ anything, nothing else is effected.
|
||||
|
||||
Let's print some request method arguments at runtime::
|
||||
|
||||
>>> requests.get('http://httpbin', hooks=dict(args=print_url))
|
||||
http://httpbin
|
||||
>>> requests.get('http://httpbin.org', hooks=dict(args=print_url))
|
||||
http://httpbin.org
|
||||
<Response [200]>
|
||||
|
||||
Let's hijack some arguments this time with a new callback::
|
||||
|
||||
def hack_headers(args):
|
||||
if not args[headers]:
|
||||
if args.get('headers') is None:
|
||||
args['headers'] = dict()
|
||||
|
||||
args['headers'].update({'X-Testing': 'True'})
|
||||
@@ -189,7 +206,7 @@ Let's hijack some arguments this time with a new callback::
|
||||
|
||||
And give it a try::
|
||||
|
||||
>>> requests.get('http://httpbin/headers', hooks=hooks, headers=headers)
|
||||
>>> requests.get('http://httpbin.org/headers', hooks=hooks, headers=headers)
|
||||
{
|
||||
"headers": {
|
||||
"Content-Length": "",
|
||||
@@ -229,7 +246,7 @@ Let's pretend that we have a web service that will only respond if the
|
||||
def __init__(self, username):
|
||||
# setup any auth-related data here
|
||||
self.username = username
|
||||
|
||||
|
||||
def __call__(self, r):
|
||||
# modify and return the request
|
||||
r.headers['X-Pizza'] = self.username
|
||||
@@ -241,6 +258,27 @@ Then, we can make a request using our Pizza Auth::
|
||||
<Response [200]>
|
||||
|
||||
|
||||
Streaming Requests
|
||||
------------------
|
||||
|
||||
With ``requests.Response.iter_lines()`` you can easily iterate over streaming
|
||||
APIs such as the `Twitter Streaming API <https://dev.twitter.com/docs/streaming-api>`_.
|
||||
|
||||
To use the Twitter Streaming API to track the keyword "requests":
|
||||
|
||||
::
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
r = requests.post('https://stream.twitter.com/1/statuses/filter.json',
|
||||
data={'track': 'requests'}, auth=('username', 'password'))
|
||||
|
||||
for line in r.iter_lines():
|
||||
if line: # filter out keep-alive new lines
|
||||
print json.loads(line)
|
||||
|
||||
|
||||
Verbose Logging
|
||||
---------------
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ Cheeseshop Mirror
|
||||
-----------------
|
||||
|
||||
If the Cheeseshop is down, you can also install Requests from Kenneth Reitz's
|
||||
personal `Cheeseshop mirror <http://pip.kreitz.co/>`_::
|
||||
personal `Cheeseshop mirror <http://pip.kennethreitz.com/>`_::
|
||||
|
||||
$ pip install -i http://pip.kreitz.co/simple requests
|
||||
$ pip install -i http://pip.kennethreitz.com/simple requests
|
||||
|
||||
|
||||
Get the Code
|
||||
|
||||
@@ -36,16 +36,42 @@ Response Content
|
||||
|
||||
We can read the content of the server's response::
|
||||
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
Requests does its best to decode content from the server. Most unicode
|
||||
charsets, ``gzip``, and ``deflate`` encodings are all seamlessly decoded.
|
||||
Requests will automatically decode content from the server. Most unicode
|
||||
charsets are seamlessly decoded.
|
||||
|
||||
When you make a request, ``r.encoding`` is set, based on the HTTP headers.
|
||||
Requests will attempt to use that encoding when you access ``r.content``. You
|
||||
can manually set ``r.encoding`` to any encoding you'd like (including ``None``),
|
||||
and that charset will be used.
|
||||
Requests will use that encoding when you access ``r.text``. If ``r.encoding``
|
||||
is ``None``, Requests will make an extremely educated guess of the encoding
|
||||
of the response body. You can manually set ``r.encoding`` to any encoding
|
||||
you'd like, and that charset will be used.
|
||||
|
||||
|
||||
Binary Response Content
|
||||
-----------------------
|
||||
|
||||
You can also access the response body as bytes, for non-text requests::
|
||||
|
||||
>>> r.content
|
||||
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
|
||||
|
||||
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
|
||||
|
||||
|
||||
Raw Response Content
|
||||
--------------------
|
||||
|
||||
In the rare case that you'd like to get the absolute raw socket response from the server,
|
||||
you can access ``r.raw``::
|
||||
|
||||
>>> r.raw
|
||||
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
|
||||
|
||||
>>> r.raw.read(10)
|
||||
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
|
||||
|
||||
|
||||
|
||||
Make a POST Request
|
||||
@@ -62,7 +88,7 @@ dictionary of data will automatically be form-encoded when the request is made::
|
||||
|
||||
>>> payload = {'key1': 'value1', 'key2': 'value2'}
|
||||
>>> r = requests.post("http://httpbin.org/post", data=payload)
|
||||
>>> print r.content
|
||||
>>> print r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {},
|
||||
@@ -117,7 +143,7 @@ Requests makes it simple to upload Multipart-encoded files::
|
||||
>>> files = {'report.xls': open('report.xls', 'rb')}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {
|
||||
@@ -143,7 +169,7 @@ Setting filename explicitly::
|
||||
>>> files = {'file': ('report.xls', open('report.xls', 'rb'))}
|
||||
|
||||
>>> r = requests.post(url, files=files)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
{
|
||||
"origin": "179.13.100.4",
|
||||
"files": {
|
||||
@@ -253,7 +279,7 @@ parameter::
|
||||
>>> cookies = dict(cookies_are='working')
|
||||
|
||||
>>> r = requests.get(url, cookies=cookies)
|
||||
>>> r.content
|
||||
>>> r.text
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
|
||||
|
||||
@@ -300,7 +326,7 @@ establishing OAuth connections. Documentation and examples can be found on the r
|
||||
Redirection and History
|
||||
-----------------------
|
||||
|
||||
Requests will automatically perform location redirection while using impodotent methods.
|
||||
Requests will automatically perform location redirection while using idempotent methods.
|
||||
|
||||
GitHub redirects all HTTP requests to HTTPS. Let's see what happens::
|
||||
|
||||
@@ -316,9 +342,9 @@ The :class:`Response.history` list contains a list of the
|
||||
:class:`Request` objects that were created in order to complete the request.
|
||||
|
||||
If you're using GET, HEAD, or OPTIONS, you can disable redirection
|
||||
handling with the ``disable_redirects`` parameter::
|
||||
handling with the ``allow_redirects`` parameter::
|
||||
|
||||
>>> r = requests.get('http://github.com')
|
||||
>>> r = requests.get('http://github.com', allow_redirects=False)
|
||||
>>> r.status_code
|
||||
301
|
||||
>>> r.history
|
||||
@@ -345,7 +371,7 @@ You can tell requests to stop waiting for a response after a given number of sec
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
``timeout`` only effects the connection process itself, not the downloading of the respone body.
|
||||
``timeout`` only effects the connection process itself, not the downloading of the response body.
|
||||
|
||||
|
||||
Errors and Exceptions
|
||||
@@ -364,6 +390,10 @@ If a request exceeds the configured number of maximum redirections, a :class:`To
|
||||
All exceptions that Requests explicitly raises inherit from
|
||||
:class:`requests.exceptions.RequestException`.
|
||||
|
||||
You can refer to :ref:`Configuration API Docs <configurations>` for immediate raising of :class:`HTTPError` exceptions
|
||||
via the ``danger_mode`` option or have Requests catch the majority of :class:`requests.exceptions.RequestException` exceptions
|
||||
with the ``safe_mode`` option.
|
||||
|
||||
-----------------------
|
||||
|
||||
Ready for more? Check out the :ref:`advanced <advanced>` section.
|
||||
|
||||
7
reqs.txt
7
reqs.txt
@@ -1,7 +0,0 @@
|
||||
envoy==0.0.2
|
||||
httpbin==0.0.4
|
||||
gunicorn
|
||||
nose
|
||||
pyflakes
|
||||
omnijson
|
||||
rudolf
|
||||
@@ -9,17 +9,18 @@
|
||||
requests
|
||||
~~~~~~~~
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.8.3'
|
||||
__build__ = 0x000803
|
||||
__version__ = '0.10.2'
|
||||
__build__ = 0x001002
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2012 Kenneth Reitz'
|
||||
|
||||
|
||||
|
||||
from . import utils
|
||||
|
||||
@@ -6,29 +6,14 @@ requests.api
|
||||
|
||||
This module implements the Requests API.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:copyright: (c) 2012 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
from . import sessions
|
||||
|
||||
|
||||
def request(method, url,
|
||||
params=None,
|
||||
data=None,
|
||||
headers=None,
|
||||
cookies=None,
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=False,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
prefetch=False,
|
||||
session=None,
|
||||
config=None):
|
||||
def request(method, url, **kwargs):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
@@ -39,33 +24,19 @@ def request(method, url,
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload.
|
||||
:param auth: (optional) Auth typle to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
:param session: (optional) A :class:`Session` object to be used for the request.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||
"""
|
||||
|
||||
s = session or sessions.session()
|
||||
return s.request(
|
||||
method=method,
|
||||
url=url,
|
||||
params=params,
|
||||
data=data,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
files=files,
|
||||
auth=auth,
|
||||
timeout=timeout,
|
||||
allow_redirects=allow_redirects,
|
||||
proxies=proxies,
|
||||
hooks=hooks,
|
||||
return_response=return_response,
|
||||
config=config,
|
||||
prefetch=prefetch
|
||||
)
|
||||
s = kwargs.pop('session') if 'session' in kwargs else sessions.session()
|
||||
return s.request(method=method, url=url, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,20 +36,25 @@ def patched(f):
|
||||
kwargs['return_response'] = False
|
||||
kwargs['prefetch'] = True
|
||||
|
||||
config = kwargs.get('config', {})
|
||||
config.update(safe_mode=True)
|
||||
|
||||
kwargs['config'] = config
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
def send(r, pool=None, prefetch=False):
|
||||
"""Sends the request object using the specified pool. If a pool isn't
|
||||
specified this method blocks. Pools are useful because you can specify size
|
||||
and can hence limit concurrency."""
|
||||
|
||||
if pools:
|
||||
r._pools = pools
|
||||
if pool != None:
|
||||
return pool.spawn(r.send, prefetch=prefetch)
|
||||
|
||||
r.send()
|
||||
|
||||
return r.response
|
||||
return gevent.spawn(r.send, prefetch=prefetch)
|
||||
|
||||
|
||||
# Patched requests.api functions.
|
||||
@@ -73,19 +78,8 @@ def map(requests, prefetch=True, size=None):
|
||||
|
||||
requests = list(requests)
|
||||
|
||||
if size:
|
||||
pool = Pool(size)
|
||||
pool.map(send, requests)
|
||||
pool.join()
|
||||
else:
|
||||
jobs = [gevent.spawn(send, r) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
if prefetch:
|
||||
[r.response.content for r in requests]
|
||||
|
||||
return [r.response for r in requests]
|
||||
|
||||
|
||||
|
||||
pool = Pool(size) if size else None
|
||||
jobs = [send(r, pool, prefetch=prefetch) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
return [r.response for r in requests]
|
||||
@@ -11,11 +11,17 @@ import time
|
||||
import hashlib
|
||||
|
||||
from base64 import b64encode
|
||||
from urlparse import urlparse
|
||||
|
||||
from .compat import urlparse, str, bytes
|
||||
from .utils import randombytes, parse_dict_header
|
||||
|
||||
|
||||
|
||||
def _basic_auth_str(username, password):
|
||||
"""Returns a Basic Auth string."""
|
||||
|
||||
return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1')
|
||||
|
||||
|
||||
class AuthBase(object):
|
||||
"""Base class that all auth implementations derive from"""
|
||||
|
||||
@@ -26,12 +32,18 @@ class AuthBase(object):
|
||||
class HTTPBasicAuth(AuthBase):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object."""
|
||||
def __init__(self, username, password):
|
||||
self.username = str(username)
|
||||
self.password = str(password)
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def __call__(self, r):
|
||||
auth_s = b64encode('%s:%s' % (self.username, self.password))
|
||||
r.headers['Authorization'] = ('Basic %s' % auth_s)
|
||||
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
|
||||
|
||||
class HTTPProxyAuth(HTTPBasicAuth):
|
||||
"""Attaches HTTP Proxy Authenetication to a given Request object."""
|
||||
def __call__(self, r):
|
||||
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
|
||||
return r
|
||||
|
||||
|
||||
@@ -62,9 +74,17 @@ class HTTPDigestAuth(AuthBase):
|
||||
algorithm = algorithm.upper()
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if algorithm == 'MD5':
|
||||
H = lambda x: hashlib.md5(x).hexdigest()
|
||||
def h(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.md5(x).hexdigest()
|
||||
H = h
|
||||
elif algorithm == 'SHA':
|
||||
H = lambda x: hashlib.sha1(x).hexdigest()
|
||||
def h(x):
|
||||
if isinstance(x, str):
|
||||
x = x.encode('utf-8')
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
H = h
|
||||
# XXX MD5-sess
|
||||
KD = lambda s, d: H("%s:%s" % (s, d))
|
||||
|
||||
@@ -74,10 +94,12 @@ class HTTPDigestAuth(AuthBase):
|
||||
# XXX not implemented yet
|
||||
entdig = None
|
||||
p_parsed = urlparse(r.request.url)
|
||||
path = p_parsed.path + p_parsed.query
|
||||
path = p_parsed.path
|
||||
if p_parsed.query:
|
||||
path += '?' + p_parsed.query
|
||||
|
||||
A1 = "%s:%s:%s" % (self.username, realm, self.password)
|
||||
A2 = "%s:%s" % (r.request.method, path)
|
||||
A1 = '%s:%s:%s' % (self.username, realm, self.password)
|
||||
A2 = '%s:%s' % (r.request.method, path)
|
||||
|
||||
if qop == 'auth':
|
||||
if nonce == last_nonce:
|
||||
@@ -87,10 +109,12 @@ class HTTPDigestAuth(AuthBase):
|
||||
last_nonce = nonce
|
||||
|
||||
ncvalue = '%08x' % nonce_count
|
||||
cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
|
||||
nonce_count, nonce, time.ctime(), randombytes(8)))
|
||||
.hexdigest()[:16]
|
||||
)
|
||||
s = str(nonce_count).encode('utf-8')
|
||||
s += nonce.encode('utf-8')
|
||||
s += time.ctime().encode('utf-8')
|
||||
s += randombytes(8)
|
||||
|
||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
|
||||
respdig = KD(H(A1), noncebit)
|
||||
elif qop is None:
|
||||
@@ -120,5 +144,5 @@ class HTTPDigestAuth(AuthBase):
|
||||
return r
|
||||
|
||||
def __call__(self, r):
|
||||
r.hooks['response'] = self.handle_401
|
||||
r.register_hook('response', self.handle_401)
|
||||
return r
|
||||
|
||||
105
requests/compat.py
Normal file
105
requests/compat.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
pythoncompat
|
||||
"""
|
||||
|
||||
|
||||
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)
|
||||
|
||||
#: Python 3.0.x
|
||||
is_py30 = (is_py3 and _ver[1] == 0)
|
||||
|
||||
#: Python 3.1.x
|
||||
is_py31 = (is_py3 and _ver[1] == 1)
|
||||
|
||||
#: Python 3.2.x
|
||||
is_py32 = (is_py3 and _ver[1] == 2)
|
||||
|
||||
#: Python 3.3.x
|
||||
is_py33 = (is_py3 and _ver[1] == 3)
|
||||
|
||||
#: Python 3.4.x
|
||||
is_py34 = (is_py3 and _ver[1] == 4)
|
||||
|
||||
#: Python 2.7.x
|
||||
is_py27 = (is_py2 and _ver[1] == 7)
|
||||
|
||||
#: Python 2.6.x
|
||||
is_py26 = (is_py2 and _ver[1] == 6)
|
||||
|
||||
#: Python 2.5.x
|
||||
is_py25 = (is_py2 and _ver[1] == 5)
|
||||
|
||||
#: Python 2.4.x
|
||||
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
|
||||
|
||||
|
||||
# ---------
|
||||
# Platforms
|
||||
# ---------
|
||||
|
||||
|
||||
# Syntax sugar.
|
||||
_ver = sys.version.lower()
|
||||
|
||||
is_pypy = ('pypy' in _ver)
|
||||
is_jython = ('jython' in _ver)
|
||||
is_ironpython = ('iron' in _ver)
|
||||
|
||||
# Assume CPython, if nothing else.
|
||||
is_cpython = not any((is_pypy, is_jython, is_ironpython))
|
||||
|
||||
# Windows-based system.
|
||||
is_windows = 'win32' in str(sys.platform).lower()
|
||||
|
||||
# Standard Linux 2+ system.
|
||||
is_linux = ('linux' in str(sys.platform).lower())
|
||||
is_osx = ('darwin' in str(sys.platform).lower())
|
||||
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
|
||||
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
|
||||
|
||||
|
||||
# ---------
|
||||
# Specifics
|
||||
# ---------
|
||||
|
||||
|
||||
if is_py2:
|
||||
from urllib import quote, unquote, urlencode
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||
from urllib2 import parse_http_list
|
||||
import cookielib
|
||||
from .packages.oreos.monkeys import SimpleCookie
|
||||
from StringIO import StringIO
|
||||
|
||||
bytes = str
|
||||
str = unicode
|
||||
basestring = basestring
|
||||
|
||||
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
|
||||
from urllib.request import parse_http_list
|
||||
from http import cookiejar as cookielib
|
||||
from http.cookies import SimpleCookie
|
||||
from io import StringIO
|
||||
|
||||
str = str
|
||||
bytes = bytes
|
||||
basestring = (str,bytes)
|
||||
|
||||
@@ -10,15 +10,13 @@ Configurations:
|
||||
|
||||
:base_headers: Default HTTP headers.
|
||||
:verbose: Stream to write request logging to.
|
||||
:timeout: Seconds until request timeout.
|
||||
:max_redirects: Maximum njumber of redirects allowed within a request.
|
||||
:decode_unicode: Decode unicode responses automatically?
|
||||
:max_redirects: Maximum number of redirects allowed within a request.s
|
||||
:keep_alive: Reuse HTTP Connections?
|
||||
:max_retries: The number of times a request should be retried in the event of a connection failure.
|
||||
:danger_mode: If true, Requests will raise errors immediately.
|
||||
:safe_mode: If true, Requests will catch all errors.
|
||||
:pool_maxsize: The maximium size of an HTTP connection pool.
|
||||
:pool_connections: The number of active HTTP connection pools to use.
|
||||
|
||||
"""
|
||||
|
||||
from . import __version__
|
||||
@@ -34,9 +32,9 @@ defaults['base_headers'] = {
|
||||
|
||||
defaults['verbose'] = None
|
||||
defaults['max_redirects'] = 30
|
||||
defaults['decode_unicode'] = True
|
||||
defaults['pool_connections'] = 10
|
||||
defaults['pool_maxsize'] = 10
|
||||
defaults['max_retries'] = 0
|
||||
defaults['danger_mode'] = False
|
||||
defaults['safe_mode'] = False
|
||||
defaults['keep_alive'] = True
|
||||
|
||||
@@ -8,15 +8,18 @@ This module contains the set of Requests' exceptions.
|
||||
|
||||
"""
|
||||
|
||||
class RequestException(Exception):
|
||||
class RequestException(RuntimeError):
|
||||
"""There was an ambiguous exception that occurred while handling your
|
||||
request."""
|
||||
|
||||
class HTTPError(RequestException):
|
||||
"""An HTTP error occured."""
|
||||
"""An HTTP error occurred."""
|
||||
|
||||
class ConnectionError(RequestException):
|
||||
"""A Connection error occured."""
|
||||
"""A Connection error occurred."""
|
||||
|
||||
class SSLError(ConnectionError):
|
||||
"""An SSL error occurred."""
|
||||
|
||||
class Timeout(RequestException):
|
||||
"""The request timed out."""
|
||||
|
||||
@@ -22,7 +22,10 @@ Available hooks:
|
||||
|
||||
"""
|
||||
|
||||
import warnings
|
||||
import traceback
|
||||
|
||||
|
||||
HOOKS = ('args', 'pre_request', 'post_request', 'response')
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
@@ -31,10 +34,15 @@ def dispatch_hook(key, hooks, hook_data):
|
||||
hooks = hooks or dict()
|
||||
|
||||
if key in hooks:
|
||||
try:
|
||||
return hooks.get(key).__call__(hook_data) or hook_data
|
||||
hooks = hooks.get(key)
|
||||
|
||||
except Exception, why:
|
||||
warnings.warn(str(why))
|
||||
if hasattr(hooks, '__call__'):
|
||||
hooks = [hooks]
|
||||
|
||||
for hook in hooks:
|
||||
try:
|
||||
hook_data = hook(hook_data) or hook_data
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
return hook_data
|
||||
|
||||
@@ -7,28 +7,34 @@ requests.models
|
||||
This module contains the primary objects that power Requests.
|
||||
"""
|
||||
|
||||
import urllib
|
||||
import zlib
|
||||
|
||||
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from .hooks import dispatch_hook
|
||||
from .hooks import dispatch_hook, HOOKS
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .status_codes import codes
|
||||
from .packages import oreos
|
||||
from .auth import HTTPBasicAuth
|
||||
|
||||
from .auth import HTTPBasicAuth, HTTPProxyAuth
|
||||
from .packages.urllib3.response import HTTPResponse
|
||||
from .packages.urllib3.exceptions import MaxRetryError
|
||||
from .packages.urllib3.exceptions import SSLError as _SSLError
|
||||
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
||||
from .packages.urllib3 import connectionpool, poolmanager
|
||||
from .packages.urllib3.filepost import encode_multipart_formdata
|
||||
from .exceptions import (
|
||||
Timeout, URLRequired, TooManyRedirects, HTTPError, ConnectionError)
|
||||
ConnectionError, HTTPError, RequestException, Timeout, TooManyRedirects,
|
||||
URLRequired, SSLError)
|
||||
from .utils import (
|
||||
get_encoding_from_headers, stream_decode_response_unicode,
|
||||
decode_gzip, stream_decode_gzip, guess_filename, requote_path)
|
||||
get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri,
|
||||
dict_from_string)
|
||||
|
||||
from .compat import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, str, bytes, SimpleCookie, is_py3, is_py2
|
||||
|
||||
# Import chardet if it is available.
|
||||
try:
|
||||
import chardet
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
|
||||
@@ -54,7 +60,9 @@ class Request(object):
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
config=None,
|
||||
_poolmanager=None):
|
||||
_poolmanager=None,
|
||||
verify=None,
|
||||
session=None):
|
||||
|
||||
#: Float describes the timeout of the request.
|
||||
# (Use socket.setdefaulttimeout() as fallback)
|
||||
@@ -79,7 +87,6 @@ class Request(object):
|
||||
#: Dictionary or byte of querystring data to attach to the
|
||||
#: :class:`Request <Request>`.
|
||||
self.params = None
|
||||
self.params = dict(params or [])
|
||||
|
||||
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
|
||||
#: and HTTPError storage).
|
||||
@@ -111,33 +118,41 @@ class Request(object):
|
||||
self.sent = False
|
||||
|
||||
#: Event-handling hooks.
|
||||
self.hooks = hooks
|
||||
self.hooks = {}
|
||||
|
||||
for event in HOOKS:
|
||||
self.hooks[event] = []
|
||||
|
||||
hooks = hooks or {}
|
||||
|
||||
for (k, v) in list(hooks.items()):
|
||||
self.register_hook(event=k, hook=v)
|
||||
|
||||
#: Session.
|
||||
self.session = None
|
||||
self.session = session
|
||||
|
||||
#: SSL Verification.
|
||||
self.verify = verify
|
||||
|
||||
if headers:
|
||||
headers = CaseInsensitiveDict(self.headers)
|
||||
else:
|
||||
headers = CaseInsensitiveDict()
|
||||
|
||||
for (k, v) in self.config.get('base_headers', {}).items():
|
||||
# Add configured base headers.
|
||||
for (k, v) in list(self.config.get('base_headers', {}).items()):
|
||||
if k not in headers:
|
||||
headers[k] = v
|
||||
|
||||
self.headers = headers
|
||||
self._poolmanager = _poolmanager
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
|
||||
|
||||
def _build_response(self, resp, is_error=False):
|
||||
def _build_response(self, resp):
|
||||
"""Build internal :class:`Response <Response>` object
|
||||
from given response.
|
||||
"""
|
||||
@@ -151,7 +166,7 @@ class Request(object):
|
||||
|
||||
if resp:
|
||||
|
||||
# Fallback to None if there's no staus_code, for whatever reason.
|
||||
# Fallback to None if there's no status_code, for whatever reason.
|
||||
response.status_code = getattr(resp, 'status', None)
|
||||
|
||||
# Make headers case-insensitive.
|
||||
@@ -166,37 +181,41 @@ class Request(object):
|
||||
# Add new cookies from the server.
|
||||
if 'set-cookie' in response.headers:
|
||||
cookie_header = response.headers['set-cookie']
|
||||
cookies = oreos.dict_from_string(cookie_header)
|
||||
cookies = dict_from_string(cookie_header)
|
||||
|
||||
# Save cookies in Response.
|
||||
response.cookies = cookies
|
||||
|
||||
# Save original resopnse for later.
|
||||
# No exceptions were harmed in the making of this request.
|
||||
response.error = getattr(resp, 'error', None)
|
||||
|
||||
# Save original response for later.
|
||||
response.raw = resp
|
||||
|
||||
if is_error:
|
||||
response.error = resp
|
||||
|
||||
response.url = self.full_url
|
||||
if isinstance(self.full_url, bytes):
|
||||
response.url = self.full_url.decode('utf-8')
|
||||
else:
|
||||
response.url = self.full_url
|
||||
|
||||
return response
|
||||
|
||||
history = []
|
||||
|
||||
r = build(resp)
|
||||
cookies = self.cookies
|
||||
|
||||
self.cookies.update(r.cookies)
|
||||
|
||||
if r.status_code in REDIRECT_STATI and not self.redirect:
|
||||
while (('location' in r.headers) and
|
||||
((r.status_code is codes.see_other) or (self.allow_redirects))):
|
||||
|
||||
while (
|
||||
('location' in r.headers) and
|
||||
((r.status_code is codes.see_other) or (self.allow_redirects))
|
||||
):
|
||||
r.content # Consume socket so it can be released
|
||||
|
||||
if not len(history) < self.config.get('max_redirects'):
|
||||
raise TooManyRedirects()
|
||||
|
||||
# Release the connection back into the pool.
|
||||
r.raw.release_conn()
|
||||
|
||||
history.append(r)
|
||||
|
||||
url = r.headers['location']
|
||||
@@ -231,16 +250,17 @@ class Request(object):
|
||||
method=method,
|
||||
params=self.session.params,
|
||||
auth=self.auth,
|
||||
cookies=cookies,
|
||||
cookies=self.cookies,
|
||||
redirect=True,
|
||||
config=self.config,
|
||||
timeout=self.timeout,
|
||||
_poolmanager=self._poolmanager,
|
||||
proxies = self.proxies,
|
||||
verify = self.verify,
|
||||
session = self.session
|
||||
)
|
||||
|
||||
request.send()
|
||||
cookies.update(request.response.cookies)
|
||||
r = request.response
|
||||
self.cookies.update(r.cookies)
|
||||
|
||||
@@ -263,16 +283,20 @@ class Request(object):
|
||||
returns it twice.
|
||||
"""
|
||||
|
||||
if hasattr(data, '__iter__'):
|
||||
if isinstance(data, bytes):
|
||||
return data, data
|
||||
|
||||
if hasattr(data, '__iter__') and not isinstance(data, str):
|
||||
data = dict(data)
|
||||
|
||||
|
||||
if hasattr(data, 'items'):
|
||||
result = []
|
||||
for k, vs in data.items():
|
||||
for k, vs in list(data.items()):
|
||||
for v in isinstance(vs, list) and vs or [vs]:
|
||||
result.append((k.encode('utf-8') if isinstance(k, unicode) else k,
|
||||
v.encode('utf-8') if isinstance(v, unicode) else v))
|
||||
return result, urllib.urlencode(result, doseq=True)
|
||||
result.append((k.encode('utf-8') if isinstance(k, str) else k,
|
||||
v.encode('utf-8') if isinstance(v, str) else v))
|
||||
return result, urlencode(result, doseq=True)
|
||||
else:
|
||||
return data, data
|
||||
|
||||
@@ -283,28 +307,45 @@ class Request(object):
|
||||
if not self.url:
|
||||
raise URLRequired()
|
||||
|
||||
url = self.url
|
||||
|
||||
# Support for unicode domain names and paths.
|
||||
scheme, netloc, path, params, query, fragment = urlparse(self.url)
|
||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
||||
|
||||
|
||||
if not scheme:
|
||||
raise ValueError()
|
||||
raise ValueError("Invalid URL %r: No schema supplied" % url)
|
||||
|
||||
netloc = netloc.encode('idna')
|
||||
netloc = netloc.encode('idna').decode('utf-8')
|
||||
|
||||
if isinstance(path, unicode):
|
||||
path = path.encode('utf-8')
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
path = requote_path(path)
|
||||
if is_py2:
|
||||
if isinstance(scheme, str):
|
||||
scheme = scheme.encode('utf-8')
|
||||
if isinstance(netloc, str):
|
||||
netloc = netloc.encode('utf-8')
|
||||
if isinstance(path, str):
|
||||
path = path.encode('utf-8')
|
||||
if isinstance(params, str):
|
||||
params = params.encode('utf-8')
|
||||
if isinstance(query, str):
|
||||
query = query.encode('utf-8')
|
||||
if isinstance(fragment, str):
|
||||
fragment = fragment.encode('utf-8')
|
||||
|
||||
url = str(urlunparse([ scheme, netloc, path, params, query, fragment ]))
|
||||
url = (urlunparse([scheme, netloc, path, params, query, fragment]))
|
||||
|
||||
if self._enc_params:
|
||||
if urlparse(url).query:
|
||||
return '%s&%s' % (url, self._enc_params)
|
||||
url = '%s&%s' % (url, self._enc_params)
|
||||
else:
|
||||
return '%s?%s' % (url, self._enc_params)
|
||||
else:
|
||||
return url
|
||||
url = '%s?%s' % (url, self._enc_params)
|
||||
|
||||
url = requote_uri(url)
|
||||
|
||||
return url
|
||||
|
||||
@property
|
||||
def path_url(self):
|
||||
@@ -321,6 +362,7 @@ class Request(object):
|
||||
path = p.path
|
||||
if not path:
|
||||
path = '/'
|
||||
|
||||
url.append(path)
|
||||
|
||||
query = p.query
|
||||
@@ -331,6 +373,11 @@ class Request(object):
|
||||
return ''.join(url)
|
||||
|
||||
|
||||
def register_hook(self, event, hook):
|
||||
"""Properly register a hook."""
|
||||
|
||||
return self.hooks[event].append(hook)
|
||||
|
||||
|
||||
def send(self, anyway=False, prefetch=False):
|
||||
"""Sends the request. Returns True of successful, false if not.
|
||||
@@ -358,14 +405,14 @@ class Request(object):
|
||||
|
||||
# Multi-part file uploads.
|
||||
if self.files:
|
||||
if not isinstance(self.data, basestring):
|
||||
if not isinstance(self.data, str):
|
||||
|
||||
try:
|
||||
fields = self.data.copy()
|
||||
except AttributeError:
|
||||
fields = dict(self.data)
|
||||
|
||||
for (k, v) in self.files.items():
|
||||
for (k, v) in list(self.files.items()):
|
||||
# support for explicit filename
|
||||
if isinstance(v, (tuple, list)):
|
||||
fn, fp = v
|
||||
@@ -382,7 +429,7 @@ class Request(object):
|
||||
if self.data:
|
||||
|
||||
body = self._enc_data
|
||||
if isinstance(self.data, basestring):
|
||||
if isinstance(self.data, str):
|
||||
content_type = None
|
||||
else:
|
||||
content_type = 'application/x-www-form-urlencoded'
|
||||
@@ -407,6 +454,12 @@ class Request(object):
|
||||
|
||||
if proxy:
|
||||
conn = poolmanager.proxy_from_url(proxy)
|
||||
_proxy = urlparse(proxy)
|
||||
if '@' in _proxy.netloc:
|
||||
auth, url = _proxy.netloc.split('@', 1)
|
||||
self.proxy_auth = HTTPProxyAuth(*auth.split(':', 1))
|
||||
r = self.proxy_auth(self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
else:
|
||||
# Check to see if keep_alive is allowed.
|
||||
if self.config.get('keep_alive'):
|
||||
@@ -414,6 +467,33 @@ class Request(object):
|
||||
else:
|
||||
conn = connectionpool.connection_from_url(url)
|
||||
|
||||
if url.startswith('https') and self.verify:
|
||||
|
||||
cert_loc = None
|
||||
|
||||
# Allow self-specified cert location.
|
||||
if self.verify is not True:
|
||||
cert_loc = self.verify
|
||||
|
||||
|
||||
# Look for configuration.
|
||||
if not cert_loc:
|
||||
cert_loc = os.environ.get('REQUESTS_CA_BUNDLE')
|
||||
|
||||
# Curl compatiblity.
|
||||
if not cert_loc:
|
||||
cert_loc = os.environ.get('CURL_CA_BUNDLE')
|
||||
|
||||
# Use the awesome certifi list.
|
||||
if not cert_loc:
|
||||
cert_loc = __import__('certifi').where()
|
||||
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
conn.ca_certs = cert_loc
|
||||
else:
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
|
||||
if not self.sent or anyway:
|
||||
|
||||
if self.cookies:
|
||||
@@ -422,8 +502,8 @@ class Request(object):
|
||||
if 'cookie' not in self.headers:
|
||||
|
||||
# Simple cookie with our dict.
|
||||
c = oreos.monkeys.SimpleCookie()
|
||||
for (k, v) in self.cookies.items():
|
||||
c = SimpleCookie()
|
||||
for (k, v) in list(self.cookies.items()):
|
||||
c[k] = v
|
||||
|
||||
# Turn it into a header.
|
||||
@@ -432,32 +512,48 @@ class Request(object):
|
||||
# Attach Cookie header to request.
|
||||
self.headers['Cookie'] = cookie_header
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', self.hooks, self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
try:
|
||||
# Send the request.
|
||||
r = conn.urlopen(
|
||||
method=self.method,
|
||||
url=self.path_url,
|
||||
body=body,
|
||||
headers=self.headers,
|
||||
redirect=False,
|
||||
assert_same_host=False,
|
||||
preload_content=prefetch,
|
||||
decode_content=False,
|
||||
retries=self.config.get('max_retries', 0),
|
||||
timeout=self.timeout,
|
||||
)
|
||||
# The inner try .. except re-raises certain exceptions as
|
||||
# internal exception types; the outer suppresses exceptions
|
||||
# when safe mode is set.
|
||||
try:
|
||||
# Send the request.
|
||||
r = conn.urlopen(
|
||||
method=self.method,
|
||||
url=self.path_url,
|
||||
body=body,
|
||||
headers=self.headers,
|
||||
redirect=False,
|
||||
assert_same_host=False,
|
||||
preload_content=False,
|
||||
decode_content=False,
|
||||
retries=self.config.get('max_retries', 0),
|
||||
timeout=self.timeout,
|
||||
)
|
||||
self.sent = True
|
||||
|
||||
|
||||
except MaxRetryError, e:
|
||||
if not self.config.get('safe_mode', False):
|
||||
except MaxRetryError as e:
|
||||
raise ConnectionError(e)
|
||||
else:
|
||||
r = None
|
||||
|
||||
except (_SSLError, _HTTPError), e:
|
||||
if not self.config.get('safe_mode', False):
|
||||
except (_SSLError, _HTTPError) as e:
|
||||
if self.verify and isinstance(e, _SSLError):
|
||||
raise SSLError(e)
|
||||
|
||||
raise Timeout('Request timed out.')
|
||||
|
||||
except RequestException as e:
|
||||
if self.config.get('safe_mode', False):
|
||||
# In safe mode, catch the exception and attach it to
|
||||
# a blank urllib3.HTTPResponse object.
|
||||
r = HTTPResponse()
|
||||
r.error = e
|
||||
else:
|
||||
raise
|
||||
|
||||
self._build_response(r)
|
||||
|
||||
# Response manipulation hook.
|
||||
@@ -469,7 +565,11 @@ class Request(object):
|
||||
|
||||
# If prefetch is True, mark content as consumed.
|
||||
if prefetch:
|
||||
self.response._content_consumed = True
|
||||
# Save the response.
|
||||
self.response.content
|
||||
|
||||
if self.config.get('danger_mode'):
|
||||
self.response.raise_for_status()
|
||||
|
||||
return self.sent
|
||||
|
||||
@@ -524,6 +624,10 @@ class Response(object):
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
return self.ok
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
return self.ok
|
||||
@@ -537,7 +641,7 @@ class Response(object):
|
||||
return True
|
||||
|
||||
|
||||
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
|
||||
def iter_content(self, chunk_size=10 * 1024, decode_unicode=False):
|
||||
"""Iterates over the response data. This avoids reading the content
|
||||
at once into memory for large responses. The chunk size is the number
|
||||
of bytes it should read into memory. This is not necessarily the
|
||||
@@ -556,13 +660,37 @@ class Response(object):
|
||||
yield chunk
|
||||
self._content_consumed = True
|
||||
|
||||
gen = generate()
|
||||
def generate_chunked():
|
||||
resp = self.raw._original_response
|
||||
fp = resp.fp
|
||||
if resp.chunk_left is not None:
|
||||
pending_bytes = resp.chunk_left
|
||||
while pending_bytes:
|
||||
chunk = fp.read(min(chunk_size, pending_bytes))
|
||||
pending_bytes-=len(chunk)
|
||||
yield chunk
|
||||
fp.read(2) # throw away crlf
|
||||
while 1:
|
||||
#XXX correct line size? (httplib has 64kb, seems insane)
|
||||
pending_bytes = fp.readline(40).strip()
|
||||
pending_bytes = int(pending_bytes, 16)
|
||||
if pending_bytes == 0:
|
||||
break
|
||||
while pending_bytes:
|
||||
chunk = fp.read(min(chunk_size, pending_bytes))
|
||||
pending_bytes-=len(chunk)
|
||||
yield chunk
|
||||
fp.read(2) # throw away crlf
|
||||
self._content_consumed = True
|
||||
fp.close()
|
||||
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
gen = stream_decode_gzip(gen)
|
||||
|
||||
if decode_unicode is None:
|
||||
decode_unicode = self.config.get('decode_unicode')
|
||||
if getattr(getattr(self.raw, '_original_response', None), 'chunked', False):
|
||||
gen = generate_chunked()
|
||||
else:
|
||||
gen = generate()
|
||||
|
||||
gen = stream_untransfer(gen, self)
|
||||
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
@@ -570,11 +698,43 @@ class Response(object):
|
||||
return gen
|
||||
|
||||
|
||||
def iter_lines(self, chunk_size=10 * 1024, decode_unicode=None):
|
||||
"""Iterates over the response data, one line at a time. This
|
||||
avoids reading the content at once into memory for large
|
||||
responses.
|
||||
"""
|
||||
|
||||
#TODO: why rstrip by default
|
||||
pending = None
|
||||
|
||||
for chunk in self.iter_content(chunk_size, decode_unicode=decode_unicode):
|
||||
|
||||
if pending is not None:
|
||||
chunk = pending + chunk
|
||||
lines = chunk.splitlines(True)
|
||||
|
||||
for line in lines[:-1]:
|
||||
yield line.rstrip()
|
||||
|
||||
# Save the last part of the chunk for next iteration, to keep full line together
|
||||
# lines may be empty for the last chunk of a chunked response
|
||||
|
||||
if lines:
|
||||
pending = lines[-1]
|
||||
#if pending is a complete line, give it baack
|
||||
if pending[-1] == '\n':
|
||||
yield pending.rstrip()
|
||||
pending = None
|
||||
else:
|
||||
pending = None
|
||||
|
||||
# Yield the last line
|
||||
if pending is not None:
|
||||
yield pending.rstrip()
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""Content of the response, in bytes or unicode
|
||||
(if available).
|
||||
"""
|
||||
"""Content of the response, in bytes."""
|
||||
|
||||
if self._content is None:
|
||||
# Read the contents.
|
||||
@@ -583,37 +743,43 @@ class Response(object):
|
||||
raise RuntimeError(
|
||||
'The content for this response was already consumed')
|
||||
|
||||
self._content = self.raw.read()
|
||||
self._content = bytes().join(self.iter_content()) or None
|
||||
# print repr(self._content)
|
||||
except AttributeError:
|
||||
self._content = None
|
||||
|
||||
content = self._content
|
||||
|
||||
# Decode GZip'd content.
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
try:
|
||||
content = decode_gzip(self._content)
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
# Decode unicode content.
|
||||
if self.config.get('decode_unicode'):
|
||||
|
||||
# Try charset from content-type
|
||||
|
||||
if self.encoding:
|
||||
try:
|
||||
content = unicode(content, self.encoding)
|
||||
except UnicodeError:
|
||||
pass
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
content = unicode(content, self.encoding, errors='replace')
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self._content_consumed = True
|
||||
return self._content
|
||||
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Content of the response, in unicode.
|
||||
|
||||
if Response.encoding is None and chardet module is available, encoding
|
||||
will be guessed.
|
||||
"""
|
||||
|
||||
# Try charset from content-type
|
||||
content = None
|
||||
encoding = self.encoding
|
||||
|
||||
# Fallback to auto-detected encoding if chardet is available.
|
||||
if self.encoding is None:
|
||||
try:
|
||||
detected = chardet.detect(self.content) or {}
|
||||
encoding = detected.get('encoding')
|
||||
|
||||
# Trust that chardet isn't available or something went terribly wrong.
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Decode unicode from given encoding.
|
||||
try:
|
||||
content = str(self.content, encoding, errors='replace')
|
||||
except (UnicodeError, TypeError):
|
||||
pass
|
||||
|
||||
return content
|
||||
|
||||
|
||||
|
||||
@@ -249,10 +249,13 @@ class CookieError(Exception):
|
||||
# quoted with a preceeding '\' slash.
|
||||
#
|
||||
# These are taken from RFC2068 and RFC2109.
|
||||
# _RFC2965Forbidden is the list of forbidden chars we accept anyway
|
||||
# _LegalChars is the list of chars which don't require "'s
|
||||
# _Translator hash-table for fast quoting
|
||||
#
|
||||
_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~[]_"
|
||||
_RFC2965Forbidden = "[]:{}"
|
||||
_LegalChars = ( string.ascii_letters + string.digits +
|
||||
"!#$%&'*+-.^_`|~_" + _RFC2965Forbidden )
|
||||
_Translator = {
|
||||
'\000' : '\\000', '\001' : '\\001', '\002' : '\\002',
|
||||
'\003' : '\\003', '\004' : '\\004', '\005' : '\\005',
|
||||
@@ -318,7 +321,7 @@ _Translator = {
|
||||
'\375' : '\\375', '\376' : '\\376', '\377' : '\\377'
|
||||
}
|
||||
|
||||
_idmap = ''.join(chr(x) for x in xrange(256))
|
||||
_idmap = ''.join(chr(x) for x in range(256))
|
||||
|
||||
def _quote(str, LegalChars=_LegalChars,
|
||||
idmap=_idmap, translate=string.translate):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
oreos.sructures
|
||||
~~~~~~~~~~~~~~~
|
||||
oreos.structures
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The plastic blue packaging.
|
||||
|
||||
@@ -362,7 +362,7 @@ class MultiDict(TypeConversionDict):
|
||||
"""
|
||||
try:
|
||||
return dict.pop(self, key)[0]
|
||||
except KeyError, e:
|
||||
except KeyError as e:
|
||||
if default is not _missing:
|
||||
return default
|
||||
raise KeyError(str(e))
|
||||
@@ -372,7 +372,7 @@ class MultiDict(TypeConversionDict):
|
||||
try:
|
||||
item = dict.popitem(self)
|
||||
return (item[0], item[1][0])
|
||||
except KeyError, e:
|
||||
except KeyError as e:
|
||||
raise KeyError(str(e))
|
||||
|
||||
def poplist(self, key):
|
||||
@@ -389,7 +389,7 @@ class MultiDict(TypeConversionDict):
|
||||
"""Pop a ``(key, list)`` tuple from the dict."""
|
||||
try:
|
||||
return dict.popitem(self)
|
||||
except KeyError, e:
|
||||
except KeyError as e:
|
||||
raise KeyError(str(e))
|
||||
|
||||
def __copy__(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/__init__.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -10,7 +10,7 @@ urllib3 - Thread-safe connection pooling and re-using.
|
||||
|
||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||
__license__ = 'MIT'
|
||||
__version__ = '1.0.2'
|
||||
__version__ = '1.1'
|
||||
|
||||
|
||||
from .connectionpool import (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/_collections.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/connectionpool.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -7,14 +7,27 @@
|
||||
import logging
|
||||
import socket
|
||||
|
||||
|
||||
from httplib import HTTPConnection, HTTPSConnection, HTTPException
|
||||
from Queue import Queue, Empty, Full
|
||||
from select import select
|
||||
from socket import error as SocketError, timeout as SocketTimeout
|
||||
|
||||
|
||||
try:
|
||||
from select import poll, POLLIN
|
||||
except ImportError: # Doesn't exist on OSX and other platforms
|
||||
from select import select
|
||||
poll = False
|
||||
|
||||
try: # Python 3
|
||||
from http.client import HTTPConnection, HTTPSConnection, HTTPException
|
||||
from http.client import HTTP_PORT, HTTPS_PORT
|
||||
except ImportError:
|
||||
from httplib import HTTPConnection, HTTPSConnection, HTTPException
|
||||
from httplib import HTTP_PORT, HTTPS_PORT
|
||||
|
||||
try: # Python 3
|
||||
from queue import Queue, Empty, Full
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty, Full
|
||||
|
||||
try: # Compiled with SSL?
|
||||
import ssl
|
||||
BaseSSLError = ssl.SSLError
|
||||
except ImportError:
|
||||
@@ -22,21 +35,29 @@ except ImportError:
|
||||
BaseSSLError = None
|
||||
|
||||
|
||||
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||
from .request import RequestMethods
|
||||
from .response import HTTPResponse
|
||||
from .exceptions import (
|
||||
SSLError,
|
||||
from .exceptions import (SSLError,
|
||||
MaxRetryError,
|
||||
TimeoutError,
|
||||
HostChangedError,
|
||||
EmptyPoolError,
|
||||
)
|
||||
|
||||
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||
from .packages import six
|
||||
|
||||
xrange = six.moves.xrange
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_Default = object()
|
||||
|
||||
port_by_scheme = {
|
||||
'http': HTTP_PORT,
|
||||
'https': HTTPS_PORT,
|
||||
}
|
||||
|
||||
## Connection objects (extension of httplib)
|
||||
|
||||
@@ -70,7 +91,8 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||
self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
|
||||
cert_reqs=self.cert_reqs,
|
||||
ca_certs=self.ca_certs)
|
||||
|
||||
if self.ca_certs:
|
||||
match_hostname(self.sock.getpeercert(), self.host)
|
||||
|
||||
## Pool objects
|
||||
|
||||
@@ -79,7 +101,16 @@ class ConnectionPool(object):
|
||||
Base class for all connection pools, such as
|
||||
:class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`.
|
||||
"""
|
||||
pass
|
||||
|
||||
scheme = None
|
||||
|
||||
def __init__(self, host, port=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def __str__(self):
|
||||
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
||||
self.host, self.port)
|
||||
|
||||
|
||||
class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
@@ -167,14 +198,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
conn = self.pool.get(block=self.block, timeout=timeout)
|
||||
|
||||
# If this is a persistent connection, check if it got disconnected
|
||||
if conn and conn.sock and select([conn.sock], [], [], 0.0)[0]:
|
||||
# Either data is buffered (bad), or the connection is dropped.
|
||||
if conn and conn.sock and is_connection_dropped(conn):
|
||||
log.info("Resetting dropped connection: %s" % self.host)
|
||||
conn.close()
|
||||
|
||||
except Empty:
|
||||
if self.block:
|
||||
raise EmptyPoolError("Pool reached maximum size and no more "
|
||||
raise EmptyPoolError(self,
|
||||
"Pool reached maximum size and no more "
|
||||
"connections are allowed.")
|
||||
pass # Oh well, we'll create a new connection then
|
||||
|
||||
@@ -210,6 +241,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
if timeout is _Default:
|
||||
timeout = self.timeout
|
||||
|
||||
conn.timeout = timeout # This only does anything in Py26+
|
||||
|
||||
conn.request(method, url, **httplib_request_kw)
|
||||
conn.sock.settimeout(timeout)
|
||||
httplib_response = conn.getresponse()
|
||||
@@ -225,11 +258,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
def is_same_host(self, url):
|
||||
"""
|
||||
Check if the given ``url`` is a member of the same host as this
|
||||
conncetion pool.
|
||||
connection pool.
|
||||
"""
|
||||
# TODO: Add optional support for socket.gethostbyname checking.
|
||||
scheme, host, port = get_host(url)
|
||||
|
||||
if self.port and not port:
|
||||
# Use explicit default port for comparison when none is given.
|
||||
port = port_by_scheme.get(scheme)
|
||||
|
||||
return (url.startswith('/') or
|
||||
get_host(url) == (self.scheme, self.host, self.port))
|
||||
(scheme, host, port) == (self.scheme, self.host, self.port))
|
||||
|
||||
def urlopen(self, method, url, body=None, headers=None, retries=3,
|
||||
redirect=True, assert_same_host=True, timeout=_Default,
|
||||
@@ -244,6 +283,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
More commonly, it's appropriate to use a convenience method provided
|
||||
by :class:`.RequestMethods`, such as :meth:`.request`.
|
||||
|
||||
.. note::
|
||||
|
||||
`release_conn` will only behave as expected if
|
||||
`preload_content=False` because we want to make
|
||||
`preload_content=False` the default behaviour someday soon without
|
||||
breaking backwards compatibility.
|
||||
|
||||
:param method:
|
||||
HTTP request method (such as GET, POST, PUT, etc.)
|
||||
|
||||
@@ -279,10 +325,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
:param release_conn:
|
||||
If False, then the urlopen call will not release the connection
|
||||
back into the pool once a response is received. This is useful if
|
||||
you're not preloading the response's content immediately. You will
|
||||
need to call ``r.release_conn()`` on the response ``r`` to return
|
||||
the connection back into the pool. If None, it takes the value of
|
||||
back into the pool once a response is received (but will release if
|
||||
you read the entire contents of the response such as when
|
||||
`preload_content=True`). This is useful if you're not preloading
|
||||
the response's content immediately. You will need to call
|
||||
``r.release_conn()`` on the response ``r`` to return the connection
|
||||
back into the pool. If None, it takes the value of
|
||||
``response_kw.get('preload_content', True)``.
|
||||
|
||||
:param \**response_kw:
|
||||
@@ -293,7 +341,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
headers = self.headers
|
||||
|
||||
if retries < 0:
|
||||
raise MaxRetryError("Max retries exceeded for url: %s" % url)
|
||||
raise MaxRetryError(self, url)
|
||||
|
||||
if timeout is _Default:
|
||||
timeout = self.timeout
|
||||
|
||||
if release_conn is None:
|
||||
release_conn = response_kw.get('preload_content', True)
|
||||
@@ -304,8 +355,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
if self.port:
|
||||
host = "%s:%d" % (host, self.port)
|
||||
|
||||
raise HostChangedError("Connection pool with host '%s' tried to "
|
||||
"open a foreign host: %s" % (host, url))
|
||||
raise HostChangedError(self, url, retries - 1)
|
||||
|
||||
conn = None
|
||||
|
||||
@@ -336,18 +386,29 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
# ``response.release_conn()`` is called (implicitly by
|
||||
# ``response.read()``)
|
||||
|
||||
except (SocketTimeout, Empty), e:
|
||||
# Timed out either by socket or queue
|
||||
raise TimeoutError("Request timed out after %s seconds" %
|
||||
self.timeout)
|
||||
except Empty as e:
|
||||
# Timed out by queue
|
||||
raise TimeoutError(self, "Request timed out. (pool_timeout=%s)" %
|
||||
pool_timeout)
|
||||
|
||||
except (BaseSSLError), e:
|
||||
except SocketTimeout as e:
|
||||
# Timed out by socket
|
||||
raise TimeoutError(self, "Request timed out. (timeout=%s)" %
|
||||
timeout)
|
||||
|
||||
except BaseSSLError as e:
|
||||
# SSL certificate error
|
||||
raise SSLError(e)
|
||||
|
||||
except (HTTPException, SocketError), e:
|
||||
except CertificateError as e:
|
||||
# Name mismatch
|
||||
raise SSLError(e)
|
||||
|
||||
except (HTTPException, SocketError) as e:
|
||||
# Connection broken, discard. It will be replaced next _get_conn().
|
||||
conn = None
|
||||
# This is necessary so we can access e below
|
||||
err = e
|
||||
|
||||
finally:
|
||||
if conn and release_conn:
|
||||
@@ -356,19 +417,16 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||
|
||||
if not conn:
|
||||
log.warn("Retrying (%d attempts remain) after connection "
|
||||
"broken by '%r': %s" % (retries, e, url))
|
||||
"broken by '%r': %s" % (retries, err, url))
|
||||
return self.urlopen(method, url, body, headers, retries - 1,
|
||||
redirect, assert_same_host) # Try again
|
||||
|
||||
# Handle redirection
|
||||
if (redirect and
|
||||
response.status in [301, 302, 303, 307] and
|
||||
'location' in response.headers): # Redirect, retry
|
||||
log.info("Redirecting %s -> %s" %
|
||||
(url, response.headers.get('location')))
|
||||
return self.urlopen(method, response.headers.get('location'), body,
|
||||
headers, retries - 1, redirect,
|
||||
assert_same_host)
|
||||
# Handle redirect?
|
||||
redirect_location = redirect and response.get_redirect_location()
|
||||
if redirect_location:
|
||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
||||
return self.urlopen(method, redirect_location, body, headers,
|
||||
retries - 1, redirect, assert_same_host)
|
||||
|
||||
return response
|
||||
|
||||
@@ -488,10 +546,12 @@ def get_host(url):
|
||||
# simplified for our needs.
|
||||
port = None
|
||||
scheme = 'http'
|
||||
if '//' in url:
|
||||
if '://' in url:
|
||||
scheme, url = url.split('://', 1)
|
||||
if '/' in url:
|
||||
url, _path = url.split('/', 1)
|
||||
if '@' in url:
|
||||
_auth, url = url.split('@', 1)
|
||||
if ':' in url:
|
||||
url, port = url.split(':', 1)
|
||||
port = int(port)
|
||||
@@ -523,3 +583,22 @@ def connection_from_url(url, **kw):
|
||||
return HTTPSConnectionPool(host, port=port, **kw)
|
||||
else:
|
||||
return HTTPConnectionPool(host, port=port, **kw)
|
||||
|
||||
|
||||
def is_connection_dropped(conn):
|
||||
"""
|
||||
Returns True if the connection is dropped and should be closed.
|
||||
|
||||
:param conn:
|
||||
``HTTPConnection`` object.
|
||||
"""
|
||||
if not poll:
|
||||
return select([conn.sock], [], [], 0.0)[0]
|
||||
|
||||
# This version is better on platforms that support it.
|
||||
p = poll()
|
||||
p.register(conn.sock, POLLIN)
|
||||
for (fno, ev) in p.poll(0.0):
|
||||
if fno == conn.sock.fileno():
|
||||
# Either data is buffered (bad), or the connection is dropped.
|
||||
return True
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/contrib/ntlmpool.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -10,7 +10,10 @@ NTLM authenticating pool, contributed by erikcederstran
|
||||
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
|
||||
"""
|
||||
|
||||
import httplib
|
||||
try:
|
||||
from http.client import HTTPSConnection
|
||||
except ImportError:
|
||||
from httplib import HTTPSConnection
|
||||
from logging import getLogger
|
||||
from ntlm import ntlm
|
||||
|
||||
@@ -53,7 +56,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
|
||||
req_header = 'Authorization'
|
||||
resp_header = 'www-authenticate'
|
||||
|
||||
conn = httplib.HTTPSConnection(host=self.host, port=self.port)
|
||||
conn = HTTPSConnection(host=self.host, port=self.port)
|
||||
|
||||
# Send negotiation message
|
||||
headers[req_header] = (
|
||||
|
||||
@@ -1,35 +1,54 @@
|
||||
# urllib3/exceptions.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
## Exceptions
|
||||
## Base Exceptions
|
||||
|
||||
class HTTPError(Exception):
|
||||
"Base exception used by this module."
|
||||
pass
|
||||
|
||||
|
||||
class SSLError(Exception):
|
||||
class PoolError(HTTPError):
|
||||
"Base exception for errors caused within a pool."
|
||||
def __init__(self, pool, message):
|
||||
self.pool = pool
|
||||
HTTPError.__init__(self, "%s: %s" % (pool, message))
|
||||
|
||||
|
||||
class SSLError(HTTPError):
|
||||
"Raised when SSL certificate fails in an HTTPS connection."
|
||||
pass
|
||||
|
||||
|
||||
class MaxRetryError(HTTPError):
|
||||
## Leaf Exceptions
|
||||
|
||||
class MaxRetryError(PoolError):
|
||||
"Raised when the maximum number of retries is exceeded."
|
||||
pass
|
||||
def __init__(self, pool, url):
|
||||
PoolError.__init__(self, pool,
|
||||
"Max retries exceeded with url: %s" % url)
|
||||
|
||||
self.url = url
|
||||
|
||||
|
||||
class TimeoutError(HTTPError):
|
||||
class HostChangedError(PoolError):
|
||||
"Raised when an existing pool gets a request for a foreign host."
|
||||
def __init__(self, pool, url, retries=3):
|
||||
PoolError.__init__(self, pool,
|
||||
"Tried to open a foreign host with url: %s" % url)
|
||||
|
||||
self.url = url
|
||||
self.retries = retries
|
||||
|
||||
|
||||
class TimeoutError(PoolError):
|
||||
"Raised when a socket timeout occurs."
|
||||
pass
|
||||
|
||||
|
||||
class HostChangedError(HTTPError):
|
||||
"Raised when an existing pool gets a request for a foreign host."
|
||||
pass
|
||||
|
||||
class EmptyPoolError(HTTPError):
|
||||
class EmptyPoolError(PoolError):
|
||||
"Raised when a pool runs out of connections and no more are allowed."
|
||||
pass
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# urllib3/filepost.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import codecs
|
||||
import mimetools
|
||||
import mimetypes
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
from mimetools import choose_boundary
|
||||
except ImportError:
|
||||
from StringIO import StringIO # pylint: disable-msg=W0404
|
||||
from .packages.mimetools_choose_boundary import choose_boundary
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .packages import six
|
||||
from .packages.six import b
|
||||
|
||||
writer = codecs.lookup('utf-8')[3]
|
||||
|
||||
@@ -35,37 +38,37 @@ def encode_multipart_formdata(fields, boundary=None):
|
||||
If not specified, then a random boundary will be generated using
|
||||
:func:`mimetools.choose_boundary`.
|
||||
"""
|
||||
body = StringIO()
|
||||
body = BytesIO()
|
||||
if boundary is None:
|
||||
boundary = mimetools.choose_boundary()
|
||||
boundary = choose_boundary()
|
||||
|
||||
for fieldname, value in fields.iteritems():
|
||||
body.write('--%s\r\n' % (boundary))
|
||||
for fieldname, value in six.iteritems(fields):
|
||||
body.write(b('--%s\r\n' % (boundary)))
|
||||
|
||||
if isinstance(value, tuple):
|
||||
filename, data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"; '
|
||||
'filename="%s"\r\n' % (fieldname, filename))
|
||||
body.write('Content-Type: %s\r\n\r\n' %
|
||||
(get_content_type(filename)))
|
||||
body.write(b('Content-Type: %s\r\n\r\n' %
|
||||
(get_content_type(filename))))
|
||||
else:
|
||||
data = value
|
||||
writer(body).write('Content-Disposition: form-data; name="%s"\r\n'
|
||||
% (fieldname))
|
||||
body.write('Content-Type: text/plain\r\n\r\n')
|
||||
body.write(b'Content-Type: text/plain\r\n\r\n')
|
||||
|
||||
if isinstance(data, int):
|
||||
data = str(data) # Backwards compatibility
|
||||
|
||||
if isinstance(data, unicode):
|
||||
if isinstance(data, six.text_type):
|
||||
writer(body).write(data)
|
||||
else:
|
||||
body.write(data)
|
||||
|
||||
body.write('\r\n')
|
||||
body.write(b'\r\n')
|
||||
|
||||
body.write('--%s--\r\n' % (boundary))
|
||||
body.write(b('--%s--\r\n' % (boundary)))
|
||||
|
||||
content_type = 'multipart/form-data; boundary=%s' % boundary
|
||||
content_type = b('multipart/form-data; boundary=%s' % boundary)
|
||||
|
||||
return body.getvalue(), content_type
|
||||
|
||||
4
requests/packages/urllib3/packages/__init__.py
Normal file
4
requests/packages/urllib3/packages/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from . import ssl_match_hostname
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""The function mimetools.choose_boundary() from Python 2.7, which seems to
|
||||
have disappeared in Python 3 (although email.generator._make_boundary() might
|
||||
work as a replacement?).
|
||||
|
||||
Tweaked to use lock from threading rather than thread.
|
||||
"""
|
||||
import os
|
||||
from threading import Lock
|
||||
_counter_lock = Lock()
|
||||
|
||||
_counter = 0
|
||||
def _get_next_counter():
|
||||
global _counter
|
||||
with _counter_lock:
|
||||
_counter += 1
|
||||
return _counter
|
||||
|
||||
_prefix = None
|
||||
|
||||
def choose_boundary():
|
||||
"""Return a string usable as a multipart boundary.
|
||||
|
||||
The string chosen is unique within a single program run, and
|
||||
incorporates the user id (if available), process id (if available),
|
||||
and current time. So it's very unlikely the returned string appears
|
||||
in message text, but there's no guarantee.
|
||||
|
||||
The boundary contains dots so you have to quote it in the header."""
|
||||
|
||||
global _prefix
|
||||
import time
|
||||
if _prefix is None:
|
||||
import socket
|
||||
try:
|
||||
hostid = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
hostid = '127.0.0.1'
|
||||
try:
|
||||
uid = repr(os.getuid())
|
||||
except AttributeError:
|
||||
uid = '1'
|
||||
try:
|
||||
pid = repr(os.getpid())
|
||||
except AttributeError:
|
||||
pid = '1'
|
||||
_prefix = hostid + '.' + uid + '.' + pid
|
||||
return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
|
||||
372
requests/packages/urllib3/packages/six.py
Normal file
372
requests/packages/urllib3/packages/six.py
Normal file
@@ -0,0 +1,372 @@
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
#Copyright (c) 2010-2011 Benjamin Peterson
|
||||
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
#this software and associated documentation files (the "Software"), to deal in
|
||||
#the Software without restriction, including without limitation the rights to
|
||||
#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
#the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
#subject to the following conditions:
|
||||
|
||||
#The above copyright notice and this permission notice shall be included in all
|
||||
#copies or substantial portions of the Software.
|
||||
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.1.0"
|
||||
|
||||
|
||||
# True if we are running on Python 3.
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result)
|
||||
# This is a bit ugly, but it avoids running this again.
|
||||
delattr(tp, self.name)
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
|
||||
class _MovedItems(types.ModuleType):
|
||||
"""Lazy loading of moved objects"""
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
del attr
|
||||
|
||||
moves = sys.modules["six.moves"] = _MovedItems("moves")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
|
||||
_iterkeys = "keys"
|
||||
_itervalues = "values"
|
||||
_iteritems = "items"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
|
||||
_iterkeys = "iterkeys"
|
||||
_itervalues = "itervalues"
|
||||
_iteritems = "iteritems"
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
|
||||
advance_iterator = next
|
||||
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
|
||||
|
||||
def iterkeys(d):
|
||||
"""Return an iterator over the keys of a dictionary."""
|
||||
return getattr(d, _iterkeys)()
|
||||
|
||||
def itervalues(d):
|
||||
"""Return an iterator over the values of a dictionary."""
|
||||
return getattr(d, _itervalues)()
|
||||
|
||||
def iteritems(d):
|
||||
"""Return an iterator over the (key, value) pairs of a dictionary."""
|
||||
return getattr(d, _iteritems)()
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
def u(s):
|
||||
return s
|
||||
if sys.version_info[1] <= 1:
|
||||
def int2byte(i):
|
||||
return bytes((i,))
|
||||
else:
|
||||
# This is about 2x faster than the implementation above on 3.2+
|
||||
int2byte = operator.methodcaller("to_bytes", 1, "big")
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
int2byte = chr
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
if PY3:
|
||||
import builtins
|
||||
exec_ = getattr(builtins, "exec")
|
||||
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(builtins, "print")
|
||||
del builtins
|
||||
|
||||
else:
|
||||
def exec_(code, globs=None, locs=None):
|
||||
"""Execute code in a namespace."""
|
||||
if globs is None:
|
||||
frame = sys._getframe(1)
|
||||
globs = frame.f_globals
|
||||
if locs is None:
|
||||
locs = frame.f_locals
|
||||
del frame
|
||||
elif locs is None:
|
||||
locs = globs
|
||||
exec("""exec code in globs, locs""")
|
||||
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
raise tp, value, tb
|
||||
""")
|
||||
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
|
||||
def with_metaclass(meta, base=object):
|
||||
"""Create a base class with a metaclass."""
|
||||
return meta("NewBase", (base,), {})
|
||||
@@ -0,0 +1,61 @@
|
||||
"""The match_hostname() function from Python 3.2, essential when using SSL."""
|
||||
|
||||
import re
|
||||
|
||||
__version__ = '3.2.2'
|
||||
|
||||
class CertificateError(ValueError):
|
||||
pass
|
||||
|
||||
def _dnsname_to_pat(dn):
|
||||
pats = []
|
||||
for frag in dn.split(r'.'):
|
||||
if frag == '*':
|
||||
# When '*' is a fragment by itself, it matches a non-empty dotless
|
||||
# fragment.
|
||||
pats.append('[^.]+')
|
||||
else:
|
||||
# Otherwise, '*' matches any dotless fragment.
|
||||
frag = re.escape(frag)
|
||||
pats.append(frag.replace(r'\*', '[^.]*'))
|
||||
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
|
||||
def match_hostname(cert, hostname):
|
||||
"""Verify that *cert* (in decoded format as returned by
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
|
||||
are mostly followed, but IP addresses are not accepted for *hostname*.
|
||||
|
||||
CertificateError is raised on failure. On success, the function
|
||||
returns nothing.
|
||||
"""
|
||||
if not cert:
|
||||
raise ValueError("empty or no certificate")
|
||||
dnsnames = []
|
||||
san = cert.get('subjectAltName', ())
|
||||
for key, value in san:
|
||||
if key == 'DNS':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if not dnsnames:
|
||||
# The subject is only checked when there is no dNSName entry
|
||||
# in subjectAltName
|
||||
for sub in cert.get('subject', ()):
|
||||
for key, value in sub:
|
||||
# XXX according to RFC 2818, the most specific Common Name
|
||||
# must be used.
|
||||
if key == 'commonName':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match either of %s"
|
||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
||||
elif len(dnsnames) == 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match %r"
|
||||
% (hostname, dnsnames[0]))
|
||||
else:
|
||||
raise CertificateError("no appropriate commonName or "
|
||||
"subjectAltName fields were found")
|
||||
@@ -1,32 +1,27 @@
|
||||
# urllib3/poolmanager.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import logging
|
||||
|
||||
from ._collections import RecentlyUsedContainer
|
||||
from .connectionpool import (
|
||||
HTTPConnectionPool, HTTPSConnectionPool,
|
||||
get_host, connection_from_url,
|
||||
)
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
from .connectionpool import get_host, connection_from_url, port_by_scheme
|
||||
from .exceptions import HostChangedError
|
||||
from .request import RequestMethods
|
||||
|
||||
|
||||
__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url']
|
||||
|
||||
|
||||
from .request import RequestMethods
|
||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||
|
||||
|
||||
pool_classes_by_scheme = {
|
||||
'http': HTTPConnectionPool,
|
||||
'https': HTTPSConnectionPool,
|
||||
}
|
||||
|
||||
port_by_scheme = {
|
||||
'http': 80,
|
||||
'https': 443,
|
||||
}
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PoolManager(RequestMethods):
|
||||
@@ -105,7 +100,12 @@ class PoolManager(RequestMethods):
|
||||
:class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
|
||||
"""
|
||||
conn = self.connection_from_url(url)
|
||||
return conn.urlopen(method, url, assert_same_host=False, **kw)
|
||||
try:
|
||||
return conn.urlopen(method, url, **kw)
|
||||
|
||||
except HostChangedError as e:
|
||||
kw['retries'] = e.retries # Persist retries countdown
|
||||
return self.urlopen(method, e.url, **kw)
|
||||
|
||||
|
||||
class ProxyManager(RequestMethods):
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# urllib3/request.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
from urllib import urlencode
|
||||
try:
|
||||
from urllib.parse import urlencode
|
||||
except ImportError:
|
||||
from urllib import urlencode
|
||||
|
||||
from .filepost import encode_multipart_formdata
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# urllib3/response.py
|
||||
# Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
# Copyright 2008-2012 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
#
|
||||
# This module is part of urllib3 and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -8,21 +8,22 @@ import gzip
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO # pylint: disable-msg=W0404
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .exceptions import HTTPError
|
||||
|
||||
|
||||
try:
|
||||
basestring = basestring
|
||||
except NameError: # Python 3
|
||||
basestring = (str, bytes)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def decode_gzip(data):
|
||||
gzipper = gzip.GzipFile(fileobj=StringIO(data))
|
||||
gzipper = gzip.GzipFile(fileobj=BytesIO(data))
|
||||
return gzipper.read()
|
||||
|
||||
|
||||
@@ -71,7 +72,7 @@ class HTTPResponse(object):
|
||||
self.strict = strict
|
||||
|
||||
self._decode_content = decode_content
|
||||
self._body = None
|
||||
self._body = body if body and isinstance(body, basestring) else None
|
||||
self._fp = None
|
||||
self._original_response = original_response
|
||||
|
||||
@@ -81,9 +82,22 @@ class HTTPResponse(object):
|
||||
if hasattr(body, 'read'):
|
||||
self._fp = body
|
||||
|
||||
if preload_content:
|
||||
if preload_content and not self._body:
|
||||
self._body = self.read(decode_content=decode_content)
|
||||
|
||||
def get_redirect_location(self):
|
||||
"""
|
||||
Should we redirect and where to?
|
||||
|
||||
:returns: Truthy redirect location string if we got a redirect status
|
||||
code and valid location. ``None`` if redirect status and no
|
||||
location. ``False`` if not a redirect status code.
|
||||
"""
|
||||
if self.status in [301, 302, 303, 307]:
|
||||
return self.headers.get('location')
|
||||
|
||||
return False
|
||||
|
||||
def release_conn(self):
|
||||
if not self._pool or not self._connection:
|
||||
return
|
||||
@@ -98,10 +112,9 @@ class HTTPResponse(object):
|
||||
return self._body
|
||||
|
||||
if self._fp:
|
||||
return self.read(decode_content=self._decode_content,
|
||||
cache_content=True)
|
||||
return self.read(cache_content=True)
|
||||
|
||||
def read(self, amt=None, decode_content=True, cache_content=False):
|
||||
def read(self, amt=None, decode_content=None, cache_content=False):
|
||||
"""
|
||||
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
|
||||
parameters: ``decode_content`` and ``cache_content``.
|
||||
@@ -124,22 +137,22 @@ class HTTPResponse(object):
|
||||
"""
|
||||
content_encoding = self.headers.get('content-encoding')
|
||||
decoder = self.CONTENT_DECODERS.get(content_encoding)
|
||||
if decode_content is None:
|
||||
decode_content = self._decode_content
|
||||
|
||||
data = self._fp and self._fp.read(amt)
|
||||
if self._fp is None:
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
if amt:
|
||||
return data
|
||||
|
||||
if not decode_content or not decoder:
|
||||
if cache_content:
|
||||
self._body = data
|
||||
|
||||
return data
|
||||
if amt is None:
|
||||
# cStringIO doesn't like amt=None
|
||||
data = self._fp.read()
|
||||
else:
|
||||
return self._fp.read(amt)
|
||||
|
||||
try:
|
||||
data = decoder(data)
|
||||
if decode_content and decoder:
|
||||
data = decoder(data)
|
||||
except IOError:
|
||||
raise HTTPError("Received response with content-encoding: %s, but "
|
||||
"failed to decode it." % content_encoding)
|
||||
@@ -150,12 +163,11 @@ class HTTPResponse(object):
|
||||
return data
|
||||
|
||||
finally:
|
||||
|
||||
if self._original_response and self._original_response.isclosed():
|
||||
self.release_conn()
|
||||
|
||||
@staticmethod
|
||||
def from_httplib(r, **response_kw):
|
||||
@classmethod
|
||||
def from_httplib(ResponseCls, r, **response_kw):
|
||||
"""
|
||||
Given an :class:`httplib.HTTPResponse` instance ``r``, return a
|
||||
corresponding :class:`urllib3.response.HTTPResponse` object.
|
||||
@@ -164,14 +176,17 @@ class HTTPResponse(object):
|
||||
with ``original_response=r``.
|
||||
"""
|
||||
|
||||
return HTTPResponse(body=r,
|
||||
headers=dict(r.getheaders()),
|
||||
status=r.status,
|
||||
version=r.version,
|
||||
reason=r.reason,
|
||||
strict=r.strict,
|
||||
original_response=r,
|
||||
**response_kw)
|
||||
# HTTPResponse objects in Python 3 don't have a .strict attribute
|
||||
strict = getattr(r, 'strict', 0)
|
||||
return ResponseCls(body=r,
|
||||
# In Python 3, the header keys are returned capitalised
|
||||
headers=dict((k.lower(), v) for k,v in r.getheaders()),
|
||||
status=r.status,
|
||||
version=r.version,
|
||||
reason=r.reason,
|
||||
strict=strict,
|
||||
original_response=r,
|
||||
**response_kw)
|
||||
|
||||
# Backwards-compatibility methods for httplib.HTTPResponse
|
||||
def getheaders(self):
|
||||
|
||||
@@ -25,7 +25,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
|
||||
if isinstance(local_kwarg, basestring):
|
||||
if isinstance(local_kwarg, str):
|
||||
return local_kwarg
|
||||
|
||||
if local_kwarg is None:
|
||||
@@ -40,7 +40,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
kwargs.update(local_kwarg)
|
||||
|
||||
# Remove keys that are set to None.
|
||||
for (k,v) in local_kwarg.items():
|
||||
for (k,v) in list(local_kwarg.items()):
|
||||
if v is None:
|
||||
del kwargs[k]
|
||||
|
||||
@@ -63,7 +63,8 @@ class Session(object):
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
params=None,
|
||||
config=None):
|
||||
config=None,
|
||||
verify=True):
|
||||
|
||||
self.headers = headers or {}
|
||||
self.cookies = cookies or {}
|
||||
@@ -73,8 +74,9 @@ class Session(object):
|
||||
self.hooks = hooks or {}
|
||||
self.params = params or {}
|
||||
self.config = config or {}
|
||||
self.verify = verify
|
||||
|
||||
for (k, v) in defaults.items():
|
||||
for (k, v) in list(defaults.items()):
|
||||
self.config.setdefault(k, v)
|
||||
|
||||
self.poolmanager = PoolManager(
|
||||
@@ -111,7 +113,8 @@ class Session(object):
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
config=None,
|
||||
prefetch=False):
|
||||
prefetch=False,
|
||||
verify=None):
|
||||
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
@@ -123,13 +126,14 @@ class Session(object):
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) Auth typle to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
:param prefetch: (optional) if ``True``, the response content will be immediately downloaded.
|
||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||
"""
|
||||
|
||||
method = str(method).upper()
|
||||
@@ -141,13 +145,17 @@ class Session(object):
|
||||
headers = {} if headers is None else headers
|
||||
params = {} if params is None else params
|
||||
hooks = {} if hooks is None else hooks
|
||||
|
||||
if verify is None:
|
||||
verify = self.verify
|
||||
|
||||
# use session's hooks as defaults
|
||||
for key, cb in self.hooks.iteritems():
|
||||
for key, cb in list(self.hooks.items()):
|
||||
hooks.setdefault(key, cb)
|
||||
|
||||
# Expand header values.
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
for k, v in list(headers.items()) or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
args = dict(
|
||||
@@ -164,6 +172,7 @@ class Session(object):
|
||||
allow_redirects=allow_redirects,
|
||||
proxies=proxies,
|
||||
config=config,
|
||||
verify=verify,
|
||||
_poolmanager=self.poolmanager
|
||||
)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ _codes = {
|
||||
|
||||
codes = LookupDict(name='status_codes')
|
||||
|
||||
for (code, titles) in _codes.items():
|
||||
for (code, titles) in list(_codes.items()):
|
||||
for title in titles:
|
||||
setattr(codes, title, code)
|
||||
if not title.startswith('\\'):
|
||||
|
||||
@@ -18,7 +18,7 @@ class CaseInsensitiveDict(dict):
|
||||
@property
|
||||
def lower_keys(self):
|
||||
if not hasattr(self, '_lower_keys') or not self._lower_keys:
|
||||
self._lower_keys = dict((k.lower(), k) for k in self.iterkeys())
|
||||
self._lower_keys = dict((k.lower(), k) for k in list(self.keys()))
|
||||
return self._lower_keys
|
||||
|
||||
def _clear_lower_keys(self):
|
||||
@@ -63,4 +63,4 @@ class LookupDict(dict):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__dict__.get(key, default)
|
||||
return self.__dict__.get(key, default)
|
||||
|
||||
@@ -4,23 +4,36 @@
|
||||
requests.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module provides utlity functions that are used within Requests
|
||||
This module provides utility functions that are used within Requests
|
||||
that are also useful for external consumption.
|
||||
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import cookielib
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import zlib
|
||||
import urllib
|
||||
|
||||
from urllib2 import parse_http_list as _parse_list_header
|
||||
from .compat import parse_http_list as _parse_list_header
|
||||
from .compat import quote, unquote, cookielib, SimpleCookie, is_py2
|
||||
from .compat import basestring, bytes
|
||||
|
||||
|
||||
def dict_from_string(s):
|
||||
"""Returns a MultiDict with Cookies."""
|
||||
|
||||
cookies = dict()
|
||||
|
||||
c = SimpleCookie()
|
||||
c.load(s)
|
||||
|
||||
for k,v in list(c.items()):
|
||||
cookies.update({k: v.value})
|
||||
|
||||
return cookies
|
||||
|
||||
def guess_filename(obj):
|
||||
"""Tries to guess the filename of the given object."""
|
||||
name = getattr(obj, 'name', None)
|
||||
@@ -132,16 +145,20 @@ def header_expand(headers):
|
||||
collector = []
|
||||
|
||||
if isinstance(headers, dict):
|
||||
headers = headers.items()
|
||||
|
||||
headers = list(headers.items())
|
||||
elif isinstance(headers, basestring):
|
||||
return headers
|
||||
elif isinstance(headers, unicode):
|
||||
# As discussed in https://github.com/kennethreitz/requests/issues/400
|
||||
# latin-1 is the most conservative encoding used on the web. Anyone
|
||||
# who needs more can encode to a byte-string before calling
|
||||
return headers.encode("latin-1")
|
||||
|
||||
for i, (value, params) in enumerate(headers):
|
||||
|
||||
_params = []
|
||||
|
||||
for (p_k, p_v) in params.items():
|
||||
for (p_k, p_v) in list(params.items()):
|
||||
|
||||
_params.append('%s=%s' % (p_k, p_v))
|
||||
|
||||
@@ -166,17 +183,11 @@ def header_expand(headers):
|
||||
|
||||
def randombytes(n):
|
||||
"""Return n random bytes."""
|
||||
# Use /dev/urandom if it is available. Fall back to random module
|
||||
# if not. It might be worthwhile to extend this function to use
|
||||
# other platform-specific mechanisms for getting random bytes.
|
||||
if os.path.exists("/dev/urandom"):
|
||||
f = open("/dev/urandom")
|
||||
s = f.read(n)
|
||||
f.close()
|
||||
return s
|
||||
else:
|
||||
if is_py2:
|
||||
L = [chr(random.randrange(0, 256)) for i in range(n)]
|
||||
return "".join(L)
|
||||
else:
|
||||
L = [chr(random.randrange(0, 256)).encode('utf-8') for i in range(n)]
|
||||
return b"".join(L)
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
@@ -187,9 +198,9 @@ def dict_from_cookiejar(cj):
|
||||
|
||||
cookie_dict = {}
|
||||
|
||||
for _, cookies in cj._cookies.items():
|
||||
for _, cookies in cookies.items():
|
||||
for cookie in cookies.values():
|
||||
for _, cookies in list(cj._cookies.items()):
|
||||
for _, cookies in list(cookies.items()):
|
||||
for cookie in list(cookies.values()):
|
||||
# print cookie
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
|
||||
@@ -221,7 +232,7 @@ def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
for k, v in cookie_dict.items():
|
||||
for k, v in list(cookie_dict.items()):
|
||||
|
||||
cookie = cookielib.Cookie(
|
||||
version=0,
|
||||
@@ -276,22 +287,8 @@ def get_encoding_from_headers(headers):
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
|
||||
|
||||
def unicode_from_html(content):
|
||||
"""Attempts to decode an HTML string into unicode.
|
||||
If unsuccessful, the original content is returned.
|
||||
"""
|
||||
|
||||
encodings = get_encodings_from_content(content)
|
||||
|
||||
for encoding in encodings:
|
||||
|
||||
try:
|
||||
return unicode(content, encoding)
|
||||
except (UnicodeError, TypeError):
|
||||
pass
|
||||
|
||||
return content
|
||||
if 'text' in content_type:
|
||||
return 'ISO-8859-1'
|
||||
|
||||
|
||||
def stream_decode_response_unicode(iterator, r):
|
||||
@@ -334,48 +331,88 @@ def get_unicode_from_response(r):
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
return unicode(r.content, encoding)
|
||||
return str(r.content, encoding)
|
||||
except UnicodeError:
|
||||
tried_encodings.append(encoding)
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
return unicode(r.content, encoding, errors='replace')
|
||||
return str(r.content, encoding, errors='replace')
|
||||
except TypeError:
|
||||
return r.content
|
||||
|
||||
def stream_decompress(iterator, mode='gzip'):
|
||||
"""
|
||||
Stream decodes an iterator over compressed data
|
||||
|
||||
def decode_gzip(content):
|
||||
"""Return gzip-decoded string.
|
||||
|
||||
:param content: bytestring to gzip-decode.
|
||||
:param iterator: An iterator over compressed data
|
||||
:param mode: 'gzip' or 'deflate'
|
||||
:return: An iterator over decompressed data
|
||||
"""
|
||||
|
||||
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
|
||||
if mode not in ['gzip', 'deflate']:
|
||||
raise ValueError('stream_decompress mode must be gzip or deflate')
|
||||
|
||||
|
||||
def stream_decode_gzip(iterator):
|
||||
"""Stream decodes a gzip-encoded iterator"""
|
||||
zlib_mode = 16 + zlib.MAX_WBITS if mode == 'gzip' else -zlib.MAX_WBITS
|
||||
dec = zlib.decompressobj(zlib_mode)
|
||||
try:
|
||||
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
for chunk in iterator:
|
||||
rv = dec.decompress(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
buf = dec.decompress('')
|
||||
except zlib.error:
|
||||
# If there was an error decompressing, just return the raw chunk
|
||||
yield chunk
|
||||
# Continue to return the rest of the raw data
|
||||
for chunk in iterator:
|
||||
yield chunk
|
||||
else:
|
||||
# Make sure everything has been returned from the decompression object
|
||||
buf = dec.decompress(bytes())
|
||||
rv = buf + dec.flush()
|
||||
if rv:
|
||||
yield rv
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
def stream_untransfer(gen, resp):
|
||||
if 'gzip' in resp.headers.get('content-encoding', ''):
|
||||
gen = stream_decompress(gen, mode='gzip')
|
||||
elif 'deflate' in resp.headers.get('content-encoding', ''):
|
||||
gen = stream_decompress(gen, mode='deflate')
|
||||
|
||||
return gen
|
||||
|
||||
|
||||
def requote_path(path):
|
||||
"""Re-quote the given URL path component.
|
||||
# The unreserved URI characters (RFC 3986)
|
||||
UNRESERVED_SET = frozenset(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
+ "0123456789-._~")
|
||||
|
||||
This function passes the given path through an unquote/quote cycle to
|
||||
ensure that it is fully and consistenty quoted.
|
||||
def unquote_unreserved(uri):
|
||||
"""Un-escape any percent-escape sequences in a URI that are unreserved
|
||||
characters.
|
||||
This leaves all reserved, illegal and non-ASCII bytes encoded.
|
||||
"""
|
||||
parts = path.split("/")
|
||||
parts = (urllib.quote(urllib.unquote(part), safe="") for part in parts)
|
||||
parts = uri.split('%')
|
||||
for i in range(1, len(parts)):
|
||||
h = parts[i][0:2]
|
||||
if len(h) == 2:
|
||||
c = chr(int(h, 16))
|
||||
if c in UNRESERVED_SET:
|
||||
parts[i] = c + parts[i][2:]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
else:
|
||||
parts[i] = '%' + parts[i]
|
||||
return ''.join(parts)
|
||||
|
||||
def requote_uri(uri):
|
||||
"""Re-quote the given URI.
|
||||
|
||||
This function passes the given URI through an unquote/quote cycle to
|
||||
ensure that it is fully and consistently quoted.
|
||||
"""
|
||||
# Unquote only the unreserved characters
|
||||
# Then quote only illegal characters (do not quote reserved, unreserved,
|
||||
# or '%')
|
||||
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
|
||||
return "/".join(parts)
|
||||
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
nose
|
||||
42
setup.py
42
setup.py
@@ -4,6 +4,7 @@
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
from requests.compat import is_py3, is_py2
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
@@ -12,18 +13,30 @@ except ImportError:
|
||||
|
||||
|
||||
|
||||
if sys.argv[-1] == "publish":
|
||||
os.system("python setup.py sdist upload")
|
||||
if sys.argv[-1] == 'publish':
|
||||
os.system('python setup.py sdist upload')
|
||||
sys.exit()
|
||||
|
||||
if sys.argv[-1] == "test":
|
||||
os.system("python test_requests.py")
|
||||
if sys.argv[-1] == 'test':
|
||||
os.system('python test_requests.py')
|
||||
sys.exit()
|
||||
|
||||
required = []
|
||||
required = ['certifi>=0.0.7',]
|
||||
packages = [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.urllib3',
|
||||
'requests.packages.urllib3.packages',
|
||||
'requests.packages.urllib3.packages.ssl_match_hostname',
|
||||
'requests.packages.urllib3.packages.mimetools_choose_boundary',
|
||||
]
|
||||
|
||||
if is_py3:
|
||||
required.append('chardet2')
|
||||
else:
|
||||
required.append('chardet>=1.0.0')
|
||||
packages.append('requests.packages.oreos')
|
||||
|
||||
if sys.version_info[:2] < (2,6):
|
||||
required.append('simplejson')
|
||||
|
||||
setup(
|
||||
name='requests',
|
||||
@@ -34,12 +47,9 @@ setup(
|
||||
author='Kenneth Reitz',
|
||||
author_email='me@kennethreitz.com',
|
||||
url='http://python-requests.org',
|
||||
packages= [
|
||||
'requests',
|
||||
'requests.packages',
|
||||
'requests.packages.urllib3',
|
||||
'requests.packages.oreos'
|
||||
],
|
||||
packages=packages,
|
||||
package_data={'': ['LICENSE', 'NOTICE']},
|
||||
include_package_data=True,
|
||||
install_requires=required,
|
||||
license='ISC',
|
||||
classifiers=(
|
||||
@@ -48,10 +58,10 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: ISC License (ISCL)',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
# 'Programming Language :: Python :: 3.0',
|
||||
# 'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.0',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
),
|
||||
)
|
||||
|
||||
582
test_requests.py
582
test_requests.py
@@ -1,582 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import time
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import envoy
|
||||
from requests import HTTPError
|
||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
|
||||
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
|
||||
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
|
||||
global _httpbin
|
||||
|
||||
if not _httpbin:
|
||||
|
||||
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
|
||||
self.httpbin = c
|
||||
_httpbin = True
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""Teardown."""
|
||||
# self.httpbin.kill()
|
||||
pass
|
||||
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = requests.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads)
|
||||
|
||||
assert heads['User-agent'] in r.content
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
"""Issue XX"""
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['User-agent'] in r.content)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = requests.get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['user-agent'] in r.content)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = requests.head(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = requests.put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PATCH(self):
|
||||
r = requests.patch(httpbin('patch'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPBasicAuth('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
auth = ('user', 'pass')
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = requests.get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = s.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post = requests.post(url).raise_for_status()
|
||||
|
||||
post = requests.post(url, data={'some': 'data'})
|
||||
self.assertEqual(post.status_code, 200)
|
||||
|
||||
post2 = requests.post(url, files={'some': open('test_requests.py')})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = requests.post(url, data='[{"some": "json"}]')
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post = requests.post(url,
|
||||
files={'some': open('test_requests.py')},
|
||||
data={'some': 'data'})
|
||||
|
||||
self.assertEqual(post.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
post2 = requests.post(url,
|
||||
files={'some': open('test_requests.py')},
|
||||
headers = {'User-Agent': 'requests-tests'})
|
||||
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = requests.get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
|
||||
def test_request_ok_set(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('status', '404'))
|
||||
# print r.status_code
|
||||
# r.raise_for_status()
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
|
||||
def test_status_raising(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = requests.get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/get')
|
||||
|
||||
requests.get(url, params={'foo': u'føø'})
|
||||
requests.get(url, params={u'føø': u'føø'})
|
||||
requests.get(url, params={'føø': 'føø'})
|
||||
requests.get(url, params={'foo': u'foo'})
|
||||
requests.get(service('ø'), params={'foo': u'foo'})
|
||||
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = HTTPBasicAuth('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = requests.get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEquals(r.status_code, 401)
|
||||
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
self.assertEquals(rbody.get('form'), {})
|
||||
self.assertEquals(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_postdata(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), data="foobar")
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
|
||||
self.assertEquals(rbody.get('form'), {})
|
||||
self.assertEquals(rbody.get('data'), 'foobar')
|
||||
|
||||
|
||||
# def test_idna(self):
|
||||
# r = requests.get(u'http://➡.ws/httpbin')
|
||||
# assert 'httpbin' in r.url
|
||||
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('get'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(service('post'), params=dict(test=['foo','baz']))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.post(
|
||||
service('post'),
|
||||
params=dict(test=['foo','baz']),
|
||||
data=dict(test2="foobar",test3=['foo','baz']))
|
||||
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(r.headers['content-type'], 'application/json')
|
||||
self.assertEquals(r.url, service('post?test=foo&test=baz'))
|
||||
rbody = json.loads(r.content)
|
||||
self.assertEquals(rbody.get('form'), dict(test2='foobar',test3='foo'))
|
||||
self.assertEquals(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(r.history), 0)
|
||||
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = requests.get(service('relative-redirect', '3'))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEquals(len(r.history), 3)
|
||||
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = s.get(httpbin('user-agent'))
|
||||
assert heads['User-agent'] in r1.content
|
||||
|
||||
r2 = s.get(httpbin('user-agent'))
|
||||
assert heads['User-agent'] in r2.content
|
||||
|
||||
new_heads = {'User-agent': 'blah'}
|
||||
r3 = s.get(httpbin('user-agent'), headers=new_heads)
|
||||
assert new_heads['User-agent'] in r3.content
|
||||
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
def test_session_persistent_cookies(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
# Internally dispatched cookies are sent.
|
||||
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
|
||||
r = s.get(httpbin('cookies'), cookies=_c)
|
||||
r = s.get(httpbin('cookies'))
|
||||
|
||||
# Those cookies persist transparently.
|
||||
c = json.loads(r.content).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Double check.
|
||||
r = s.get(httpbin('cookies'), cookies={})
|
||||
c = json.loads(r.content).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Remove a cookie by setting it's value to None.
|
||||
r = s.get(httpbin('cookies'), cookies={'bessie': None})
|
||||
c = json.loads(r.content).get('cookies')
|
||||
del _c['bessie']
|
||||
assert c == _c
|
||||
|
||||
# Test session-level cookies.
|
||||
s = requests.session(cookies=_c)
|
||||
r = s.get(httpbin('cookies'))
|
||||
c = json.loads(r.content).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Have the server set a cookie.
|
||||
r = s.get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True)
|
||||
c = json.loads(r.content).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
# And server-set cookie persistience.
|
||||
r = s.get(httpbin('cookies'))
|
||||
c = json.loads(r.content).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
|
||||
|
||||
def test_session_persistent_params(self):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = s.get(httpbin('get'))
|
||||
assert params['a'] in r1.content
|
||||
|
||||
|
||||
params2 = {'b': 'b_test'}
|
||||
|
||||
r2 = s.get(httpbin('get'), params=params2)
|
||||
assert params['a'] in r2.content
|
||||
assert params2['b'] in r2.content
|
||||
|
||||
|
||||
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
|
||||
|
||||
r3 = s.get(httpbin('get'), params=params3)
|
||||
|
||||
assert not params['a'] in r3.content
|
||||
assert params3['b'] in r3.content
|
||||
assert params3['c'] in r3.content
|
||||
|
||||
def test_invalid_content(self):
|
||||
# WARNING: if you're using a terrible DNS provider (comcast),
|
||||
# this will fail.
|
||||
try:
|
||||
hah = 'http://somedomainthatclearlydoesntexistg.com'
|
||||
r = requests.get(hah, allow_redirects=False)
|
||||
except requests.ConnectionError:
|
||||
pass # \o/
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
config = {'safe_mode': True}
|
||||
r = requests.get(hah, allow_redirects=False, config=config)
|
||||
assert r.content == None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
819
tests/test_requests.py
Executable file
819
tests/test_requests.py
Executable file
@@ -0,0 +1,819 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# from __future__ import unicode_literals
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import io
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
from requests.compat import str, bytes, StringIO
|
||||
# import envoy
|
||||
from requests import HTTPError
|
||||
from requests import get, post, head, put
|
||||
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||
|
||||
|
||||
|
||||
if (sys.platform == 'win32') and ('HTTPBIN_URL' not in os.environ):
|
||||
os.environ['HTTPBIN_URL'] = 'http://httpbin.org/'
|
||||
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
HTTPBIN_URL = os.environ.get('HTTPBIN_URL', 'http://0.0.0.0:%s/' % (PORT))
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
"""Returns url for HTTPBIN resource."""
|
||||
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
class TestSetup(object):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
|
||||
global _httpbin
|
||||
|
||||
if (not 'HTTPBIN_URL' in os.environ) and not _httpbin:
|
||||
# c = envoy.connect('httpbin %s' % (PORT))
|
||||
# time.sleep(1)
|
||||
_httpbin = True
|
||||
|
||||
|
||||
class RequestsTestSuite(TestSetup, unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, get, 'hiwpefhipowhefopw')
|
||||
|
||||
|
||||
def test_path_is_not_double_encoded(self):
|
||||
request = requests.Request("http://0.0.0.0/get/test case")
|
||||
|
||||
assert request.path_url == "/get/test%20case"
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_response_sent(self):
|
||||
r = get(httpbin('/get'))
|
||||
|
||||
self.assertTrue(r.request.sent)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads)
|
||||
|
||||
assert heads['User-agent'] in r.text
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
r = get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# def test_unicode_headers(self):
|
||||
# # Simply calling requests with a unicode instance should simply work
|
||||
# # when the characters are all representable using latin-1:
|
||||
# heads = { u'User-Agent': u'Requests Test Suite' }
|
||||
# requests.get(url=httpbin('get'), headers=heads)
|
||||
|
||||
# # Characters outside latin-1 should raise an exception:
|
||||
# heads = { u'User-Agent': u'\u30cd\u30c3\u30c8\u30ef\u30fc\u30af' }
|
||||
# self.assertRaises(UnicodeEncodeError, requests.get,
|
||||
# url=httpbin('get'), headers=heads)
|
||||
|
||||
# def test_session_with_escaped_url(self):
|
||||
# # Test a URL that contains percent-escaped characters
|
||||
# # This URL should not be modified (double-escaped)
|
||||
# # Tests:
|
||||
# # - Quoted illegal characters ("%20" (' '), "%3C" ('<'), "%3E" ('>'))
|
||||
# # - Quoted reserved characters ("%25" ('%'), "%23" ('#'), "%2F" ('/'))
|
||||
# # - Quoted non-ASCII characters ("%C3%98", "%C3%A5")
|
||||
# path_fully_escaped = '%3Ca%25b%23c%2Fd%3E/%C3%98%20%C3%A5'
|
||||
# url = httpbin('get/' + path_fully_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
|
||||
|
||||
# # Test that illegal characters in a path get properly percent-escaped
|
||||
# # Tests:
|
||||
# # - Bare illegal characters (space, '<')
|
||||
# # - Bare non-ASCII characters ('\u00d8')
|
||||
# path = u'<a%25b%23c%2Fd%3E/\u00d8 %C3%A5'
|
||||
# url = httpbin('get/' + path)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_fully_escaped))
|
||||
|
||||
# # Test that reserved characters in a path do not get percent-escaped
|
||||
# # Tests:
|
||||
# # - All reserved characters (RFC 3986), except '?', '#', '[' and ']',
|
||||
# # which are not allowed in the path, and ';' which delimits
|
||||
# # parameters.
|
||||
# # All such characters must be allowed bare in path, and must not be
|
||||
# # encoded.
|
||||
# # - Special unreserved characters (RFC 3986), which should not be
|
||||
# # encoded (even though it wouldn't hurt).
|
||||
# path_reserved = '!$&\'()*+,/:=@-._~'
|
||||
# url = httpbin('get/' + path_reserved)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_reserved))
|
||||
|
||||
# # Test that percent-encoded unreserved characters in a path get
|
||||
# # normalised to their un-encoded forms.
|
||||
# path_unreserved = 'ABCDwxyz1234-._~'
|
||||
# path_unreserved_escaped = '%41%42%43%44%77%78%79%7A%31%32%33%34%2D%2E%5F%7E'
|
||||
# url = httpbin('get/' + path_unreserved_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/' + path_unreserved))
|
||||
|
||||
# # Re-run all of the same tests on the query part of the URI
|
||||
# query_fully_escaped = '%3Ca%25b%23c%2Fd%3E=%C3%98%20%C3%A5'
|
||||
# url = httpbin('get/?' + query_fully_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
|
||||
|
||||
# query = u'<a%25b%23c%2Fd%3E=\u00d8 %C3%A5'
|
||||
# url = httpbin('get/?' + query)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_fully_escaped))
|
||||
|
||||
# # The legal characters in query happens to be the same as in path
|
||||
# query_reserved = '!$&\'()*+,/:=@-._~'
|
||||
# url = httpbin('get/?' + query_reserved)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_reserved))
|
||||
|
||||
# query_unreserved = 'ABCDwxyz=1234-._~'
|
||||
# query_unreserved_escaped = '%41%42%43%44%77%78%79%7A=%31%32%33%34%2D%2E%5F%7E'
|
||||
# url = httpbin('get/?' + query_unreserved_escaped)
|
||||
# response = get(url)
|
||||
# self.assertEqual(response.url, httpbin('get/?' + query_unreserved))
|
||||
|
||||
|
||||
def test_user_agent_transfers(self):
|
||||
"""Issue XX"""
|
||||
|
||||
heads = {
|
||||
'User-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['User-agent'] in r.text)
|
||||
|
||||
heads = {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (github.com/kennethreitz/requests)'
|
||||
}
|
||||
|
||||
r = get(httpbin('user-agent'), headers=heads);
|
||||
self.assertTrue(heads['user-agent'] in r.text)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = head(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_PUT(self):
|
||||
r = put(httpbin('put'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = ('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPBasicAuth('user', 'pass')
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
auth = ('user', 'pass')
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
auth = HTTPDigestAuth('user', 'pass')
|
||||
url = service('digest-auth', 'auth', 'user', 'pass')
|
||||
|
||||
r = get(url, auth=auth)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = get(url)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
s = requests.session(auth=auth)
|
||||
r = get(url, session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
post1 = post(url).raise_for_status()
|
||||
|
||||
post1 = post(url, data={'some': 'data'})
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
with open('test_requests.py') as f:
|
||||
post2 = post(url, files={'some': f})
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
post3 = post(url, data='[{"some": "json"}]')
|
||||
self.assertEqual(post3.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
with open('test_requests.py') as f:
|
||||
url = service('post')
|
||||
post1 = post(url,
|
||||
files={'some': f},
|
||||
data={'some': 'data'})
|
||||
|
||||
self.assertEqual(post1.status_code, 200)
|
||||
|
||||
|
||||
def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('post')
|
||||
|
||||
with open('test_requests.py') as f:
|
||||
|
||||
post2 = post(url,
|
||||
files={'some': f},
|
||||
headers = {'User-Agent': 'requests-tests'})
|
||||
|
||||
self.assertEqual(post2.status_code, 200)
|
||||
|
||||
|
||||
def test_nonzero_evaluation(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
|
||||
def test_request_ok_set(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('status', '404'))
|
||||
# print r.status_code
|
||||
# r.raise_for_status()
|
||||
self.assertEqual(r.ok, False)
|
||||
|
||||
|
||||
def test_status_raising(self):
|
||||
r = get(httpbin('status', '404'))
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
def test_default_status_raising(self):
|
||||
config = {'danger_mode': True}
|
||||
args = [httpbin('status', '404')]
|
||||
kwargs = dict(config=config)
|
||||
self.assertRaises(HTTPError, get, *args, **kwargs)
|
||||
|
||||
r = get(httpbin('status', '200'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_decompress_gzip(self):
|
||||
|
||||
r = get(httpbin('gzip'))
|
||||
r.content.decode('ascii')
|
||||
|
||||
def test_response_has_unicode_url(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('get')
|
||||
|
||||
response = get(url)
|
||||
|
||||
assert isinstance(response.url, str)
|
||||
|
||||
|
||||
def test_unicode_get(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/get')
|
||||
|
||||
get(url, params={'foo': 'føø'})
|
||||
get(url, params={'føø': 'føø'})
|
||||
get(url, params={'føø': 'føø'})
|
||||
get(url, params={'foo': 'foo'})
|
||||
get(service('ø'), params={'foo': 'foo'})
|
||||
|
||||
|
||||
def test_httpauth_recursion(self):
|
||||
|
||||
http_auth = HTTPBasicAuth('user', 'BADpass')
|
||||
|
||||
for service in SERVICES:
|
||||
r = get(service('basic-auth', 'user', 'pass'), auth=http_auth)
|
||||
self.assertEqual(r.status_code, 401)
|
||||
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
|
||||
self.assertEqual(rbody.get('form'), dict(test='fooaowpeuf'))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data='fooaowpeuf')
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
# Body wasn't valid url encoded data, so the server returns None as
|
||||
# "form" and the raw body as "data".
|
||||
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), 'fooaowpeuf')
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), params=dict(test='fooaowpeuf'))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(
|
||||
service('post'),
|
||||
params=dict(test='fooaowpeuf'),
|
||||
data=dict(test2="foobar"))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=fooaowpeuf'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), dict(test2='foobar'))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_nonurlencoded_postdata(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), data="foobar")
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
|
||||
assert rbody.get('form') in (None, {})
|
||||
self.assertEqual(rbody.get('data'), 'foobar')
|
||||
|
||||
|
||||
# def test_idna(self):
|
||||
# r = get(u'http://➡.ws/httpbin')
|
||||
# assert 'httpbin' in r.url
|
||||
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('get'), params=dict(test=['foo','baz']))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.url, service('get?test=foo&test=baz'))
|
||||
|
||||
|
||||
def test_urlencoded_post_querystring_multivalued(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(service('post'), params=dict(test=['foo','baz']))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), {}) # No form supplied
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_urlencoded_post_query_multivalued_and_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = post(
|
||||
service('post'),
|
||||
params=dict(test=['foo','baz']),
|
||||
data=dict(test2="foobar",test3=['foo','baz']))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.headers['content-type'], 'application/json')
|
||||
self.assertEqual(r.url, service('post?test=foo&test=baz'))
|
||||
|
||||
# print(r.text)
|
||||
# print('-----------------------')
|
||||
|
||||
rbody = json.loads(r.text)
|
||||
self.assertEqual(rbody.get('form'), dict(test2='foobar',test3=['foo','baz']))
|
||||
self.assertEqual(rbody.get('data'), '')
|
||||
|
||||
|
||||
def test_GET_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(r.history), 0)
|
||||
|
||||
|
||||
def test_HEAD_no_redirect(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = head(service('redirect', '3'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(len(r.history), 0)
|
||||
|
||||
|
||||
def test_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('redirect', '3'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.history), 3)
|
||||
|
||||
|
||||
def test_relative_redirect_history(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
r = get(service('relative-redirect', '3'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(len(r.history), 3)
|
||||
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = requests.session()
|
||||
r = get(httpbin('/get'), session=s)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_persistent_headers(self):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = get(httpbin('user-agent'), session=s)
|
||||
assert heads['User-agent'] in r1.text
|
||||
|
||||
r2 = get(httpbin('user-agent'), session=s)
|
||||
assert heads['User-agent'] in r2.text
|
||||
|
||||
new_heads = {'User-agent': 'blah'}
|
||||
r3 = get(httpbin('user-agent'), headers=new_heads, session=s)
|
||||
assert new_heads['User-agent'] in r3.text
|
||||
|
||||
self.assertEqual(r2.status_code, 200)
|
||||
|
||||
def test_single_hook(self):
|
||||
|
||||
def add_foo_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Foo': 'foo'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
for service in SERVICES:
|
||||
url = service('headers')
|
||||
|
||||
response = get(
|
||||
url = url,
|
||||
hooks = {
|
||||
'args': add_foo_header
|
||||
}
|
||||
)
|
||||
|
||||
assert 'foo' in response.text
|
||||
|
||||
def test_multiple_hooks(self):
|
||||
|
||||
def add_foo_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Foo': 'foo'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
def add_bar_header(args):
|
||||
if not args.get('headers'):
|
||||
args['headers'] = {}
|
||||
|
||||
args['headers'].update({
|
||||
'X-Bar': 'bar'
|
||||
})
|
||||
|
||||
return args
|
||||
|
||||
for service in SERVICES:
|
||||
url = service('headers')
|
||||
|
||||
response = get(
|
||||
url = url,
|
||||
hooks = {
|
||||
'args': [add_foo_header, add_bar_header]
|
||||
}
|
||||
)
|
||||
|
||||
assert 'foo' in response.text
|
||||
assert 'bar' in response.text
|
||||
|
||||
def test_session_persistent_cookies(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
# Internally dispatched cookies are sent.
|
||||
_c = {'kenneth': 'reitz', 'bessie': 'monke'}
|
||||
r = get(httpbin('cookies'), cookies=_c, session=s)
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
|
||||
# Those cookies persist transparently.
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Double check.
|
||||
r = get(httpbin('cookies'), cookies={}, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Remove a cookie by setting it's value to None.
|
||||
r = get(httpbin('cookies'), cookies={'bessie': None}, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
del _c['bessie']
|
||||
assert c == _c
|
||||
|
||||
# Test session-level cookies.
|
||||
s = requests.session(cookies=_c)
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
assert c == _c
|
||||
|
||||
# Have the server set a cookie.
|
||||
r = get(httpbin('cookies', 'set', 'k', 'v'), allow_redirects=True, session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
# And server-set cookie persistience.
|
||||
r = get(httpbin('cookies'), session=s)
|
||||
c = json.loads(r.text).get('cookies')
|
||||
|
||||
assert 'k' in c
|
||||
|
||||
|
||||
|
||||
def test_session_persistent_params(self):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
r1 = get(httpbin('get'), session=s)
|
||||
assert params['a'] in r1.text
|
||||
|
||||
|
||||
params2 = {'b': 'b_test'}
|
||||
|
||||
r2 = get(httpbin('get'), params=params2, session=s)
|
||||
assert params['a'] in r2.text
|
||||
assert params2['b'] in r2.text
|
||||
|
||||
|
||||
params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
|
||||
|
||||
r3 = get(httpbin('get'), params=params3, session=s)
|
||||
|
||||
assert not params['a'] in r3.text
|
||||
assert params3['b'] in r3.text
|
||||
assert params3['c'] in r3.text
|
||||
|
||||
def test_invalid_content(self):
|
||||
# WARNING: if you're using a terrible DNS provider (comcast),
|
||||
# this will fail.
|
||||
try:
|
||||
hah = 'http://somedomainthatclearlydoesntexistg.com'
|
||||
r = get(hah, allow_redirects=False)
|
||||
except requests.ConnectionError:
|
||||
pass # \o/
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
config = {'safe_mode': True}
|
||||
r = get(hah, allow_redirects=False, config=config)
|
||||
assert r.content == None
|
||||
|
||||
def test_cached_response(self):
|
||||
|
||||
r1 = get(httpbin('get'), prefetch=False)
|
||||
assert not r1._content
|
||||
assert r1.content
|
||||
assert r1.text
|
||||
|
||||
r2 = get(httpbin('get'), prefetch=True)
|
||||
assert r2._content
|
||||
assert r2.content
|
||||
assert r2.text
|
||||
|
||||
def test_iter_lines(self):
|
||||
|
||||
lines = (0, 2, 10, 100)
|
||||
|
||||
for i in lines:
|
||||
r = get(httpbin('stream', str(i)), prefetch=False)
|
||||
lines = list(r.iter_lines())
|
||||
len_lines = len(lines)
|
||||
|
||||
self.assertEqual(i, len_lines)
|
||||
|
||||
# Test 'dangling' fragment in responses that do not terminate in
|
||||
# a newline.
|
||||
quote = (
|
||||
'''Why will he not upon our fair request\n'''
|
||||
'''Untent his person and share the air with us?'''
|
||||
)
|
||||
|
||||
# Make a request and monkey-patch its contents
|
||||
r = get(httpbin('get'))
|
||||
r.raw = StringIO(quote)
|
||||
|
||||
# Make sure iter_lines doesn't chop the trailing bit
|
||||
lines = '\n'.join(r.iter_lines())
|
||||
self.assertEqual(lines, quote)
|
||||
|
||||
def test_safe_mode(self):
|
||||
|
||||
safe = requests.session(config=dict(safe_mode=True))
|
||||
|
||||
# Safe mode creates empty responses for failed requests.
|
||||
# Iterating on these responses should produce empty sequences
|
||||
r = get('http://_/', session=safe)
|
||||
self.assertEqual(list(r.iter_lines()), [])
|
||||
assert isinstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
r = get('http://_/', session=safe)
|
||||
self.assertEqual(list(r.iter_content()), [])
|
||||
assert isinstance(r.error, requests.exceptions.ConnectionError)
|
||||
|
||||
# When not in safe mode, should raise Timeout exception
|
||||
self.assertRaises(
|
||||
requests.exceptions.Timeout,
|
||||
get,
|
||||
httpbin('stream', '1000'), timeout=0.0001)
|
||||
|
||||
# In safe mode, should return a blank response
|
||||
r = get(httpbin('stream', '1000'), timeout=0.0001,
|
||||
config=dict(safe_mode=True))
|
||||
assert r.content is None
|
||||
assert isinstance(r.error, requests.exceptions.Timeout)
|
||||
|
||||
def test_upload_binary_data(self):
|
||||
|
||||
r = requests.get(httpbin('post'), auth=('a', 'b'), data='\xff')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
62
tests/test_requests_async.py
Normal file
62
tests/test_requests_async.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from requests import async
|
||||
import envoy
|
||||
|
||||
sys.path.append('.')
|
||||
from test_requests import httpbin, RequestsTestSuite, SERVICES
|
||||
|
||||
|
||||
class RequestsTestSuiteUsingAsyncApi(RequestsTestSuite):
|
||||
"""Requests async test cases."""
|
||||
|
||||
def patched(f):
|
||||
"""Automatically send request after creation."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
|
||||
request = f(*args, **kwargs)
|
||||
return async.map([request])[0]
|
||||
|
||||
return wrapped
|
||||
|
||||
# Patched requests.api functions.
|
||||
global request
|
||||
request = patched(async.request)
|
||||
|
||||
global delete, get, head, options, patch, post, put
|
||||
delete = patched(async.delete)
|
||||
get = patched(async.get)
|
||||
head = patched(async.head)
|
||||
options = patched(async.options)
|
||||
patch = patched(async.patch)
|
||||
post = patched(async.post)
|
||||
put = patched(async.put)
|
||||
|
||||
|
||||
def test_entry_points(self):
|
||||
|
||||
async.request
|
||||
|
||||
async.delete
|
||||
async.get
|
||||
async.head
|
||||
async.options
|
||||
async.patch
|
||||
async.post
|
||||
async.put
|
||||
|
||||
async.map
|
||||
async.send
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
54
tests/test_requests_ext.py
Normal file
54
tests/test_requests_ext.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Path hack.
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
|
||||
# It goes to eleven.
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def test_addition(self):
|
||||
assert (1 + 1) == 2
|
||||
|
||||
|
||||
def test_ssl_hostname_ok(self):
|
||||
requests.get('https://github.com', verify=True)
|
||||
|
||||
|
||||
def test_ssl_hostname_not_ok(self):
|
||||
requests.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, requests.get, 'https://kennethreitz.com')
|
||||
|
||||
|
||||
def test_ssl_hostname_session_not_ok(self):
|
||||
|
||||
s = requests.session()
|
||||
|
||||
self.assertRaises(requests.exceptions.SSLError, s.get, 'https://kennethreitz.com')
|
||||
|
||||
s.get('https://kennethreitz.com', verify=False)
|
||||
|
||||
|
||||
def test_binary_post(self):
|
||||
utf8_string = (u'Smörgås').encode('utf-8')
|
||||
requests.post('http://www.google.com/', data=utf8_string)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user