fe4bec1bd33eafb2af6a8cb61661fb2a33bef5b5
[sdc/sdc-distribution-client.git] /
1 # -*- coding: utf-8 -*-
2
3 """
4 requests.models
5 ~~~~~~~~~~~~~~~
6
7 This module contains the primary objects that power Requests.
8 """
9
10 import collections
11 import datetime
12
13 from io import BytesIO, UnsupportedOperation
14 from .hooks import default_hooks
15 from .structures import CaseInsensitiveDict
16
17 from .auth import HTTPBasicAuth
18 from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
19 from .packages.urllib3.fields import RequestField
20 from .packages.urllib3.filepost import encode_multipart_formdata
21 from .packages.urllib3.util import parse_url
22 from .packages.urllib3.exceptions import (
23     DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
24 from .exceptions import (
25     HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
26     ContentDecodingError, ConnectionError, StreamConsumedError)
27 from .utils import (
28     guess_filename, get_auth_from_url, requote_uri,
29     stream_decode_response_unicode, to_key_val_list, parse_header_links,
30     iter_slices, guess_json_utf, super_len, to_native_string)
31 from .compat import (
32     cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
33     is_py2, chardet, builtin_str, basestring)
34 from .compat import json as complexjson
35 from .status_codes import codes
36
37 #: The set of HTTP status codes that indicate an automatically
38 #: processable redirect.
39 REDIRECT_STATI = (
40     codes.moved,              # 301
41     codes.found,              # 302
42     codes.other,              # 303
43     codes.temporary_redirect, # 307
44     codes.permanent_redirect, # 308
45 )
46
47 DEFAULT_REDIRECT_LIMIT = 30
48 CONTENT_CHUNK_SIZE = 10 * 1024
49 ITER_CHUNK_SIZE = 512
50
51
52 class RequestEncodingMixin(object):
53     @property
54     def path_url(self):
55         """Build the path URL to use."""
56
57         url = []
58
59         p = urlsplit(self.url)
60
61         path = p.path
62         if not path:
63             path = '/'
64
65         url.append(path)
66
67         query = p.query
68         if query:
69             url.append('?')
70             url.append(query)
71
72         return ''.join(url)
73
74     @staticmethod
75     def _encode_params(data):
76         """Encode parameters in a piece of data.
77
78         Will successfully encode parameters when passed as a dict or a list of
79         2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
80         if parameters are supplied as a dict.
81         """
82
83         if isinstance(data, (str, bytes)):
84             return data
85         elif hasattr(data, 'read'):
86             return data
87         elif hasattr(data, '__iter__'):
88             result = []
89             for k, vs in to_key_val_list(data):
90                 if isinstance(vs, basestring) or not hasattr(vs, '__iter__'):
91                     vs = [vs]
92                 for v in vs:
93                     if v is not None:
94                         result.append(
95                             (k.encode('utf-8') if isinstance(k, str) else k,
96                              v.encode('utf-8') if isinstance(v, str) else v))
97             return urlencode(result, doseq=True)
98         else:
99             return data
100
101     @staticmethod
102     def _encode_files(files, data):
103         """Build the body for a multipart/form-data request.
104
105         Will successfully encode files when passed as a dict or a list of
106         tuples. Order is retained if data is a list of tuples but arbitrary
107         if parameters are supplied as a dict.
108         The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
109         or 4-tuples (filename, fileobj, contentype, custom_headers).
110
111         """
112         if (not files):
113             raise ValueError("Files must be provided.")
114         elif isinstance(data, basestring):
115             raise ValueError("Data must not be a string.")
116
117         new_fields = []
118         fields = to_key_val_list(data or {})
119         files = to_key_val_list(files or {})
120
121         for field, val in fields:
122             if isinstance(val, basestring) or not hasattr(val, '__iter__'):
123                 val = [val]
124             for v in val:
125                 if v is not None:
126                     # Don't call str() on bytestrings: in Py3 it all goes wrong.
127                     if not isinstance(v, bytes):
128                         v = str(v)
129
130                     new_fields.append(
131                         (field.decode('utf-8') if isinstance(field, bytes) else field,
132                          v.encode('utf-8') if isinstance(v, str) else v))
133
134         for (k, v) in files:
135             # support for explicit filename
136             ft = None
137             fh = None
138             if isinstance(v, (tuple, list)):
139                 if len(v) == 2:
140                     fn, fp = v
141                 elif len(v) == 3:
142                     fn, fp, ft = v
143                 else:
144                     fn, fp, ft, fh = v
145             else:
146                 fn = guess_filename(v) or k
147                 fp = v
148
149             if isinstance(fp, (str, bytes, bytearray)):
150                 fdata = fp
151             else:
152                 fdata = fp.read()
153
154             rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
155             rf.make_multipart(content_type=ft)
156             new_fields.append(rf)
157
158         body, content_type = encode_multipart_formdata(new_fields)
159
160         return body, content_type
161
162
163 class RequestHooksMixin(object):
164     def register_hook(self, event, hook):
165         """Properly register a hook."""
166
167         if event not in self.hooks:
168             raise ValueError('Unsupported event specified, with event name "%s"' % (event))
169
170         if isinstance(hook, collections.Callable):
171             self.hooks[event].append(hook)
172         elif hasattr(hook, '__iter__'):
173             self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable))
174
175     def deregister_hook(self, event, hook):
176         """Deregister a previously registered hook.
177         Returns True if the hook existed, False if not.
178         """
179
180         try:
181             self.hooks[event].remove(hook)
182             return True
183         except ValueError:
184             return False
185
186
187 class Request(RequestHooksMixin):
188     """A user-created :class:`Request <Request>` object.
189
190     Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server.
191
192     :param method: HTTP method to use.
193     :param url: URL to send.
194     :param headers: dictionary of headers to send.
195     :param files: dictionary of {filename: fileobject} files to multipart upload.
196     :param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place.
197     :param json: json for the body to attach to the request (if files or data is not specified).
198     :param params: dictionary of URL parameters to append to the URL.
199     :param auth: Auth handler or (user, pass) tuple.
200     :param cookies: dictionary or CookieJar of cookies to attach to this request.
201     :param hooks: dictionary of callback hooks, for internal usage.
202
203     Usage::
204
205       >>> import requests
206       >>> req = requests.Request('GET', 'http://httpbin.org/get')
207       >>> req.prepare()
208       <PreparedRequest [GET]>
209
210     """
211     def __init__(self, method=None, url=None, headers=None, files=None,
212         data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
213
214         # Default empty dicts for dict params.
215         data = [] if data is None else data
216         files = [] if files is None else files
217         headers = {} if headers is None else headers
218         params = {} if params is None else params
219         hooks = {} if hooks is None else hooks
220
221         self.hooks = default_hooks()
222         for (k, v) in list(hooks.items()):
223             self.register_hook(event=k, hook=v)
224
225         self.method = method
226         self.url = url
227         self.headers = headers
228         self.files = files
229         self.data = data
230         self.json = json
231         self.params = params
232         self.auth = auth
233         self.cookies = cookies
234
235     def __repr__(self):
236         return '<Request [%s]>' % (self.method)
237
238     def prepare(self):
239         """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
240         p = PreparedRequest()
241         p.prepare(
242             method=self.method,
243             url=self.url,
244             headers=self.headers,
245             files=self.files,
246             data=self.data,
247             json=self.json,
248             params=self.params,
249             auth=self.auth,
250             cookies=self.cookies,
251             hooks=self.hooks,
252         )
253         return p
254
255
256 class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
257     """The fully mutable :class:`PreparedRequest <PreparedRequest>` object,
258     containing the exact bytes that will be sent to the server.
259
260     Generated from either a :class:`Request <Request>` object or manually.
261
262     Usage::
263
264       >>> import requests
265       >>> req = requests.Request('GET', 'http://httpbin.org/get')
266       >>> r = req.prepare()
267       <PreparedRequest [GET]>
268
269       >>> s = requests.Session()
270       >>> s.send(r)
271       <Response [200]>
272
273     """
274
275     def __init__(self):
276         #: HTTP verb to send to the server.
277         self.method = None
278         #: HTTP URL to send the request to.
279         self.url = None
280         #: dictionary of HTTP headers.
281         self.headers = None
282         # The `CookieJar` used to create the Cookie header will be stored here
283         # after prepare_cookies is called
284         self._cookies = None
285         #: request body to send to the server.
286         self.body = None
287         #: dictionary of callback hooks, for internal usage.
288         self.hooks = default_hooks()
289
290     def prepare(self, method=None, url=None, headers=None, files=None,
291         data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
292         """Prepares the entire request with the given parameters."""
293
294         self.prepare_method(method)
295         self.prepare_url(url, params)
296         self.prepare_headers(headers)
297         self.prepare_cookies(cookies)
298         self.prepare_body(data, files, json)
299         self.prepare_auth(auth, url)
300
301         # Note that prepare_auth must be last to enable authentication schemes
302         # such as OAuth to work on a fully prepared request.
303
304         # This MUST go after prepare_auth. Authenticators could add a hook
305         self.prepare_hooks(hooks)
306
307     def __repr__(self):
308         return '<PreparedRequest [%s]>' % (self.method)
309
310     def copy(self):
311         p = PreparedRequest()
312         p.method = self.method
313         p.url = self.url
314         p.headers = self.headers.copy() if self.headers is not None else None
315         p._cookies = _copy_cookie_jar(self._cookies)
316         p.body = self.body
317         p.hooks = self.hooks
318         return p
319
320     def prepare_method(self, method):
321         """Prepares the given HTTP method."""
322         self.method = method
323         if self.method is not None:
324             self.method = to_native_string(self.method.upper())
325
326     def prepare_url(self, url, params):
327         """Prepares the given HTTP URL."""
328         #: Accept objects that have string representations.
329         #: We're unable to blindly call unicode/str functions
330         #: as this will include the bytestring indicator (b'')
331         #: on python 3.x.
332         #: https://github.com/kennethreitz/requests/pull/2238
333         if isinstance(url, bytes):
334             url = url.decode('utf8')
335         else:
336             url = unicode(url) if is_py2 else str(url)
337
338         # Don't do any URL preparation for non-HTTP schemes like `mailto`,
339         # `data` etc to work around exceptions from `url_parse`, which
340         # handles RFC 3986 only.
341         if ':' in url and not url.lower().startswith('http'):
342             self.url = url
343             return
344
345         # Support for unicode domain names and paths.
346         try:
347             scheme, auth, host, port, path, query, fragment = parse_url(url)
348         except LocationParseError as e:
349             raise InvalidURL(*e.args)
350
351         if not scheme:
352             error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
353             error = error.format(to_native_string(url, 'utf8'))
354
355             raise MissingSchema(error)
356
357         if not host:
358             raise InvalidURL("Invalid URL %r: No host supplied" % url)
359
360         # Only want to apply IDNA to the hostname
361         try:
362             host = host.encode('idna').decode('utf-8')
363         except UnicodeError:
364             raise InvalidURL('URL has an invalid label.')
365
366         # Carefully reconstruct the network location
367         netloc = auth or ''
368         if netloc:
369             netloc += '@'
370         netloc += host
371         if port:
372             netloc += ':' + str(port)
373
374         # Bare domains aren't valid URLs.
375         if not path:
376             path = '/'
377
378         if is_py2:
379             if isinstance(scheme, str):
380                 scheme = scheme.encode('utf-8')
381             if isinstance(netloc, str):
382                 netloc = netloc.encode('utf-8')
383             if isinstance(path, str):
384                 path = path.encode('utf-8')
385             if isinstance(query, str):
386                 query = query.encode('utf-8')
387             if isinstance(fragment, str):
388                 fragment = fragment.encode('utf-8')
389
390         if isinstance(params, (str, bytes)):
391             params = to_native_string(params)
392
393         enc_params = self._encode_params(params)
394         if enc_params:
395             if query:
396                 query = '%s&%s' % (query, enc_params)
397             else:
398                 query = enc_params
399
400         url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
401         self.url = url
402
403     def prepare_headers(self, headers):
404         """Prepares the given HTTP headers."""
405
406         if headers:
407             self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items())
408         else:
409             self.headers = CaseInsensitiveDict()
410
411     def prepare_body(self, data, files, json=None):
412         """Prepares the given HTTP body data."""
413
414         # Check if file, fo, generator, iterator.
415         # If not, run through normal process.
416
417         # Nottin' on you.
418         body = None
419         content_type = None
420         length = None
421
422         if not data and json is not None:
423             content_type = 'application/json'
424             body = complexjson.dumps(json)
425
426         is_stream = all([
427             hasattr(data, '__iter__'),
428             not isinstance(data, (basestring, list, tuple, dict))
429         ])
430
431         try:
432             length = super_len(data)
433         except (TypeError, AttributeError, UnsupportedOperation):
434             length = None
435
436         if is_stream:
437             body = data
438
439             if files:
440                 raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
441
442             if length:
443                 self.headers['Content-Length'] = builtin_str(length)
444             else:
445                 self.headers['Transfer-Encoding'] = 'chunked'
446         else:
447             # Multi-part file uploads.
448             if files:
449                 (body, content_type) = self._encode_files(files, data)
450             else:
451                 if data:
452                     body = self._encode_params(data)
453                     if isinstance(data, basestring) or hasattr(data, 'read'):
454                         content_type = None
455                     else:
456                         content_type = 'application/x-www-form-urlencoded'
457
458             self.prepare_content_length(body)
459
460             # Add content-type if it wasn't explicitly provided.
461             if content_type and ('content-type' not in self.headers):
462                 self.headers['Content-Type'] = content_type
463
464         self.body = body
465
466     def prepare_content_length(self, body):
467         if hasattr(body, 'seek') and hasattr(body, 'tell'):
468             curr_pos = body.tell()
469             body.seek(0, 2)
470             end_pos = body.tell()
471             self.headers['Content-Length'] = builtin_str(max(0, end_pos - curr_pos))
472             body.seek(curr_pos, 0)
473         elif body is not None:
474             l = super_len(body)
475             if l:
476                 self.headers['Content-Length'] = builtin_str(l)
477         elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None):
478             self.headers['Content-Length'] = '0'
479
480     def prepare_auth(self, auth, url=''):
481         """Prepares the given HTTP auth data."""
482
483         # If no Auth is explicitly provided, extract it from the URL first.
484         if auth is None:
485             url_auth = get_auth_from_url(self.url)
486             auth = url_auth if any(url_auth) else None
487
488         if auth:
489             if isinstance(auth, tuple) and len(auth) == 2:
490                 # special-case basic HTTP auth
491                 auth = HTTPBasicAuth(*auth)
492
493             # Allow auth to make its changes.
494             r = auth(self)
495
496             # Update self to reflect the auth changes.
497             self.__dict__.update(r.__dict__)
498
499             # Recompute Content-Length
500             self.prepare_content_length(self.body)
501
502     def prepare_cookies(self, cookies):
503         """Prepares the given HTTP cookie data.
504
505         This function eventually generates a ``Cookie`` header from the
506         given cookies using cookielib. Due to cookielib's design, the header
507         will not be regenerated if it already exists, meaning this function
508         can only be called once for the life of the
509         :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
510         to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
511         header is removed beforehand."""
512
513         if isinstance(cookies, cookielib.CookieJar):
514             self._cookies = cookies
515         else:
516             self._cookies = cookiejar_from_dict(cookies)
517
518         cookie_header = get_cookie_header(self._cookies, self)
519         if cookie_header is not None:
520             self.headers['Cookie'] = cookie_header
521
522     def prepare_hooks(self, hooks):
523         """Prepares the given hooks."""
524         # hooks can be passed as None to the prepare method and to this
525         # method. To prevent iterating over None, simply use an empty list
526         # if hooks is False-y
527         hooks = hooks or []
528         for event in hooks:
529             self.register_hook(event, hooks[event])
530
531
532 class Response(object):
533     """The :class:`Response <Response>` object, which contains a
534     server's response to an HTTP request.
535     """
536
537     __attrs__ = [
538         '_content', 'status_code', 'headers', 'url', 'history',
539         'encoding', 'reason', 'cookies', 'elapsed', 'request'
540     ]
541
542     def __init__(self):
543         super(Response, self).__init__()
544
545         self._content = False
546         self._content_consumed = False
547
548         #: Integer Code of responded HTTP Status, e.g. 404 or 200.
549         self.status_code = None
550
551         #: Case-insensitive Dictionary of Response Headers.
552         #: For example, ``headers['content-encoding']`` will return the
553         #: value of a ``'Content-Encoding'`` response header.
554         self.headers = CaseInsensitiveDict()
555
556         #: File-like object representation of response (for advanced usage).
557         #: Use of ``raw`` requires that ``stream=True`` be set on the request.
558         # This requirement does not apply for use internally to Requests.
559         self.raw = None
560
561         #: Final URL location of Response.
562         self.url = None
563
564         #: Encoding to decode with when accessing r.text.
565         self.encoding = None
566
567         #: A list of :class:`Response <Response>` objects from
568         #: the history of the Request. Any redirect responses will end
569         #: up here. The list is sorted from the oldest to the most recent request.
570         self.history = []
571
572         #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
573         self.reason = None
574
575         #: A CookieJar of Cookies the server sent back.
576         self.cookies = cookiejar_from_dict({})
577
578         #: The amount of time elapsed between sending the request
579         #: and the arrival of the response (as a timedelta).
580         #: This property specifically measures the time taken between sending
581         #: the first byte of the request and finishing parsing the headers. It
582         #: is therefore unaffected by consuming the response content or the
583         #: value of the ``stream`` keyword argument.
584         self.elapsed = datetime.timedelta(0)
585
586         #: The :class:`PreparedRequest <PreparedRequest>` object to which this
587         #: is a response.
588         self.request = None
589
590     def __getstate__(self):
591         # Consume everything; accessing the content attribute makes
592         # sure the content has been fully read.
593         if not self._content_consumed:
594             self.content
595
596         return dict(
597             (attr, getattr(self, attr, None))
598             for attr in self.__attrs__
599         )
600
601     def __setstate__(self, state):
602         for name, value in state.items():
603             setattr(self, name, value)
604
605         # pickled objects do not have .raw
606         setattr(self, '_content_consumed', True)
607         setattr(self, 'raw', None)
608
609     def __repr__(self):
610         return '<Response [%s]>' % (self.status_code)
611
612     def __bool__(self):
613         """Returns true if :attr:`status_code` is 'OK'."""
614         return self.ok
615
616     def __nonzero__(self):
617         """Returns true if :attr:`status_code` is 'OK'."""
618         return self.ok
619
620     def __iter__(self):
621         """Allows you to use a response as an iterator."""
622         return self.iter_content(128)
623
624     @property
625     def ok(self):
626         try:
627             self.raise_for_status()
628         except HTTPError:
629             return False
630         return True
631
632     @property
633     def is_redirect(self):
634         """True if this Response is a well-formed HTTP redirect that could have
635         been processed automatically (by :meth:`Session.resolve_redirects`).
636         """
637         return ('location' in self.headers and self.status_code in REDIRECT_STATI)
638
639     @property
640     def is_permanent_redirect(self):
641         """True if this Response one of the permanent versions of redirect"""
642         return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
643
644     @property
645     def apparent_encoding(self):
646         """The apparent encoding, provided by the chardet library"""
647         return chardet.detect(self.content)['encoding']
648
649     def iter_content(self, chunk_size=1, decode_unicode=False):
650         """Iterates over the response data.  When stream=True is set on the
651         request, this avoids reading the content at once into memory for
652         large responses.  The chunk size is the number of bytes it should
653         read into memory.  This is not necessarily the length of each item
654         returned as decoding can take place.
655
656         If decode_unicode is True, content will be decoded using the best
657         available encoding based on the response.
658         """
659
660         def generate():
661             # Special case for urllib3.
662             if hasattr(self.raw, 'stream'):
663                 try:
664                     for chunk in self.raw.stream(chunk_size, decode_content=True):
665                         yield chunk
666                 except ProtocolError as e:
667                     raise ChunkedEncodingError(e)
668                 except DecodeError as e:
669                     raise ContentDecodingError(e)
670                 except ReadTimeoutError as e:
671                     raise ConnectionError(e)
672             else:
673                 # Standard file-like object.
674                 while True:
675                     chunk = self.raw.read(chunk_size)
676                     if not chunk:
677                         break
678                     yield chunk
679
680             self._content_consumed = True
681
682         if self._content_consumed and isinstance(self._content, bool):
683             raise StreamConsumedError()
684         # simulate reading small chunks of the content
685         reused_chunks = iter_slices(self._content, chunk_size)
686
687         stream_chunks = generate()
688
689         chunks = reused_chunks if self._content_consumed else stream_chunks
690
691         if decode_unicode:
692             chunks = stream_decode_response_unicode(chunks, self)
693
694         return chunks
695
696     def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None):
697         """Iterates over the response data, one line at a time.  When
698         stream=True is set on the request, this avoids reading the
699         content at once into memory for large responses.
700
701         .. note:: This method is not reentrant safe.
702         """
703
704         pending = None
705
706         for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
707
708             if pending is not None:
709                 chunk = pending + chunk
710
711             if delimiter:
712                 lines = chunk.split(delimiter)
713             else:
714                 lines = chunk.splitlines()
715
716             if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]:
717                 pending = lines.pop()
718             else:
719                 pending = None
720
721             for line in lines:
722                 yield line
723
724         if pending is not None:
725             yield pending
726
727     @property
728     def content(self):
729         """Content of the response, in bytes."""
730
731         if self._content is False:
732             # Read the contents.
733             try:
734                 if self._content_consumed:
735                     raise RuntimeError(
736                         'The content for this response was already consumed')
737
738                 if self.status_code == 0:
739                     self._content = None
740                 else:
741                     self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
742
743             except AttributeError:
744                 self._content = None
745
746         self._content_consumed = True
747         # don't need to release the connection; that's been handled by urllib3
748         # since we exhausted the data.
749         return self._content
750
751     @property
752     def text(self):
753         """Content of the response, in unicode.
754
755         If Response.encoding is None, encoding will be guessed using
756         ``chardet``.
757
758         The encoding of the response content is determined based solely on HTTP
759         headers, following RFC 2616 to the letter. If you can take advantage of
760         non-HTTP knowledge to make a better guess at the encoding, you should
761         set ``r.encoding`` appropriately before accessing this property.
762         """
763
764         # Try charset from content-type
765         content = None
766         encoding = self.encoding
767
768         if not self.content:
769             return str('')
770
771         # Fallback to auto-detected encoding.
772         if self.encoding is None:
773             encoding = self.apparent_encoding
774
775         # Decode unicode from given encoding.
776         try:
777             content = str(self.content, encoding, errors='replace')
778         except (LookupError, TypeError):
779             # A LookupError is raised if the encoding was not found which could
780             # indicate a misspelling or similar mistake.
781             #
782             # A TypeError can be raised if encoding is None
783             #
784             # So we try blindly encoding.
785             content = str(self.content, errors='replace')
786
787         return content
788
789     def json(self, **kwargs):
790         """Returns the json-encoded content of a response, if any.
791
792         :param \*\*kwargs: Optional arguments that ``json.loads`` takes.
793         """
794
795         if not self.encoding and len(self.content) > 3:
796             # No encoding set. JSON RFC 4627 section 3 states we should expect
797             # UTF-8, -16 or -32. Detect which one to use; If the detection or
798             # decoding fails, fall back to `self.text` (using chardet to make
799             # a best guess).
800             encoding = guess_json_utf(self.content)
801             if encoding is not None:
802                 try:
803                     return complexjson.loads(
804                         self.content.decode(encoding), **kwargs
805                     )
806                 except UnicodeDecodeError:
807                     # Wrong UTF codec detected; usually because it's not UTF-8
808                     # but some other 8-bit codec.  This is an RFC violation,
809                     # and the server didn't bother to tell us what codec *was*
810                     # used.
811                     pass
812         return complexjson.loads(self.text, **kwargs)
813
814     @property
815     def links(self):
816         """Returns the parsed header links of the response, if any."""
817
818         header = self.headers.get('link')
819
820         # l = MultiDict()
821         l = {}
822
823         if header:
824             links = parse_header_links(header)
825
826             for link in links:
827                 key = link.get('rel') or link.get('url')
828                 l[key] = link
829
830         return l
831
832     def raise_for_status(self):
833         """Raises stored :class:`HTTPError`, if one occurred."""
834
835         http_error_msg = ''
836
837         if 400 <= self.status_code < 500:
838             http_error_msg = '%s Client Error: %s for url: %s' % (self.status_code, self.reason, self.url)
839
840         elif 500 <= self.status_code < 600:
841             http_error_msg = '%s Server Error: %s for url: %s' % (self.status_code, self.reason, self.url)
842
843         if http_error_msg:
844             raise HTTPError(http_error_msg, response=self)
845
846     def close(self):
847         """Releases the connection back to the pool. Once this method has been
848         called the underlying ``raw`` object must not be accessed again.
849
850         *Note: Should not normally need to be called explicitly.*
851         """
852         if not self._content_consumed:
853             return self.raw.close()
854
855         return self.raw.release_conn()