Spaces:
Runtime error
Runtime error
""" | |
requests.models | |
~~~~~~~~~~~~~~~ | |
This module contains the primary objects that power Requests. | |
""" | |
import datetime | |
# 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/psf/requests/issues/3578. | |
import encodings.idna # noqa: F401 | |
from io import UnsupportedOperation | |
from pip._vendor.urllib3.exceptions import ( | |
DecodeError, | |
LocationParseError, | |
ProtocolError, | |
ReadTimeoutError, | |
SSLError, | |
) | |
from pip._vendor.urllib3.fields import RequestField | |
from pip._vendor.urllib3.filepost import encode_multipart_formdata | |
from pip._vendor.urllib3.util import parse_url | |
from ._internal_utils import to_native_string, unicode_is_ascii | |
from .auth import HTTPBasicAuth | |
from .compat import ( | |
Callable, | |
JSONDecodeError, | |
Mapping, | |
basestring, | |
builtin_str, | |
chardet, | |
cookielib, | |
) | |
from .compat import json as complexjson | |
from .compat import urlencode, urlsplit, urlunparse | |
from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header | |
from .exceptions import ( | |
ChunkedEncodingError, | |
ConnectionError, | |
ContentDecodingError, | |
HTTPError, | |
InvalidJSONError, | |
InvalidURL, | |
) | |
from .exceptions import JSONDecodeError as RequestsJSONDecodeError | |
from .exceptions import MissingSchema | |
from .exceptions import SSLError as RequestsSSLError | |
from .exceptions import StreamConsumedError | |
from .hooks import default_hooks | |
from .status_codes import codes | |
from .structures import CaseInsensitiveDict | |
from .utils import ( | |
check_header_validity, | |
get_auth_from_url, | |
guess_filename, | |
guess_json_utf, | |
iter_slices, | |
parse_header_links, | |
requote_uri, | |
stream_decode_response_unicode, | |
super_len, | |
to_key_val_list, | |
) | |
#: 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: | |
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) | |
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 | |
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 | |
elif hasattr(fp, "read"): | |
fdata = fp.read() | |
elif fp is None: | |
continue | |
else: | |
fdata = fp | |
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: | |
def register_hook(self, event, hook): | |
"""Properly register a hook.""" | |
if event not in self.hooks: | |
raise ValueError(f'Unsupported event specified, with event name "{event}"') | |
if isinstance(hook, Callable): | |
self.hooks[event].append(hook) | |
elif hasattr(hook, "__iter__"): | |
self.hooks[event].extend(h for h in hook if isinstance(h, 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 or | |
list of tuples ``[(key, value)]`` 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: URL parameters to append to the URL. If a dictionary or | |
list of tuples ``[(key, value)]`` is provided, form-encoding will | |
take place. | |
: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', 'https://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 f"<Request [{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. | |
Instances are generated from a :class:`Request <Request>` object, and | |
should not be instantiated manually; doing so may produce undesirable | |
effects. | |
Usage:: | |
>>> import requests | |
>>> req = requests.Request('GET', 'https://httpbin.org/get') | |
>>> r = req.prepare() | |
>>> r | |
<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 f"<PreparedRequest [{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()) | |
def _get_idna_encoded_host(host): | |
from pip._vendor import 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/psf/requests/pull/2238 | |
if isinstance(url, bytes): | |
url = url.decode("utf8") | |
else: | |
url = 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: | |
raise MissingSchema( | |
f"Invalid URL {url!r}: No scheme supplied. " | |
f"Perhaps you meant https://{url}?" | |
) | |
if not host: | |
raise InvalidURL(f"Invalid URL {url!r}: No host supplied") | |
# 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(("*", ".")): | |
raise InvalidURL("URL has an invalid label.") | |
# Carefully reconstruct the network location | |
netloc = auth or "" | |
if netloc: | |
netloc += "@" | |
netloc += host | |
if port: | |
netloc += f":{port}" | |
# Bare domains aren't valid URLs. | |
if not path: | |
path = "/" | |
if isinstance(params, (str, bytes)): | |
params = to_native_string(params) | |
enc_params = self._encode_params(params) | |
if enc_params: | |
if query: | |
query = f"{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" | |
try: | |
body = complexjson.dumps(json, allow_nan=False) | |
except ValueError as ve: | |
raise InvalidJSONError(ve, request=self) | |
if not isinstance(body, bytes): | |
body = body.encode("utf-8") | |
is_stream = all( | |
[ | |
hasattr(data, "__iter__"), | |
not isinstance(data, (basestring, list, tuple, Mapping)), | |
] | |
) | |
if is_stream: | |
try: | |
length = super_len(data) | |
except (TypeError, AttributeError, UnsupportedOperation): | |
length = None | |
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 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: | |
"""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): | |
self._content = False | |
self._content_consumed = False | |
self._next = None | |
#: 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 __enter__(self): | |
return self | |
def __exit__(self, *args): | |
self.close() | |
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 {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 f"<Response [{self.status_code}]>" | |
def __bool__(self): | |
"""Returns True if :attr:`status_code` is less than 400. | |
This attribute checks if the status code of the response is between | |
400 and 600 to see if there was a client error or a server error. If | |
the status code, is between 200 and 400, this will return True. This | |
is **not** a check to see if the response code is ``200 OK``. | |
""" | |
return self.ok | |
def __nonzero__(self): | |
"""Returns True if :attr:`status_code` is less than 400. | |
This attribute checks if the status code of the response is between | |
400 and 600 to see if there was a client error or a server error. If | |
the status code, is between 200 and 400, this will return True. This | |
is **not** a check to see if the response code is ``200 OK``. | |
""" | |
return self.ok | |
def __iter__(self): | |
"""Allows you to use a response as an iterator.""" | |
return self.iter_content(128) | |
def ok(self): | |
"""Returns True if :attr:`status_code` is less than 400, False if not. | |
This attribute checks if the status code of the response is between | |
400 and 600 to see if there was a client error or a server error. If | |
the status code is between 200 and 400, this will return True. This | |
is **not** a check to see if the response code is ``200 OK``. | |
""" | |
try: | |
self.raise_for_status() | |
except HTTPError: | |
return False | |
return True | |
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 | |
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, | |
) | |
def next(self): | |
"""Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" | |
return self._next | |
def apparent_encoding(self): | |
"""The apparent encoding, provided by the charset_normalizer or chardet libraries.""" | |
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: | |
yield from self.raw.stream(chunk_size, decode_content=True) | |
except ProtocolError as e: | |
raise ChunkedEncodingError(e) | |
except DecodeError as e: | |
raise ContentDecodingError(e) | |
except ReadTimeoutError as e: | |
raise ConnectionError(e) | |
except SSLError as e: | |
raise RequestsSSLError(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( | |
f"chunk_size must be an int, it is instead a {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=False, 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 | |
yield from lines | |
if pending is not None: | |
yield pending | |
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 = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b"" | |
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 | |
def text(self): | |
"""Content of the response, in unicode. | |
If Response.encoding is None, encoding will be guessed using | |
``charset_normalizer`` or ``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 "" | |
# 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): | |
r"""Returns the json-encoded content of a response, if any. | |
:param \*\*kwargs: Optional arguments that ``json.loads`` takes. | |
:raises requests.exceptions.JSONDecodeError: 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 charset_normalizer 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 | |
except JSONDecodeError as e: | |
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) | |
try: | |
return complexjson.loads(self.text, **kwargs) | |
except JSONDecodeError as e: | |
# Catch JSON-related errors and raise as requests.JSONDecodeError | |
# This aliases json.JSONDecodeError and simplejson.JSONDecodeError | |
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) | |
def links(self): | |
"""Returns the parsed header links of the response, if any.""" | |
header = self.headers.get("link") | |
resolved_links = {} | |
if header: | |
links = parse_header_links(header) | |
for link in links: | |
key = link.get("rel") or link.get("url") | |
resolved_links[key] = link | |
return resolved_links | |
def raise_for_status(self): | |
"""Raises :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 = ( | |
f"{self.status_code} Client Error: {reason} for url: {self.url}" | |
) | |
elif 500 <= self.status_code < 600: | |
http_error_msg = ( | |
f"{self.status_code} Server Error: {reason} for url: {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() | |