aboutsummaryrefslogtreecommitdiff
path: root/requests
diff options
context:
space:
mode:
authorSVN-Git Migration <python-modules-team@lists.alioth.debian.org>2015-10-08 13:41:23 -0700
committerSVN-Git Migration <python-modules-team@lists.alioth.debian.org>2015-10-08 13:41:23 -0700
commitd4aa2de2bb89ca384ad81db731bb99735e1db788 (patch)
tree68092157eef070d7d87c330f206eb96209e09074 /requests
parent3a4ef8165fb2951781a7bcc4189e90faf26caf2d (diff)
downloadpython-requests-d4aa2de2bb89ca384ad81db731bb99735e1db788.tar
python-requests-d4aa2de2bb89ca384ad81db731bb99735e1db788.tar.gz
Imported Upstream version 0.12.1
Diffstat (limited to 'requests')
-rw-r--r--requests/__init__.py4
-rw-r--r--requests/auth.py41
-rw-r--r--requests/compat.py4
-rw-r--r--requests/cookies.py264
-rw-r--r--requests/hooks.py5
-rw-r--r--requests/models.py204
-rw-r--r--requests/sessions.py45
-rw-r--r--requests/utils.py84
8 files changed, 472 insertions, 179 deletions
diff --git a/requests/__init__.py b/requests/__init__.py
index 96ef774..483ac87 100644
--- a/requests/__init__.py
+++ b/requests/__init__.py
@@ -15,8 +15,8 @@ requests
"""
__title__ = 'requests'
-__version__ = '0.11.2'
-__build__ = 0x001102
+__version__ = '0.12.1'
+__build__ = 0x001201
__author__ = 'Kenneth Reitz'
__license__ = 'ISC'
__copyright__ = 'Copyright 2012 Kenneth Reitz'
diff --git a/requests/auth.py b/requests/auth.py
index 353180a..a20c545 100644
--- a/requests/auth.py
+++ b/requests/auth.py
@@ -11,10 +11,20 @@ import time
import hashlib
from base64 import b64encode
+
from .compat import urlparse, str
from .utils import randombytes, parse_dict_header
+try:
+ from oauthlib.oauth1.rfc5849 import (Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER)
+ from oauthlib.common import extract_params
+ # hush pyflakes:
+ SIGNATURE_HMAC; SIGNATURE_TYPE_AUTH_HEADER
+except (ImportError, SyntaxError):
+ SIGNATURE_HMAC = None
+ SIGNATURE_TYPE_AUTH_HEADER = None
+CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
@@ -29,6 +39,37 @@ class AuthBase(object):
raise NotImplementedError('Auth hooks must be callable.')
+class OAuth1(AuthBase):
+ """Signs the request using OAuth 1 (RFC5849)"""
+ def __init__(self, client_key,
+ client_secret=None,
+ resource_owner_key=None,
+ resource_owner_secret=None,
+ callback_uri=None,
+ signature_method=SIGNATURE_HMAC,
+ signature_type=SIGNATURE_TYPE_AUTH_HEADER,
+ rsa_key=None, verifier=None):
+
+ try:
+ signature_type = signature_type.upper()
+ except AttributeError:
+ pass
+
+ self.client = Client(client_key, client_secret, resource_owner_key,
+ resource_owner_secret, callback_uri, signature_method,
+ signature_type, rsa_key, verifier)
+
+ def __call__(self, r):
+ contenttype = r.headers.get('Content-Type', None)
+ decoded_body = extract_params(r.data)
+ if contenttype == None and decoded_body != None:
+ r.headers['Content-Type'] = 'application/x-www-form-urlencoded'
+
+ r.url, r.headers, r.data = self.client.sign(
+ unicode(r.url), unicode(r.method), r.data, r.headers)
+ return r
+
+
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
def __init__(self, username, password):
diff --git a/requests/compat.py b/requests/compat.py
index fec7a01..37063f5 100644
--- a/requests/compat.py
+++ b/requests/compat.py
@@ -83,7 +83,7 @@ if is_py2:
from urlparse import urlparse, urlunparse, urljoin, urlsplit
from urllib2 import parse_http_list
import cookielib
- from .packages.oreos.monkeys import SimpleCookie
+ from Cookie import Morsel
from StringIO import StringIO
bytes = str
@@ -96,7 +96,7 @@ elif is_py3:
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
from urllib.request import parse_http_list
from http import cookiejar as cookielib
- from http.cookies import SimpleCookie
+ from http.cookies import Morsel
from io import StringIO
str = str
diff --git a/requests/cookies.py b/requests/cookies.py
new file mode 100644
index 0000000..0e0dd67
--- /dev/null
+++ b/requests/cookies.py
@@ -0,0 +1,264 @@
+"""
+Compatibility code to be able to use `cookielib.CookieJar` with requests.
+
+requests.utils imports from here, so be careful with imports.
+"""
+
+import collections
+from .compat import cookielib, urlparse, Morsel
+
+try:
+ import threading
+ # grr, pyflakes: this fixes "redefinition of unused 'threading'"
+ threading
+except ImportError:
+ import dummy_threading as threading
+
+class MockRequest(object):
+ """Wraps a `requests.Request` to mimic a `urllib2.Request`.
+
+ The code in `cookielib.CookieJar` expects this interface in order to correctly
+ manage cookie policies, i.e., determine whether a cookie can be set, given the
+ domains of the request and the cookie.
+
+ The original request object is read-only. The client is responsible for collecting
+ the new headers via `get_new_headers()` and interpreting them appropriately. You
+ probably want `get_cookie_header`, defined below.
+ """
+
+ def __init__(self, request):
+ self._r = request
+ self._new_headers = {}
+
+ def get_type(self):
+ return urlparse(self._r.full_url).scheme
+
+ def get_host(self):
+ return urlparse(self._r.full_url).netloc
+
+ def get_origin_req_host(self):
+ if self._r.response.history:
+ r = self._r.response.history[0]
+ return urlparse(r).netloc
+ else:
+ return self.get_host()
+
+ def get_full_url(self):
+ return self._r.full_url
+
+ def is_unverifiable(self):
+ # unverifiable == redirected
+ return bool(self._r.response.history)
+
+ def has_header(self, name):
+ return name in self._r.headers or name in self._new_headers
+
+ def get_header(self, name, default=None):
+ return self._r.headers.get(name, self._new_headers.get(name, default))
+
+ def add_header(self, key, val):
+ """cookielib has no legitimate use for this method; add it back if you find one."""
+ raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
+
+ def add_unredirected_header(self, name, value):
+ self._new_headers[name] = value
+
+ def get_new_headers(self):
+ return self._new_headers
+
+class MockResponse(object):
+ """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
+
+ ...what? Basically, expose the parsed HTTP headers from the server response
+ the way `cookielib` expects to see them.
+ """
+
+ def __init__(self, headers):
+ """Make a MockResponse for `cookielib` to read.
+
+ :param headers: a httplib.HTTPMessage or analogous carrying the headers
+ """
+ self._headers = headers
+
+ def info(self):
+ return self._headers
+
+ def getheaders(self, name):
+ self._headers.getheaders(name)
+
+def extract_cookies_to_jar(jar, request, response):
+ """Extract the cookies from the response into a CookieJar.
+
+ :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
+ :param request: our own requests.Request object
+ :param response: urllib3.HTTPResponse object
+ """
+ # the _original_response field is the wrapped httplib.HTTPResponse object,
+ # and in safe mode, it may be None if the request didn't actually complete.
+ # in that case, just skip the cookie extraction.
+ if response._original_response is not None:
+ req = MockRequest(request)
+ # pull out the HTTPMessage with the headers and put it in the mock:
+ res = MockResponse(response._original_response.msg)
+ jar.extract_cookies(res, req)
+
+def get_cookie_header(jar, request):
+ """Produce an appropriate Cookie header string to be sent with `request`, or None."""
+ r = MockRequest(request)
+ jar.add_cookie_header(r)
+ return r.get_new_headers().get('Cookie')
+
+def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
+ """Unsets a cookie by name, by default over all domains and paths.
+
+ Wraps CookieJar.clear(), is O(n).
+ """
+ clearables = []
+ for cookie in cookiejar:
+ if cookie.name == name:
+ if domain is None or domain == cookie.domain:
+ if path is None or path == cookie.path:
+ clearables.append((cookie.domain, cookie.path, cookie.name))
+
+ for domain, path, name in clearables:
+ cookiejar.clear(domain, path, name)
+
+class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
+ """Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
+
+ This is the CookieJar we create by default for requests and sessions that
+ don't specify one, since some clients may expect response.cookies and
+ session.cookies to support dict operations.
+
+ Don't use the dict interface internally; it's just for compatibility with
+ with external client code. All `requests` code should work out of the box
+ with externally provided instances of CookieJar, e.g., LWPCookieJar and
+ FileCookieJar.
+
+ Caution: dictionary operations that are normally O(1) may be O(n).
+
+ Unlike a regular CookieJar, this class is pickleable.
+ """
+
+ def get(self, name, domain=None, path=None, default=None):
+ try:
+ return self._find(name, domain, path)
+ except KeyError:
+ return default
+
+ def set(self, name, value, **kwargs):
+ # support client code that unsets cookies by assignment of a None value:
+ if value is None:
+ remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
+ return
+
+ if isinstance(value, Morsel):
+ c = morsel_to_cookie(value)
+ else:
+ c = create_cookie(name, value, **kwargs)
+ self.set_cookie(c)
+ return c
+
+ def __getitem__(self, name):
+ return self._find(name)
+
+ def __setitem__(self, name, value):
+ self.set(name, value)
+
+ def __delitem__(self, name):
+ remove_cookie_by_name(self, name)
+
+ def _find(self, name, domain=None, path=None):
+ for cookie in iter(self):
+ if cookie.name == name:
+ if domain is None or cookie.domain == domain:
+ if path is None or cookie.path == path:
+ return cookie.value
+
+ raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ # remove the unpickleable RLock object
+ state.pop('_cookies_lock')
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+ if '_cookies_lock' not in self.__dict__:
+ self._cookies_lock = threading.RLock()
+
+ def copy(self):
+ """We're probably better off forbidding this."""
+ raise NotImplementedError
+
+def create_cookie(name, value, **kwargs):
+ """Make a cookie from underspecified parameters.
+
+ By default, the pair of `name` and `value` will be set for the domain ''
+ and sent on every request (this is sometimes called a "supercookie").
+ """
+ result = dict(
+ version=0,
+ name=name,
+ value=value,
+ port=None,
+ domain='',
+ path='/',
+ secure=False,
+ expires=None,
+ discard=True,
+ comment=None,
+ comment_url=None,
+ rest={'HttpOnly': None},
+ rfc2109=False,
+ )
+
+ badargs = set(kwargs) - set(result)
+ if badargs:
+ err = 'create_cookie() got unexpected keyword arguments: %s'
+ raise TypeError(err % list(badargs))
+
+ result.update(kwargs)
+ result['port_specified'] = bool(result['port'])
+ result['domain_specified'] = bool(result['domain'])
+ result['domain_initial_dot'] = result['domain'].startswith('.')
+ result['path_specified'] = bool(result['path'])
+
+ return cookielib.Cookie(**result)
+
+def morsel_to_cookie(morsel):
+ """Convert a Morsel object into a Cookie containing the one k/v pair."""
+ c = create_cookie(
+ name=morsel.key,
+ value=morsel.value,
+ version=morsel['version'] or 0,
+ port=None,
+ port_specified=False,
+ domain=morsel['domain'],
+ domain_specified=bool(morsel['domain']),
+ domain_initial_dot=morsel['domain'].startswith('.'),
+ path=morsel['path'],
+ path_specified=bool(morsel['path']),
+ secure=bool(morsel['secure']),
+ expires=morsel['max-age'] or morsel['expires'],
+ discard=False,
+ comment=morsel['comment'],
+ comment_url=bool(morsel['comment']),
+ rest={'HttpOnly': morsel['httponly']},
+ rfc2109=False,
+ )
+ return c
+
+def cookiejar_from_dict(cookie_dict, cookiejar=None):
+ """Returns a CookieJar from a key/value dictionary.
+
+ :param cookie_dict: Dict of key/values to insert into CookieJar.
+ """
+ if cookiejar is None:
+ cookiejar = RequestsCookieJar()
+
+ if cookie_dict is not None:
+ for name in cookie_dict:
+ cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
+ return cookiejar
diff --git a/requests/hooks.py b/requests/hooks.py
index 3560b89..13d0eb5 100644
--- a/requests/hooks.py
+++ b/requests/hooks.py
@@ -12,6 +12,9 @@ Available hooks:
A dictionary of the arguments being sent to Request().
``pre_request``:
+ The Request object, directly after being created.
+
+``pre_send``:
The Request object, directly before being sent.
``post_request``:
@@ -25,7 +28,7 @@ Available hooks:
import traceback
-HOOKS = ('args', 'pre_request', 'post_request', 'response')
+HOOKS = ('args', 'pre_request', 'pre_send', 'post_request', 'response')
def dispatch_hook(key, hooks, hook_data):
diff --git a/requests/models.py b/requests/models.py
index 60f58d2..fbdfb56 100644
--- a/requests/models.py
+++ b/requests/models.py
@@ -7,6 +7,7 @@ requests.models
This module contains the primary objects that power Requests.
"""
+import json
import os
from datetime import datetime
@@ -15,6 +16,7 @@ from .structures import CaseInsensitiveDict
from .status_codes import codes
from .auth import HTTPBasicAuth, HTTPProxyAuth
+from .cookies import cookiejar_from_dict, extract_cookies_to_jar, get_cookie_header
from .packages.urllib3.response import HTTPResponse
from .packages.urllib3.exceptions import MaxRetryError, LocationParseError
from .packages.urllib3.exceptions import SSLError as _SSLError
@@ -27,20 +29,22 @@ from .exceptions import (
URLRequired, SSLError, MissingSchema, InvalidSchema, InvalidURL)
from .utils import (
get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri,
- dict_from_string, stream_decode_response_unicode, get_netrc_auth,
+ stream_decode_response_unicode, get_netrc_auth, get_environ_proxies,
DEFAULT_CA_BUNDLE_PATH)
from .compat import (
- urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
- SimpleCookie, is_py2)
+ cookielib, urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
+ StringIO, is_py2)
# Import chardet if it is available.
try:
import chardet
+ # hush pyflakes
+ chardet
except ImportError:
- pass
+ chardet = None
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
-
+CONTENT_CHUNK_SIZE = 10 * 1024
class Request(object):
"""The :class:`Request <Request>` object. It carries out all functionality of
@@ -109,14 +113,11 @@ class Request(object):
# If no proxies are given, allow configuration by environment variables
# HTTP_PROXY and HTTPS_PROXY.
if not self.proxies and self.config.get('trust_env'):
- if 'HTTP_PROXY' in os.environ:
- self.proxies['http'] = os.environ['HTTP_PROXY']
- if 'HTTPS_PROXY' in os.environ:
- self.proxies['https'] = os.environ['HTTPS_PROXY']
+ self.proxies = get_environ_proxies()
- self.data, self._enc_data = self._encode_params(data)
- self.params, self._enc_params = self._encode_params(params)
- self.files, self._enc_files = self._encode_files(files)
+ self.data = data
+ self.params = params
+ self.files = files
#: :class:`Response <Response>` instance, containing
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
@@ -126,7 +127,10 @@ class Request(object):
self.auth = auth
#: CookieJar to attach to :class:`Request <Request>`.
- self.cookies = dict(cookies or [])
+ if isinstance(cookies, cookielib.CookieJar):
+ self.cookies = cookies
+ else:
+ self.cookies = cookiejar_from_dict(cookies)
#: True if Request has been sent.
self.sent = False
@@ -193,16 +197,11 @@ class Request(object):
# Set encoding.
response.encoding = get_encoding_from_headers(response.headers)
- # Start off with our local cookies.
- cookies = self.cookies or dict()
-
# Add new cookies from the server.
- if 'set-cookie' in response.headers:
- cookie_header = response.headers['set-cookie']
- cookies = dict_from_string(cookie_header)
+ extract_cookies_to_jar(self.cookies, self, resp)
# Save cookies in Response.
- response.cookies = cookies
+ response.cookies = self.cookies
# No exceptions were harmed in the making of this request.
response.error = getattr(resp, 'error', None)
@@ -220,8 +219,6 @@ class Request(object):
r = build(resp)
- self.cookies.update(r.cookies)
-
if r.status_code in REDIRECT_STATI and not self.redirect:
while (('location' in r.headers) and
@@ -239,6 +236,7 @@ class Request(object):
url = r.headers['location']
data = self.data
+ files = self.files
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'):
@@ -257,6 +255,7 @@ class Request(object):
if r.status_code is codes.see_other:
method = 'GET'
data = None
+ files = None
else:
method = self.method
@@ -266,10 +265,12 @@ class Request(object):
if r.status_code in (codes.moved, codes.found) and self.method == 'POST':
method = 'GET'
data = None
+ files = None
if (r.status_code == 303) and self.method != 'HEAD':
method = 'GET'
data = None
+ files = None
# Remove the cookie headers that were sent.
headers = self.headers
@@ -281,7 +282,7 @@ class Request(object):
request = Request(
url=url,
headers=headers,
- files=self.files,
+ files=files,
method=method,
params=self.session.params,
auth=self.auth,
@@ -299,46 +300,46 @@ class Request(object):
request.send()
r = request.response
- self.cookies.update(r.cookies)
r.history = history
self.response = r
self.response.request = self
- self.response.cookies.update(self.cookies)
@staticmethod
def _encode_params(data):
"""Encode parameters in a piece of data.
- If the data supplied is a dictionary, encodes each parameter in it, and
- returns a list of tuples containing the encoded parameters, and a urlencoded
- version of that.
-
- Otherwise, assumes the data is already encoded appropriately, and
- returns it twice.
+ Will successfully encode parameters when passed as a dict or a list of
+ 2-tuples. Order is retained if data is a list of 2-tuples but abritrary
+ if parameters are supplied as a dict.
"""
if isinstance(data, bytes):
- return data, data
-
- if hasattr(data, '__iter__') and not isinstance(data, str):
- data = dict(data)
+ return data
+ if isinstance(data, str):
+ return data
+ elif hasattr(data, '__iter__'):
+ try:
+ dict(data)
+ except ValueError:
+ raise ValueError('Unable to encode lists with elements that are not 2-tuples.')
- if hasattr(data, 'items'):
+ params = list(data.items() if isinstance(data, dict) else data)
result = []
- for k, vs in list(data.items()):
+ for k, vs in params:
for v in isinstance(vs, list) and vs or [vs]:
- result.append((k.encode('utf-8') if isinstance(k, str) else k,
- v.encode('utf-8') if isinstance(v, str) else v))
- return result, urlencode(result, doseq=True)
+ result.append(
+ (k.encode('utf-8') if isinstance(k, str) else k,
+ v.encode('utf-8') if isinstance(v, str) else v))
+ return urlencode(result, doseq=True)
else:
- return data, data
+ return data
- def _encode_files(self,files):
+ def _encode_files(self, files):
if (not files) or isinstance(self.data, str):
- return None, None
+ return None
try:
fields = self.data.copy()
@@ -352,11 +353,13 @@ class Request(object):
else:
fn = guess_filename(v) or k
fp = v
+ if isinstance(fp, (bytes, str)):
+ fp = StringIO(fp)
fields.update({k: (fn, fp.read())})
(body, content_type) = encode_multipart_formdata(fields)
- return files, (body, content_type)
+ return (body, content_type)
@property
def full_url(self):
@@ -381,7 +384,6 @@ class Request(object):
if not path:
path = '/'
-
if is_py2:
if isinstance(scheme, str):
scheme = scheme.encode('utf-8')
@@ -398,11 +400,12 @@ class Request(object):
url = (urlunparse([scheme, netloc, path, params, query, fragment]))
- if self._enc_params:
+ enc_params = self._encode_params(self.params)
+ if enc_params:
if urlparse(url).query:
- url = '%s&%s' % (url, self._enc_params)
+ url = '%s&%s' % (url, enc_params)
else:
- url = '%s?%s' % (url, self._enc_params)
+ url = '%s?%s' % (url, enc_params)
if self.config.get('encode_uri', True):
url = requote_uri(url)
@@ -439,7 +442,7 @@ class Request(object):
self.hooks[event].append(hook)
- def deregister_hook(self,event,hook):
+ def deregister_hook(self, event, hook):
"""Deregister a previously registered hook.
Returns True if the hook existed, False if not.
"""
@@ -464,6 +467,10 @@ class Request(object):
# Build the URL
url = self.full_url
+ # Pre-request hook.
+ r = dispatch_hook('pre_request', self.hooks, self)
+ self.__dict__.update(r.__dict__)
+
# Logging
if self.config.get('verbose'):
self.config.get('verbose').write('%s %s %s\n' % (
@@ -474,22 +481,6 @@ class Request(object):
body = None
content_type = None
- # Multi-part file uploads.
- if self.files:
- (body, content_type) = self._enc_files
- else:
- if self.data:
-
- body = self._enc_data
- if isinstance(self.data, str):
- content_type = None
- else:
- content_type = 'application/x-www-form-urlencoded'
-
- # Add content-type if it wasn't explicitly provided.
- if (content_type) and (not 'content-type' in self.headers):
- self.headers['Content-Type'] = content_type
-
# Use .netrc auth if none was provided.
if not self.auth and self.config.get('trust_env'):
self.auth = get_netrc_auth(url)
@@ -505,6 +496,22 @@ class Request(object):
# Update self to reflect the auth changes.
self.__dict__.update(r.__dict__)
+ # Multi-part file uploads.
+ if self.files:
+ (body, content_type) = self._encode_files(self.files)
+ else:
+ if self.data:
+
+ body = self._encode_params(self.data)
+ if isinstance(self.data, str):
+ content_type = None
+ else:
+ content_type = 'application/x-www-form-urlencoded'
+
+ # Add content-type if it wasn't explicitly provided.
+ if (content_type) and (not 'content-type' in self.headers):
+ self.headers['Content-Type'] = content_type
+
_p = urlparse(url)
proxy = self.proxies.get(_p.scheme)
@@ -523,6 +530,7 @@ class Request(object):
conn = self._poolmanager.connection_from_url(url)
else:
conn = connectionpool.connection_from_url(url)
+ self.headers['Connection'] = 'close'
except LocationParseError as e:
raise InvalidURL(e)
@@ -563,24 +571,14 @@ class Request(object):
if not self.sent or anyway:
- if self.cookies:
-
- # Skip if 'cookie' header is explicitly set.
- if 'cookie' not in self.headers:
-
- # Simple cookie with our dict.
- c = SimpleCookie()
- for (k, v) in list(self.cookies.items()):
- c[k] = v
-
- # Turn it into a header.
- cookie_header = c.output(header='', sep='; ').strip()
-
- # Attach Cookie header to request.
+ # Skip if 'cookie' header is explicitly set.
+ if 'cookie' not in self.headers:
+ cookie_header = get_cookie_header(self.cookies, self)
+ if cookie_header is not None:
self.headers['Cookie'] = cookie_header
- # Pre-request hook.
- r = dispatch_hook('pre_request', self.hooks, self)
+ # Pre-send hook.
+ r = dispatch_hook('pre_send', self.hooks, self)
self.__dict__.update(r.__dict__)
try:
@@ -621,7 +619,15 @@ class Request(object):
else:
raise
- self._build_response(r)
+ # build_response can throw TooManyRedirects
+ try:
+ self._build_response(r)
+ except RequestException as e:
+ if self.config.get('safe_mode', False):
+ # In safe mode, catch the exception
+ self.response.error = e
+ else:
+ raise
# Response manipulation hook.
self.response = dispatch_hook('response', self.hooks, self.response)
@@ -681,8 +687,8 @@ class Response(object):
#: The :class:`Request <Request>` that created the Response.
self.request = None
- #: A dictionary of Cookies the server sent back.
- self.cookies = {}
+ #: A CookieJar of Cookies the server sent back.
+ self.cookies = None
#: Dictionary of configurations for this request.
self.config = {}
@@ -748,7 +754,7 @@ class Response(object):
chunk = pending + chunk
lines = chunk.splitlines()
- if lines[-1][-1] == chunk[-1]:
+ if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
pending = lines.pop()
else:
pending = None
@@ -773,7 +779,7 @@ class Response(object):
if self.status_code is 0:
self._content = None
else:
- self._content = bytes().join(self.iter_content()) or bytes()
+ self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
except AttributeError:
self._content = None
@@ -781,16 +787,6 @@ class Response(object):
self._content_consumed = True
return self._content
- def _detected_encoding(self):
- try:
- detected = chardet.detect(self.content) or {}
- return detected.get('encoding')
-
- # Trust that chardet isn't available or something went terribly wrong.
- except Exception:
- pass
-
-
@property
def text(self):
"""Content of the response, in unicode.
@@ -803,9 +799,10 @@ class Response(object):
content = None
encoding = self.encoding
- # Fallback to auto-detected encoding if chardet is available.
+ # Fallback to auto-detected encoding.
if self.encoding is None:
- encoding = self._detected_encoding()
+ if chardet is not None:
+ encoding = chardet.detect(self.content)['encoding']
# Decode unicode from given encoding.
try:
@@ -816,11 +813,17 @@ class Response(object):
#
# So we try blindly encoding.
content = str(self.content, errors='replace')
- except (UnicodeError, TypeError):
- pass
return content
+ @property
+ def json(self):
+ """Returns the json-encoded content of a request, if any."""
+ try:
+ return json.loads(self.text or self.content)
+ except ValueError:
+ return None
+
def raise_for_status(self, allow_redirects=True):
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""
@@ -837,7 +840,6 @@ class Response(object):
http_error.response = self
raise http_error
-
elif (self.status_code >= 500) and (self.status_code < 600):
http_error = HTTPError('%s Server Error' % self.status_code)
http_error.response = self
diff --git a/requests/sessions.py b/requests/sessions.py
index 0e43030..8d517ab 100644
--- a/requests/sessions.py
+++ b/requests/sessions.py
@@ -9,13 +9,14 @@ requests (cookies, auth, proxies).
"""
+from .compat import cookielib
+from .cookies import cookiejar_from_dict, remove_cookie_by_name
from .defaults import defaults
from .models import Request
from .hooks import dispatch_hook
from .utils import header_expand
from .packages.urllib3.poolmanager import PoolManager
-
def merge_kwargs(local_kwarg, default_kwarg):
"""Merges kwarg dictionaries.
@@ -69,7 +70,6 @@ class Session(object):
cert=None):
self.headers = headers or {}
- self.cookies = cookies or {}
self.auth = auth
self.timeout = timeout
self.proxies = proxies or {}
@@ -86,11 +86,10 @@ class Session(object):
self.init_poolmanager()
# Set up a CookieJar to be used by default
- self.cookies = {}
-
- # Add passed cookies in.
- if cookies is not None:
- self.cookies.update(cookies)
+ if isinstance(cookies, cookielib.CookieJar):
+ self.cookies = cookies
+ else:
+ self.cookies = cookiejar_from_dict(cookies)
def init_poolmanager(self):
self.poolmanager = PoolManager(
@@ -134,7 +133,7 @@ class Session(object):
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
- :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
+ :param auth: (optional) Auth tuple or callable 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 by default.
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
@@ -148,7 +147,6 @@ class Session(object):
method = str(method).upper()
# Default empty dicts for dict params.
- cookies = {} if cookies is None else cookies
data = {} if data is None else data
files = {} if files is None else files
headers = {} if headers is None else headers
@@ -185,11 +183,33 @@ class Session(object):
_poolmanager=self.poolmanager
)
+ # merge session cookies into passed-in ones
+ dead_cookies = None
+ # passed-in cookies must become a CookieJar:
+ if not isinstance(cookies, cookielib.CookieJar):
+ args['cookies'] = cookiejar_from_dict(cookies)
+ # support unsetting cookies that have been passed in with None values
+ # this is only meaningful when `cookies` is a dict ---
+ # for a real CookieJar, the client should use session.cookies.clear()
+ if cookies is not None:
+ dead_cookies = [name for name in cookies if cookies[name] is None]
+ # merge the session's cookies into the passed-in cookies:
+ for cookie in self.cookies:
+ args['cookies'].set_cookie(cookie)
+ # remove the unset cookies from the jar we'll be using with the current request
+ # (but not from the session's own store of cookies):
+ if dead_cookies is not None:
+ for name in dead_cookies:
+ remove_cookie_by_name(args['cookies'], name)
+
# Merge local kwargs with session kwargs.
for attr in self.__attrs__:
+ # we already merged cookies:
+ if attr == 'cookies':
+ continue
+
session_val = getattr(self, attr, None)
local_val = args.get(attr)
-
args[attr] = merge_kwargs(local_val, session_val)
# Arguments manipulation hook.
@@ -209,7 +229,10 @@ class Session(object):
r.send(prefetch=prefetch)
# Send any cookies back up the to the session.
- self.cookies.update(r.response.cookies)
+ # (in safe mode, cookies may be None if the request didn't succeed)
+ if r.response.cookies is not None:
+ for cookie in r.response.cookies:
+ self.cookies.set_cookie(cookie)
# Return the response.
return r.response
diff --git a/requests/utils.py b/requests/utils.py
index 925547a..8365cc3 100644
--- a/requests/utils.py
+++ b/requests/utils.py
@@ -18,8 +18,11 @@ import zlib
from netrc import netrc, NetrcParseError
from .compat import parse_http_list as _parse_list_header
-from .compat import quote, cookielib, SimpleCookie, is_py2, urlparse
+from .compat import quote, is_py2, urlparse
from .compat import basestring, bytes, str
+from .cookies import RequestsCookieJar, cookiejar_from_dict
+
+_hush_pyflakes = (RequestsCookieJar,)
CERTIFI_BUNDLE_PATH = None
try:
@@ -97,25 +100,6 @@ def get_netrc_auth(url):
pass
-
-def dict_from_string(s):
- """Returns a MultiDict with Cookies."""
-
- cookies = dict()
-
- try:
- c = SimpleCookie()
- c.load(s)
-
- for k, v in list(c.items()):
- cookies.update({k: v.value})
- # This stuff is not to be trusted.
- except Exception:
- pass
-
- return cookies
-
-
def guess_filename(obj):
"""Tries to guess the filename of the given object."""
name = getattr(obj, 'name', None)
@@ -290,24 +274,6 @@ def dict_from_cookiejar(cj):
return cookie_dict
-def cookiejar_from_dict(cookie_dict):
- """Returns a CookieJar from a key/value dictionary.
-
- :param cookie_dict: Dict of key/values to insert into CookieJar.
- """
-
- # return cookiejar if one was passed in
- if isinstance(cookie_dict, cookielib.CookieJar):
- return cookie_dict
-
- # create cookiejar
- cj = cookielib.CookieJar()
-
- cj = add_dict_to_cookiejar(cj, cookie_dict)
-
- return cj
-
-
def add_dict_to_cookiejar(cj, cookie_dict):
"""Returns a CookieJar from a key/value dictionary.
@@ -315,31 +281,9 @@ def add_dict_to_cookiejar(cj, cookie_dict):
:param cookie_dict: Dict of key/values to insert into CookieJar.
"""
- for k, v in list(cookie_dict.items()):
-
- cookie = cookielib.Cookie(
- version=0,
- name=k,
- value=v,
- port=None,
- port_specified=False,
- domain='',
- domain_specified=False,
- domain_initial_dot=False,
- path='/',
- path_specified=True,
- secure=False,
- expires=None,
- discard=True,
- comment=None,
- comment_url=None,
- rest={'HttpOnly': None},
- rfc2109=False
- )
-
- # add cookie to cookiejar
+ cj2 = cookiejar_from_dict(cookie_dict)
+ for cookie in cj2:
cj.set_cookie(cookie)
-
return cj
@@ -502,3 +446,19 @@ def requote_uri(uri):
# Then quote only illegal characters (do not quote reserved, unreserved,
# or '%')
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
+
+def get_environ_proxies():
+ """Return a dict of environment proxies."""
+
+ proxy_keys = [
+ 'all',
+ 'http',
+ 'https',
+ 'ftp',
+ 'socks',
+ 'no'
+ ]
+
+ get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
+ proxies = [(key, get_proxy(key + '_proxy')) for key in proxy_keys]
+ return dict([(key, val) for (key, val) in proxies if val])