Merge branch 'develop'

This commit is contained in:
Kenneth Reitz
2011-11-27 10:45:19 -05:00
14 changed files with 163 additions and 92 deletions
+3 -1
View File
@@ -60,4 +60,6 @@ Patches and Suggestions
- Juergen Brendel
- Juan Riaza
- Ryan Kelly
- Rolando Espinoza La fuente
- Rolando Espinoza La fuente
- Robert Gieseke
- Idan Gazit
+7
View File
@@ -1,6 +1,13 @@
History
-------
0.8.3 (2011-11-27)
++++++++++++++++++
* Converted auth system to use simpler callable objects.
* New session parameter to API methods.
* Display full URL while logging.
0.8.2 (2011-11-19)
++++++++++++++++++
+17 -1
View File
@@ -4,12 +4,28 @@
</a>
</p>
<p>
<iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
<p>
Requests is an elegant and simple HTTP library for Python, built for
human beings. You are currently looking at the documentation of the
development release.
</p>
<h3>Support Requests</h3>
<p>
If you love Requests, consider making a small donation <a href="https://flattr.com/thing/442264/Requests">on Flattr</a>:
</p>
<p>
<a class="FlattrButton" style="display:none;" rev="flattr;button:compact;" href="http://docs.python-requests.org/"></a>
<noscript><a href="http://flattr.com/thing/442264/Requests" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a></noscript>
</p>
<h3>Feedback</h3>
<p>
Feedback is greatly appreciated. If you have any questions, comments,
@@ -20,8 +36,8 @@
<h3>Useful Links</h3>
<ul>
<li><a href="http://python-requests.org/">The Requests Website</a></li>
<li><a href="http://pypi.python.org/pypi/requests">Requests @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/requests">Requests @ GitHub</a></li>
<li><a href="https://flattr.com/thing/442264/Requests">Requests @ Flattr</a></li>
<li><a href="http://github.com/kennethreitz/requests/issues">Issue Tracker</a></li>
</ul>
+14
View File
@@ -3,9 +3,23 @@
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Logo"/>
</a>
</p>
<p>
<iframe src="http://markdotto.github.com/github-buttons/github-btn.html?user=kennethreitz&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
<p>
Requests is an elegant and simple HTTP library for Python, built for
human beings. You are currently looking at the documentation of the
development release.
</p>
<h3>Support Requests</h3>
<p>
If you love Requests, consider making a small donation <a href="https://flattr.com/thing/442264/Requests">on Flattr</a>:
</p>
<p>
<a class="FlattrButton" style="display:none;" rev="flattr;button:compact;" href="http://docs.python-requests.org/"></a>
<noscript><a href="http://flattr.com/thing/442264/Requests" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a></noscript>
</p>
+11
View File
@@ -14,6 +14,17 @@
<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">
/* <![CDATA[ */
(function() {
var s = document.createElement('script'), t = document.getElementsByTagName('script')[0];
s.type = 'text/javascript';
s.async = true;
s.src = 'http://api.flattr.com/js/0.6/load.js?mode=auto';
t.parentNode.insertBefore(s, t);
})();
/* ]]> */
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
+17 -16
View File
@@ -211,32 +211,33 @@ 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.
Any callable which is passed as the ``auth`` argument to a request method will
have the opportunity to modify the request before it is dispatched.
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.
Authentication implementations are subclasses of ``requests.auth.AuthBase``,
and are easy to define. Requests provides two common authentication scheme
implementations in ``requests.auth``: ``HTTPBasicAuth`` and ``HTTPDigestAuth``.
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
from requests.auth import AuthBase
class PizzaAuth(AuthBase):
"""Attaches HTTP Pizza Authentication to the given Request object."""
def __init__(self, username):
# setup any auth-related data here
self.username = username
def __call__(self, r):
# modify and return the request
r.headers['X-Pizza'] = self.username
return r
Then, we can make a request using our Pizza Auth::
>>> requests.get('http://pizzabin.org/admin', auth=(pizza_auth, 'kenneth'))
>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>
+18 -5
View File
@@ -265,25 +265,38 @@ authentication, but the most common is HTTP Basic Auth.
Making requests with Basic Auth is extremely simple::
>>> from requests.auth import HTTPBasicAuth
>>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
<Response [200]>
Due to the prevalence of HTTP Basic Auth, requests provides a shorthand for
this authentication method::
>>> requests.get('https://api.github.com/user', auth=('user', 'pass'))
<Response [200]>
OAuth Authentication
--------------------
Providing the credentials as a tuple in this fashion is functionally equivalent
to the ``HTTPBasicAuth`` example above.
Miguel Araujo's `requests-oauth <http://pypi.python.org/pypi/requests-oauth>`_ project provides a simple interface for
establishing OAuth connections. Documentation and examples can be found on the requests-oauth `git repository <https://github.com/maraujop/requests-oauth>`_.
Digest Authentication
---------------------
Another popular form of web service protection is Digest Authentication::
>>> from requests.auth import HTTPDigestAuth
>>> url = 'http://httpbin.org/digest-auth/auth/user/pass'
>>> requests.get(url, auth=('digest', 'user', 'pass'))
>>> requests.get(url, auth=HTTPDigestAuth('user', 'pass'))
<Response [200]>
OAuth Authentication
--------------------
Miguel Araujo's `requests-oauth <http://pypi.python.org/pypi/requests-oauth>`_ project provides a simple interface for
establishing OAuth connections. Documentation and examples can be found on the requests-oauth `git repository <https://github.com/maraujop/requests-oauth>`_.
Redirection and History
-----------------------
+2 -2
View File
@@ -15,8 +15,8 @@ requests
"""
__title__ = 'requests'
__version__ = '0.8.2'
__build__ = 0x000802
__version__ = '0.8.3'
__build__ = 0x000803
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2011 Kenneth Reitz'
+4 -2
View File
@@ -11,7 +11,7 @@ This module implements the Requests API.
"""
from .sessions import session
from . import sessions
def request(method, url,
@@ -27,6 +27,7 @@ def request(method, url,
hooks=None,
return_response=True,
prefetch=False,
session=None,
config=None):
"""Constructs and sends a :class:`Request <Request>`.
Returns :class:`Response <Response>` object.
@@ -43,10 +44,11 @@ def request(method, url,
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param return_response: (optional) If False, an un-sent Request object will returned.
:param session: (optional) A :class:`Session` object to be used for the request.
:param config: (optional) A configuration dictionary.
"""
s = session()
s = session or sessions.session()
return s.request(
method=method,
url=url,
+2
View File
@@ -71,6 +71,8 @@ def map(requests, prefetch=True, size=None):
:param size: Specifies the number of requests to make at a time. If None, no throttling occurs.
"""
requests = list(requests)
if size:
pool = Pool(size)
pool.map(send, requests)
+27 -49
View File
@@ -16,26 +16,32 @@ 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.
class AuthBase(object):
"""Base class that all auth implementations derive from"""
"""
username = str(username)
password = str(password)
auth_s = b64encode('%s:%s' % (username, password))
r.headers['Authorization'] = ('Basic %s' % auth_s)
return r
def __call__(self, r):
raise NotImplementedError('Auth hooks must be callable.')
def http_digest(r, username, password):
"""Attaches HTTP Digest Authentication to the given Request object.
Arguments should be considered non-positional.
"""
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
def __init__(self, username, password):
self.username = str(username)
self.password = str(password)
def handle_401(r):
def __call__(self, r):
auth_s = b64encode('%s:%s' % (self.username, self.password))
r.headers['Authorization'] = ('Basic %s' % auth_s)
return r
class HTTPDigestAuth(AuthBase):
"""Attaches HTTP Digest Authentication to the given Request object."""
def __init__(self, username, password):
self.username = username
self.password = password
def handle_401(self, r):
"""Takes the given response and tries digest-auth, if needed."""
s_auth = r.headers.get('www-authenticate', '')
@@ -70,7 +76,7 @@ def http_digest(r, username, password):
p_parsed = urlparse(r.request.url)
path = p_parsed.path + p_parsed.query
A1 = "%s:%s:%s" % (username, realm, password)
A1 = "%s:%s:%s" % (self.username, realm, self.password)
A2 = "%s:%s" % (r.request.method, path)
if qop == 'auth':
@@ -95,7 +101,7 @@ def http_digest(r, username, password):
# XXX should the partial digests be encoded too?
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
'response="%s"' % (username, realm, nonce, path, respdig)
'response="%s"' % (self.username, realm, nonce, path, respdig)
if opaque:
base += ', opaque="%s"' % opaque
if entdig:
@@ -104,7 +110,6 @@ def http_digest(r, username, password):
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
@@ -114,33 +119,6 @@ def http_digest(r, username, password):
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:]))
def __call__(self, r):
r.hooks['response'] = self.handle_401
return r
+12 -12
View File
@@ -13,11 +13,11 @@ import zlib
from urlparse import urlparse, urlunparse, urljoin, urlsplit
from datetime import datetime
from .auth import dispatch as auth_dispatch
from .hooks import dispatch_hook
from .structures import CaseInsensitiveDict
from .status_codes import codes
from .packages import oreos
from .auth import HTTPBasicAuth
from .packages.urllib3.exceptions import MaxRetryError
from .packages.urllib3.exceptions import SSLError as _SSLError
from .packages.urllib3.exceptions import HTTPError as _HTTPError
@@ -98,9 +98,8 @@ class Request(object):
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
self.response = Response()
#: Authentication tuple to attach to :class:`Request <Request>`.
self._auth = auth
self.auth = auth_dispatch(auth)
#: Authentication tuple or object to attach to :class:`Request <Request>`.
self.auth = auth
#: CookieJar to attach to :class:`Request <Request>`.
self.cookies = dict(cookies or [])
@@ -231,7 +230,7 @@ class Request(object):
files=self.files,
method=method,
params=self.session.params,
auth=self._auth,
auth=self.auth,
cookies=cookies,
redirect=True,
config=self.config,
@@ -344,15 +343,15 @@ class Request(object):
already been sent.
"""
# Build the URL
url = self.full_url
# Logging
if self.config.get('verbose'):
self.config.get('verbose').write('%s %s %s\n' % (
datetime.now().isoformat(), self.method, self.url
datetime.now().isoformat(), self.method, url
))
# Build the URL
url = self.full_url
# Nottin' on you.
body = None
content_type = None
@@ -392,12 +391,13 @@ class Request(object):
if (content_type) and (not 'content-type' in self.headers):
self.headers['Content-Type'] = content_type
if self.auth:
auth_func, auth_args = self.auth
if isinstance(self.auth, tuple) and len(self.auth) == 2:
# special-case basic HTTP auth
self.auth = HTTPBasicAuth(*self.auth)
# Allow auth to make its changes.
r = auth_func(self, *auth_args)
r = self.auth(self)
# Update self to reflect the auth changes.
self.__dict__.update(r.__dict__)
+2 -1
View File
@@ -37,7 +37,8 @@ setup(
packages= [
'requests',
'requests.packages',
'requests.packages.urllib3'
'requests.packages.urllib3',
'requests.packages.oreos'
],
install_requires=required,
license='ISC',
+27 -3
View File
@@ -10,6 +10,7 @@ import unittest
import requests
import envoy
from requests import HTTPError
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
try:
import omnijson as json
@@ -140,7 +141,7 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEqual(r.status_code, 200)
def test_BASICAUTH_HTTP_200_OK_GET(self):
def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self):
for service in SERVICES:
@@ -159,11 +160,34 @@ class RequestsTestSuite(unittest.TestCase):
self.assertEqual(r.status_code, 200)
def test_BASICAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = HTTPBasicAuth('user', 'pass')
url = service('basic-auth', 'user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
auth = ('user', 'pass')
r = requests.get(url, auth=auth)
self.assertEqual(r.status_code, 200)
r = requests.get(url)
self.assertEqual(r.status_code, 401)
s = requests.session(auth=auth)
r = s.get(url)
self.assertEqual(r.status_code, 200)
def test_DIGESTAUTH_HTTP_200_OK_GET(self):
for service in SERVICES:
auth = ('digest', 'user', 'pass')
auth = HTTPDigestAuth('user', 'pass')
url = service('digest-auth', 'auth', 'user', 'pass')
r = requests.get(url, auth=auth)
@@ -270,7 +294,7 @@ class RequestsTestSuite(unittest.TestCase):
def test_httpauth_recursion(self):
http_auth = ('user', 'BADpass')
http_auth = HTTPBasicAuth('user', 'BADpass')
for service in SERVICES:
r = requests.get(service('basic-auth', 'user', 'pass'), auth=http_auth)