From 7fdb09b766a84534dadfb56e46033697292cab60 Mon Sep 17 00:00:00 2001 From: Idan Gazit Date: Thu, 17 Nov 2011 13:44:13 +0200 Subject: [PATCH 01/15] Converted auth to use callable objects instead of tuples. My attempt to address #275 on kennethreitz/requests. --- docs/user/advanced.rst | 33 ++++++++--------- docs/user/quickstart.rst | 6 ++-- requests/auth.py | 76 ++++++++++++++-------------------------- requests/models.py | 10 ++---- test_requests.py | 7 ++-- 5 files changed, 55 insertions(+), 77 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 0c26f2d3..05267816 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -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')) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 542a5bc6..cf8d1a71 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -235,7 +235,8 @@ authentication, but the most common is HTTP Basic Auth. Making requests with Basic Auth is extremely simple:: - >>> requests.get('https://api.github.com/user', auth=('user', 'pass')) + >>> from requests.auth import HTTPBasicAuth + >>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass')) OAuth Authentication @@ -249,8 +250,9 @@ 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')) diff --git a/requests/auth.py b/requests/auth.py index aabeb866..fad6eb79 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -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 diff --git a/requests/models.py b/requests/models.py index 97237e77..9c4d1333 100644 --- a/requests/models.py +++ b/requests/models.py @@ -14,7 +14,6 @@ from Cookie import SimpleCookie 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 @@ -99,8 +98,7 @@ class Request(object): self.response = Response() #: Authentication tuple to attach to :class:`Request `. - self._auth = auth - self.auth = auth_dispatch(auth) + self.auth = auth #: CookieJar to attach to :class:`Request `. self.cookies = dict(cookies or []) @@ -235,7 +233,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, @@ -392,10 +390,8 @@ class Request(object): if self.auth: - auth_func, auth_args = 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__) diff --git a/test_requests.py b/test_requests.py index 61953a37..1cd73cfb 100755 --- a/test_requests.py +++ b/test_requests.py @@ -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 @@ -144,7 +145,7 @@ class RequestsTestSuite(unittest.TestCase): for service in SERVICES: - auth = ('user', 'pass') + auth = HTTPBasicAuth('user', 'pass') url = service('basic-auth', 'user', 'pass') r = requests.get(url, auth=auth) @@ -163,7 +164,7 @@ class RequestsTestSuite(unittest.TestCase): 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 +271,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) From 9d8d1d14f1aae1933440920953ef7841c449bef0 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 19 Nov 2011 16:28:22 -0500 Subject: [PATCH 02/15] package --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 17e612ba..53e770a4 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,8 @@ setup( packages= [ 'requests', 'requests.packages', - 'requests.packages.urllib3' + 'requests.packages.urllib3', + 'requests.packages.oreos' ], install_requires=required, license='ISC', From 1933d3c70768db0e8ed509f683f9b830b6bfc44c Mon Sep 17 00:00:00 2001 From: Idan Gazit Date: Sat, 19 Nov 2011 23:59:27 +0200 Subject: [PATCH 03/15] Added support for HTTP Basic Auth credentials in 2-tuple --- docs/user/quickstart.rst | 19 +++++++++++++++---- requests/models.py | 8 ++++++-- test_requests.py | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index cf8d1a71..3a4a91ff 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -239,11 +239,15 @@ Making requests with Basic Auth is extremely simple:: >>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass')) -OAuth Authentication --------------------- +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')) + + +Providing the credentials as a tuple in this fashion is functionally equivalent +to the ``HTTPBasicAuth`` example above. -Miguel Araujo's `requests-oauth `_ project provides a simple interface for -establishing OAuth connections. Documentation and examples can be found on the requests-oauth `git repository `_. Digest Authentication --------------------- @@ -256,6 +260,13 @@ Another popular form of web service protection is Digest Authentication:: +OAuth Authentication +-------------------- + +Miguel Araujo's `requests-oauth `_ project provides a simple interface for +establishing OAuth connections. Documentation and examples can be found on the requests-oauth `git repository `_. + + Redirection and History ----------------------- diff --git a/requests/models.py b/requests/models.py index 9c4d1333..0053c08a 100644 --- a/requests/models.py +++ b/requests/models.py @@ -17,6 +17,7 @@ from datetime import datetime from .hooks import dispatch_hook from .structures import CaseInsensitiveDict from .status_codes import codes +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 @@ -97,7 +98,7 @@ class Request(object): #: content and metadata of HTTP Response, once :attr:`sent `. self.response = Response() - #: Authentication tuple to attach to :class:`Request `. + #: Authentication tuple or object to attach to :class:`Request `. self.auth = auth #: CookieJar to attach to :class:`Request `. @@ -388,8 +389,11 @@ class Request(object): if (content_type) and (not 'content-type' in self.headers): self.headers['Content-Type'] = content_type - if 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 = self.auth(self) diff --git a/test_requests.py b/test_requests.py index 1cd73cfb..9ac6b51c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -141,6 +141,25 @@ class RequestsTestSuite(unittest.TestCase): self.assertEqual(r.status_code, 200) + def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self): + + for service in SERVICES: + + auth = ('user', 'pass') + url = service('basic-auth', 'user', 'pass') + + r = requests.get(url, auth=auth) + self.assertEqual(r.status_code, 200) + + r = requests.get(url) + self.assertEqual(r.status_code, 401) + + + s = requests.session(auth=auth) + r = s.get(url) + self.assertEqual(r.status_code, 200) + + def test_BASICAUTH_HTTP_200_OK_GET(self): for service in SERVICES: From 8ae4b440e347a232167caea2a64949efd35a7827 Mon Sep 17 00:00:00 2001 From: Robert Gieseke Date: Sun, 20 Nov 2011 10:14:51 +0100 Subject: [PATCH 04/15] Display full URL when logging. --- requests/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index 0be3e896..ab121b96 100644 --- a/requests/models.py +++ b/requests/models.py @@ -344,15 +344,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 From 99b6e3594078da14161000ee7eec3985468ee82e Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Fri, 25 Nov 2011 07:45:12 +0530 Subject: [PATCH 05/15] Allow generators or any iterators for async.map --- requests/async.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requests/async.py b/requests/async.py index 8bafb1ee..c91025fc 100644 --- a/requests/async.py +++ b/requests/async.py @@ -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) From 5c72601599b085985f5b345717e8e8d127091c81 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Date: Fri, 25 Nov 2011 08:32:07 +0530 Subject: [PATCH 06/15] Add use_session argument to request method. This allows usage of an existing session to make a request with the request and the get, post etc. function. --- requests/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requests/api.py b/requests/api.py index 9e0c96f5..21c7c7a8 100644 --- a/requests/api.py +++ b/requests/api.py @@ -27,6 +27,7 @@ def request(method, url, hooks=None, return_response=True, prefetch=False, + use_session=None, config=None): """Constructs and sends a :class:`Request `. Returns :class:`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 use_session: (optional) A :class:`Session` object to be used for the request. :param config: (optional) A configuration dictionary. """ - s = session() + s = use_session or session() return s.request( method=method, url=url, From a350ceffb934b4bced2eadd1cb7338dcb04fe598 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Date: Fri, 25 Nov 2011 11:41:40 +0530 Subject: [PATCH 07/15] Rename use_session to session. --- requests/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/api.py b/requests/api.py index 21c7c7a8..0b124e03 100644 --- a/requests/api.py +++ b/requests/api.py @@ -11,7 +11,7 @@ This module implements the Requests API. """ -from .sessions import session +from . import sessions def request(method, url, @@ -27,7 +27,7 @@ def request(method, url, hooks=None, return_response=True, prefetch=False, - use_session=None, + session=None, config=None): """Constructs and sends a :class:`Request `. Returns :class:`Response ` object. @@ -44,11 +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 use_session: (optional) A :class:`Session` object to be used for the request. + :param session: (optional) A :class:`Session` object to be used for the request. :param config: (optional) A configuration dictionary. """ - s = use_session or session() + s = session or sessions.session() return s.request( method=method, url=url, From 65c06579b477d64c10c3f544c00832f81d0d1d6f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 26 Nov 2011 10:10:57 -0500 Subject: [PATCH 08/15] Update AUTHORS.rst --- AUTHORS.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index ef2c060b..b27737fd 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -60,4 +60,5 @@ Patches and Suggestions - Juergen Brendel - Juan Riaza - Ryan Kelly -- Rolando Espinoza La fuente \ No newline at end of file +- Rolando Espinoza La fuente +- Robert Gieseke \ No newline at end of file From c74b66a53e21b209531be87f9d8374ceb4542d9e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 27 Nov 2011 10:00:40 -0500 Subject: [PATCH 09/15] v0.8.3 --- HISTORY.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index d7b023a0..4cc45fab 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,13 @@ History ------- +0.8.3 (2011-11-26) +++++++++++++++++++ + +* 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) ++++++++++++++++++ From ba00c4411c2465ddb203ce812240d8d961ff7860 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 27 Nov 2011 10:00:47 -0500 Subject: [PATCH 10/15] add github buttons to sidebars --- docs/_templates/sidebarintro.html | 5 +++++ docs/_templates/sidebarlogo.html | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index a13888a7..6917cc0f 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -4,6 +4,11 @@

+

+ +

+

Requests is an elegant and simple HTTP library for Python, built for human beings. You are currently looking at the documentation of the diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index 3b62ed23..bd9a7201 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -3,6 +3,10 @@

+

+ +

Requests is an elegant and simple HTTP library for Python, built for From 356bf42373f16ec9754cc73c63bc116a46e2f239 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 27 Nov 2011 10:06:39 -0500 Subject: [PATCH 11/15] v0.8.3 --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index 9d2319a5..e09c8345 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -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' From 9f29a31f662ba389e6276962df93451084dddc24 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 27 Nov 2011 10:08:19 -0500 Subject: [PATCH 12/15] v0.8.3 --- AUTHORS.rst | 3 ++- HISTORY.rst | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index b27737fd..43644e52 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -61,4 +61,5 @@ Patches and Suggestions - Juan Riaza - Ryan Kelly - Rolando Espinoza La fuente -- Robert Gieseke \ No newline at end of file +- Robert Gieseke +- Idan Gazit \ No newline at end of file diff --git a/HISTORY.rst b/HISTORY.rst index 4cc45fab..487cc867 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,7 +1,7 @@ History ------- -0.8.3 (2011-11-26) +0.8.3 (2011-11-27) ++++++++++++++++++ * Converted auth system to use simpler callable objects. From e7ed1224d222838ef9addf3bfa188ece82c9f9e7 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 27 Nov 2011 10:32:36 -0500 Subject: [PATCH 13/15] flattr --- docs/_templates/sidebarintro.html | 14 +++++++++++++- docs/_templates/sidebarlogo.html | 10 ++++++++++ docs/_themes/kr/layout.html | 11 +++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 6917cc0f..862ad976 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -15,6 +15,17 @@ development release.

+ +

Support Requests

+

+ If you love Requests, consider making a small donation on Flattr: +

+

+ + +

+

Feedback

Feedback is greatly appreciated. If you have any questions, comments, @@ -25,8 +36,9 @@

Useful Links

diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index bd9a7201..0260ad6d 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -12,4 +12,14 @@ 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. +

+ +

Support Requests

+

+ If you love Requests, consider making a small donation on Flattr: +

+

+ +

\ No newline at end of file diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index 7a9c7e0c..5e68347b 100644 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -14,6 +14,17 @@ Fork me on GitHub +