Compare commits
236 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c90c610cd | ||
|
|
1a85e110b8 | ||
|
|
12fa108ede | ||
|
|
9c3ca6e740 | ||
|
|
12ada03548 | ||
|
|
daa4fbf4e2 | ||
|
|
7ce305fa89 | ||
|
|
04cdd0f2e7 | ||
|
|
33ee732082 | ||
|
|
cf36d8c753 | ||
|
|
bd562a7ab3 | ||
|
|
72c8163aee | ||
|
|
2bae57e0a6 | ||
|
|
f7968b6797 | ||
|
|
4cae63a297 | ||
|
|
628325ae90 | ||
|
|
b3e2410f44 | ||
|
|
a02189a8e9 | ||
|
|
d343fda7f0 | ||
|
|
f9bcf7c611 | ||
|
|
23b0535a0d | ||
|
|
32c7cbf1ad | ||
|
|
8fe6f8c618 | ||
|
|
80c55f5767 | ||
|
|
3efd5dbab4 | ||
|
|
eaf1264879 | ||
|
|
f5592ceb48 | ||
|
|
e32840f631 | ||
|
|
4596c02339 | ||
|
|
4fcf14e124 | ||
|
|
797301a63b | ||
|
|
3615cbc9df | ||
|
|
e43b438729 | ||
|
|
8836c4b12a | ||
|
|
3a4f6ba8b3 | ||
|
|
6ac1e02691 | ||
|
|
6140fac6aa | ||
|
|
73ed950e70 | ||
|
|
6ef5094be6 | ||
|
|
077758f713 | ||
|
|
c108c5300f | ||
|
|
0008b035e2 | ||
|
|
be69529dca | ||
|
|
e257151ffc | ||
|
|
84a2b35b71 | ||
|
|
e15834da8e | ||
|
|
f1bb25b1ec | ||
|
|
6b9cb059f6 | ||
|
|
02cf3dce37 | ||
|
|
4fb72a2e68 | ||
|
|
58b1c69b6a | ||
|
|
8f8dbec0d5 | ||
|
|
e46251b727 | ||
|
|
82cbae479c | ||
|
|
b88fbb5e0c | ||
|
|
68b4830948 | ||
|
|
9966017a49 | ||
|
|
53c7b77735 | ||
|
|
491a3c075d | ||
|
|
4916b1c2ce | ||
|
|
60b37e54b5 | ||
|
|
b7c788cc56 | ||
|
|
12db1576e9 | ||
|
|
af6aa3e572 | ||
|
|
20e0ee4968 | ||
|
|
4892a305d9 | ||
|
|
495c3d5006 | ||
|
|
06a5aed8e2 | ||
|
|
0dae52220b | ||
|
|
d23c4b94c1 | ||
|
|
94c587d642 | ||
|
|
340f7a0733 | ||
|
|
b8d4a5ea61 | ||
|
|
5b5b3def00 | ||
|
|
1a4dfad52d | ||
|
|
b66871e871 | ||
|
|
a74568d8d5 | ||
|
|
302c4d9c41 | ||
|
|
97e3c84084 | ||
|
|
0ee6c1fa68 | ||
|
|
f0fe8b971b | ||
|
|
8c34fa8477 | ||
|
|
6be69e303b | ||
|
|
2f6cf50a6c | ||
|
|
f9dfbb73dd | ||
|
|
903f96cc6a | ||
|
|
5c81fba1eb | ||
|
|
4dc48b6521 | ||
|
|
ab3200b4cc | ||
|
|
185450c5e1 | ||
|
|
958c424c89 | ||
|
|
61be59d996 | ||
|
|
e007a5a13f | ||
|
|
3d29f6535f | ||
|
|
98dc08236c | ||
|
|
4ab0bfefd3 | ||
|
|
e418631ce8 | ||
|
|
4d97cc7506 | ||
|
|
2b3bd78685 | ||
|
|
597ace5718 | ||
|
|
f269c6c89a | ||
|
|
d72d116214 | ||
|
|
d552e6c884 | ||
|
|
77909f1d49 | ||
|
|
dbf70fc40f | ||
|
|
3a6d9b09db | ||
|
|
b19d8785a4 | ||
|
|
41e60b8a00 | ||
|
|
eddf5254fb | ||
|
|
ba5eddcf5e | ||
|
|
d63cd5f547 | ||
|
|
348d2a2d51 | ||
|
|
194ff1ad89 | ||
|
|
3b6904c5ef | ||
|
|
8d0e03ce41 | ||
|
|
baa3f78430 | ||
|
|
4f984b0fe6 | ||
|
|
c5ed4c9953 | ||
|
|
681956e0c4 | ||
|
|
ad6a8d53d6 | ||
|
|
0e9d7aad27 | ||
|
|
6dc664caaf | ||
|
|
98c5ded4f3 | ||
|
|
5568f77ad5 | ||
|
|
cfd38263d1 | ||
|
|
c052029399 | ||
|
|
b7243684de | ||
|
|
055f7c1eb6 | ||
|
|
e27241df73 | ||
|
|
23efd56b12 | ||
|
|
d938f32089 | ||
|
|
7dc75ce3ce | ||
|
|
8c74860fd1 | ||
|
|
492133ba64 | ||
|
|
82e8fdbb29 | ||
|
|
b37b977d20 | ||
|
|
ec94f5030c | ||
|
|
65a765fe88 | ||
|
|
9645e882b2 | ||
|
|
ef1e27ea9f | ||
|
|
6bd8e9e73f | ||
|
|
de4035fb96 | ||
|
|
38bd234936 | ||
|
|
7481980c1a | ||
|
|
a4234e97be | ||
|
|
9b42c954c6 | ||
|
|
d0b9357f1b | ||
|
|
a688f026dc | ||
|
|
60917e0b9f | ||
|
|
f0bcdd075e | ||
|
|
7a0f3638b5 | ||
|
|
12997080a8 | ||
|
|
6e840f46e1 | ||
|
|
12d6697815 | ||
|
|
d85c5356dc | ||
|
|
ff6dbc556b | ||
|
|
35bfa9e91d | ||
|
|
d98de70f4a | ||
|
|
e1dfb7c883 | ||
|
|
15755be3c7 | ||
|
|
84e89429f0 | ||
|
|
daf9b60550 | ||
|
|
1382d655ed | ||
|
|
f0cfba708b | ||
|
|
435bd1242a | ||
|
|
4f6fc98509 | ||
|
|
9011852db0 | ||
|
|
29548bfab6 | ||
|
|
efc267ce59 | ||
|
|
244172bdd4 | ||
|
|
f1f56c3081 | ||
|
|
bfa010383e | ||
|
|
daf7aeb8c3 | ||
|
|
573ab095e4 | ||
|
|
030d877ed5 | ||
|
|
33dec52b8e | ||
|
|
aa31b6b4dd | ||
|
|
46f0146adf | ||
|
|
b14fa0764b | ||
|
|
0d6773c68c | ||
|
|
41876fd8c6 | ||
|
|
6f84ce72f2 | ||
|
|
6d25fff3f9 | ||
|
|
9bfe04586d | ||
|
|
1ecb593c7b | ||
|
|
fed17cb58a | ||
|
|
35fc3b4dbb | ||
|
|
dba7a5a7e2 | ||
|
|
2fa180e9ea | ||
|
|
3b9fc987b8 | ||
|
|
e272ec13c3 | ||
|
|
b97b4439c0 | ||
|
|
1d0a5b2665 | ||
|
|
636e61d68b | ||
|
|
a94541d38c | ||
|
|
ce3b85a62e | ||
|
|
281aa5ebc4 | ||
|
|
75027f9557 | ||
|
|
e8dbecb4db | ||
|
|
317c5b693a | ||
|
|
a86af4477e | ||
|
|
59924b9223 | ||
|
|
c8110222a5 | ||
|
|
1143d4237f | ||
|
|
8d5108b142 | ||
|
|
03e18bb7ca | ||
|
|
6a96652396 | ||
|
|
d4bdd84364 | ||
|
|
b45b329861 | ||
|
|
28ee9788a0 | ||
|
|
596aceb185 | ||
|
|
1f3e53b32e | ||
|
|
13db806861 | ||
|
|
d0fdf0eb38 | ||
|
|
c5d787d6fd | ||
|
|
873d128c5a | ||
|
|
7f35ec7665 | ||
|
|
86cc274cf0 | ||
|
|
c19b2c5172 | ||
|
|
4906686186 | ||
|
|
f038ec3b6e | ||
|
|
a609f726e6 | ||
|
|
7bd47777a9 | ||
|
|
21cd47d223 | ||
|
|
882ea76aa9 | ||
|
|
850e1c7d40 | ||
|
|
01411e37d6 | ||
|
|
4922daf498 | ||
|
|
8fbb1e6d97 | ||
|
|
7fc8c7ccfd | ||
|
|
7eb272b299 | ||
|
|
8bfa909ff2 | ||
|
|
5f16a8a6b1 | ||
|
|
d26f3333e4 | ||
|
|
820e2c73e6 | ||
|
|
8c737069a5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ pylint.txt
|
||||
docs/_build
|
||||
toy.py
|
||||
.gitignore
|
||||
junit-report.xml
|
||||
10
AUTHORS
10
AUTHORS
@@ -40,3 +40,13 @@ Patches and Suggestions
|
||||
- Mikko Ohtamaa
|
||||
- Den Shabalin
|
||||
- Daniel Miller <danielm@vs-networks.com>
|
||||
- Alejandro Giacometti
|
||||
- Rick Mak
|
||||
- Johan Bergström
|
||||
- Josselin Jacquard
|
||||
- Travis N. Vaught
|
||||
- Fredrik Möllerstrand
|
||||
- Daniel Hengeveld
|
||||
- Dan Head
|
||||
- Bruno Renié
|
||||
- David Fischer
|
||||
40
AUTHORS.orig
40
AUTHORS.orig
@@ -1,40 +0,0 @@
|
||||
Requests is written and maintained by Kenneth Reitz and
|
||||
various contributors:
|
||||
|
||||
Development Lead
|
||||
````````````````
|
||||
|
||||
- Kenneth Reitz <me@kennethreitz.com>
|
||||
|
||||
|
||||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Various Pocoo Members
|
||||
- Chris Adams
|
||||
- Flavio Percoco Premoli
|
||||
- Dj Gilcrease
|
||||
- Justin Murphy
|
||||
- Rob Madole
|
||||
- Aram Dulyan
|
||||
- Johannes Gorset
|
||||
- 村山めがね (Megane Murayama)
|
||||
- James Rowe
|
||||
- Daniel Schauenberg
|
||||
- Zbigniew Siciarz
|
||||
- Daniele Tricoli 'Eriol'
|
||||
- Richard Boulton
|
||||
- Miguel Olivares <miguel@moliware.com>
|
||||
- Alberto Paro
|
||||
- Jérémy Bethmont
|
||||
- 潘旭 (Xu Pan)
|
||||
- Tamás Gulácsi
|
||||
- Rubén Abad
|
||||
- Peter Manser
|
||||
- Jeremy Selie
|
||||
<<<<<<< HEAD
|
||||
- Jens Diemer
|
||||
- Alex <@alopatin>
|
||||
=======
|
||||
- Tom Hogans <tomhsx@gmail.com>
|
||||
>>>>>>> 0ed641a26ec2200de00e4bbf3d170c767375351e
|
||||
15
HACKING
15
HACKING
@@ -1,15 +0,0 @@
|
||||
Where possible, please follow PEP8 with regard to coding style. Sometimes the
|
||||
line length restriction is too hard to follow, so don't bend over backwards
|
||||
there.
|
||||
|
||||
Triple-quotes should always be """, single quotes are ' unless using " would
|
||||
result in less escaping within the string.
|
||||
|
||||
All modules, functions, and methods should be well documented reStructuredText
|
||||
for Sphinx AutoDoc.
|
||||
|
||||
All functionality should be available in pure Python. Optional C (via Cython)
|
||||
implementations may be written for performance reasons, but should never
|
||||
replace the Python implementation.
|
||||
|
||||
Lastly, don't take yourself too seriously :)
|
||||
70
HISTORY.rst
70
HISTORY.rst
@@ -1,6 +1,74 @@
|
||||
History
|
||||
-------
|
||||
|
||||
0.7.6 (2011-11-07)
|
||||
++++++++++++++++++
|
||||
|
||||
* Digest authentication bugfix (attach query data to path)
|
||||
|
||||
0.7.5 (2011-11-04)
|
||||
++++++++++++++++++
|
||||
|
||||
* Response.content = None if there was an invalid repsonse.
|
||||
* Redirection auth handling.
|
||||
|
||||
0.7.4 (2011-10-26)
|
||||
++++++++++++++++++
|
||||
|
||||
* Sesion Hooks fix.
|
||||
|
||||
0.7.3 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
* Digest Auth fix.
|
||||
|
||||
|
||||
0.7.2 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
* PATCH Fix.
|
||||
|
||||
|
||||
0.7.1 (2011-10-23)
|
||||
++++++++++++++++++
|
||||
|
||||
* Move away from urllib2 authentication handling.
|
||||
* Fully Remove AuthManager, AuthObject, &c.
|
||||
* New tuple-based auth system with handler callbacks.
|
||||
|
||||
|
||||
0.7.0 (2011-10-22)
|
||||
++++++++++++++++++
|
||||
|
||||
* Sessions are now the primary interface.
|
||||
* Deprecated InvalidMethodException.
|
||||
* PATCH fix.
|
||||
* New config system (no more global settings).
|
||||
|
||||
|
||||
0.6.6 (2011-10-19)
|
||||
++++++++++++++++++
|
||||
|
||||
* Session parameter bugfix (params merging).
|
||||
|
||||
|
||||
0.6.5 (2011-10-18)
|
||||
++++++++++++++++++
|
||||
|
||||
* Offline (fast) test suite.
|
||||
* Session dictionary argument merging.
|
||||
|
||||
|
||||
0.6.4 (2011-10-13)
|
||||
++++++++++++++++++
|
||||
|
||||
* Automatic decoding of unicode, based on HTTP Headers.
|
||||
* New ``decode_unicode`` setting.
|
||||
* Removal of ``r.read/close`` methods.
|
||||
* New ``r.faw`` interface for advanced response usage.*
|
||||
* Automatic expansion of parameterized headers.
|
||||
|
||||
|
||||
0.6.3 (2011-10-13)
|
||||
++++++++++++++++++
|
||||
|
||||
@@ -10,7 +78,7 @@ History
|
||||
0.6.2 (2011-10-09)
|
||||
++++++++++++++++++
|
||||
|
||||
* GET/HEAD obeys allow_redirects=False
|
||||
* GET/HEAD obeys allow_redirects=False.
|
||||
|
||||
|
||||
0.6.1 (2011-08-20)
|
||||
|
||||
@@ -1 +1 @@
|
||||
include README.rst LICENSE HISTORY.rst
|
||||
include README.rst LICENSE HISTORY.rst test_requests.py
|
||||
|
||||
6
Makefile
6
Makefile
@@ -2,10 +2,12 @@ init:
|
||||
pip install -r reqs.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py --processes=30
|
||||
nosetests test_requests.py
|
||||
|
||||
ci: init
|
||||
nosetests --search-test --processes=30 --with-nosexunit test_requests.py
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
stats:
|
||||
pyflakes requests | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' > violations.pyflakes.txt
|
||||
|
||||
site:
|
||||
|
||||
28
README.rst
28
README.rst
@@ -34,19 +34,15 @@ all the hard work and crazy hacks for you.
|
||||
Features
|
||||
--------
|
||||
|
||||
- Extremely simple HEAD, GET, POST, PUT, PATCH, DELETE Requests
|
||||
+ Simple HTTP Header Request Attachment
|
||||
+ Simple Data/Params Request Attachment
|
||||
+ Simple Multipart File Uploads
|
||||
+ CookieJar Support
|
||||
+ Redirection History
|
||||
+ Proxy Support
|
||||
+ Redirection Recursion Urllib Fix
|
||||
+ Auto Decompression of GZipped Content
|
||||
+ Unicode URL Support
|
||||
|
||||
- Simple Authentication
|
||||
+ Simple URL + HTTP Auth Registry
|
||||
- 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
|
||||
@@ -97,8 +93,10 @@ But, you really shouldn't do that.
|
||||
Contribute
|
||||
----------
|
||||
|
||||
If you'd like to contribute, simply fork `the repository`_, commit your changes to the **develop** branch (or branch off of it), and send a pull request. Make sure you add yourself to AUTHORS_.
|
||||
|
||||
#. 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_.
|
||||
|
||||
.. _`the repository`: http://github.com/kennethreitz/requests
|
||||
.. _AUTHORS: http://github.com/kennethreitz/requests/blob/master/AUTHORS
|
||||
|
||||
5
debian/changelog
vendored
5
debian/changelog
vendored
@@ -1,5 +0,0 @@
|
||||
python-requests (0.4.1-0) testing; urgency=low
|
||||
|
||||
* Initial Debian package
|
||||
|
||||
-- Bruno Clermont <bruno.clermont@gmail.com> Thu, 26 May 2011 16:25:00 -0500
|
||||
1
debian/compat
vendored
1
debian/compat
vendored
@@ -1 +0,0 @@
|
||||
5
|
||||
13
debian/control
vendored
13
debian/control
vendored
@@ -1,13 +0,0 @@
|
||||
Source: python-requests
|
||||
Section: python
|
||||
Priority: optional
|
||||
Maintainer: Bruno Clermont <bruno.clermont@gmail.com>
|
||||
Homepage: https://github.com/bclermont/requests
|
||||
Bugs: https://github.com/bclermont/requests/issues
|
||||
Build-Depends: debhelper, python-support
|
||||
|
||||
Package: python-requests
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}, python-support
|
||||
Provides: ${python:Provides}
|
||||
Description: Python HTTP Requests for Humans.
|
||||
1
debian/docs
vendored
1
debian/docs
vendored
@@ -1 +0,0 @@
|
||||
docs/user/*.rst
|
||||
1
debian/pyversions
vendored
1
debian/pyversions
vendored
@@ -1 +0,0 @@
|
||||
2.4-
|
||||
42
debian/rules
vendored
42
debian/rules
vendored
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
# Verbose mode
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
clean:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
|
||||
rm -rf build requests.egg-info
|
||||
# find django-sentry/ -name *.pyc | xargs rm -f
|
||||
|
||||
dh_clean
|
||||
|
||||
build:
|
||||
dh_testdir
|
||||
|
||||
python setup.py build
|
||||
|
||||
install:
|
||||
dh_testdir
|
||||
dh_installdirs
|
||||
|
||||
python setup.py install --root $(CURDIR)/debian/python-requests
|
||||
|
||||
binary-indep: install
|
||||
|
||||
binary-arch: install
|
||||
dh_install
|
||||
dh_installdocs
|
||||
# dh_installchangelogs
|
||||
dh_compress
|
||||
dh_fixperms
|
||||
dh_pysupport
|
||||
dh_gencontrol
|
||||
dh_installdeb
|
||||
dh_md5sums
|
||||
dh_builddeb -- -Z lzma -z9
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
.PHONY: build clean binary-indep binary-arch binary
|
||||
|
||||
7
docs/_themes/kr/layout.html
vendored
7
docs/_themes/kr/layout.html
vendored
@@ -2,18 +2,19 @@
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9">
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
</div>
|
||||
<a href="https://github.com/kennethreitz/requests">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" />
|
||||
<a href="https://github.com/kennethreitz/requests" class="github">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" class="github"/>
|
||||
</a>
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
13
docs/_themes/kr/static/flasky.css_t
vendored
13
docs/_themes/kr/static/flasky.css_t
vendored
@@ -393,6 +393,11 @@ a:hover tt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
@@ -415,6 +420,10 @@ a:hover tt {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -423,7 +432,9 @@ a:hover tt {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
|
||||
.github {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
20
docs/_themes/kr/static/small_flask.css
vendored
20
docs/_themes/kr/static/small_flask.css
vendored
@@ -68,3 +68,23 @@ div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.rtd_doc_footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.document {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.github {
|
||||
display: none;
|
||||
}
|
||||
18
docs/api.rst
18
docs/api.rst
@@ -31,6 +31,21 @@ They all return an instance of the :class:`Response <Response>` object.
|
||||
.. autoclass:: Response
|
||||
:inherited-members:
|
||||
|
||||
Async
|
||||
-----
|
||||
|
||||
.. module:: requests.async
|
||||
|
||||
|
||||
.. autofunction:: map
|
||||
.. autofunction:: request
|
||||
.. autofunction:: head
|
||||
.. autofunction:: get
|
||||
.. autofunction:: post
|
||||
.. autofunction:: put
|
||||
.. autofunction:: patch
|
||||
.. autofunction:: delete
|
||||
|
||||
|
||||
|
||||
Utilities
|
||||
@@ -48,9 +63,6 @@ Cookies
|
||||
.. autofunction:: cookiejar_from_dict
|
||||
.. autofunction:: add_dict_to_cookiejar
|
||||
|
||||
Curl
|
||||
~~~~
|
||||
.. autofunction:: curl_from_request
|
||||
|
||||
Encodings
|
||||
~~~~~~~~~
|
||||
|
||||
@@ -9,7 +9,7 @@ Encoded Data?
|
||||
-------------
|
||||
|
||||
Requests automatically decompresses gzip-encoded responses, and does
|
||||
its best to decodes response content to unicode when possible.
|
||||
its best to decode response content to unicode when possible.
|
||||
|
||||
You can get direct access to the raw response (and even the socket),
|
||||
if needed as well.
|
||||
|
||||
66
docs/community/out-there.rst
Normal file
66
docs/community/out-there.rst
Normal file
@@ -0,0 +1,66 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
- `requests-oauth-hook <https://github.com/maraujop/requests-oauth-hook>`_, adds OAuth support to Requests.
|
||||
- `FacePy <https://github.com/jgorset/facepy>`_, a Python wrapper to the Facebook API.
|
||||
- `robotframework-requests <https://github.com/bulkan/robotframework-requests>`_, a Robot Framework API wrapper.
|
||||
- `fullerene <https://github.com/bitprophet/fullerene>`_, a Graphite Dashboard.
|
||||
- `urbanairship-python <https://github.com/benjaminws/urbanairship-python>`_, a fork of the Urban Airship API wrapper.
|
||||
- `WhitespaceBot <https://github.com/Gunio/WhitespaceBot/>`_, a project that automatically forks repos, strips trailing whitespace, and sends a pull request.
|
||||
- `python-rexster <https://github.com/CulturePlex/python-rexster>`_, Rexter client that provides a simple interface for graph databases.
|
||||
- `daikon <https://github.com/neogenix/daikon>`_, a CLI for ElasticSearch.
|
||||
|
||||
Articles & Talks
|
||||
================
|
||||
- `Python for the Web <http://gun.io/blog/python-for-the-web/>`_ teaches how to use Python to interact with the web, using Requests.
|
||||
- `Daniel Greenfield's Review of Requests <http://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
|
||||
- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <http://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
|
||||
- `Issac Kelly's 'Consuming Web APIs' talk <http://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
|
||||
- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
|
||||
- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
|
||||
- `French blog post introducing Requests <http://www.nicosphere.net/requests-urllib2-de-python-simplifie-2432/>`_
|
||||
|
||||
|
||||
Integrations
|
||||
============
|
||||
|
||||
ScraperWiki
|
||||
------------
|
||||
|
||||
`ScraperWiki <https://scraperwiki.com/>`_ is an excellent service that allows
|
||||
you to run Python, Ruby, and PHP scraper scripts on the web. Now, Requests
|
||||
v0.6.1 is available to use in your scrapers!
|
||||
|
||||
To give it a try, simply::
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
Managed Packages
|
||||
================
|
||||
|
||||
Requests is available in a number of popular package formats. Of course,
|
||||
the ideal way to install Requests is via The Cheeseshop.
|
||||
|
||||
|
||||
Ubuntu & Debian
|
||||
---------------
|
||||
|
||||
Requests is available installed as a Debian package! Debian Etch Ubuntu, since Oneiric::
|
||||
|
||||
$ apt-get install python-requests
|
||||
|
||||
Unfortunately, the most recent version available is v0.5.0. If you're on the
|
||||
Debian Python Package team, I'd love an update of that :)
|
||||
|
||||
|
||||
Fedora and RedHat
|
||||
-----------------
|
||||
|
||||
You can easily install Requests v0.6.1 with yum on rpm-based systems::
|
||||
|
||||
$ yum install python-requests
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Support
|
||||
=======
|
||||
|
||||
If you have a questions or issues about Requests, there are serveral options:
|
||||
If you have a questions or issues about Requests, there are several options:
|
||||
|
||||
Send a Tweet
|
||||
------------
|
||||
|
||||
@@ -4,7 +4,7 @@ Updates
|
||||
=======
|
||||
|
||||
If you'd like to stay up to date on the community and development of Requests,
|
||||
there are serveral options:
|
||||
there are several options:
|
||||
|
||||
GitHub
|
||||
------
|
||||
|
||||
@@ -39,8 +39,9 @@ all the hard work and crazy hacks for you.
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
`Twitter, Inc <http://twitter.com>`_,
|
||||
`The Washington Post <http://www.washingtonpost.com/>`_, `Twitter, Inc <http://twitter.com>`_,
|
||||
a U.S. Federal Institution,
|
||||
NIH,
|
||||
`Readability <http://readability.com>`_, and
|
||||
`Work for Pie <http://workforpie.com>`_
|
||||
use Requests internally.
|
||||
@@ -87,6 +88,7 @@ Requests ecosystem and community.
|
||||
:maxdepth: 2
|
||||
|
||||
community/faq
|
||||
community/out-there.rst
|
||||
community/support
|
||||
community/updates
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ A session object has all the methods of the main Requests API.
|
||||
|
||||
Let's persist some cookies across requests::
|
||||
|
||||
with requests.session() as s:
|
||||
s = requests.session()
|
||||
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
|
||||
print r.content
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
print r.content
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
|
||||
|
||||
Sessions can also be used to provide default data to the request methods::
|
||||
@@ -39,8 +39,8 @@ Sessions can also be used to provide default data to the request methods::
|
||||
|
||||
.. admonition:: Global Settings
|
||||
|
||||
Certain parameters are best set at the ``request.config`` level
|
||||
(e.g.. a global proxy, user agent header).
|
||||
Certain parameters are best set in the ``config`` dictionary
|
||||
(e.g. user agent header).
|
||||
|
||||
|
||||
Asynchronous Requests
|
||||
@@ -50,7 +50,7 @@ Requests has first-class support for non-blocking i/o requests, powered
|
||||
by gevent. This allows you to send a bunch of HTTP requests at the same
|
||||
|
||||
First, let's import the async module. Heads up — if you don't have
|
||||
**gevent** installed, this will fail.::
|
||||
`gevent <gevent>`_ this will fail::
|
||||
|
||||
from requests import async
|
||||
|
||||
@@ -69,9 +69,10 @@ We can build a list of ``Request`` objects easily::
|
||||
|
||||
rs = [async.get(u) for u in urls]
|
||||
|
||||
Now we have a list of ``Request`` objects, ready to be sent. We could
|
||||
send them one at a time with ``Request.send()``, but that would take a while.
|
||||
Instead, we'll send them all at the same time with ``async.map()``::
|
||||
Now we have a list of ``Request`` objects, ready to be sent. We could send them
|
||||
one at a time with ``Request.send()``, but that would take a while. Instead,
|
||||
we'll send them all at the same time with ``async.map()``. Using ``async.map()``
|
||||
will also guarantee execution of the ``response`` hook, described below. ::
|
||||
|
||||
>>> async.map(rs)
|
||||
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
|
||||
@@ -156,6 +157,39 @@ And give it a try::
|
||||
}
|
||||
|
||||
|
||||
Custom Authentication
|
||||
---------------------
|
||||
|
||||
Requests allows you to use specify your own authentication mechanism.
|
||||
|
||||
When you pass our authentication tuple to a request method, the first
|
||||
string is the type of authentication. 'basic' is inferred if none is
|
||||
provided.
|
||||
|
||||
You can pass in a callable object instead of a string for the first item
|
||||
in the tuple, and it will be used in place of the built in authentication
|
||||
callbacks.
|
||||
|
||||
Let's pretend that we have a web service that will only respond if the
|
||||
``X-Pizza`` header is set to a password value. Unlikely, but just go with it.
|
||||
|
||||
We simply need to define a callback function that will be used to update the
|
||||
Request object, right before it is dispatched.
|
||||
|
||||
::
|
||||
|
||||
def pizza_auth(r, username):
|
||||
"""Attaches HTTP Pizza Authentication to the given Request object.
|
||||
"""
|
||||
r.headers['X-Pizza'] = username
|
||||
|
||||
return r
|
||||
|
||||
Then, we can make a request using our Pizza Auth::
|
||||
|
||||
>>> requests.get('http://pizzabin.org/admin', auth=(pizza_auth, 'kenneth'))
|
||||
<Response [200]>
|
||||
|
||||
|
||||
Verbose Logging
|
||||
---------------
|
||||
|
||||
@@ -18,7 +18,7 @@ or, with `easy_install <http://pypi.python.org/pypi/setuptools>`_::
|
||||
|
||||
$ easy_install requests
|
||||
|
||||
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/index.html#pip-compared-to-easy-install>`_.
|
||||
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/other-tools.html#pip-compared-to-easy-install>`_.
|
||||
|
||||
|
||||
|
||||
@@ -54,3 +54,25 @@ Once you have a copy of the source, you can embed it in your Python package,
|
||||
or install it into your site-packages easily::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
.. _gevent:
|
||||
|
||||
Installing Gevent
|
||||
-----------------
|
||||
|
||||
If you are using the ``requests.async`` module for making concurrent
|
||||
requests, you need to install gevent.
|
||||
|
||||
To install gevent, you'll need ``libevent``.
|
||||
|
||||
OSX::
|
||||
|
||||
$ brew install libevent
|
||||
|
||||
Ubuntu::
|
||||
|
||||
$ apt-get install libevent-dev
|
||||
|
||||
Once you have ``libevent``, you can install ``gevent`` with ``pip``::
|
||||
|
||||
$ pip install gevent
|
||||
|
||||
@@ -47,5 +47,3 @@ Requests License
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -136,3 +136,32 @@ parameter::
|
||||
>>> r = requests.get(url, cookies=cookies)
|
||||
>>> r.content
|
||||
'{"cookies": {"cookies_are": "working"}}'
|
||||
|
||||
|
||||
Basic Authentication
|
||||
--------------------
|
||||
|
||||
Most web services require authentication. There many different types of
|
||||
authentication, but the most common is called HTTP Basic Auth.
|
||||
|
||||
Making requests with Basic Auth is easy, with Requests::
|
||||
|
||||
>>> requests.get('https://api.github.com/user', auth=('user', 'pass'))
|
||||
<Response [200]>
|
||||
|
||||
|
||||
Digest Authentication
|
||||
---------------------
|
||||
|
||||
Another popular form of protecting web service is Digest Authentication.
|
||||
|
||||
Requests supports it!::
|
||||
|
||||
>>> url = 'http://httpbin.org/digest-auth/auth/user/pass'
|
||||
>>> requests.get(url, auth=('digest', 'user', 'pass'))
|
||||
<Response [200]>
|
||||
|
||||
|
||||
-----------------------
|
||||
|
||||
Ready for more? Check out the :ref:`advanced <advanced>` section.
|
||||
|
||||
7
reqs.txt
Normal file
7
reqs.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
envoy==0.0.2
|
||||
httpbin==0.0.4
|
||||
gunicorn
|
||||
nose
|
||||
pyflakes
|
||||
omnijson
|
||||
rudolf
|
||||
@@ -1,4 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from core import *
|
||||
from core import __version__
|
||||
# __
|
||||
# /__) _ _ _ _ _/ _
|
||||
# / ( (- (/ (/ (- _) / _)
|
||||
# /
|
||||
|
||||
"""
|
||||
requests
|
||||
~~~~~~~~
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.7.6'
|
||||
__build__ = 0x000706
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
from . import utils
|
||||
from .models import Request, Response
|
||||
from .api import request, get, head, post, patch, put, delete
|
||||
from .sessions import session
|
||||
from .status_codes import codes
|
||||
from .exceptions import (
|
||||
RequestException, AuthenticationError, Timeout, URLRequired,
|
||||
TooManyRedirects
|
||||
)
|
||||
|
||||
141
requests/api.py
141
requests/api.py
@@ -4,28 +4,33 @@
|
||||
requests.api
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module impliments the Requests API.
|
||||
This module implements the Requests API.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
import config
|
||||
from .models import Request, Response, AuthObject
|
||||
from .status_codes import codes
|
||||
from .hooks import dispatch_hook
|
||||
from .utils import cookiejar_from_dict
|
||||
|
||||
from .sessions import session
|
||||
|
||||
__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete')
|
||||
|
||||
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):
|
||||
|
||||
"""Constructs and sends a :class:`Request <models.Request>`.
|
||||
Returns :class:`Response <models.Response>` object.
|
||||
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,
|
||||
config=None):
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
@@ -34,68 +39,28 @@ 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 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param auth: (optional) Auth typle 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.
|
||||
"""
|
||||
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
args = dict(
|
||||
method = method,
|
||||
url = url,
|
||||
data = data,
|
||||
params = params,
|
||||
headers = headers,
|
||||
cookiejar = cookies,
|
||||
files = files,
|
||||
auth = auth,
|
||||
hooks = hooks,
|
||||
timeout = timeout or config.settings.timeout,
|
||||
allow_redirects = allow_redirects,
|
||||
proxies = proxies or config.settings.proxies,
|
||||
s = session()
|
||||
return s.request(
|
||||
method, url, params, data, headers, cookies, files, auth,
|
||||
timeout, allow_redirects, proxies, hooks, return_response,
|
||||
config
|
||||
)
|
||||
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('args', hooks, args)
|
||||
|
||||
r = Request(**args)
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, r)
|
||||
|
||||
# Don't send if asked nicely.
|
||||
if not return_response:
|
||||
return r
|
||||
|
||||
# Send the HTTP Request.
|
||||
r.send()
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', hooks, r)
|
||||
|
||||
# Response manipulation hook.
|
||||
r.response = dispatch_hook('response', hooks, r.response)
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
def get(url, **kwargs):
|
||||
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
|
||||
: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 auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to False to disable redirect following.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -103,17 +68,10 @@ def get(url, **kwargs):
|
||||
|
||||
|
||||
def head(url, **kwargs):
|
||||
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to False to disable redirect following.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
@@ -121,22 +79,14 @@ def head(url, **kwargs):
|
||||
|
||||
|
||||
def post(url, data='', **kwargs):
|
||||
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed.
|
||||
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('POST', url, data=data, **kwargs)
|
||||
return request('post', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def put(url, data='', **kwargs):
|
||||
@@ -144,17 +94,10 @@ def put(url, data='', **kwargs):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed.
|
||||
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('PUT', url, data=data, **kwargs)
|
||||
return request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(url, data='', **kwargs):
|
||||
@@ -162,31 +105,17 @@ def patch(url, data='', **kwargs):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed.
|
||||
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('PATCH', url, **kwargs)
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary of parameters, or bytes, to be sent in the query string for the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('DELETE', url, **kwargs)
|
||||
return request('delete', url, **kwargs)
|
||||
|
||||
@@ -28,16 +28,19 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
def _patched(f):
|
||||
def patched(f):
|
||||
"""Patches a given API function to not send."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
return f(*args, return_response=False, **kwargs)
|
||||
|
||||
kwargs['return_response'] = False
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _send(r, pools=None):
|
||||
def send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
|
||||
if pools:
|
||||
@@ -45,23 +48,17 @@ def _send(r, pools=None):
|
||||
|
||||
r.send()
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', r.hooks, r)
|
||||
|
||||
# Response manipulation hook.
|
||||
r.response = dispatch_hook('response', r.hooks, r.response)
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
# Patched requests.api functions.
|
||||
get = _patched(api.get)
|
||||
head = _patched(api.head)
|
||||
post = _patched(api.post)
|
||||
put = _patched(api.put)
|
||||
patch = _patched(api.patch)
|
||||
delete = _patched(api.delete)
|
||||
request = _patched(api.request)
|
||||
get = patched(api.get)
|
||||
head = patched(api.head)
|
||||
post = patched(api.post)
|
||||
put = patched(api.put)
|
||||
patch = patched(api.patch)
|
||||
delete = patched(api.delete)
|
||||
request = patched(api.request)
|
||||
|
||||
|
||||
def map(requests, prefetch=True):
|
||||
@@ -71,7 +68,7 @@ def map(requests, prefetch=True):
|
||||
:param prefetch: If False, the content will not be downloaded immediately.
|
||||
"""
|
||||
|
||||
jobs = [gevent.spawn(_send, r) for r in requests]
|
||||
jobs = [gevent.spawn(send, r) for r in requests]
|
||||
gevent.joinall(jobs)
|
||||
|
||||
if prefetch:
|
||||
|
||||
146
requests/auth.py
Normal file
146
requests/auth.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.auth
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This module contains the authentication handlers for Requests.
|
||||
"""
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
from base64 import b64encode
|
||||
from urlparse import urlparse
|
||||
|
||||
from .utils import randombytes, parse_dict_header
|
||||
|
||||
|
||||
def http_basic(r, username, password):
|
||||
"""Attaches HTTP Basic Authentication to the given Request object.
|
||||
Arguments should be considered non-positional.
|
||||
|
||||
"""
|
||||
username = str(username)
|
||||
password = str(password)
|
||||
|
||||
auth_s = b64encode('%s:%s' % (username, password))
|
||||
r.headers['Authorization'] = ('Basic %s' % auth_s)
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def http_digest(r, username, password):
|
||||
"""Attaches HTTP Digest Authentication to the given Request object.
|
||||
Arguments should be considered non-positional.
|
||||
"""
|
||||
|
||||
def handle_401(r):
|
||||
"""Takes the given response and tries digest-auth, if needed."""
|
||||
|
||||
s_auth = r.headers.get('www-authenticate', '')
|
||||
|
||||
if 'digest' in s_auth.lower():
|
||||
|
||||
last_nonce = ''
|
||||
nonce_count = 0
|
||||
|
||||
chal = parse_dict_header(s_auth.replace('Digest ', ''))
|
||||
|
||||
realm = chal['realm']
|
||||
nonce = chal['nonce']
|
||||
qop = chal.get('qop')
|
||||
algorithm = chal.get('algorithm', 'MD5')
|
||||
opaque = chal.get('opaque', None)
|
||||
|
||||
algorithm = algorithm.upper()
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if algorithm == 'MD5':
|
||||
H = lambda x: hashlib.md5(x).hexdigest()
|
||||
elif algorithm == 'SHA':
|
||||
H = lambda x: hashlib.sha1(x).hexdigest()
|
||||
# XXX MD5-sess
|
||||
KD = lambda s, d: H("%s:%s" % (s, d))
|
||||
|
||||
if H is None:
|
||||
return None
|
||||
|
||||
# XXX not implemented yet
|
||||
entdig = None
|
||||
p_parsed = urlparse(r.request.url)
|
||||
path = p_parsed.path + p_parsed.query
|
||||
|
||||
A1 = "%s:%s:%s" % (username, realm, password)
|
||||
A2 = "%s:%s" % (r.request.method, path)
|
||||
|
||||
if qop == 'auth':
|
||||
if nonce == last_nonce:
|
||||
nonce_count += 1
|
||||
else:
|
||||
nonce_count = 1
|
||||
last_nonce = nonce
|
||||
|
||||
ncvalue = '%08x' % nonce_count
|
||||
cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
|
||||
nonce_count, nonce, time.ctime(), randombytes(8)))
|
||||
.hexdigest()[:16]
|
||||
)
|
||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
|
||||
respdig = KD(H(A1), noncebit)
|
||||
elif qop is None:
|
||||
respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
|
||||
else:
|
||||
# XXX handle auth-int.
|
||||
return None
|
||||
|
||||
# XXX should the partial digests be encoded too?
|
||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||
'response="%s"' % (username, realm, nonce, path, respdig)
|
||||
if opaque:
|
||||
base += ', opaque="%s"' % opaque
|
||||
if entdig:
|
||||
base += ', digest="%s"' % entdig
|
||||
base += ', algorithm="%s"' % algorithm
|
||||
if qop:
|
||||
base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
|
||||
|
||||
|
||||
r.request.headers['Authorization'] = 'Digest %s' % (base)
|
||||
r.request.send(anyway=True)
|
||||
_r = r.request.response
|
||||
_r.history.append(r)
|
||||
|
||||
return _r
|
||||
|
||||
return r
|
||||
|
||||
r.hooks['response'] = handle_401
|
||||
return r
|
||||
|
||||
|
||||
def dispatch(t):
|
||||
"""Given an auth tuple, return an expanded version."""
|
||||
|
||||
if not t:
|
||||
return t
|
||||
else:
|
||||
t = list(t)
|
||||
|
||||
# Make sure they're passing in something.
|
||||
assert len(t) >= 2
|
||||
|
||||
# If only two items are passed in, assume HTTPBasic.
|
||||
if (len(t) == 2):
|
||||
t.insert(0, 'basic')
|
||||
|
||||
# Allow built-in string referenced auths.
|
||||
if isinstance(t[0], basestring):
|
||||
if t[0] in ('basic', 'forced_basic'):
|
||||
t[0] = http_basic
|
||||
elif t[0] in ('digest',):
|
||||
t[0] = http_digest
|
||||
|
||||
# Return a custom callable.
|
||||
return (t[0], tuple(t[1:]))
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.config
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides the Requests settings feature set.
|
||||
|
||||
"""
|
||||
|
||||
class Settings(object):
|
||||
_singleton = {}
|
||||
|
||||
# attributes with defaults
|
||||
__attrs__ = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Settings, self).__init__()
|
||||
|
||||
self.__dict__ = self._singleton
|
||||
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# new instance of class to call
|
||||
r = self.__class__()
|
||||
|
||||
# cache previous settings for __exit__
|
||||
r.__cache = self.__dict__.copy()
|
||||
map(self.__cache.setdefault, self.__attrs__)
|
||||
|
||||
# set new settings
|
||||
self.__dict__.update(*args, **kwargs)
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
|
||||
def __exit__(self, *args):
|
||||
|
||||
# restore cached copy
|
||||
self.__dict__.update(self.__cache.copy())
|
||||
del self.__cache
|
||||
|
||||
|
||||
def __getattribute__(self, key):
|
||||
if key in object.__getattribute__(self, '__attrs__'):
|
||||
try:
|
||||
return object.__getattribute__(self, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
return object.__getattribute__(self, key)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
settings.base_headers = {'User-Agent': 'python-requests.org'}
|
||||
settings.accept_gzip = True
|
||||
settings.proxies = None
|
||||
settings.verbose = None
|
||||
settings.timeout = None
|
||||
settings.max_redirects = 30
|
||||
|
||||
#: Use socket.setdefaulttimeout() as fallback?
|
||||
settings.timeout_fallback = True
|
||||
@@ -1,27 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.core
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This module implements the main Requests system.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.6.3'
|
||||
__build__ = 0x000603
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
from models import HTTPError
|
||||
from api import *
|
||||
from exceptions import *
|
||||
from sessions import session
|
||||
from status_codes import codes
|
||||
from config import settings
|
||||
39
requests/defaults.py
Normal file
39
requests/defaults.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.defaults
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides the Requests configuration defaults.
|
||||
|
||||
settings parameters:
|
||||
|
||||
- :base_headers: - Sets default User-Agent to `python-requests.org`
|
||||
- :accept_gzip: - Whether or not to accept gzip-compressed data
|
||||
- :proxies: - http proxies?
|
||||
- :verbose: - display verbose information?
|
||||
- :timeout: - timeout time until request terminates
|
||||
- :max_redirects: - maximum number of allowed redirects?
|
||||
- :decode_unicode: - whether or not to accept unicode?
|
||||
|
||||
"""
|
||||
|
||||
from . import __version__
|
||||
|
||||
defaults = dict()
|
||||
|
||||
|
||||
defaults['base_headers'] = {
|
||||
'User-Agent': 'python-requests/%s' % __version__,
|
||||
'Accept-Encoding': ', '.join(('identity', 'deflate', 'compress', 'gzip')),
|
||||
'Accept': '*/*'
|
||||
}
|
||||
|
||||
defaults['proxies'] = {}
|
||||
defaults['verbose'] = None
|
||||
defaults['timeout'] = None
|
||||
defaults['max_redirects'] = 30
|
||||
defaults['decode_unicode'] = True
|
||||
defaults['timeout_fallback'] = True
|
||||
# defaults['keep_alive'] = True
|
||||
# defaults['max_connections'] = 10
|
||||
@@ -2,25 +2,24 @@
|
||||
|
||||
"""
|
||||
requests.exceptions
|
||||
~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains the set of Requests' exceptions.
|
||||
|
||||
"""
|
||||
|
||||
class RequestException(Exception):
|
||||
"""There was an ambiguous exception that occured while handling your
|
||||
"""There was an ambiguous exception that occurred while handling your
|
||||
request."""
|
||||
|
||||
class AuthenticationError(RequestException):
|
||||
"""The authentication credentials provided were invalid."""
|
||||
|
||||
|
||||
class Timeout(RequestException):
|
||||
"""The request timed out."""
|
||||
|
||||
class URLRequired(RequestException):
|
||||
"""A valid URL is required to make a request."""
|
||||
|
||||
class InvalidMethod(RequestException):
|
||||
"""An inappropriate method was attempted."""
|
||||
|
||||
class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
|
||||
@@ -26,7 +26,7 @@ import warnings
|
||||
|
||||
|
||||
def dispatch_hook(key, hooks, hook_data):
|
||||
"""Dipatches a hook dictionary on a given peice of data."""
|
||||
"""Dispatches a hook dictionary on a given piece of data."""
|
||||
|
||||
hooks = hooks or dict()
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -11,18 +12,21 @@ import urllib2
|
||||
import socket
|
||||
import zlib
|
||||
|
||||
from urllib2 import HTTPError
|
||||
from urlparse import urlparse, urlunparse, urljoin
|
||||
from datetime import datetime
|
||||
|
||||
from .config import settings
|
||||
from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler
|
||||
from .auth import dispatch as auth_dispatch
|
||||
from .hooks import dispatch_hook
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .packages.poster.encode import multipart_encode
|
||||
from .packages.poster.streaminghttp import register_openers, get_handlers
|
||||
from .utils import dict_from_cookiejar
|
||||
from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod, TooManyRedirects
|
||||
from .status_codes import codes
|
||||
from .exceptions import Timeout, URLRequired, TooManyRedirects
|
||||
from .monkeys import Request as _Request
|
||||
from .monkeys import HTTPRedirectHandler
|
||||
from .utils import (
|
||||
dict_from_cookiejar, get_unicode_from_response,
|
||||
stream_decode_response_unicode, decode_gzip, stream_decode_gzip)
|
||||
|
||||
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
@@ -30,40 +34,51 @@ REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""The :class:`Request <models.Request>` object. It carries out all functionality of
|
||||
"""The :class:`Request <Request>` object. It carries out all functionality of
|
||||
Requests. Recommended interface is with the Requests functions.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
url=None, headers=dict(), files=None, method=None, data=dict(),
|
||||
params=dict(), auth=None, cookiejar=None, timeout=None, redirect=False,
|
||||
allow_redirects=False, proxies=None, hooks=None):
|
||||
url=None,
|
||||
headers=dict(),
|
||||
files=None,
|
||||
method=None,
|
||||
data=dict(),
|
||||
params=dict(),
|
||||
auth=None,
|
||||
cookies=None,
|
||||
timeout=None,
|
||||
redirect=False,
|
||||
allow_redirects=False,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
config=None):
|
||||
|
||||
#: Float describ the timeout of the request.
|
||||
#: Float describes the timeout of the request.
|
||||
# (Use socket.setdefaulttimeout() as fallback)
|
||||
self.timeout = timeout
|
||||
|
||||
#: Request URL.
|
||||
self.url = url
|
||||
|
||||
#: Dictonary of HTTP Headers to attach to the :class:`Request <models.Request>`.
|
||||
#: Dictionary of HTTP Headers to attach to the :class:`Request <Request>`.
|
||||
self.headers = headers
|
||||
|
||||
#: Dictionary of files to multipart upload (``{filename: content}``).
|
||||
self.files = files
|
||||
|
||||
#: HTTP Method to use. Available: GET, HEAD, PUT, POST, DELETE.
|
||||
#: HTTP Method to use.
|
||||
self.method = method
|
||||
|
||||
#: Dictionary or byte of request body data to attach to the
|
||||
#: :class:`Request <models.Request>`.
|
||||
#: :class:`Request <Request>`.
|
||||
self.data = None
|
||||
|
||||
#: Dictionary or byte of querystring data to attach to the
|
||||
#: :class:`Request <models.Request>`.
|
||||
#: :class:`Request <Request>`.
|
||||
self.params = None
|
||||
|
||||
#: True if :class:`Request <models.Request>` is part of a redirect chain (disables history
|
||||
#: True if :class:`Request <Request>` is part of a redirect chain (disables history
|
||||
#: and HTTPError storage).
|
||||
self.redirect = redirect
|
||||
|
||||
@@ -76,20 +91,19 @@ class Request(object):
|
||||
self.data, self._enc_data = self._encode_params(data)
|
||||
self.params, self._enc_params = self._encode_params(params)
|
||||
|
||||
#: :class:`Response <models.Response>` instance, containing
|
||||
#: :class:`Response <Response>` instance, containing
|
||||
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
|
||||
self.response = Response()
|
||||
|
||||
if isinstance(auth, (list, tuple)):
|
||||
auth = AuthObject(*auth)
|
||||
if not auth:
|
||||
auth = auth_manager.get_auth(self.url)
|
||||
#: Authentication tuple to attach to :class:`Request <Request>`.
|
||||
self._auth = auth
|
||||
self.auth = auth_dispatch(auth)
|
||||
|
||||
#: :class:`AuthObject` to attach to :class:`Request <models.Request>`.
|
||||
self.auth = auth
|
||||
#: CookieJar to attach to :class:`Request <Request>`.
|
||||
self.cookies = cookies
|
||||
|
||||
#: CookieJar to attach to :class:`Request <models.Request>`.
|
||||
self.cookiejar = cookiejar
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = config
|
||||
|
||||
#: True if Request has been sent.
|
||||
self.sent = False
|
||||
@@ -97,50 +111,33 @@ class Request(object):
|
||||
#: Event-handling hooks.
|
||||
self.hooks = hooks
|
||||
|
||||
# Header manipulation and defaults.
|
||||
|
||||
if settings.accept_gzip:
|
||||
settings.base_headers.update({'Accept-Encoding': 'gzip'})
|
||||
|
||||
if headers:
|
||||
headers = CaseInsensitiveDict(self.headers)
|
||||
else:
|
||||
headers = CaseInsensitiveDict()
|
||||
|
||||
for (k, v) in settings.base_headers.items():
|
||||
for (k, v) in self.config.get('base_headers', {}).items():
|
||||
if k not in headers:
|
||||
headers[k] = v
|
||||
|
||||
self.headers = headers
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
|
||||
|
||||
def _checks(self):
|
||||
"""Deterministic checks for consistency."""
|
||||
|
||||
if not self.url:
|
||||
raise URLRequired
|
||||
|
||||
|
||||
def _get_opener(self):
|
||||
"""Creates appropriate opener object for urllib2."""
|
||||
|
||||
_handlers = []
|
||||
|
||||
if self.cookiejar is not None:
|
||||
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
|
||||
|
||||
if self.auth:
|
||||
if not isinstance(self.auth.handler, (urllib2.AbstractBasicAuthHandler, urllib2.AbstractDigestAuthHandler)):
|
||||
# TODO: REMOVE THIS COMPLETELY
|
||||
auth_manager.add_password(self.auth.realm, self.url, self.auth.username, self.auth.password)
|
||||
self.auth.handler = self.auth.handler(auth_manager)
|
||||
auth_manager.add_auth(self.url, self.auth)
|
||||
|
||||
_handlers.append(self.auth.handler)
|
||||
if self.cookies is not None:
|
||||
_handlers.append(urllib2.HTTPCookieProcessor(self.cookies))
|
||||
|
||||
if self.proxies:
|
||||
_handlers.append(urllib2.ProxyHandler(self.proxies))
|
||||
@@ -168,22 +165,23 @@ class Request(object):
|
||||
|
||||
|
||||
def _build_response(self, resp, is_error=False):
|
||||
"""Build internal :class:`Response <models.Response>` object from given response."""
|
||||
"""Build internal :class:`Response <Response>` object
|
||||
from given response.
|
||||
"""
|
||||
|
||||
|
||||
def build(resp):
|
||||
|
||||
response = Response()
|
||||
response.config = self.config
|
||||
response.status_code = getattr(resp, 'code', None)
|
||||
|
||||
try:
|
||||
response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None))
|
||||
response.read = resp.read
|
||||
response._resp = resp
|
||||
response._close = resp.close
|
||||
response.raw = resp
|
||||
|
||||
if self.cookiejar:
|
||||
|
||||
response.cookies = dict_from_cookiejar(self.cookiejar)
|
||||
if self.cookies:
|
||||
response.cookies = dict_from_cookiejar(self.cookies)
|
||||
|
||||
|
||||
except AttributeError:
|
||||
@@ -208,9 +206,9 @@ class Request(object):
|
||||
((r.status_code is codes.see_other) or (self.allow_redirects))
|
||||
):
|
||||
|
||||
r.close()
|
||||
r.raw.close()
|
||||
|
||||
if not len(history) < settings.max_redirects:
|
||||
if not len(history) < self.config.get('max_redirects'):
|
||||
raise TooManyRedirects()
|
||||
|
||||
history.append(r)
|
||||
@@ -234,9 +232,16 @@ class Request(object):
|
||||
method = self.method
|
||||
|
||||
request = Request(
|
||||
url, self.headers, self.files, method,
|
||||
self.data, self.params, self.auth, self.cookiejar,
|
||||
redirect=True
|
||||
url=url,
|
||||
headers=self.headers,
|
||||
files=self.files,
|
||||
method=method,
|
||||
# data=self.data,
|
||||
# params=self.params,
|
||||
auth=self._auth,
|
||||
cookies=self.cookies,
|
||||
redirect=True,
|
||||
config=self.config
|
||||
)
|
||||
request.send()
|
||||
r = request.response
|
||||
@@ -257,8 +262,8 @@ class Request(object):
|
||||
|
||||
Otherwise, assumes the data is already encoded appropriately, and
|
||||
returns it twice.
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(data, 'items'):
|
||||
result = []
|
||||
for k, vs in data.items():
|
||||
@@ -301,33 +306,44 @@ class Request(object):
|
||||
already been sent.
|
||||
"""
|
||||
|
||||
self._checks()
|
||||
success = False
|
||||
# Some people...
|
||||
if not self.url:
|
||||
raise URLRequired
|
||||
|
||||
# Logging
|
||||
if settings.verbose:
|
||||
settings.verbose.write('%s %s %s\n' % (
|
||||
if self.config.get('verbose'):
|
||||
self.config.get('verbose').write('%s %s %s\n' % (
|
||||
datetime.now().isoformat(), self.method, self.url
|
||||
))
|
||||
|
||||
|
||||
# Build the URL
|
||||
url = self._build_url()
|
||||
if self.method in ('GET', 'HEAD', 'DELETE'):
|
||||
req = _Request(url, method=self.method)
|
||||
|
||||
# Attach uploaded files.
|
||||
if self.files:
|
||||
register_openers()
|
||||
|
||||
# Add form-data to the multipart.
|
||||
if self.data:
|
||||
self.files.update(self.data)
|
||||
|
||||
data, headers = multipart_encode(self.files)
|
||||
|
||||
else:
|
||||
data = self._enc_data
|
||||
headers = {}
|
||||
|
||||
if self.files:
|
||||
register_openers()
|
||||
if self.auth:
|
||||
auth_func, auth_args = self.auth
|
||||
|
||||
if self.data:
|
||||
self.files.update(self.data)
|
||||
r = auth_func(self, *auth_args)
|
||||
|
||||
datagen, headers = multipart_encode(self.files)
|
||||
req = _Request(url, data=datagen, headers=headers, method=self.method)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
else:
|
||||
req = _Request(url, data=self._enc_data, method=self.method)
|
||||
# Build the Urllib2 Request.
|
||||
req = _Request(url, data=data, headers=headers, method=self.method)
|
||||
|
||||
# Add the headers to the request.
|
||||
if self.headers:
|
||||
for k,v in self.headers.iteritems():
|
||||
req.add_header(k, v)
|
||||
@@ -345,19 +361,19 @@ class Request(object):
|
||||
if not 'timeout' in str(err):
|
||||
raise
|
||||
|
||||
if settings.timeout_fallback:
|
||||
if self.config.get('timeout_fallback'):
|
||||
# fall-back and use global socket timeout (This is not thread-safe!)
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
socket.setdefaulttimeout(self.timeout)
|
||||
|
||||
resp = opener(req)
|
||||
|
||||
if settings.timeout_fallback:
|
||||
# restore gobal timeout
|
||||
if self.config.get('timeout_fallback'):
|
||||
# restore global timeout
|
||||
socket.setdefaulttimeout(old_timeout)
|
||||
|
||||
if self.cookiejar is not None:
|
||||
self.cookiejar.extract_cookies(resp, req)
|
||||
if self.cookies is not None:
|
||||
self.cookies.extract_cookies(resp, req)
|
||||
|
||||
except (urllib2.HTTPError, urllib2.URLError), why:
|
||||
if hasattr(why, 'reason'):
|
||||
@@ -368,7 +384,6 @@ class Request(object):
|
||||
|
||||
self._build_response(why, is_error=True)
|
||||
|
||||
|
||||
else:
|
||||
self._build_response(resp)
|
||||
self.response.ok = True
|
||||
@@ -376,43 +391,62 @@ class Request(object):
|
||||
|
||||
self.sent = self.response.ok
|
||||
|
||||
# Response manipulation hook.
|
||||
self.response = dispatch_hook('response', self.hooks, self.response)
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', self.hooks, self)
|
||||
self.__dict__.update(r.__dict__)
|
||||
|
||||
return self.sent
|
||||
|
||||
|
||||
|
||||
class Response(object):
|
||||
"""The core :class:`Response <models.Response>` object. All
|
||||
:class:`Request <models.Request>` objects contain a
|
||||
:class:`response <models.Response>` attribute, which is an instance
|
||||
"""The core :class:`Response <Response>` object. All
|
||||
:class:`Request <Request>` objects contain a
|
||||
:class:`response <Response>` attribute, which is an instance
|
||||
of this class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
#: Raw content of the response, in bytes.
|
||||
#: If ``content-encoding`` of response was set to ``gzip``, the
|
||||
#: response data will be automatically deflated.
|
||||
|
||||
self._content = None
|
||||
self._content_consumed = False
|
||||
|
||||
#: Integer Code of responded HTTP Status.
|
||||
self.status_code = None
|
||||
|
||||
#: Case-insensitive Dictionary of Response Headers.
|
||||
#: For example, ``headers['content-encoding']`` will return the
|
||||
#: value of a ``'Content-Encoding'`` response header.
|
||||
self.headers = CaseInsensitiveDict()
|
||||
|
||||
#: File-like object representation of response (for advanced usage).
|
||||
self.raw = None
|
||||
|
||||
#: Final URL location of Response.
|
||||
self.url = None
|
||||
#: True if no :attr:`error` occured.
|
||||
|
||||
#: True if no :attr:`error` occurred.
|
||||
self.ok = False
|
||||
#: Resulting :class:`HTTPError` of request, if one occured.
|
||||
|
||||
#: Resulting :class:`HTTPError` of request, if one occurred.
|
||||
self.error = None
|
||||
#: A list of :class:`Response <models.Response>` objects from
|
||||
|
||||
#: A list of :class:`Response <Response>` objects from
|
||||
#: the history of the Request. Any redirect responses will end
|
||||
#: up here.
|
||||
self.history = []
|
||||
#: The Request that created the Response.
|
||||
|
||||
#: The :class:`Request <Request>` that created the Response.
|
||||
self.request = None
|
||||
|
||||
#: A dictionary of Cookies the server sent back.
|
||||
self.cookies = None
|
||||
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = None
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
@@ -420,206 +454,72 @@ class Response(object):
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
|
||||
return not self.error
|
||||
|
||||
def iter_content(self, chunk_size=10 * 1024, decode_unicode=None):
|
||||
"""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
|
||||
length of each item returned as decoding can take place.
|
||||
"""
|
||||
if self._content_consumed:
|
||||
raise RuntimeError('The content for this response was '
|
||||
'already consumed')
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Read and returns the full stream when accessing to :attr: `content`"""
|
||||
if name == 'content':
|
||||
if self._content is not None:
|
||||
return self._content
|
||||
self._content = self.read()
|
||||
if self.headers.get('content-encoding', '') == 'gzip':
|
||||
try:
|
||||
self._content = zlib.decompress(self._content, 16+zlib.MAX_WBITS)
|
||||
except zlib.error:
|
||||
pass
|
||||
def generate():
|
||||
while 1:
|
||||
chunk = self.raw.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
self._content_consumed = True
|
||||
gen = generate()
|
||||
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 decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
return gen
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""Content of the response, in bytes or unicode
|
||||
(if available).
|
||||
"""
|
||||
|
||||
if self._content is not None:
|
||||
return self._content
|
||||
else:
|
||||
raise AttributeError
|
||||
|
||||
if self._content_consumed:
|
||||
raise RuntimeError('The content for this response was '
|
||||
'already consumed')
|
||||
|
||||
# Read the contents.
|
||||
try:
|
||||
self._content = self.raw.read()
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
# Decode GZip'd content.
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
try:
|
||||
self._content = decode_gzip(self._content)
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
# Decode unicode content.
|
||||
if self.config.get('decode_unicode'):
|
||||
self._content = get_unicode_from_response(self)
|
||||
|
||||
self._content_consumed = True
|
||||
return self._content
|
||||
|
||||
|
||||
def raise_for_status(self):
|
||||
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occured."""
|
||||
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""
|
||||
if self.error:
|
||||
raise self.error
|
||||
|
||||
|
||||
def close(self):
|
||||
if self._resp.fp is not None and hasattr(self._resp.fp, '_sock'):
|
||||
self._resp.fp._sock.recv = None
|
||||
self._close()
|
||||
|
||||
class AuthManager(object):
|
||||
"""Requests Authentication Manager."""
|
||||
|
||||
def __new__(cls):
|
||||
singleton = cls.__dict__.get('__singleton__')
|
||||
if singleton is not None:
|
||||
return singleton
|
||||
|
||||
cls.__singleton__ = singleton = object.__new__(cls)
|
||||
|
||||
return singleton
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.passwd = {}
|
||||
self._auth = {}
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<AuthManager [%s]>' % (self.method)
|
||||
|
||||
|
||||
def add_auth(self, uri, auth):
|
||||
"""Registers AuthObject to AuthManager."""
|
||||
|
||||
uri = self.reduce_uri(uri, False)
|
||||
|
||||
# try to make it an AuthObject
|
||||
if not isinstance(auth, AuthObject):
|
||||
try:
|
||||
auth = AuthObject(*auth)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self._auth[uri] = auth
|
||||
|
||||
|
||||
def add_password(self, realm, uri, user, passwd):
|
||||
"""Adds password to AuthManager."""
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
reduced_uri = tuple([self.reduce_uri(u, False) for u in uri])
|
||||
|
||||
if reduced_uri not in self.passwd:
|
||||
self.passwd[reduced_uri] = {}
|
||||
self.passwd[reduced_uri] = (user, passwd)
|
||||
|
||||
|
||||
def find_user_password(self, realm, authuri):
|
||||
for uris, authinfo in self.passwd.iteritems():
|
||||
reduced_authuri = self.reduce_uri(authuri, False)
|
||||
for uri in uris:
|
||||
if self.is_suburi(uri, reduced_authuri):
|
||||
return authinfo
|
||||
|
||||
return (None, None)
|
||||
|
||||
|
||||
def get_auth(self, uri):
|
||||
(in_domain, in_path) = self.reduce_uri(uri, False)
|
||||
|
||||
for domain, path, authority in (
|
||||
(i[0][0], i[0][1], i[1]) for i in self._auth.iteritems()
|
||||
):
|
||||
if in_domain == domain:
|
||||
if path in in_path:
|
||||
return authority
|
||||
|
||||
|
||||
def reduce_uri(self, uri, default_port=True):
|
||||
"""Accept authority or URI and extract only the authority and path."""
|
||||
|
||||
# note HTTP URLs do not have a userinfo component
|
||||
parts = urllib2.urlparse.urlsplit(uri)
|
||||
|
||||
if parts[1]:
|
||||
# URI
|
||||
scheme = parts[0]
|
||||
authority = parts[1]
|
||||
path = parts[2] or '/'
|
||||
else:
|
||||
# host or host:port
|
||||
scheme = None
|
||||
authority = uri
|
||||
path = '/'
|
||||
|
||||
host, port = urllib2.splitport(authority)
|
||||
|
||||
if default_port and port is None and scheme is not None:
|
||||
dport = {"http": 80,
|
||||
"https": 443,
|
||||
}.get(scheme)
|
||||
if dport is not None:
|
||||
authority = "%s:%d" % (host, dport)
|
||||
|
||||
return authority, path
|
||||
|
||||
|
||||
def is_suburi(self, base, test):
|
||||
"""Check if test is below base in a URI tree
|
||||
|
||||
Both args must be URIs in reduced form.
|
||||
"""
|
||||
if base == test:
|
||||
return True
|
||||
if base[0] != test[0]:
|
||||
return False
|
||||
common = urllib2.posixpath.commonprefix((base[1], test[1]))
|
||||
if len(common) == len(base[1]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def empty(self):
|
||||
self.passwd = {}
|
||||
|
||||
|
||||
def remove(self, uri, realm=None):
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
for default_port in True, False:
|
||||
reduced_uri = tuple([self.reduce_uri(u, default_port) for u in uri])
|
||||
del self.passwd[reduced_uri][realm]
|
||||
|
||||
|
||||
def __contains__(self, uri):
|
||||
# uri could be a single URI or a sequence
|
||||
if isinstance(uri, basestring):
|
||||
uri = [uri]
|
||||
|
||||
uri = tuple([self.reduce_uri(u, False) for u in uri])
|
||||
|
||||
if uri in self.passwd:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
auth_manager = AuthManager()
|
||||
|
||||
|
||||
|
||||
class AuthObject(object):
|
||||
"""The :class:`AuthObject` is a simple HTTP Authentication token. When
|
||||
given to a Requests function, it enables Basic HTTP Authentication for that
|
||||
Request. You can also enable Authorization for domain realms with AutoAuth.
|
||||
See AutoAuth for more details.
|
||||
|
||||
:param username: Username to authenticate with.
|
||||
:param password: Password for given username.
|
||||
:param realm: (optional) the realm this auth applies to
|
||||
:param handler: (optional) basic || digest || proxy_basic || proxy_digest
|
||||
"""
|
||||
|
||||
_handlers = {
|
||||
'basic': HTTPBasicAuthHandler,
|
||||
'forced_basic': HTTPForcedBasicAuthHandler,
|
||||
'digest': HTTPDigestAuthHandler,
|
||||
'proxy_basic': urllib2.ProxyBasicAuthHandler,
|
||||
'proxy_digest': urllib2.ProxyDigestAuthHandler
|
||||
}
|
||||
|
||||
def __init__(self, username, password, handler='forced_basic', realm=None):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
|
||||
if isinstance(handler, basestring):
|
||||
self.handler = self._handlers.get(handler.lower(), HTTPForcedBasicAuthHandler)
|
||||
else:
|
||||
self.handler = handler
|
||||
|
||||
@@ -29,120 +29,11 @@ class Request(urllib2.Request):
|
||||
|
||||
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||
"""HTTP Redirect handler."""
|
||||
def http_error_301(self, req, fp, code, msg, headers):
|
||||
def _pass(self, req, fp, code, msg, headers):
|
||||
pass
|
||||
|
||||
http_error_302 = http_error_303 = http_error_307 = http_error_301
|
||||
http_error_302 = _pass
|
||||
http_error_303 = _pass
|
||||
http_error_307 = _pass
|
||||
http_error_301 = _pass
|
||||
|
||||
|
||||
|
||||
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
|
||||
"""HTTP Basic Auth Handler with authentication loop fixes."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
|
||||
self.retried_req = None
|
||||
self.retried = 0
|
||||
|
||||
|
||||
def reset_retry_count(self):
|
||||
# Python 2.6.5 will call this on 401 or 407 errors and thus loop
|
||||
# forever. We disable reset_retry_count completely and reset in
|
||||
# http_error_auth_reqed instead.
|
||||
pass
|
||||
|
||||
|
||||
def http_error_auth_reqed(self, auth_header, host, req, headers):
|
||||
# Reset the retry counter once for each request.
|
||||
if req is not self.retried_req:
|
||||
self.retried_req = req
|
||||
self.retried = 0
|
||||
|
||||
return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
|
||||
self, auth_header, host, req, headers
|
||||
)
|
||||
|
||||
|
||||
|
||||
class HTTPForcedBasicAuthHandler(HTTPBasicAuthHandler):
|
||||
"""HTTP Basic Auth Handler with forced Authentication."""
|
||||
|
||||
auth_header = 'Authorization'
|
||||
rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+'
|
||||
'realm=(["\'])(.*?)\\2', re.I)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
def http_error_401(self, req, fp, code, msg, headers):
|
||||
url = req.get_full_url()
|
||||
response = self._http_error_auth_reqed('www-authenticate', url, req, headers)
|
||||
self.reset_retry_count()
|
||||
return response
|
||||
|
||||
http_error_404 = http_error_401
|
||||
|
||||
|
||||
def _http_error_auth_reqed(self, authreq, host, req, headers):
|
||||
|
||||
authreq = headers.get(authreq, None)
|
||||
|
||||
if self.retried > 5:
|
||||
# retry sending the username:password 5 times before failing.
|
||||
raise urllib2.HTTPError(req.get_full_url(), 401, "basic auth failed",
|
||||
headers, None)
|
||||
else:
|
||||
self.retried += 1
|
||||
|
||||
if authreq:
|
||||
|
||||
mo = self.rx.search(authreq)
|
||||
|
||||
if mo:
|
||||
scheme, quote, realm = mo.groups()
|
||||
|
||||
if scheme.lower() == 'basic':
|
||||
response = self.retry_http_basic_auth(host, req, realm)
|
||||
|
||||
if response and response.code not in (401, 404):
|
||||
self.retried = 0
|
||||
return response
|
||||
else:
|
||||
response = self.retry_http_basic_auth(host, req, 'Realm')
|
||||
|
||||
if response and response.code not in (401, 404):
|
||||
self.retried = 0
|
||||
return response
|
||||
|
||||
|
||||
|
||||
class HTTPDigestAuthHandler(urllib2.HTTPDigestAuthHandler):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
|
||||
self.retried_req = None
|
||||
|
||||
def reset_retry_count(self):
|
||||
# Python 2.6.5 will call this on 401 or 407 errors and thus loop
|
||||
# forever. We disable reset_retry_count completely and reset in
|
||||
# http_error_auth_reqed instead.
|
||||
pass
|
||||
|
||||
def http_error_auth_reqed(self, auth_header, host, req, headers):
|
||||
# Reset the retry counter once for each request.
|
||||
if req is not self.retried_req:
|
||||
self.retried_req = req
|
||||
self.retried = 0
|
||||
# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
|
||||
# it doesn't know about the auth type requested. This can happen if
|
||||
# somebody is using BasicAuth and types a bad password.
|
||||
|
||||
try:
|
||||
return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
|
||||
self, auth_header, host, req, headers)
|
||||
except ValueError, inst:
|
||||
arg = inst.args[0]
|
||||
if arg.startswith("AbstractDigestAuthHandler doesn't know "):
|
||||
return
|
||||
raise
|
||||
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.monkeys
|
||||
"""
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
"""
|
||||
requests.session
|
||||
~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a Session object to manage and persist settings across
|
||||
requests (cookies, auth, proxies).
|
||||
@@ -11,30 +11,78 @@ requests (cookies, auth, proxies).
|
||||
|
||||
import cookielib
|
||||
|
||||
from . import api
|
||||
from .utils import add_dict_to_cookiejar
|
||||
from .defaults import defaults
|
||||
from .models import Request
|
||||
from .hooks import dispatch_hook
|
||||
from .utils import add_dict_to_cookiejar, cookiejar_from_dict, header_expand
|
||||
|
||||
|
||||
def merge_kwargs(local_kwarg, default_kwarg):
|
||||
"""Merges kwarg dictionaries.
|
||||
|
||||
If a local key in the dictionary is set to None, it will be removed.
|
||||
"""
|
||||
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
|
||||
if isinstance(local_kwarg, basestring):
|
||||
return local_kwarg
|
||||
|
||||
if local_kwarg is None:
|
||||
return default_kwarg
|
||||
|
||||
# Bypass if not a dictionary (e.g. timeout)
|
||||
if not hasattr(default_kwarg, 'items'):
|
||||
return local_kwarg
|
||||
|
||||
# Update new values.
|
||||
kwargs = default_kwarg.copy()
|
||||
kwargs.update(local_kwarg)
|
||||
|
||||
# Remove keys that are set to None.
|
||||
for (k,v) in local_kwarg.items():
|
||||
if v is None:
|
||||
del kwargs[k]
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
class Session(object):
|
||||
"""A Requests session."""
|
||||
|
||||
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks']
|
||||
__attrs__ = [
|
||||
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
||||
'params', 'config']
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self,
|
||||
headers=None,
|
||||
cookies=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
params=None,
|
||||
config=None,
|
||||
keep_alive=True):
|
||||
|
||||
self.headers = headers or {}
|
||||
self.cookies = cookies or {}
|
||||
self.auth = auth
|
||||
self.timeout = timeout
|
||||
self.proxies = proxies or {}
|
||||
self.hooks = hooks or {}
|
||||
self.params = params or {}
|
||||
self.config = config or {}
|
||||
self.keep_alive = keep_alive
|
||||
|
||||
for (k, v) in defaults.items():
|
||||
self.config.setdefault(k, v)
|
||||
|
||||
# Set up a CookieJar to be used by default
|
||||
self.cookies = cookielib.FileCookieJar()
|
||||
|
||||
# Map args from kwargs to instance-local variables
|
||||
map(lambda k, v: (k in self.__attrs__) and setattr(self, k, v),
|
||||
kwargs.iterkeys(), kwargs.itervalues())
|
||||
|
||||
# Map and wrap requests.api methods
|
||||
self._map_api_methods()
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<requests-client at 0x%x>' % (id(self))
|
||||
|
||||
@@ -42,43 +90,160 @@ class Session(object):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
# print args
|
||||
pass
|
||||
|
||||
def request(self, 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,
|
||||
config=None):
|
||||
|
||||
def _map_api_methods(self):
|
||||
"""Reads each available method from requests.api and decorates
|
||||
them with a wrapper, which inserts any instance-local attributes
|
||||
(from __attrs__) that have been set, combining them with **kwargs.
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
: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 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.
|
||||
"""
|
||||
|
||||
def pass_args(func):
|
||||
def wrapper_func(*args, **kwargs):
|
||||
inst_attrs = dict((k, v) for k, v in self.__dict__.iteritems()
|
||||
if k in self.__attrs__)
|
||||
# Combine instance-local values with kwargs values, with
|
||||
# priority to values in kwargs
|
||||
kwargs = dict(inst_attrs.items() + kwargs.items())
|
||||
method = str(method).upper()
|
||||
|
||||
# If a session request has a cookie_dict, inject the
|
||||
# values into the existing CookieJar instead.
|
||||
if isinstance(kwargs.get('cookies', None), dict):
|
||||
kwargs['cookies'] = add_dict_to_cookiejar(
|
||||
inst_attrs['cookies'], kwargs['cookies']
|
||||
)
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
if kwargs.get('headers', None) and inst_attrs.get('headers', None):
|
||||
kwargs['headers'].update(inst_attrs['headers'])
|
||||
if isinstance(cookies, dict):
|
||||
cookies = add_dict_to_cookiejar(self.cookies, cookies)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper_func
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Expand header values
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
args = dict(
|
||||
method=method,
|
||||
url=url,
|
||||
data=data,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
files=files,
|
||||
auth=auth,
|
||||
hooks=hooks,
|
||||
timeout=timeout,
|
||||
allow_redirects=allow_redirects,
|
||||
proxies=proxies,
|
||||
config=config
|
||||
)
|
||||
|
||||
for attr in self.__attrs__:
|
||||
session_val = getattr(self, attr, None)
|
||||
local_val = args.get(attr)
|
||||
|
||||
args[attr] = merge_kwargs(local_val, session_val)
|
||||
|
||||
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('args', args['hooks'], args)
|
||||
|
||||
r = Request(**args)
|
||||
|
||||
# Don't send if asked nicely.
|
||||
if not return_response:
|
||||
return r
|
||||
|
||||
# Send the HTTP Request.
|
||||
r.send()
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('GET', url, **kwargs)
|
||||
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('HEAD', url, **kwargs)
|
||||
|
||||
|
||||
def post(self, url, data='', **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('post', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def put(self, url, data='', **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(self, url, data='', **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('patch', url, data='', **kwargs)
|
||||
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('delete', url, **kwargs)
|
||||
|
||||
# Map and decorate each function available in requests.api
|
||||
map(lambda fn: setattr(self, fn, pass_args(getattr(api, fn))),
|
||||
api.__all__)
|
||||
|
||||
|
||||
def session(**kwargs):
|
||||
"""Returns a :class:`Session` for context-managment."""
|
||||
"""Returns a :class:`Session` for context-management."""
|
||||
|
||||
return Session(**kwargs)
|
||||
@@ -47,15 +47,18 @@ _codes = {
|
||||
412: ('precondition_failed', 'precondition'),
|
||||
413: ('request_entity_too_large',),
|
||||
414: ('request_uri_too_large',),
|
||||
415: ('unspported_media_type', 'unspported_media', 'media_type'),
|
||||
415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
|
||||
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
|
||||
417: ('expectation_failed',),
|
||||
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
|
||||
422: ('unprocessable_entity', 'unprocessable'),
|
||||
423: ('locked',),
|
||||
424: ('failed_depdendency', 'depdendency'),
|
||||
424: ('failed_dependency', 'dependency'),
|
||||
425: ('unordered_collection', 'unordered'),
|
||||
426: ('upgrade_required', 'upgrade'),
|
||||
428: ('precondition_required', 'precondition'),
|
||||
429: ('too_many_requests', 'too_many'),
|
||||
431: ('header_fields_too_large', 'fields_too_large'),
|
||||
444: ('no_response', 'none'),
|
||||
449: ('retry_with', 'retry'),
|
||||
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
requests.structures
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Datastructures that power Requests.
|
||||
Data structures that power Requests.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -9,15 +9,178 @@ that are also useful for external consumption.
|
||||
|
||||
"""
|
||||
|
||||
import cgi
|
||||
import codecs
|
||||
import cookielib
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import zlib
|
||||
|
||||
from urllib2 import parse_http_list as _parse_list_header
|
||||
|
||||
|
||||
def dict_from_cookiejar(cookiejar):
|
||||
"""Returns a key/value dictionary from a CookieJar."""
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def parse_list_header(value):
|
||||
"""Parse lists as described by RFC 2068 Section 2.
|
||||
|
||||
In particular, parse comma-separated lists where the elements of
|
||||
the list may include quoted-strings. A quoted-string could
|
||||
contain a comma. A non-quoted string could have quotes in the
|
||||
middle. Quotes are removed automatically after parsing.
|
||||
|
||||
It basically works like :func:`parse_set_header` just that items
|
||||
may appear multiple times and case sensitivity is preserved.
|
||||
|
||||
The return value is a standard :class:`list`:
|
||||
|
||||
>>> parse_list_header('token, "quoted value"')
|
||||
['token', 'quoted value']
|
||||
|
||||
To create a header from the :class:`list` again, use the
|
||||
:func:`dump_header` function.
|
||||
|
||||
:param value: a string with a list header.
|
||||
:return: :class:`list`
|
||||
"""
|
||||
result = []
|
||||
for item in _parse_list_header(value):
|
||||
if item[:1] == item[-1:] == '"':
|
||||
item = unquote_header_value(item[1:-1])
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def parse_dict_header(value):
|
||||
"""Parse lists of key, value pairs as described by RFC 2068 Section 2 and
|
||||
convert them into a python dict:
|
||||
|
||||
>>> d = parse_dict_header('foo="is a fish", bar="as well"')
|
||||
>>> type(d) is dict
|
||||
True
|
||||
>>> sorted(d.items())
|
||||
[('bar', 'as well'), ('foo', 'is a fish')]
|
||||
|
||||
If there is no value for a key it will be `None`:
|
||||
|
||||
>>> parse_dict_header('key_without_value')
|
||||
{'key_without_value': None}
|
||||
|
||||
To create a header from the :class:`dict` again, use the
|
||||
:func:`dump_header` function.
|
||||
|
||||
:param value: a string with a dict header.
|
||||
:return: :class:`dict`
|
||||
"""
|
||||
result = {}
|
||||
for item in _parse_list_header(value):
|
||||
if '=' not in item:
|
||||
result[item] = None
|
||||
continue
|
||||
name, value = item.split('=', 1)
|
||||
if value[:1] == value[-1:] == '"':
|
||||
value = unquote_header_value(value[1:-1])
|
||||
result[name] = value
|
||||
return result
|
||||
|
||||
|
||||
# From mitsuhiko/werkzeug (used with permission).
|
||||
def unquote_header_value(value, is_filename=False):
|
||||
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
|
||||
This does not use the real unquoting but what browsers are actually
|
||||
using for quoting.
|
||||
|
||||
:param value: the header value to unquote.
|
||||
"""
|
||||
if value and value[0] == value[-1] == '"':
|
||||
# this is not the real unquoting, but fixing this so that the
|
||||
# RFC is met will result in bugs with internet explorer and
|
||||
# probably some other browsers as well. IE for example is
|
||||
# uploading files with "C:\foo\bar.txt" as filename
|
||||
value = value[1:-1]
|
||||
|
||||
# if this is a filename and the starting characters look like
|
||||
# a UNC path, then just return the value without quotes. Using the
|
||||
# replace sequence below on a UNC path has the effect of turning
|
||||
# the leading double slash into a single slash and then
|
||||
# _fix_ie_filename() doesn't work correctly. See #458.
|
||||
if not is_filename or value[:2] != '\\\\':
|
||||
return value.replace('\\\\', '\\').replace('\\"', '"')
|
||||
return value
|
||||
|
||||
|
||||
def header_expand(headers):
|
||||
"""Returns an HTTP Header value string from a dictionary.
|
||||
|
||||
Example expansion::
|
||||
|
||||
{'text/x-dvi': {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}, 'text/x-c': {}}
|
||||
# Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
|
||||
|
||||
(('text/x-dvi', {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}), ('text/x-c', {}))
|
||||
# Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
|
||||
"""
|
||||
|
||||
collector = []
|
||||
|
||||
if isinstance(headers, dict):
|
||||
headers = headers.items()
|
||||
|
||||
elif isinstance(headers, basestring):
|
||||
return headers
|
||||
|
||||
for i, (value, params) in enumerate(headers):
|
||||
|
||||
_params = []
|
||||
|
||||
for (p_k, p_v) in params.items():
|
||||
|
||||
_params.append('%s=%s' % (p_k, p_v))
|
||||
|
||||
collector.append(value)
|
||||
collector.append('; ')
|
||||
|
||||
if len(params):
|
||||
|
||||
collector.append('; '.join(_params))
|
||||
|
||||
if not len(headers) == i+1:
|
||||
collector.append(', ')
|
||||
|
||||
|
||||
# Remove trailing separators.
|
||||
if collector[-1] in (', ', '; '):
|
||||
del collector[-1]
|
||||
|
||||
return ''.join(collector)
|
||||
|
||||
|
||||
|
||||
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:
|
||||
L = [chr(random.randrange(0, 256)) for i in range(n)]
|
||||
return "".join(L)
|
||||
|
||||
|
||||
def dict_from_cookiejar(cj):
|
||||
"""Returns a key/value dictionary from a CookieJar.
|
||||
|
||||
:param cj: CookieJar object to extract cookies from.
|
||||
"""
|
||||
|
||||
cookie_dict = {}
|
||||
|
||||
for _, cookies in cookiejar._cookies.items():
|
||||
for _, cookies in cj._cookies.items():
|
||||
for _, cookies in cookies.items():
|
||||
for cookie in cookies.values():
|
||||
# print cookie
|
||||
@@ -27,7 +190,10 @@ def dict_from_cookiejar(cookiejar):
|
||||
|
||||
|
||||
def cookiejar_from_dict(cookie_dict):
|
||||
"""Returns a CookieJar from a key/value dictionary."""
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
# return cookiejar if one was passed in
|
||||
if isinstance(cookie_dict, cookielib.CookieJar):
|
||||
@@ -42,7 +208,11 @@ def cookiejar_from_dict(cookie_dict):
|
||||
|
||||
|
||||
def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
"""Returns a CookieJar from a key/value dictionary."""
|
||||
"""Returns a CookieJar from a key/value dictionary.
|
||||
|
||||
:param cj: CookieJar to insert cookies into.
|
||||
:param cookie_dict: Dict of key/values to insert into CookieJar.
|
||||
"""
|
||||
|
||||
for k, v in cookie_dict.items():
|
||||
|
||||
@@ -70,3 +240,124 @@ def add_dict_to_cookiejar(cj, cookie_dict):
|
||||
cj.set_cookie(cookie)
|
||||
|
||||
return cj
|
||||
|
||||
|
||||
def get_encodings_from_content(content):
|
||||
"""Returns encodings from given content string.
|
||||
|
||||
:param content: bytestring to extract encodings from.
|
||||
"""
|
||||
|
||||
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
|
||||
|
||||
return charset_re.findall(content)
|
||||
|
||||
|
||||
def get_encoding_from_headers(headers):
|
||||
"""Returns encodings from given HTTP Header Dict.
|
||||
|
||||
:param headers: dictionary to extract encoding from.
|
||||
"""
|
||||
|
||||
content_type = headers.get('content-type')
|
||||
|
||||
if not content_type:
|
||||
return None
|
||||
|
||||
content_type, params = cgi.parse_header(content_type)
|
||||
|
||||
if 'charset' in params:
|
||||
return params['charset'].strip("'\"")
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def stream_decode_response_unicode(iterator, r):
|
||||
"""Stream decodes a iterator."""
|
||||
encoding = get_encoding_from_headers(r.headers)
|
||||
if encoding is None:
|
||||
for item in iterator:
|
||||
yield item
|
||||
return
|
||||
|
||||
decoder = codecs.getincrementaldecoder(encoding)(errors='replace')
|
||||
for chunk in iterator:
|
||||
rv = decoder.decode(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
rv = decoder.decode('', final=True)
|
||||
if rv:
|
||||
yield rv
|
||||
|
||||
|
||||
def get_unicode_from_response(r):
|
||||
"""Returns the requested content back in unicode.
|
||||
|
||||
:param r: Response object to get unicode content from.
|
||||
|
||||
Tried:
|
||||
|
||||
1. charset from content-type
|
||||
|
||||
2. every encodings from ``<meta ... charset=XXX>``
|
||||
|
||||
3. fall back and replace all unicode characters
|
||||
|
||||
"""
|
||||
|
||||
tried_encodings = []
|
||||
|
||||
# Try charset from content-type
|
||||
encoding = get_encoding_from_headers(r.headers)
|
||||
|
||||
if encoding:
|
||||
try:
|
||||
return unicode(r.content, encoding)
|
||||
except UnicodeError:
|
||||
tried_encodings.append(encoding)
|
||||
|
||||
# Fall back:
|
||||
try:
|
||||
return unicode(r.content, encoding, errors='replace')
|
||||
except TypeError:
|
||||
return r.content
|
||||
|
||||
|
||||
def decode_gzip(content):
|
||||
"""Return gzip-decoded string.
|
||||
|
||||
:param content: bytestring to gzip-decode.
|
||||
"""
|
||||
|
||||
return zlib.decompress(content, 16 + zlib.MAX_WBITS)
|
||||
|
||||
|
||||
def stream_decode_gzip(iterator):
|
||||
"""Stream decodes a gzip-encoded iterator"""
|
||||
try:
|
||||
dec = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||
for chunk in iterator:
|
||||
rv = dec.decompress(chunk)
|
||||
if rv:
|
||||
yield rv
|
||||
buf = dec.decompress('')
|
||||
rv = buf + dec.flush()
|
||||
if rv:
|
||||
yield rv
|
||||
except zlib.error:
|
||||
pass
|
||||
|
||||
192
test_requests.py
192
test_requests.py
@@ -3,24 +3,26 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import unittest
|
||||
import time
|
||||
import cookielib
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import envoy
|
||||
from urllib2 import HTTPError
|
||||
|
||||
try:
|
||||
import omnijson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from requests.sessions import Session
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
|
||||
|
||||
HTTPBIN_URL = 'http://httpbin.ep.io/'
|
||||
HTTPSBIN_URL = 'https://httpbin.ep.io/'
|
||||
|
||||
# HTTPBIN_URL = 'http://staging.httpbin.org/'
|
||||
# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/'
|
||||
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
|
||||
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
|
||||
|
||||
|
||||
def httpbin(*suffix):
|
||||
@@ -29,15 +31,9 @@ def httpbin(*suffix):
|
||||
return HTTPBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
def httpsbin(*suffix):
|
||||
"""Returns url for HTTPSBIN resource."""
|
||||
|
||||
return HTTPSBIN_URL + '/'.join(suffix)
|
||||
|
||||
|
||||
SERVICES = (httpbin, httpsbin)
|
||||
|
||||
SERVICES = (httpbin, )
|
||||
|
||||
_httpbin = False
|
||||
|
||||
class RequestsTestSuite(unittest.TestCase):
|
||||
"""Requests test cases."""
|
||||
@@ -46,27 +42,52 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
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."""
|
||||
pass
|
||||
# self.httpbin.kill()
|
||||
|
||||
def test_entry_points(self):
|
||||
import requests
|
||||
|
||||
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('/'))
|
||||
r = requests.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTPS_200_OK_GET(self):
|
||||
r = requests.get(httpsbin('/'))
|
||||
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'}
|
||||
@@ -105,12 +126,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
|
||||
def test_HTTP_200_OK_HEAD(self):
|
||||
r = requests.head(httpbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTPS_200_OK_HEAD(self):
|
||||
r = requests.head(httpsbin('/'))
|
||||
r = requests.head(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
@@ -119,22 +135,12 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_HTTPS_200_OK_PUT(self):
|
||||
r = requests.put(httpsbin('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_HTTPS_200_OK_PATCH(self):
|
||||
r = requests.patch(httpsbin('patch'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_AUTH_HTTP_200_OK_GET(self):
|
||||
def test_BASICAUTH_HTTP_200_OK_GET(self):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
@@ -142,14 +148,35 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
url = service('basic-auth', 'user', 'pass')
|
||||
|
||||
r = requests.get(url, auth=auth)
|
||||
# print r.__dict__
|
||||
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 = ('digest', '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:
|
||||
@@ -199,7 +226,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
r = requests.get(service('status', '500'))
|
||||
self.assertEqual(bool(r), False)
|
||||
|
||||
r = requests.get(service('/'))
|
||||
r = requests.get(service('/get'))
|
||||
self.assertEqual(bool(r), True)
|
||||
|
||||
|
||||
@@ -213,7 +240,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
def test_status_raising(self):
|
||||
r = requests.get(httpbin('status', '404'))
|
||||
self.assertRaises(requests.HTTPError, r.raise_for_status)
|
||||
self.assertRaises(HTTPError, r.raise_for_status)
|
||||
|
||||
r = requests.get(httpbin('status', '200'))
|
||||
self.assertFalse(r.error)
|
||||
@@ -250,7 +277,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
for service in SERVICES:
|
||||
|
||||
url = service('/')
|
||||
url = service('/get')
|
||||
|
||||
requests.get(url, params={'foo': u'føø'})
|
||||
requests.get(url, params={u'føø': u'føø'})
|
||||
@@ -268,19 +295,6 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
self.assertEquals(r.status_code, 401)
|
||||
|
||||
|
||||
def test_settings(self):
|
||||
|
||||
def test():
|
||||
r = requests.get(httpbin(''))
|
||||
r.raise_for_status()
|
||||
|
||||
with requests.settings(timeout=0.0000001):
|
||||
self.assertRaises(requests.Timeout, test)
|
||||
|
||||
with requests.settings(timeout=100):
|
||||
requests.get(httpbin(''))
|
||||
|
||||
|
||||
def test_urlencoded_post_data(self):
|
||||
|
||||
for service in SERVICES:
|
||||
@@ -379,9 +393,9 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
self.assertEquals(rbody.get('data'), 'foobar')
|
||||
|
||||
|
||||
def test_idna(self):
|
||||
r = requests.get(u'http://➡.ws/httpbin')
|
||||
assert 'httpbin' in r.url
|
||||
# def test_idna(self):
|
||||
# r = requests.get(u'http://➡.ws/httpbin')
|
||||
# assert 'httpbin' in r.url
|
||||
|
||||
|
||||
def test_urlencoded_get_query_multivalued_param(self):
|
||||
@@ -462,15 +476,8 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = Session()
|
||||
r = s.get(httpbin('/'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
def test_session_HTTPS_200_OK_GET(self):
|
||||
|
||||
s = Session()
|
||||
r = s.get(httpsbin('/'))
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
||||
@@ -478,18 +485,55 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = Session()
|
||||
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'))
|
||||
|
||||
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_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):
|
||||
|
||||
r = requests.get('http://somedomainthatclearlydoesntexistg.com')
|
||||
assert r.content == None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user