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