Index: third_party/google-endpoints/requests/models.py |
diff --git a/third_party/google-endpoints/requests/models.py b/third_party/google-endpoints/requests/models.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d1a9c8687ddf1dfe070b301b9a16c0a3bdbafa43 |
--- /dev/null |
+++ b/third_party/google-endpoints/requests/models.py |
@@ -0,0 +1,922 @@ |
+# -*- coding: utf-8 -*- |
+ |
+""" |
+requests.models |
+~~~~~~~~~~~~~~~ |
+ |
+This module contains the primary objects that power Requests. |
+""" |
+ |
+import collections |
+import datetime |
+import sys |
+ |
+# Import encoding now, to avoid implicit import later. |
+# Implicit import within threads may cause LookupError when standard library is in a ZIP, |
+# such as in Embedded Python. See https://github.com/kennethreitz/requests/issues/3578. |
+import encodings.idna |
+ |
+from io import BytesIO, UnsupportedOperation |
+from .hooks import default_hooks |
+from .structures import CaseInsensitiveDict |
+ |
+from .auth import HTTPBasicAuth |
+from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar |
+from .packages.urllib3.fields import RequestField |
+from .packages.urllib3.filepost import encode_multipart_formdata |
+from .packages.urllib3.util import parse_url |
+from .packages.urllib3.exceptions import ( |
+ DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) |
+from .exceptions import ( |
+ HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, |
+ ContentDecodingError, ConnectionError, StreamConsumedError) |
+from ._internal_utils import to_native_string, unicode_is_ascii |
+from .utils import ( |
+ guess_filename, get_auth_from_url, requote_uri, |
+ stream_decode_response_unicode, to_key_val_list, parse_header_links, |
+ iter_slices, guess_json_utf, super_len, check_header_validity) |
+from .compat import ( |
+ cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, |
+ is_py2, chardet, builtin_str, basestring) |
+from .compat import json as complexjson |
+from .status_codes import codes |
+ |
+#: The set of HTTP status codes that indicate an automatically |
+#: processable redirect. |
+REDIRECT_STATI = ( |
+ codes.moved, # 301 |
+ codes.found, # 302 |
+ codes.other, # 303 |
+ codes.temporary_redirect, # 307 |
+ codes.permanent_redirect, # 308 |
+) |
+ |
+DEFAULT_REDIRECT_LIMIT = 30 |
+CONTENT_CHUNK_SIZE = 10 * 1024 |
+ITER_CHUNK_SIZE = 512 |
+ |
+ |
+class RequestEncodingMixin(object): |
+ @property |
+ def path_url(self): |
+ """Build the path URL to use.""" |
+ |
+ url = [] |
+ |
+ p = urlsplit(self.url) |
+ |
+ path = p.path |
+ if not path: |
+ path = '/' |
+ |
+ url.append(path) |
+ |
+ query = p.query |
+ if query: |
+ url.append('?') |
+ url.append(query) |
+ |
+ return ''.join(url) |
+ |
+ @staticmethod |
+ def _encode_params(data): |
+ """Encode parameters in a piece of data. |
+ |
+ 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 arbitrary |
+ if parameters are supplied as a dict. |
+ """ |
+ |
+ if isinstance(data, (str, bytes)): |
+ return data |
+ elif hasattr(data, 'read'): |
+ return data |
+ elif hasattr(data, '__iter__'): |
+ result = [] |
+ for k, vs in to_key_val_list(data): |
+ if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): |
+ vs = [vs] |
+ for v in vs: |
+ if v is not None: |
+ 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 |
+ |
+ @staticmethod |
+ def _encode_files(files, data): |
+ """Build the body for a multipart/form-data request. |
+ |
+ Will successfully encode files when passed as a dict or a list of |
+ tuples. Order is retained if data is a list of tuples but arbitrary |
+ if parameters are supplied as a dict. |
+ The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) |
+ or 4-tuples (filename, fileobj, contentype, custom_headers). |
+ """ |
+ if (not files): |
+ raise ValueError("Files must be provided.") |
+ elif isinstance(data, basestring): |
+ raise ValueError("Data must not be a string.") |
+ |
+ new_fields = [] |
+ fields = to_key_val_list(data or {}) |
+ files = to_key_val_list(files or {}) |
+ |
+ for field, val in fields: |
+ if isinstance(val, basestring) or not hasattr(val, '__iter__'): |
+ val = [val] |
+ for v in val: |
+ if v is not None: |
+ # Don't call str() on bytestrings: in Py3 it all goes wrong. |
+ if not isinstance(v, bytes): |
+ v = str(v) |
+ |
+ new_fields.append( |
+ (field.decode('utf-8') if isinstance(field, bytes) else field, |
+ v.encode('utf-8') if isinstance(v, str) else v)) |
+ |
+ for (k, v) in files: |
+ # support for explicit filename |
+ ft = None |
+ fh = None |
+ if isinstance(v, (tuple, list)): |
+ if len(v) == 2: |
+ fn, fp = v |
+ elif len(v) == 3: |
+ fn, fp, ft = v |
+ else: |
+ fn, fp, ft, fh = v |
+ else: |
+ fn = guess_filename(v) or k |
+ fp = v |
+ |
+ if isinstance(fp, (str, bytes, bytearray)): |
+ fdata = fp |
+ else: |
+ fdata = fp.read() |
+ |
+ rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) |
+ rf.make_multipart(content_type=ft) |
+ new_fields.append(rf) |
+ |
+ body, content_type = encode_multipart_formdata(new_fields) |
+ |
+ return body, content_type |
+ |
+ |
+class RequestHooksMixin(object): |
+ def register_hook(self, event, hook): |
+ """Properly register a hook.""" |
+ |
+ if event not in self.hooks: |
+ raise ValueError('Unsupported event specified, with event name "%s"' % (event)) |
+ |
+ if isinstance(hook, collections.Callable): |
+ self.hooks[event].append(hook) |
+ elif hasattr(hook, '__iter__'): |
+ self.hooks[event].extend(h for h in hook if isinstance(h, collections.Callable)) |
+ |
+ def deregister_hook(self, event, hook): |
+ """Deregister a previously registered hook. |
+ Returns True if the hook existed, False if not. |
+ """ |
+ |
+ try: |
+ self.hooks[event].remove(hook) |
+ return True |
+ except ValueError: |
+ return False |
+ |
+ |
+class Request(RequestHooksMixin): |
+ """A user-created :class:`Request <Request>` object. |
+ |
+ Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. |
+ |
+ :param method: HTTP method to use. |
+ :param url: URL to send. |
+ :param headers: dictionary of headers to send. |
+ :param files: dictionary of {filename: fileobject} files to multipart upload. |
+ :param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place. |
+ :param json: json for the body to attach to the request (if files or data is not specified). |
+ :param params: dictionary of URL parameters to append to the URL. |
+ :param auth: Auth handler or (user, pass) tuple. |
+ :param cookies: dictionary or CookieJar of cookies to attach to this request. |
+ :param hooks: dictionary of callback hooks, for internal usage. |
+ |
+ Usage:: |
+ |
+ >>> import requests |
+ >>> req = requests.Request('GET', 'http://httpbin.org/get') |
+ >>> req.prepare() |
+ <PreparedRequest [GET]> |
+ """ |
+ |
+ def __init__(self, method=None, url=None, headers=None, files=None, |
+ data=None, params=None, auth=None, cookies=None, hooks=None, json=None): |
+ |
+ # Default empty dicts for dict params. |
+ data = [] if data is None else data |
+ files = [] if files is None else files |
+ headers = {} if headers is None else headers |
+ params = {} if params is None else params |
+ hooks = {} if hooks is None else hooks |
+ |
+ self.hooks = default_hooks() |
+ for (k, v) in list(hooks.items()): |
+ self.register_hook(event=k, hook=v) |
+ |
+ self.method = method |
+ self.url = url |
+ self.headers = headers |
+ self.files = files |
+ self.data = data |
+ self.json = json |
+ self.params = params |
+ self.auth = auth |
+ self.cookies = cookies |
+ |
+ def __repr__(self): |
+ return '<Request [%s]>' % (self.method) |
+ |
+ def prepare(self): |
+ """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" |
+ p = PreparedRequest() |
+ p.prepare( |
+ method=self.method, |
+ url=self.url, |
+ headers=self.headers, |
+ files=self.files, |
+ data=self.data, |
+ json=self.json, |
+ params=self.params, |
+ auth=self.auth, |
+ cookies=self.cookies, |
+ hooks=self.hooks, |
+ ) |
+ return p |
+ |
+ |
+class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): |
+ """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, |
+ containing the exact bytes that will be sent to the server. |
+ |
+ Generated from either a :class:`Request <Request>` object or manually. |
+ |
+ Usage:: |
+ |
+ >>> import requests |
+ >>> req = requests.Request('GET', 'http://httpbin.org/get') |
+ >>> r = req.prepare() |
+ <PreparedRequest [GET]> |
+ |
+ >>> s = requests.Session() |
+ >>> s.send(r) |
+ <Response [200]> |
+ """ |
+ |
+ def __init__(self): |
+ #: HTTP verb to send to the server. |
+ self.method = None |
+ #: HTTP URL to send the request to. |
+ self.url = None |
+ #: dictionary of HTTP headers. |
+ self.headers = None |
+ # The `CookieJar` used to create the Cookie header will be stored here |
+ # after prepare_cookies is called |
+ self._cookies = None |
+ #: request body to send to the server. |
+ self.body = None |
+ #: dictionary of callback hooks, for internal usage. |
+ self.hooks = default_hooks() |
+ #: integer denoting starting position of a readable file-like body. |
+ self._body_position = None |
+ |
+ def prepare(self, method=None, url=None, headers=None, files=None, |
+ data=None, params=None, auth=None, cookies=None, hooks=None, json=None): |
+ """Prepares the entire request with the given parameters.""" |
+ |
+ self.prepare_method(method) |
+ self.prepare_url(url, params) |
+ self.prepare_headers(headers) |
+ self.prepare_cookies(cookies) |
+ self.prepare_body(data, files, json) |
+ self.prepare_auth(auth, url) |
+ |
+ # Note that prepare_auth must be last to enable authentication schemes |
+ # such as OAuth to work on a fully prepared request. |
+ |
+ # This MUST go after prepare_auth. Authenticators could add a hook |
+ self.prepare_hooks(hooks) |
+ |
+ def __repr__(self): |
+ return '<PreparedRequest [%s]>' % (self.method) |
+ |
+ def copy(self): |
+ p = PreparedRequest() |
+ p.method = self.method |
+ p.url = self.url |
+ p.headers = self.headers.copy() if self.headers is not None else None |
+ p._cookies = _copy_cookie_jar(self._cookies) |
+ p.body = self.body |
+ p.hooks = self.hooks |
+ p._body_position = self._body_position |
+ return p |
+ |
+ def prepare_method(self, method): |
+ """Prepares the given HTTP method.""" |
+ self.method = method |
+ if self.method is not None: |
+ self.method = to_native_string(self.method.upper()) |
+ |
+ @staticmethod |
+ def _get_idna_encoded_host(host): |
+ try: |
+ from .packages import idna |
+ except ImportError: |
+ # tolerate the possibility of downstream repackagers unvendoring `requests` |
+ # For more information, read: packages/__init__.py |
+ import idna |
+ sys.modules['requests.packages.idna'] = idna |
+ |
+ try: |
+ host = idna.encode(host, uts46=True).decode('utf-8') |
+ except idna.IDNAError: |
+ raise UnicodeError |
+ return host |
+ |
+ def prepare_url(self, url, params): |
+ """Prepares the given HTTP URL.""" |
+ #: Accept objects that have string representations. |
+ #: We're unable to blindly call unicode/str functions |
+ #: as this will include the bytestring indicator (b'') |
+ #: on python 3.x. |
+ #: https://github.com/kennethreitz/requests/pull/2238 |
+ if isinstance(url, bytes): |
+ url = url.decode('utf8') |
+ else: |
+ url = unicode(url) if is_py2 else str(url) |
+ |
+ # Remove leading whitespaces from url |
+ url = url.lstrip() |
+ |
+ # Don't do any URL preparation for non-HTTP schemes like `mailto`, |
+ # `data` etc to work around exceptions from `url_parse`, which |
+ # handles RFC 3986 only. |
+ if ':' in url and not url.lower().startswith('http'): |
+ self.url = url |
+ return |
+ |
+ # Support for unicode domain names and paths. |
+ try: |
+ scheme, auth, host, port, path, query, fragment = parse_url(url) |
+ except LocationParseError as e: |
+ raise InvalidURL(*e.args) |
+ |
+ if not scheme: |
+ error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?") |
+ error = error.format(to_native_string(url, 'utf8')) |
+ |
+ raise MissingSchema(error) |
+ |
+ if not host: |
+ raise InvalidURL("Invalid URL %r: No host supplied" % url) |
+ |
+ # In general, we want to try IDNA encoding the hostname if the string contains |
+ # non-ASCII characters. This allows users to automatically get the correct IDNA |
+ # behaviour. For strings containing only ASCII characters, we need to also verify |
+ # it doesn't start with a wildcard (*), before allowing the unencoded hostname. |
+ if not unicode_is_ascii(host): |
+ try: |
+ host = self._get_idna_encoded_host(host) |
+ except UnicodeError: |
+ raise InvalidURL('URL has an invalid label.') |
+ elif host.startswith(u'*'): |
+ raise InvalidURL('URL has an invalid label.') |
+ |
+ # Carefully reconstruct the network location |
+ netloc = auth or '' |
+ if netloc: |
+ netloc += '@' |
+ netloc += host |
+ if port: |
+ netloc += ':' + str(port) |
+ |
+ # Bare domains aren't valid URLs. |
+ if not path: |
+ path = '/' |
+ |
+ if is_py2: |
+ if isinstance(scheme, str): |
+ scheme = scheme.encode('utf-8') |
+ if isinstance(netloc, str): |
+ netloc = netloc.encode('utf-8') |
+ if isinstance(path, str): |
+ path = path.encode('utf-8') |
+ if isinstance(query, str): |
+ query = query.encode('utf-8') |
+ if isinstance(fragment, str): |
+ fragment = fragment.encode('utf-8') |
+ |
+ if isinstance(params, (str, bytes)): |
+ params = to_native_string(params) |
+ |
+ enc_params = self._encode_params(params) |
+ if enc_params: |
+ if query: |
+ query = '%s&%s' % (query, enc_params) |
+ else: |
+ query = enc_params |
+ |
+ url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) |
+ self.url = url |
+ |
+ def prepare_headers(self, headers): |
+ """Prepares the given HTTP headers.""" |
+ |
+ self.headers = CaseInsensitiveDict() |
+ if headers: |
+ for header in headers.items(): |
+ # Raise exception on invalid header value. |
+ check_header_validity(header) |
+ name, value = header |
+ self.headers[to_native_string(name)] = value |
+ |
+ def prepare_body(self, data, files, json=None): |
+ """Prepares the given HTTP body data.""" |
+ |
+ # Check if file, fo, generator, iterator. |
+ # If not, run through normal process. |
+ |
+ # Nottin' on you. |
+ body = None |
+ content_type = None |
+ |
+ if not data and json is not None: |
+ # urllib3 requires a bytes-like body. Python 2's json.dumps |
+ # provides this natively, but Python 3 gives a Unicode string. |
+ content_type = 'application/json' |
+ body = complexjson.dumps(json) |
+ if not isinstance(body, bytes): |
+ body = body.encode('utf-8') |
+ |
+ is_stream = all([ |
+ hasattr(data, '__iter__'), |
+ not isinstance(data, (basestring, list, tuple, collections.Mapping)) |
+ ]) |
+ |
+ try: |
+ length = super_len(data) |
+ except (TypeError, AttributeError, UnsupportedOperation): |
+ length = None |
+ |
+ if is_stream: |
+ body = data |
+ |
+ if getattr(body, 'tell', None) is not None: |
+ # Record the current file position before reading. |
+ # This will allow us to rewind a file in the event |
+ # of a redirect. |
+ try: |
+ self._body_position = body.tell() |
+ except (IOError, OSError): |
+ # This differentiates from None, allowing us to catch |
+ # a failed `tell()` later when trying to rewind the body |
+ self._body_position = object() |
+ |
+ if files: |
+ raise NotImplementedError('Streamed bodies and files are mutually exclusive.') |
+ |
+ if length: |
+ self.headers['Content-Length'] = builtin_str(length) |
+ else: |
+ self.headers['Transfer-Encoding'] = 'chunked' |
+ else: |
+ # Multi-part file uploads. |
+ if files: |
+ (body, content_type) = self._encode_files(files, data) |
+ else: |
+ if data: |
+ body = self._encode_params(data) |
+ if isinstance(data, basestring) or hasattr(data, 'read'): |
+ content_type = None |
+ else: |
+ content_type = 'application/x-www-form-urlencoded' |
+ |
+ self.prepare_content_length(body) |
+ |
+ # Add content-type if it wasn't explicitly provided. |
+ if content_type and ('content-type' not in self.headers): |
+ self.headers['Content-Type'] = content_type |
+ |
+ self.body = body |
+ |
+ def prepare_content_length(self, body): |
+ """Prepare Content-Length header based on request method and body""" |
+ if body is not None: |
+ length = super_len(body) |
+ if length: |
+ # If length exists, set it. Otherwise, we fallback |
+ # to Transfer-Encoding: chunked. |
+ self.headers['Content-Length'] = builtin_str(length) |
+ elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: |
+ # Set Content-Length to 0 for methods that can have a body |
+ # but don't provide one. (i.e. not GET or HEAD) |
+ self.headers['Content-Length'] = '0' |
+ |
+ def prepare_auth(self, auth, url=''): |
+ """Prepares the given HTTP auth data.""" |
+ |
+ # If no Auth is explicitly provided, extract it from the URL first. |
+ if auth is None: |
+ url_auth = get_auth_from_url(self.url) |
+ auth = url_auth if any(url_auth) else None |
+ |
+ if auth: |
+ if isinstance(auth, tuple) and len(auth) == 2: |
+ # special-case basic HTTP auth |
+ auth = HTTPBasicAuth(*auth) |
+ |
+ # Allow auth to make its changes. |
+ r = auth(self) |
+ |
+ # Update self to reflect the auth changes. |
+ self.__dict__.update(r.__dict__) |
+ |
+ # Recompute Content-Length |
+ self.prepare_content_length(self.body) |
+ |
+ def prepare_cookies(self, cookies): |
+ """Prepares the given HTTP cookie data. |
+ |
+ This function eventually generates a ``Cookie`` header from the |
+ given cookies using cookielib. Due to cookielib's design, the header |
+ will not be regenerated if it already exists, meaning this function |
+ can only be called once for the life of the |
+ :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls |
+ to ``prepare_cookies`` will have no actual effect, unless the "Cookie" |
+ header is removed beforehand. |
+ """ |
+ if isinstance(cookies, cookielib.CookieJar): |
+ self._cookies = cookies |
+ else: |
+ self._cookies = cookiejar_from_dict(cookies) |
+ |
+ cookie_header = get_cookie_header(self._cookies, self) |
+ if cookie_header is not None: |
+ self.headers['Cookie'] = cookie_header |
+ |
+ def prepare_hooks(self, hooks): |
+ """Prepares the given hooks.""" |
+ # hooks can be passed as None to the prepare method and to this |
+ # method. To prevent iterating over None, simply use an empty list |
+ # if hooks is False-y |
+ hooks = hooks or [] |
+ for event in hooks: |
+ self.register_hook(event, hooks[event]) |
+ |
+ |
+class Response(object): |
+ """The :class:`Response <Response>` object, which contains a |
+ server's response to an HTTP request. |
+ """ |
+ |
+ __attrs__ = [ |
+ '_content', 'status_code', 'headers', 'url', 'history', |
+ 'encoding', 'reason', 'cookies', 'elapsed', 'request' |
+ ] |
+ |
+ def __init__(self): |
+ super(Response, self).__init__() |
+ |
+ self._content = False |
+ self._content_consumed = False |
+ |
+ #: Integer Code of responded HTTP Status, e.g. 404 or 200. |
+ self.status_code = None |
+ |
+ #: Case-insensitive Dictionary of Response Headers. |
+ #: For example, ``headers['content-encoding']`` will return the |
+ #: value of a ``'Content-Encoding'`` response header. |
+ self.headers = CaseInsensitiveDict() |
+ |
+ #: File-like object representation of response (for advanced usage). |
+ #: Use of ``raw`` requires that ``stream=True`` be set on the request. |
+ # This requirement does not apply for use internally to Requests. |
+ self.raw = None |
+ |
+ #: Final URL location of Response. |
+ self.url = None |
+ |
+ #: Encoding to decode with when accessing r.text. |
+ self.encoding = None |
+ |
+ #: A list of :class:`Response <Response>` objects from |
+ #: the history of the Request. Any redirect responses will end |
+ #: up here. The list is sorted from the oldest to the most recent request. |
+ self.history = [] |
+ |
+ #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". |
+ self.reason = None |
+ |
+ #: A CookieJar of Cookies the server sent back. |
+ self.cookies = cookiejar_from_dict({}) |
+ |
+ #: The amount of time elapsed between sending the request |
+ #: and the arrival of the response (as a timedelta). |
+ #: This property specifically measures the time taken between sending |
+ #: the first byte of the request and finishing parsing the headers. It |
+ #: is therefore unaffected by consuming the response content or the |
+ #: value of the ``stream`` keyword argument. |
+ self.elapsed = datetime.timedelta(0) |
+ |
+ #: The :class:`PreparedRequest <PreparedRequest>` object to which this |
+ #: is a response. |
+ self.request = None |
+ |
+ def __getstate__(self): |
+ # Consume everything; accessing the content attribute makes |
+ # sure the content has been fully read. |
+ if not self._content_consumed: |
+ self.content |
+ |
+ return dict( |
+ (attr, getattr(self, attr, None)) |
+ for attr in self.__attrs__ |
+ ) |
+ |
+ def __setstate__(self, state): |
+ for name, value in state.items(): |
+ setattr(self, name, value) |
+ |
+ # pickled objects do not have .raw |
+ setattr(self, '_content_consumed', True) |
+ setattr(self, 'raw', None) |
+ |
+ def __repr__(self): |
+ return '<Response [%s]>' % (self.status_code) |
+ |
+ def __bool__(self): |
+ """Returns true if :attr:`status_code` is 'OK'.""" |
+ return self.ok |
+ |
+ def __nonzero__(self): |
+ """Returns true if :attr:`status_code` is 'OK'.""" |
+ return self.ok |
+ |
+ def __iter__(self): |
+ """Allows you to use a response as an iterator.""" |
+ return self.iter_content(128) |
+ |
+ @property |
+ def ok(self): |
+ try: |
+ self.raise_for_status() |
+ except HTTPError: |
+ return False |
+ return True |
+ |
+ @property |
+ def is_redirect(self): |
+ """True if this Response is a well-formed HTTP redirect that could have |
+ been processed automatically (by :meth:`Session.resolve_redirects`). |
+ """ |
+ return ('location' in self.headers and self.status_code in REDIRECT_STATI) |
+ |
+ @property |
+ def is_permanent_redirect(self): |
+ """True if this Response one of the permanent versions of redirect""" |
+ return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) |
+ |
+ @property |
+ def apparent_encoding(self): |
+ """The apparent encoding, provided by the chardet library""" |
+ return chardet.detect(self.content)['encoding'] |
+ |
+ def iter_content(self, chunk_size=1, decode_unicode=False): |
+ """Iterates over the response data. When stream=True is set on the |
+ request, this avoids reading the content at once into memory for |
+ large responses. The chunk size is the number of bytes it should |
+ read into memory. This is not necessarily the length of each item |
+ returned as decoding can take place. |
+ |
+ chunk_size must be of type int or None. A value of None will |
+ function differently depending on the value of `stream`. |
+ stream=True will read data as it arrives in whatever size the |
+ chunks are received. If stream=False, data is returned as |
+ a single chunk. |
+ |
+ If decode_unicode is True, content will be decoded using the best |
+ available encoding based on the response. |
+ """ |
+ |
+ def generate(): |
+ # Special case for urllib3. |
+ if hasattr(self.raw, 'stream'): |
+ try: |
+ for chunk in self.raw.stream(chunk_size, decode_content=True): |
+ yield chunk |
+ except ProtocolError as e: |
+ raise ChunkedEncodingError(e) |
+ except DecodeError as e: |
+ raise ContentDecodingError(e) |
+ except ReadTimeoutError as e: |
+ raise ConnectionError(e) |
+ else: |
+ # Standard file-like object. |
+ while True: |
+ chunk = self.raw.read(chunk_size) |
+ if not chunk: |
+ break |
+ yield chunk |
+ |
+ self._content_consumed = True |
+ |
+ if self._content_consumed and isinstance(self._content, bool): |
+ raise StreamConsumedError() |
+ elif chunk_size is not None and not isinstance(chunk_size, int): |
+ raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) |
+ # simulate reading small chunks of the content |
+ reused_chunks = iter_slices(self._content, chunk_size) |
+ |
+ stream_chunks = generate() |
+ |
+ chunks = reused_chunks if self._content_consumed else stream_chunks |
+ |
+ if decode_unicode: |
+ chunks = stream_decode_response_unicode(chunks, self) |
+ |
+ return chunks |
+ |
+ def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None): |
+ """Iterates over the response data, one line at a time. When |
+ stream=True is set on the request, this avoids reading the |
+ content at once into memory for large responses. |
+ |
+ .. note:: This method is not reentrant safe. |
+ """ |
+ |
+ pending = None |
+ |
+ for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): |
+ |
+ if pending is not None: |
+ chunk = pending + chunk |
+ |
+ if delimiter: |
+ lines = chunk.split(delimiter) |
+ else: |
+ lines = chunk.splitlines() |
+ |
+ if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: |
+ pending = lines.pop() |
+ else: |
+ pending = None |
+ |
+ for line in lines: |
+ yield line |
+ |
+ if pending is not None: |
+ yield pending |
+ |
+ @property |
+ def content(self): |
+ """Content of the response, in bytes.""" |
+ |
+ if self._content is False: |
+ # Read the contents. |
+ if self._content_consumed: |
+ raise RuntimeError( |
+ 'The content for this response was already consumed') |
+ |
+ if self.status_code == 0 or self.raw is None: |
+ self._content = None |
+ else: |
+ self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes() |
+ |
+ self._content_consumed = True |
+ # don't need to release the connection; that's been handled by urllib3 |
+ # since we exhausted the data. |
+ return self._content |
+ |
+ @property |
+ def text(self): |
+ """Content of the response, in unicode. |
+ |
+ If Response.encoding is None, encoding will be guessed using |
+ ``chardet``. |
+ |
+ The encoding of the response content is determined based solely on HTTP |
+ headers, following RFC 2616 to the letter. If you can take advantage of |
+ non-HTTP knowledge to make a better guess at the encoding, you should |
+ set ``r.encoding`` appropriately before accessing this property. |
+ """ |
+ |
+ # Try charset from content-type |
+ content = None |
+ encoding = self.encoding |
+ |
+ if not self.content: |
+ return str('') |
+ |
+ # Fallback to auto-detected encoding. |
+ if self.encoding is None: |
+ encoding = self.apparent_encoding |
+ |
+ # Decode unicode from given encoding. |
+ try: |
+ content = str(self.content, encoding, errors='replace') |
+ except (LookupError, TypeError): |
+ # A LookupError is raised if the encoding was not found which could |
+ # indicate a misspelling or similar mistake. |
+ # |
+ # A TypeError can be raised if encoding is None |
+ # |
+ # So we try blindly encoding. |
+ content = str(self.content, errors='replace') |
+ |
+ return content |
+ |
+ def json(self, **kwargs): |
+ """Returns the json-encoded content of a response, if any. |
+ |
+ :param \*\*kwargs: Optional arguments that ``json.loads`` takes. |
+ :raises ValueError: If the response body does not contain valid json. |
+ """ |
+ |
+ if not self.encoding and self.content and len(self.content) > 3: |
+ # No encoding set. JSON RFC 4627 section 3 states we should expect |
+ # UTF-8, -16 or -32. Detect which one to use; If the detection or |
+ # decoding fails, fall back to `self.text` (using chardet to make |
+ # a best guess). |
+ encoding = guess_json_utf(self.content) |
+ if encoding is not None: |
+ try: |
+ return complexjson.loads( |
+ self.content.decode(encoding), **kwargs |
+ ) |
+ except UnicodeDecodeError: |
+ # Wrong UTF codec detected; usually because it's not UTF-8 |
+ # but some other 8-bit codec. This is an RFC violation, |
+ # and the server didn't bother to tell us what codec *was* |
+ # used. |
+ pass |
+ return complexjson.loads(self.text, **kwargs) |
+ |
+ @property |
+ def links(self): |
+ """Returns the parsed header links of the response, if any.""" |
+ |
+ header = self.headers.get('link') |
+ |
+ # l = MultiDict() |
+ l = {} |
+ |
+ if header: |
+ links = parse_header_links(header) |
+ |
+ for link in links: |
+ key = link.get('rel') or link.get('url') |
+ l[key] = link |
+ |
+ return l |
+ |
+ def raise_for_status(self): |
+ """Raises stored :class:`HTTPError`, if one occurred.""" |
+ |
+ http_error_msg = '' |
+ if isinstance(self.reason, bytes): |
+ # We attempt to decode utf-8 first because some servers |
+ # choose to localize their reason strings. If the string |
+ # isn't utf-8, we fall back to iso-8859-1 for all other |
+ # encodings. (See PR #3538) |
+ try: |
+ reason = self.reason.decode('utf-8') |
+ except UnicodeDecodeError: |
+ reason = self.reason.decode('iso-8859-1') |
+ else: |
+ reason = self.reason |
+ |
+ if 400 <= self.status_code < 500: |
+ http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) |
+ |
+ elif 500 <= self.status_code < 600: |
+ http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) |
+ |
+ if http_error_msg: |
+ raise HTTPError(http_error_msg, response=self) |
+ |
+ def close(self): |
+ """Releases the connection back to the pool. Once this method has been |
+ called the underlying ``raw`` object must not be accessed again. |
+ |
+ *Note: Should not normally need to be called explicitly.* |
+ """ |
+ if not self._content_consumed: |
+ self.raw.close() |
+ |
+ release_conn = getattr(self.raw, 'release_conn', None) |
+ if release_conn is not None: |
+ release_conn() |