1"""
2requests.sessions
3~~~~~~~~~~~~~~~~~
4
5This module provides a Session object to manage and persist settings across
6requests (cookies, auth, proxies).
7"""
8
9from __future__ import annotations
10
11import os
12import sys
13import time
14from collections import OrderedDict
15from collections.abc import Generator, Mapping, MutableMapping
16from datetime import timedelta
17from typing import TYPE_CHECKING, Any, cast
18
19from ._internal_utils import to_native_string
20from ._types import is_prepared as _is_prepared
21from .adapters import HTTPAdapter
22from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]
23from .compat import cookielib, urljoin, urlparse
24from .cookies import (
25 RequestsCookieJar,
26 cookiejar_from_dict,
27 extract_cookies_to_jar,
28 merge_cookies,
29)
30from .exceptions import (
31 ChunkedEncodingError,
32 ContentDecodingError,
33 InvalidSchema,
34 TooManyRedirects,
35)
36from .hooks import default_hooks, dispatch_hook
37
38# formerly defined here, reexposed here for backward compatibility
39from .models import ( # noqa: F401
40 DEFAULT_REDIRECT_LIMIT,
41 REDIRECT_STATI, # type: ignore[reportUnusedImport]
42 PreparedRequest,
43 Request,
44 Response,
45)
46from .status_codes import codes
47from .structures import CaseInsensitiveDict
48from .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
61if 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.
70if sys.platform == "win32":
71 preferred_clock = time.perf_counter
72else:
73 preferred_clock = time.time
74
75
76def 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
108def 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
127class 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
395class 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
908def 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()