← requests  /  src/requests/models.py

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