Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e15834da8e | ||
|
|
f1bb25b1ec | ||
|
|
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 | ||
|
|
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 | ||
|
|
4f6fc98509 | ||
|
|
15511584d1 |
3
AUTHORS
3
AUTHORS
@@ -44,3 +44,6 @@ Patches and Suggestions
|
||||
- Rick Mak
|
||||
- Johan Bergström
|
||||
- Josselin Jacquard
|
||||
- Travis N. Vaught
|
||||
- Fredrik Möllerstrand
|
||||
- Daniel Hengeveld
|
||||
41
HISTORY.rst
41
HISTORY.rst
@@ -1,7 +1,36 @@
|
||||
History
|
||||
-------
|
||||
|
||||
0.6.5 (2011-10-19)
|
||||
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.
|
||||
@@ -12,20 +41,22 @@ History
|
||||
++++++++++++++++++
|
||||
|
||||
* Automatic decoding of unicode, based on HTTP Headers.
|
||||
* New ``decode_unicode`` setting
|
||||
* Removal of ``r.read/close`` methods
|
||||
* New ``decode_unicode`` setting.
|
||||
* Removal of ``r.read/close`` methods.
|
||||
* New ``r.faw`` interface for advanced response usage.*
|
||||
* Automatic expansion of parameterized headers
|
||||
* Automatic expansion of parameterized headers.
|
||||
|
||||
|
||||
0.6.3 (2011-10-13)
|
||||
++++++++++++++++++
|
||||
|
||||
* Beautiful ``requests.async`` module, for making async requests w/ gevent.
|
||||
|
||||
|
||||
0.6.2 (2011-10-09)
|
||||
++++++++++++++++++
|
||||
|
||||
* GET/HEAD obeys allow_redirects=False
|
||||
* GET/HEAD obeys allow_redirects=False.
|
||||
|
||||
|
||||
0.6.1 (2011-08-20)
|
||||
|
||||
2
Makefile
2
Makefile
@@ -2,7 +2,7 @@ init:
|
||||
pip install -r reqs.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py --with-color
|
||||
nosetests test_requests.py
|
||||
|
||||
ci: init
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
@@ -9,8 +9,7 @@ Encoded Data?
|
||||
-------------
|
||||
|
||||
Requests automatically decompresses gzip-encoded responses, and does
|
||||
its best to decodes response content to unicode when possible.
|
||||
it's 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.
|
||||
|
||||
@@ -6,7 +6,7 @@ Modules
|
||||
- `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.
|
||||
|
||||
Articles & Talks
|
||||
================
|
||||
|
||||
@@ -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
|
||||
------
|
||||
|
||||
@@ -41,6 +41,7 @@ Testimonials
|
||||
|
||||
`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.
|
||||
|
||||
@@ -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
|
||||
@@ -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>`_.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 advanced_ section.
|
||||
@@ -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.2'
|
||||
__build__ = 0x000702
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
from . import utils
|
||||
from .models import HTTPError, 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
|
||||
)
|
||||
|
||||
@@ -4,103 +4,48 @@
|
||||
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, header_expand
|
||||
|
||||
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):
|
||||
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.
|
||||
: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) 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 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.
|
||||
"""
|
||||
|
||||
method = str(method).upper()
|
||||
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
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,
|
||||
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 **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('GET', url, **kwargs)
|
||||
|
||||
@@ -146,7 +91,7 @@ def patch(url, data='', **kwargs):
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('patch', url, **kwargs)
|
||||
return request('patch', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
|
||||
@@ -28,7 +28,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
def _patched(f):
|
||||
def patched(f):
|
||||
"""Patches a given API function to not send."""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
@@ -37,7 +37,7 @@ def _patched(f):
|
||||
return wrapped
|
||||
|
||||
|
||||
def _send(r, pools=None):
|
||||
def send(r, pools=None):
|
||||
"""Sends a given Request object."""
|
||||
|
||||
if pools:
|
||||
@@ -45,23 +45,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 +65,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:
|
||||
|
||||
151
requests/auth.py
Normal file
151
requests/auth.py
Normal file
@@ -0,0 +1,151 @@
|
||||
# -*- 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
|
||||
path = urlparse(r.request.url).path
|
||||
|
||||
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)
|
||||
print _r.status_code
|
||||
|
||||
# r.request.response
|
||||
|
||||
print locals()
|
||||
|
||||
print _r.headers
|
||||
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,68 +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
|
||||
settings.decode_unicode = True
|
||||
|
||||
#: Use socket.setdefaulttimeout() as fallback?
|
||||
settings.timeout_fallback = True
|
||||
@@ -1,29 +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.5'
|
||||
__build__ = 0x000605
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
from models import HTTPError, Request, Response
|
||||
from api import *
|
||||
from exceptions import *
|
||||
from sessions import session
|
||||
from status_codes import codes
|
||||
from config import settings
|
||||
|
||||
import utils
|
||||
38
requests/defaults.py
Normal file
38
requests/defaults.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- 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')),
|
||||
}
|
||||
|
||||
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 -*-
|
||||
|
||||
"""
|
||||
@@ -9,23 +10,23 @@ requests.models
|
||||
import urllib
|
||||
import urllib2
|
||||
import socket
|
||||
import codecs
|
||||
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 .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, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip
|
||||
from .utils import (dict_from_cookiejar, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip)
|
||||
from .status_codes import codes
|
||||
from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod, TooManyRedirects
|
||||
from .exceptions import Timeout, URLRequired, TooManyRedirects
|
||||
from .monkeys import Request as _Request
|
||||
from .monkeys import HTTPRedirectHandler
|
||||
|
||||
from .auth import dispatch as auth_dispatch
|
||||
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
|
||||
@@ -37,18 +38,29 @@ class Request(object):
|
||||
"""
|
||||
|
||||
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 <Request>`.
|
||||
#: Dictionary of HTTP Headers to attach to the :class:`Request <Request>`.
|
||||
self.headers = headers
|
||||
|
||||
#: Dictionary of files to multipart upload (``{filename: content}``).
|
||||
@@ -82,16 +94,14 @@ class Request(object):
|
||||
#: 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)
|
||||
|
||||
#: :class:`AuthObject` to attach to :class:`Request <Request>`.
|
||||
self.auth = auth
|
||||
#: Authentication tuple to attach to :class:`Request <Request>`.
|
||||
self.auth = auth_dispatch(auth)
|
||||
|
||||
#: CookieJar to attach to :class:`Request <Request>`.
|
||||
self.cookiejar = cookiejar
|
||||
self.cookies = cookies
|
||||
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = config
|
||||
|
||||
#: True if Request has been sent.
|
||||
self.sent = False
|
||||
@@ -99,57 +109,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))
|
||||
@@ -185,14 +171,15 @@ class Request(object):
|
||||
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.raw = resp
|
||||
|
||||
if self.cookiejar:
|
||||
response.cookies = dict_from_cookiejar(self.cookiejar)
|
||||
if self.cookies:
|
||||
response.cookies = dict_from_cookiejar(self.cookies)
|
||||
|
||||
|
||||
except AttributeError:
|
||||
@@ -219,7 +206,7 @@ class Request(object):
|
||||
|
||||
r.raw.close()
|
||||
|
||||
if not len(history) < settings.max_redirects:
|
||||
if not len(history) < self.config.get('max_redirects'):
|
||||
raise TooManyRedirects()
|
||||
|
||||
history.append(r)
|
||||
@@ -243,9 +230,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
|
||||
@@ -310,32 +304,44 @@ class Request(object):
|
||||
already been sent.
|
||||
"""
|
||||
|
||||
self._checks()
|
||||
# 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)
|
||||
@@ -353,19 +359,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'):
|
||||
@@ -383,6 +389,13 @@ 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
|
||||
|
||||
|
||||
@@ -412,10 +425,10 @@ class Response(object):
|
||||
#: 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 <Response>` objects from
|
||||
@@ -429,6 +442,9 @@ class Response(object):
|
||||
#: 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)
|
||||
@@ -460,7 +476,7 @@ class Response(object):
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
gen = stream_decode_gzip(gen)
|
||||
if decode_unicode is None:
|
||||
decode_unicode = settings.decode_unicode
|
||||
decode_unicode = self.config.get('decode_unicode')
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
return gen
|
||||
@@ -489,7 +505,7 @@ class Response(object):
|
||||
pass
|
||||
|
||||
# Decode unicode content.
|
||||
if settings.decode_unicode:
|
||||
if self.config.get('decode_unicode'):
|
||||
self._content = get_unicode_from_response(self)
|
||||
|
||||
self._content_consumed = True
|
||||
@@ -497,183 +513,7 @@ class Response(object):
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
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
|
||||
@@ -11,9 +11,10 @@ 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):
|
||||
@@ -25,6 +26,9 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
|
||||
if isinstance(local_kwarg, basestring):
|
||||
return local_kwarg
|
||||
|
||||
if local_kwarg is None:
|
||||
return default_kwarg
|
||||
|
||||
@@ -32,8 +36,6 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if not hasattr(default_kwarg, 'items'):
|
||||
return local_kwarg
|
||||
|
||||
|
||||
|
||||
# Update new values.
|
||||
kwargs = default_kwarg.copy()
|
||||
kwargs.update(local_kwarg)
|
||||
@@ -49,7 +51,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
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,
|
||||
@@ -58,7 +60,9 @@ class Session(object):
|
||||
auth=None,
|
||||
timeout=None,
|
||||
proxies=None,
|
||||
hooks=None):
|
||||
hooks=None,
|
||||
params=None,
|
||||
config=None):
|
||||
|
||||
self.headers = headers or {}
|
||||
self.cookies = cookies or {}
|
||||
@@ -66,14 +70,15 @@ class Session(object):
|
||||
self.timeout = timeout
|
||||
self.proxies = proxies or {}
|
||||
self.hooks = hooks or {}
|
||||
self.params = params or {}
|
||||
self.config = config or {}
|
||||
|
||||
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 and wrap requests.api methods
|
||||
self._map_api_methods()
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<requests-client at 0x%x>' % (id(self))
|
||||
|
||||
@@ -83,51 +88,158 @@ class Session(object):
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
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.
|
||||
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):
|
||||
|
||||
"""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):
|
||||
method = str(method).upper()
|
||||
|
||||
# Argument collector.
|
||||
_kwargs = {}
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
# 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(
|
||||
self.cookies, kwargs['cookies']
|
||||
)
|
||||
if isinstance(cookies, dict):
|
||||
cookies = add_dict_to_cookiejar(self.cookies, cookies)
|
||||
|
||||
for attr in self.__attrs__:
|
||||
# for attr in ['headers',]:
|
||||
s_val = self.__dict__.get(attr)
|
||||
r_val = kwargs.get(attr)
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
new_attr = merge_kwargs(r_val, s_val)
|
||||
# Expand header values
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
# Skip attributes that were set to None.
|
||||
if new_attr is not None:
|
||||
_kwargs[attr] = new_attr
|
||||
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
|
||||
)
|
||||
|
||||
# Make sure we didn't miss anything.
|
||||
for (k, v) in kwargs.items():
|
||||
if k not in _kwargs:
|
||||
_kwargs[k] = v
|
||||
for attr in self.__attrs__:
|
||||
session_val = getattr(self, attr, None)
|
||||
local_val = args.get(attr)
|
||||
|
||||
return func(*args, **_kwargs)
|
||||
args[attr] = merge_kwargs(local_val, session_val)
|
||||
|
||||
return wrapper_func
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -12,9 +12,103 @@ 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
|
||||
|
||||
|
||||
# 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.
|
||||
@@ -55,7 +149,7 @@ def header_expand(headers):
|
||||
collector.append(', ')
|
||||
|
||||
|
||||
# Remove trailing seperators.
|
||||
# Remove trailing separators.
|
||||
if collector[-1] in (', ', '; '):
|
||||
del collector[-1]
|
||||
|
||||
@@ -63,6 +157,21 @@ def header_expand(headers):
|
||||
|
||||
|
||||
|
||||
def randombytes(n):
|
||||
"""Return n random bytes."""
|
||||
# Use /dev/urandom if it is available. Fall back to random module
|
||||
# if not. It might be worthwhile to extend this function to use
|
||||
# other platform-specific mechanisms for getting random bytes.
|
||||
if os.path.exists("/dev/urandom"):
|
||||
f = open("/dev/urandom")
|
||||
s = f.read(n)
|
||||
f.close()
|
||||
return s
|
||||
else:
|
||||
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.
|
||||
|
||||
@@ -199,7 +308,7 @@ def stream_decode_response_unicode(iterator, r):
|
||||
def get_unicode_from_response(r):
|
||||
"""Returns the requested content back in unicode.
|
||||
|
||||
:param r: Reponse object to get unicode content from.
|
||||
:param r: Response object to get unicode content from.
|
||||
|
||||
Tried:
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import cookielib
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import envoy
|
||||
|
||||
try:
|
||||
@@ -15,11 +16,9 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from requests.sessions import Session
|
||||
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7045')
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
|
||||
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
|
||||
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
|
||||
@@ -47,8 +46,9 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
if not _httpbin:
|
||||
|
||||
self.httpbin = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
|
||||
self.httpbin = c
|
||||
_httpbin = True
|
||||
time.sleep(1)
|
||||
|
||||
@@ -58,22 +58,34 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
"""Teardown."""
|
||||
# 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('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
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)
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
@@ -127,7 +139,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
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:
|
||||
|
||||
@@ -135,14 +147,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:
|
||||
@@ -442,7 +475,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = Session()
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
@@ -451,7 +484,7 @@ 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
|
||||
@@ -468,5 +501,33 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user