15
16
from .status_codes import codes
17
18
from .auth import HTTPBasicAuth, HTTPProxyAuth
19
from .cookies import cookiejar_from_dict, extract_cookies_to_jar, get_cookie_header
18
20
from .packages.urllib3.response import HTTPResponse
19
21
from .packages.urllib3.exceptions import MaxRetryError, LocationParseError
20
22
from .packages.urllib3.exceptions import SSLError as _SSLError
27
29
URLRequired, SSLError, MissingSchema, InvalidSchema, InvalidURL)
28
30
from .utils import (
29
31
get_encoding_from_headers, stream_untransfer, guess_filename, requote_uri,
30
dict_from_string, stream_decode_response_unicode, get_netrc_auth,
32
stream_decode_response_unicode, get_netrc_auth, get_environ_proxies,
31
33
DEFAULT_CA_BUNDLE_PATH)
32
34
from .compat import (
33
urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
35
cookielib, urlparse, urlunparse, urljoin, urlsplit, urlencode, str, bytes,
36
38
# Import chardet if it is available.
39
43
except ImportError:
42
46
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
47
CONTENT_CHUNK_SIZE = 10 * 1024
45
49
class Request(object):
46
50
"""The :class:`Request <Request>` object. It carries out all functionality of
109
113
# If no proxies are given, allow configuration by environment variables
110
114
# HTTP_PROXY and HTTPS_PROXY.
111
115
if not self.proxies and self.config.get('trust_env'):
112
if 'HTTP_PROXY' in os.environ:
113
self.proxies['http'] = os.environ['HTTP_PROXY']
114
if 'HTTPS_PROXY' in os.environ:
115
self.proxies['https'] = os.environ['HTTPS_PROXY']
116
self.proxies = get_environ_proxies()
117
self.data, self._enc_data = self._encode_params(data)
118
self.params, self._enc_params = self._encode_params(params)
119
self.files, self._enc_files = self._encode_files(files)
121
122
#: :class:`Response <Response>` instance, containing
122
123
#: content and metadata of HTTP Response, once :attr:`sent <send>`.
128
129
#: CookieJar to attach to :class:`Request <Request>`.
129
self.cookies = dict(cookies or [])
130
if isinstance(cookies, cookielib.CookieJar):
131
self.cookies = cookies
133
self.cookies = cookiejar_from_dict(cookies)
131
135
#: True if Request has been sent.
132
136
self.sent = False
194
198
response.encoding = get_encoding_from_headers(response.headers)
196
# Start off with our local cookies.
197
cookies = self.cookies or dict()
199
200
# Add new cookies from the server.
200
if 'set-cookie' in response.headers:
201
cookie_header = response.headers['set-cookie']
202
cookies = dict_from_string(cookie_header)
201
extract_cookies_to_jar(self.cookies, self, resp)
204
203
# Save cookies in Response.
205
response.cookies = cookies
204
response.cookies = self.cookies
207
206
# No exceptions were harmed in the making of this request.
208
207
response.error = getattr(resp, 'error', None)
301
302
r = request.response
302
self.cookies.update(r.cookies)
304
304
r.history = history
306
306
self.response = r
307
307
self.response.request = self
308
self.response.cookies.update(self.cookies)
311
310
def _encode_params(data):
312
311
"""Encode parameters in a piece of data.
314
If the data supplied is a dictionary, encodes each parameter in it, and
315
returns a list of tuples containing the encoded parameters, and a urlencoded
318
Otherwise, assumes the data is already encoded appropriately, and
313
Will successfully encode parameters when passed as a dict or a list of
314
2-tuples. Order is retained if data is a list of 2-tuples but abritrary
315
if parameters are supplied as a dict.
322
318
if isinstance(data, bytes):
325
if hasattr(data, '__iter__') and not isinstance(data, str):
328
if hasattr(data, 'items'):
320
if isinstance(data, str):
322
elif hasattr(data, '__iter__'):
326
raise ValueError('Unable to encode lists with elements that are not 2-tuples.')
328
params = list(data.items() if isinstance(data, dict) else data)
330
for k, vs in list(data.items()):
331
331
for v in isinstance(vs, list) and vs or [vs]:
332
result.append((k.encode('utf-8') if isinstance(k, str) else k,
333
v.encode('utf-8') if isinstance(v, str) else v))
334
return result, urlencode(result, doseq=True)
333
(k.encode('utf-8') if isinstance(k, str) else k,
334
v.encode('utf-8') if isinstance(v, str) else v))
335
return urlencode(result, doseq=True)
338
def _encode_files(self,files):
339
def _encode_files(self, files):
340
341
if (not files) or isinstance(self.data, str):
344
345
fields = self.data.copy()
353
354
fn = guess_filename(v) or k
356
if isinstance(fp, (bytes, str)):
355
358
fields.update({k: (fn, fp.read())})
357
360
(body, content_type) = encode_multipart_formdata(fields)
359
return files, (body, content_type)
362
return (body, content_type)
362
365
def full_url(self):
399
401
url = (urlunparse([scheme, netloc, path, params, query, fragment]))
403
enc_params = self._encode_params(self.params)
402
405
if urlparse(url).query:
403
url = '%s&%s' % (url, self._enc_params)
406
url = '%s&%s' % (url, enc_params)
405
url = '%s?%s' % (url, self._enc_params)
408
url = '%s?%s' % (url, enc_params)
407
410
if self.config.get('encode_uri', True):
408
411
url = requote_uri(url)
440
443
self.hooks[event].append(hook)
442
def deregister_hook(self,event,hook):
445
def deregister_hook(self, event, hook):
443
446
"""Deregister a previously registered hook.
444
447
Returns True if the hook existed, False if not.
475
482
content_type = None
477
# Multi-part file uploads.
479
(body, content_type) = self._enc_files
483
body = self._enc_data
484
if isinstance(self.data, str):
487
content_type = 'application/x-www-form-urlencoded'
489
# Add content-type if it wasn't explicitly provided.
490
if (content_type) and (not 'content-type' in self.headers):
491
self.headers['Content-Type'] = content_type
493
484
# Use .netrc auth if none was provided.
494
485
if not self.auth and self.config.get('trust_env'):
495
486
self.auth = get_netrc_auth(url)
505
496
# Update self to reflect the auth changes.
506
497
self.__dict__.update(r.__dict__)
499
# Multi-part file uploads.
501
(body, content_type) = self._encode_files(self.files)
505
body = self._encode_params(self.data)
506
if isinstance(self.data, str):
509
content_type = 'application/x-www-form-urlencoded'
511
# Add content-type if it wasn't explicitly provided.
512
if (content_type) and (not 'content-type' in self.headers):
513
self.headers['Content-Type'] = content_type
508
515
_p = urlparse(url)
509
516
proxy = self.proxies.get(_p.scheme)
564
572
if not self.sent or anyway:
568
# Skip if 'cookie' header is explicitly set.
569
if 'cookie' not in self.headers:
571
# Simple cookie with our dict.
573
for (k, v) in list(self.cookies.items()):
576
# Turn it into a header.
577
cookie_header = c.output(header='', sep='; ').strip()
579
# Attach Cookie header to request.
574
# Skip if 'cookie' header is explicitly set.
575
if 'cookie' not in self.headers:
576
cookie_header = get_cookie_header(self.cookies, self)
577
if cookie_header is not None:
580
578
self.headers['Cookie'] = cookie_header
583
r = dispatch_hook('pre_request', self.hooks, self)
581
r = dispatch_hook('pre_send', self.hooks, self)
584
582
self.__dict__.update(r.__dict__)
624
self._build_response(r)
622
# build_response can throw TooManyRedirects
624
self._build_response(r)
625
except RequestException as e:
626
if self.config.get('safe_mode', False):
627
# In safe mode, catch the exception
628
self.response.error = e
626
632
# Response manipulation hook.
627
633
self.response = dispatch_hook('response', self.hooks, self.response)
781
787
self._content_consumed = True
782
788
return self._content
784
def _detected_encoding(self):
786
detected = chardet.detect(self.content) or {}
787
return detected.get('encoding')
789
# Trust that chardet isn't available or something went terribly wrong.
796
792
"""Content of the response, in unicode.
804
800
encoding = self.encoding
806
# Fallback to auto-detected encoding if chardet is available.
802
# Fallback to auto-detected encoding.
807
803
if self.encoding is None:
808
encoding = self._detected_encoding()
804
if chardet is not None:
805
encoding = chardet.detect(self.content)['encoding']
810
807
# Decode unicode from given encoding.
817
814
# So we try blindly encoding.
818
815
content = str(self.content, errors='replace')
819
except (UnicodeError, TypeError):
821
"""Returns the json-encoded content of a request, if any."""
823
return json.loads(self.text or self.content)
824
827
def raise_for_status(self, allow_redirects=True):
825
828
"""Raises stored :class:`HTTPError` or :class:`URLError`, if one occurred."""