← requests  /  src/requests/sessions.py

1
"""
2
requests.sessions
3
~~~~~~~~~~~~~~~~~
4
5
This module provides a Session object to manage and persist settings across
6
requests (cookies, auth, proxies).
7
"""
8
9
from __future__ import annotations
10
11
import os
12
import sys
13
import time
14
from collections import OrderedDict
15
from collections.abc import Generator, Mapping, MutableMapping
16
from datetime import timedelta
17
from typing import TYPE_CHECKING, Any, cast
18
19
from ._internal_utils import to_native_string
20
from ._types import is_prepared as _is_prepared
21
from .adapters import HTTPAdapter
22
from .auth import _basic_auth_str  # type: ignore[reportPrivateUsage]
23
from .compat import cookielib, urljoin, urlparse
24
from .cookies import (
25
    RequestsCookieJar,
26
    cookiejar_from_dict,
27
    extract_cookies_to_jar,
28
    merge_cookies,
29
)
30
from .exceptions import (
31
    ChunkedEncodingError,
32
    ContentDecodingError,
33
    InvalidSchema,
34
    TooManyRedirects,
35
)
36
from .hooks import default_hooks, dispatch_hook
37
38
# formerly defined here, reexposed here for backward compatibility
39
from .models import (  # noqa: F401
40
    DEFAULT_REDIRECT_LIMIT,
41
    REDIRECT_STATI,  # type: ignore[reportUnusedImport]
42
    PreparedRequest,
43
    Request,
44
    Response,
45
)
46
from .status_codes import codes
47
from .structures import CaseInsensitiveDict
48
from .utils import (  # noqa: F401
49
    DEFAULT_PORTS,
50
    default_headers,
51
    get_auth_from_url,
52
    get_environ_proxies,
53
    get_netrc_auth,
54
    requote_uri,
55
    resolve_proxies,
56
    rewind_body,
57
    should_bypass_proxies,  # type: ignore[reportUnusedImport]  # re-export for external consumers
58
    to_key_val_list,
59
)
60
61
if TYPE_CHECKING:
62
    from http.cookiejar import CookieJar
63
64
    from typing_extensions import Self, Unpack
65
66
    from . import _types as _t
67
    from .adapters import BaseAdapter
68
69
# Preferred clock, based on which one is more accurate on a given system.
70
if sys.platform == "win32":
71
    preferred_clock = time.perf_counter
72
else:
73
    preferred_clock = time.time
74
75
76
def merge_setting(
77
    request_setting: Any, session_setting: Any, dict_class: type = OrderedDict
78
) -> Any:
79
    """Determines appropriate setting for a given request, taking into account
80
    the explicit setting on that request, and the setting in the session. If a
81
    setting is a dictionary, they will be merged together using `dict_class`
82
    """
83
84
    if session_setting is None:
85
        return request_setting
86
87
    if request_setting is None:
88
        return session_setting
89
90
    # Bypass if not a dictionary (e.g. verify)
91
    if not (
92
        isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
93
    ):
94
        return request_setting
95
96
    merged_setting = dict_class(to_key_val_list(session_setting))  # type: ignore[arg-type]  # isinstance narrows Any to Mapping[Unknown]
97
    merged_setting.update(to_key_val_list(request_setting))  # type: ignore[arg-type]
98
99
    # Remove keys that are set to None. Extract keys first to avoid altering
100
    # the dictionary during iteration.
101
    none_keys = [k for (k, v) in merged_setting.items() if v is None]
102
    for key in none_keys:
103
        del merged_setting[key]
104
105
    return merged_setting
106
107
108
def merge_hooks(
109
    request_hooks: _t.HooksType,
110
    session_hooks: _t.HooksType,
111
    dict_class: type = OrderedDict,
112
) -> _t.HooksType:
113
    """Properly merges both requests and session hooks.
114
115
    This is necessary because when request_hooks == {'response': []}, the
116
    merge breaks Session hooks entirely.
117
    """
118
    if session_hooks is None or session_hooks.get("response") == []:
119
        return request_hooks
120
121
    if request_hooks is None or request_hooks.get("response") == []:
122
        return session_hooks
123
124
    return merge_setting(request_hooks, session_hooks, dict_class)
125
126
127
class SessionRedirectMixin:
128
    max_redirects: int
129
    trust_env: bool
130
    cookies: RequestsCookieJar
131
132
    def send(self, request: PreparedRequest, **kwargs: Any) -> Response: ...
133
134
    def get_redirect_target(self, resp: Response) -> str | None:
135
        """Receives a Response. Returns a redirect URI or ``None``"""
136
        # Due to the nature of how requests processes redirects this method will
137
        # be called at least once upon the original response and at least twice
138
        # on each subsequent redirect response (if any).
139
        # If a custom mixin is used to handle this logic, it may be advantageous
140
        # to cache the redirect location onto the response object as a private
141
        # attribute.
142
        if resp.is_redirect:
143
            location = resp.headers["location"]
144
            # Currently the underlying http module on py3 decode headers
145
            # in latin1, but empirical evidence suggests that latin1 is very
146
            # rarely used with non-ASCII characters in HTTP headers.
147
            # It is more likely to get UTF8 header rather than latin1.
148
            # This causes incorrect handling of UTF8 encoded location headers.
149
            # To solve this, we re-encode the location in latin1.
150
            location = location.encode("latin1")
151
            return to_native_string(location, "utf8")
152
        return None
153
154
    def should_strip_auth(self, old_url: str, new_url: str) -> bool:
155
        """Decide whether Authorization header should be removed when redirecting"""
156
        old_parsed = urlparse(old_url)
157
        new_parsed = urlparse(new_url)
158
        if old_parsed.hostname != new_parsed.hostname:
159
            return True
160
        # Special case: allow http -> https redirect when using the standard
161
        # ports. This isn't specified by RFC 7235, but is kept to avoid
162
        # breaking backwards compatibility with older versions of requests
163
        # that allowed any redirects on the same host.
164
        if (
165
            old_parsed.scheme == "http"
166
            and old_parsed.port in (80, None)
167
            and new_parsed.scheme == "https"
168
            and new_parsed.port in (443, None)
169
        ):
170
            return False
171
172
        # Handle default port usage corresponding to scheme.
173
        changed_port = old_parsed.port != new_parsed.port
174
        changed_scheme = old_parsed.scheme != new_parsed.scheme
175
        default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
176
        if (
177
            not changed_scheme
178
            and old_parsed.port in default_port
179
            and new_parsed.port in default_port
180
        ):
181
            return False
182
183
        # Standard case: root URI must match
184
        return changed_port or changed_scheme
185
186
    def resolve_redirects(
187
        self,
188
        resp: Response,
189
        req: PreparedRequest,
190
        stream: bool = False,
191
        timeout: _t.TimeoutType = None,
192
        verify: _t.VerifyType = True,
193
        cert: _t.CertType = None,
194
        proxies: dict[str, str] | None = None,
195
        yield_requests: bool = False,
196
        **adapter_kwargs: Any,
197
    ) -> Generator[Response, None, None]:
198
        """Receives a Response. Returns a generator of Responses or Requests."""
199
200
        hist: list[Response] = []  # keep track of history
201
202
        url = self.get_redirect_target(resp)
203
        previous_fragment = urlparse(req.url).fragment
204
        while url:
205
            prepared_request = req.copy()
206
207
            # Update history and keep track of redirects.
208
            resp.history = hist[:]
209
            hist.append(resp)
210
211
            try:
212
                resp.content  # Consume socket so it can be released
213
            except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
214
                resp.raw.read(decode_content=False)
215
216
            if len(resp.history) >= self.max_redirects:
217
                raise TooManyRedirects(
218
                    f"Exceeded {self.max_redirects} redirects.", response=resp
219
                )
220
221
            # Release the connection back into the pool.
222
            resp.close()
223
224
            # Handle redirection without scheme (see: RFC 1808 Section 4)
225
            if url.startswith("//"):
226
                parsed_rurl = urlparse(resp.url)
227
                url = ":".join([to_native_string(parsed_rurl.scheme), url])
228
229
            # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
230
            parsed = urlparse(url)
231
            if parsed.fragment == "" and previous_fragment:
232
                parsed = parsed._replace(fragment=previous_fragment)
233
            elif parsed.fragment:
234
                previous_fragment = parsed.fragment
235
            url = parsed.geturl()
236
237
            # Facilitate relative 'location' headers, as allowed by RFC 7231.
238
            # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
239
            # Compliant with RFC3986, we percent encode the url.
240
            if not parsed.netloc:
241
                url = urljoin(resp.url, requote_uri(url))
242
            else:
243
                url = requote_uri(url)
244
245
            prepared_request.url = to_native_string(url)
246
247
            self.rebuild_method(prepared_request, resp)
248
249
            # https://github.com/psf/requests/issues/1084
250
            if resp.status_code not in (
251
                codes.temporary_redirect,
252
                codes.permanent_redirect,
253
            ):
254
                # https://github.com/psf/requests/issues/3490
255
                purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
256
                for header in purged_headers:
257
                    prepared_request.headers.pop(header, None)
258
                prepared_request.body = None
259
260
            headers = prepared_request.headers
261
            headers.pop("Cookie", None)
262
263
            # Extract any cookies sent on the response to the cookiejar
264
            # in the new request. Because we've mutated our copied prepared
265
            # request, use the old one that we haven't yet touched.
266
            cookie_jar = cast("CookieJar", prepared_request._cookies)  # type: ignore[reportPrivateUsage]
267
            extract_cookies_to_jar(cookie_jar, req, resp.raw)
268
            merge_cookies(cookie_jar, self.cookies)
269
            prepared_request.prepare_cookies(cookie_jar)
270
271
            # Rebuild auth and proxy information.
272
            proxies = self.rebuild_proxies(prepared_request, proxies)
273
            self.rebuild_auth(prepared_request, resp)
274
275
            # A failed tell() sets `_body_position` to `object()`. This non-None
276
            # value ensures `rewindable` will be True, allowing us to raise an
277
            # UnrewindableBodyError, instead of hanging the connection.
278
            rewindable = prepared_request._body_position is not None and (  # type: ignore[reportPrivateUsage]
279
                "Content-Length" in headers or "Transfer-Encoding" in headers
280
            )
281
282
            # Attempt to rewind consumed file-like object.
283
            if rewindable:
284
                rewind_body(prepared_request)
285
286
            # Override the original request.
287
            req = prepared_request
288
289
            if yield_requests:
290
                yield req  # type: ignore[misc]  # Internal use only, returns PreparedRequest
291
            else:
292
                resp = self.send(
293
                    req,
294
                    stream=stream,
295
                    timeout=timeout,
296
                    verify=verify,
297
                    cert=cert,
298
                    proxies=proxies,
299
                    allow_redirects=False,
300
                    **adapter_kwargs,
301
                )
302
303
                extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
304
305
                # extract redirect url, if any, for the next loop
306
                url = self.get_redirect_target(resp)
307
                yield resp
308
309
    def rebuild_auth(
310
        self, prepared_request: PreparedRequest, response: Response
311
    ) -> None:
312
        """When being redirected we may want to strip authentication from the
313
        request to avoid leaking credentials. This method intelligently removes
314
        and reapplies authentication where possible to avoid credential loss.
315
        """
316
        original_request = response.request
317
        assert _is_prepared(original_request)
318
        assert _is_prepared(prepared_request)
319
320
        headers = prepared_request.headers
321
        original_url = original_request.url
322
        url = prepared_request.url
323
324
        if "Authorization" in headers and self.should_strip_auth(original_url, url):
325
            # If we get redirected to a new host, we should strip out any
326
            # authentication headers.
327
            del headers["Authorization"]
328
329
        # .netrc might have more auth for us on our new host.
330
        new_auth = get_netrc_auth(url) if self.trust_env else None
331
        if new_auth is not None:
332
            prepared_request.prepare_auth(new_auth)
333
334
    def rebuild_proxies(
335
        self,
336
        prepared_request: PreparedRequest,
337
        proxies: dict[str, str] | None,
338
    ) -> dict[str, str]:
339
        """This method re-evaluates the proxy configuration by considering the
340
        environment variables. If we are redirected to a URL covered by
341
        NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
342
        proxy keys for this URL (in case they were stripped by a previous
343
        redirect).
344
345
        This method also replaces the Proxy-Authorization header where
346
        necessary.
347
348
        :rtype: dict
349
        """
350
        assert _is_prepared(prepared_request)
351
        headers = prepared_request.headers
352
        scheme = urlparse(prepared_request.url).scheme
353
        new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
354
355
        if "Proxy-Authorization" in headers:
356
            del headers["Proxy-Authorization"]
357
358
        try:
359
            username, password = get_auth_from_url(new_proxies[scheme])
360
        except KeyError:
361
            username, password = None, None
362
363
        # urllib3 handles proxy authorization for us in the standard adapter.
364
        # Avoid appending this to TLS tunneled requests where it may be leaked.
365
        if not scheme.startswith("https") and username and password:
366
            headers["Proxy-Authorization"] = _basic_auth_str(username, password)
367
368
        return new_proxies
369
370
    def rebuild_method(
371
        self, prepared_request: PreparedRequest, response: Response
372
    ) -> None:
373
        """When being redirected we may want to change the method of the request
374
        based on certain specs or browser behavior.
375
        """
376
        method = prepared_request.method
377
378
        # https://tools.ietf.org/html/rfc7231#section-6.4.4
379
        if response.status_code == codes.see_other and method != "HEAD":
380
            method = "GET"
381
382
        # Do what the browsers do, despite standards...
383
        # First, turn 302s into GETs.
384
        if response.status_code == codes.found and method != "HEAD":
385
            method = "GET"
386
387
        # Second, if a POST is responded to with a 301, turn it into a GET.
388
        # This bizarre behaviour is explained in Issue 1704.
389
        if response.status_code == codes.moved and method == "POST":
390
            method = "GET"
391
392
        prepared_request.method = method
393
394
395
class Session(SessionRedirectMixin):
396
    """A Requests session.
397
398
    Provides cookie persistence, connection-pooling, and configuration.
399
400
    Basic Usage::
401
402
      >>> import requests
403
      >>> s = requests.Session()
404
      >>> s.get('https://httpbin.org/get')
405
      <Response [200]>
406
407
    Or as a context manager::
408
409
      >>> with requests.Session() as s:
410
      ...     s.get('https://httpbin.org/get')
411
      <Response [200]>
412
    """
413
414
    headers: CaseInsensitiveDict[str]
415
    auth: _t.AuthType
416
    proxies: dict[str, str]
417
    hooks: dict[str, list[_t.HookType]]
418
    params: MutableMapping[str, Any]
419
    stream: bool
420
    verify: _t.VerifyType
421
    cert: _t.CertType
422
    max_redirects: int
423
    trust_env: bool
424
    cookies: RequestsCookieJar
425
    adapters: MutableMapping[str, BaseAdapter]
426
427
    __attrs__: list[str] = [
428
        "headers",
429
        "cookies",
430
        "auth",
431
        "proxies",
432
        "hooks",
433
        "params",
434
        "verify",
435
        "cert",
436
        "adapters",
437
        "stream",
438
        "trust_env",
439
        "max_redirects",
440
    ]
441
442
    def __init__(self) -> None:
443
        #: A case-insensitive dictionary of headers to be sent on each
444
        #: :class:`Request <Request>` sent from this
445
        #: :class:`Session <Session>`.
446
        self.headers = default_headers()
447
448
        #: Default Authentication tuple or object to attach to
449
        #: :class:`Request <Request>`.
450
        self.auth = None
451
452
        #: Dictionary mapping protocol or protocol and host to the URL of the proxy
453
        #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
454
        #: be used on each :class:`Request <Request>`.
455
        self.proxies = {}
456
457
        #: Event-handling hooks.
458
        self.hooks = default_hooks()
459
460
        #: Dictionary of querystring data to attach to each
461
        #: :class:`Request <Request>`. The dictionary values may be lists for
462
        #: representing multivalued query parameters.
463
        self.params = {}
464
465
        #: Stream response content default.
466
        self.stream = False
467
468
        #: SSL Verification default.
469
        #: Defaults to `True`, requiring requests to verify the TLS certificate at the
470
        #: remote end.
471
        #: If verify is set to `False`, requests will accept any TLS certificate
472
        #: presented by the server, and will ignore hostname mismatches and/or
473
        #: expired certificates, which will make your application vulnerable to
474
        #: man-in-the-middle (MitM) attacks.
475
        #: Only set this to `False` for testing.
476
        #: If verify is set to a string, it must be the path to a CA bundle file
477
        #: that will be used to verify the TLS certificate.
478
        self.verify = True
479
480
        #: SSL client certificate default, if String, path to ssl client
481
        #: cert file (.pem). If Tuple, ('cert', 'key') pair.
482
        self.cert = None
483
484
        #: Maximum number of redirects allowed. If the request exceeds this
485
        #: limit, a :class:`TooManyRedirects` exception is raised.
486
        #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
487
        #: 30.
488
        self.max_redirects = DEFAULT_REDIRECT_LIMIT
489
490
        #: Trust environment settings for proxy configuration, default
491
        #: authentication and similar.
492
        self.trust_env = True
493
494
        #: A CookieJar containing all currently outstanding cookies set on this
495
        #: session. By default it is a
496
        #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
497
        #: may be any other ``cookielib.CookieJar`` compatible object.
498
        self.cookies = cookiejar_from_dict({})
499
500
        # Default connection adapters.
501
        self.adapters = OrderedDict()
502
        self.mount("https://", HTTPAdapter())
503
        self.mount("http://", HTTPAdapter())
504
505
    def __enter__(self) -> Self:
506
        return self
507
508
    def __exit__(self, *args: Any) -> None:
509
        self.close()
510
511
    def prepare_request(self, request: Request) -> PreparedRequest:
512
        """Constructs a :class:`PreparedRequest <PreparedRequest>` for
513
        transmission and returns it. The :class:`PreparedRequest` has settings
514
        merged from the :class:`Request <Request>` instance and those of the
515
        :class:`Session`.
516
517
        :param request: :class:`Request` instance to prepare with this
518
            session's settings.
519
        :rtype: requests.PreparedRequest
520
        """
521
        url = cast("_t.UriType", request.url)
522
        method = cast(str, request.method)
523
524
        cookies = request.cookies or {}
525
526
        # Bootstrap CookieJar.
527
        if not isinstance(cookies, cookielib.CookieJar):
528
            cookies = cookiejar_from_dict(cookies)
529
530
        # Merge with session cookies
531
        merged_cookies = merge_cookies(
532
            merge_cookies(RequestsCookieJar(), self.cookies), cookies
533
        )
534
535
        # Set environment's basic authentication if not explicitly set.
536
        auth = request.auth
537
        if self.trust_env and not auth and not self.auth:
538
            auth = get_netrc_auth(url)
539
540
        p = PreparedRequest()
541
        p.prepare(
542
            method=method.upper(),
543
            url=url,
544
            files=request.files,
545
            data=request.data,
546
            json=request.json,
547
            headers=merge_setting(
548
                request.headers, self.headers, dict_class=CaseInsensitiveDict
549
            ),
550
            params=merge_setting(request.params, self.params),
551
            auth=merge_setting(auth, self.auth),
552
            cookies=merged_cookies,
553
            hooks=merge_hooks(request.hooks, self.hooks),
554
        )
555
        return p
556
557
    def request(
558
        self,
559
        method: str,
560
        url: _t.UriType,
561
        params: _t.ParamsType = None,
562
        data: _t.DataType = None,
563
        headers: _t.HeadersType = None,
564
        cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
565
        files: _t.FilesType = None,
566
        auth: _t.AuthType = None,
567
        timeout: _t.TimeoutType = None,
568
        allow_redirects: bool = True,
569
        proxies: dict[str, str] | None = None,
570
        hooks: _t.HooksInputType | None = None,
571
        stream: bool | None = None,
572
        verify: _t.VerifyType | None = None,
573
        cert: _t.CertType = None,
574
        json: _t.JsonType = None,
575
    ) -> Response:
576
        """Constructs a :class:`Request <Request>`, prepares it and sends it.
577
        Returns :class:`Response <Response>` object.
578
579
        :param method: method for the new :class:`Request` object.
580
        :param url: URL for the new :class:`Request` object.
581
        :param params: (optional) Dictionary or bytes to be sent in the query
582
            string for the :class:`Request`.
583
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
584
            object to send in the body of the :class:`Request`.
585
        :param json: (optional) json to send in the body of the
586
            :class:`Request`.
587
        :param headers: (optional) Dictionary of HTTP Headers to send with the
588
            :class:`Request`.
589
        :param cookies: (optional) Dict or CookieJar object to send with the
590
            :class:`Request`.
591
        :param files: (optional) Dictionary of ``'filename': file-like-objects``
592
            for multipart encoding upload.
593
        :param auth: (optional) Auth tuple or callable to enable
594
            Basic/Digest/Custom HTTP Auth.
595
        :param timeout: (optional) How many seconds to wait for the server to send
596
            data before giving up, as a float, or a :ref:`(connect timeout,
597
            read timeout) <timeouts>` tuple.
598
        :type timeout: float or tuple
599
        :param allow_redirects: (optional) Set to True by default.
600
        :type allow_redirects: bool
601
        :param proxies: (optional) Dictionary mapping protocol or protocol and
602
            hostname to the URL of the proxy.
603
        :param hooks: (optional) Dictionary mapping hook name to one event or
604
            list of events, event must be callable.
605
        :param stream: (optional) whether to immediately download the response
606
            content. Defaults to ``False``.
607
        :param verify: (optional) Either a boolean, in which case it controls whether we verify
608
            the server's TLS certificate, or a string, in which case it must be a path
609
            to a CA bundle to use. Defaults to ``True``. When set to
610
            ``False``, requests will accept any TLS certificate presented by
611
            the server, and will ignore hostname mismatches and/or expired
612
            certificates, which will make your application vulnerable to
613
            man-in-the-middle (MitM) attacks. Setting verify to ``False``
614
            may be useful during local development or testing.
615
        :param cert: (optional) if String, path to ssl client cert file (.pem).
616
            If Tuple, ('cert', 'key') pair.
617
        :rtype: requests.Response
618
        """
619
        if isinstance(url, bytes):
620
            url = url.decode("utf-8")
621
622
        # Create the Request.
623
        req = Request(
624
            method=method.upper(),
625
            url=url,
626
            headers=headers,
627
            files=files,
628
            data=data or {},
629
            json=json,
630
            params=params or {},
631
            auth=auth,
632
            cookies=cookies,
633
            hooks=hooks,
634
        )
635
        prep = self.prepare_request(req)
636
637
        assert _is_prepared(prep)
638
639
        proxies = proxies or {}
640
641
        settings = self.merge_environment_settings(
642
            prep.url, proxies, stream, verify, cert
643
        )
644
645
        # Send the request.
646
        send_kwargs = {
647
            "timeout": timeout,
648
            "allow_redirects": allow_redirects,
649
        }
650
        send_kwargs.update(settings)
651
        resp = self.send(prep, **send_kwargs)
652
653
        return resp
654
655
    def get(
656
        self,
657
        url: _t.UriType,
658
        params: _t.ParamsType = None,
659
        **kwargs: Unpack[_t.GetKwargs],
660
    ) -> Response:
661
        r"""Sends a GET request. Returns :class:`Response` object.
662
663
        :param url: URL for the new :class:`Request` object.
664
        :param params: (optional) Dictionary, list of tuples or bytes to send
665
            in the query string for the :class:`Request`.
666
        :param \*\*kwargs: Optional arguments that ``request`` takes.
667
        :rtype: requests.Response
668
        """
669
670
        kwargs.setdefault("allow_redirects", True)
671
        return self.request("GET", url, params=params, **kwargs)
672
673
    def options(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
674
        r"""Sends a OPTIONS request. Returns :class:`Response` object.
675
676
        :param url: URL for the new :class:`Request` object.
677
        :param \*\*kwargs: Optional arguments that ``request`` takes.
678
        :rtype: requests.Response
679
        """
680
681
        kwargs.setdefault("allow_redirects", True)
682
        return self.request("OPTIONS", url, **kwargs)
683
684
    def head(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
685
        r"""Sends a HEAD request. Returns :class:`Response` object.
686
687
        :param url: URL for the new :class:`Request` object.
688
        :param \*\*kwargs: Optional arguments that ``request`` takes.
689
        :rtype: requests.Response
690
        """
691
692
        kwargs.setdefault("allow_redirects", False)
693
        return self.request("HEAD", url, **kwargs)
694
695
    def post(
696
        self,
697
        url: _t.UriType,
698
        data: _t.DataType = None,
699
        json: _t.JsonType = None,
700
        **kwargs: Unpack[_t.PostKwargs],
701
    ) -> Response:
702
        r"""Sends a POST request. Returns :class:`Response` object.
703
704
        :param url: URL for the new :class:`Request` object.
705
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
706
            object to send in the body of the :class:`Request`.
707
        :param json: (optional) json to send in the body of the :class:`Request`.
708
        :param \*\*kwargs: Optional arguments that ``request`` takes.
709
        :rtype: requests.Response
710
        """
711
712
        return self.request("POST", url, data=data, json=json, **kwargs)
713
714
    def put(
715
        self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
716
    ) -> Response:
717
        r"""Sends a PUT request. Returns :class:`Response` object.
718
719
        :param url: URL for the new :class:`Request` object.
720
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
721
            object to send in the body of the :class:`Request`.
722
        :param \*\*kwargs: Optional arguments that ``request`` takes.
723
        :rtype: requests.Response
724
        """
725
726
        return self.request("PUT", url, data=data, **kwargs)
727
728
    def patch(
729
        self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
730
    ) -> Response:
731
        r"""Sends a PATCH request. Returns :class:`Response` object.
732
733
        :param url: URL for the new :class:`Request` object.
734
        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
735
            object to send in the body of the :class:`Request`.
736
        :param \*\*kwargs: Optional arguments that ``request`` takes.
737
        :rtype: requests.Response
738
        """
739
740
        return self.request("PATCH", url, data=data, **kwargs)
741
742
    def delete(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
743
        r"""Sends a DELETE request. Returns :class:`Response` object.
744
745
        :param url: URL for the new :class:`Request` object.
746
        :param \*\*kwargs: Optional arguments that ``request`` takes.
747
        :rtype: requests.Response
748
        """
749
750
        return self.request("DELETE", url, **kwargs)
751
752
    def send(self, request: PreparedRequest, **kwargs: Any) -> Response:
753
        """Send a given PreparedRequest.
754
755
        :rtype: requests.Response
756
        """
757
        # Set defaults that the hooks can utilize to ensure they always have
758
        # the correct parameters to reproduce the previous request.
759
        kwargs.setdefault("stream", self.stream)
760
        kwargs.setdefault("verify", self.verify)
761
        kwargs.setdefault("cert", self.cert)
762
        if "proxies" not in kwargs:
763
            kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)
764
765
        # It's possible that users might accidentally send a Request object.
766
        # Guard against that specific failure case.
767
        if isinstance(request, Request):
768
            raise ValueError("You can only send PreparedRequests.")
769
770
        assert _is_prepared(request)
771
772
        # Set up variables needed for resolve_redirects and dispatching of hooks
773
        allow_redirects = kwargs.pop("allow_redirects", True)
774
        stream = kwargs.get("stream")
775
        hooks = request.hooks
776
777
        # Get the appropriate adapter to use
778
        adapter = self.get_adapter(url=request.url)
779
780
        # Start time (approximately) of the request
781
        start = preferred_clock()
782
783
        # Send the request
784
        r = adapter.send(request, **kwargs)
785
786
        # Total elapsed time of the request (approximately)
787
        elapsed = preferred_clock() - start
788
        r.elapsed = timedelta(seconds=elapsed)
789
790
        # Response manipulation hooks
791
        r = dispatch_hook("response", hooks, r, **kwargs)
792
793
        # Persist cookies
794
        if r.history:
795
            # If the hooks create history then we want those cookies too
796
            for resp in r.history:
797
                extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
798
799
        extract_cookies_to_jar(self.cookies, request, r.raw)
800
801
        # Resolve redirects if allowed.
802
        if allow_redirects:
803
            # Redirect resolving generator.
804
            gen = self.resolve_redirects(r, request, **kwargs)
805
            history = [resp for resp in gen]
806
        else:
807
            history = []
808
809
        # Shuffle things around if there's history.
810
        if history:
811
            # Insert the first (original) request at the start
812
            history.insert(0, r)
813
            # Get the last request made
814
            r = history.pop()
815
            r.history = history
816
817
        # If redirects aren't being followed, store the response on the Request for Response.next().
818
        if not allow_redirects:
819
            try:
820
                r._next = next(  # type: ignore[assignment]  # yield_requests=True returns PreparedRequest
821
                    self.resolve_redirects(r, request, yield_requests=True, **kwargs)
822
                )
823
            except StopIteration:
824
                pass
825
826
        if not stream:
827
            r.content
828
829
        return r
830
831
    def merge_environment_settings(
832
        self,
833
        url: str,
834
        proxies: dict[str, str] | None,
835
        stream: bool | None,
836
        verify: _t.VerifyType | None,
837
        cert: _t.CertType,
838
    ) -> dict[str, Any]:
839
        """
840
        Check the environment and merge it with some settings.
841
842
        :rtype: dict
843
        """
844
        # Gather clues from the surrounding environment.
845
        if self.trust_env:
846
            # Set environment's proxies.
847
            no_proxy = proxies.get("no_proxy") if proxies is not None else None
848
            env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
849
            if proxies is not None:
850
                for k, v in env_proxies.items():
851
                    proxies.setdefault(k, v)
852
853
            # Look for requests environment configuration
854
            # and be compatible with cURL.
855
            if verify is True or verify is None:
856
                verify = (
857
                    os.environ.get("REQUESTS_CA_BUNDLE")
858
                    or os.environ.get("CURL_CA_BUNDLE")
859
                    or verify
860
                )
861
862
        # Merge all the kwargs.
863
        proxies = merge_setting(proxies, self.proxies)
864
        stream = merge_setting(stream, self.stream)
865
        verify = merge_setting(verify, self.verify)
866
        cert = merge_setting(cert, self.cert)
867
868
        return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
869
870
    def get_adapter(self, url: str) -> BaseAdapter:
871
        """
872
        Returns the appropriate connection adapter for the given URL.
873
874
        :rtype: requests.adapters.BaseAdapter
875
        """
876
        for prefix, adapter in self.adapters.items():
877
            if url.lower().startswith(prefix.lower()):
878
                return adapter
879
880
        # Nothing matches :-/
881
        raise InvalidSchema(f"No connection adapters were found for {url!r}")
882
883
    def close(self) -> None:
884
        """Closes all adapters and as such the session"""
885
        for v in self.adapters.values():
886
            v.close()
887
888
    def mount(self, prefix: str, adapter: BaseAdapter) -> None:
889
        """Registers a connection adapter to a prefix.
890
891
        Adapters are sorted in descending order by prefix length.
892
        """
893
        self.adapters[prefix] = adapter
894
        keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
895
896
        for key in keys_to_move:
897
            self.adapters[key] = self.adapters.pop(key)
898
899
    def __getstate__(self) -> dict[str, Any]:
900
        state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
901
        return state
902
903
    def __setstate__(self, state: dict[str, Any]) -> None:
904
        for attr, value in state.items():
905
            setattr(self, attr, value)
906
907
908
def session() -> Session:
909
    """
910
    Returns a :class:`Session` for context-management.
911
912
    .. deprecated:: 1.0.0
913
914
        This method has been deprecated since version 1.0.0 and is only kept for
915
        backwards compatibility. New code should use :class:`~requests.sessions.Session`
916
        to create a session. This may be removed at a future date.
917
918
    :rtype: Session
919
    """
920
    return Session()
921