codekingpro/portable-devtools
114k
1"""2requests.sessions3~~~~~~~~~~~~~~~~~4 5This module provides a Session object to manage and persist settings across6requests (cookies, auth, proxies).7"""8 9from __future__ import annotations10 11import os12import sys13import time14from collections import OrderedDict15from collections.abc import Generator, Mapping, MutableMapping16from datetime import timedelta17from typing import TYPE_CHECKING, Any, cast18 19from ._internal_utils import to_native_string20from ._types import is_prepared as _is_prepared21from .adapters import HTTPAdapter22from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]23from .compat import cookielib, urljoin, urlparse24from .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_hook37 38# formerly defined here, reexposed here for backward compatibility39from .models import ( # noqa: F40140 DEFAULT_REDIRECT_LIMIT,41 REDIRECT_STATI, # type: ignore[reportUnusedImport]42 PreparedRequest,43 Request,44 Response,45)46from .status_codes import codes47from .structures import CaseInsensitiveDict48from .utils import ( # noqa: F40149 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 consumers58 to_key_val_list,59)60 61if TYPE_CHECKING:62 from http.cookiejar import CookieJar63 64 from typing_extensions import Self, Unpack65 66 from . import _types as _t67 from .adapters import BaseAdapter68 69# Preferred clock, based on which one is more accurate on a given system.70if sys.platform == "win32":71 preferred_clock = time.perf_counter72else:73 preferred_clock = time.time74 75 76def merge_setting(77 request_setting: Any, session_setting: Any, dict_class: type = OrderedDict78) -> Any:79 """Determines appropriate setting for a given request, taking into account80 the explicit setting on that request, and the setting in the session. If a81 setting is a dictionary, they will be merged together using `dict_class`82 """83 84 if session_setting is None:85 return request_setting86 87 if request_setting is None:88 return session_setting89 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_setting95 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 altering100 # 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_setting106 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': []}, the116 merge breaks Session hooks entirely.117 """118 if session_hooks is None or session_hooks.get("response") == []:119 return request_hooks120 121 if request_hooks is None or request_hooks.get("response") == []:122 return session_hooks123 124 return merge_setting(request_hooks, session_hooks, dict_class)125 126 127class SessionRedirectMixin:128 max_redirects: int129 trust_env: bool130 cookies: RequestsCookieJar131 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 will137 # be called at least once upon the original response and at least twice138 # on each subsequent redirect response (if any).139 # If a custom mixin is used to handle this logic, it may be advantageous140 # to cache the redirect location onto the response object as a private141 # attribute.142 if resp.is_redirect:143 location = resp.headers["location"]144 # Currently the underlying http module on py3 decode headers145 # in latin1, but empirical evidence suggests that latin1 is very146 # 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 None153 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 True160 # Special case: allow http -> https redirect when using the standard161 # ports. This isn't specified by RFC 7235, but is kept to avoid162 # breaking backwards compatibility with older versions of requests163 # 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 False171 172 # Handle default port usage corresponding to scheme.173 changed_port = old_parsed.port != new_parsed.port174 changed_scheme = old_parsed.scheme != new_parsed.scheme175 default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)176 if (177 not changed_scheme178 and old_parsed.port in default_port179 and new_parsed.port in default_port180 ):181 return False182 183 # Standard case: root URI must match184 return changed_port or changed_scheme185 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 history201 202 url = self.get_redirect_target(resp)203 previous_fragment = urlparse(req.url).fragment204 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 released213 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=resp219 )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.fragment235 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/1084250 if resp.status_code not in (251 codes.temporary_redirect,252 codes.permanent_redirect,253 ):254 # https://github.com/psf/requests/issues/3490255 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 = None259 260 headers = prepared_request.headers261 headers.pop("Cookie", None)262 263 # Extract any cookies sent on the response to the cookiejar264 # in the new request. Because we've mutated our copied prepared265 # 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-None276 # value ensures `rewindable` will be True, allowing us to raise an277 # 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 headers280 )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_request288 289 if yield_requests:290 yield req # type: ignore[misc] # Internal use only, returns PreparedRequest291 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 loop306 url = self.get_redirect_target(resp)307 yield resp308 309 def rebuild_auth(310 self, prepared_request: PreparedRequest, response: Response311 ) -> None:312 """When being redirected we may want to strip authentication from the313 request to avoid leaking credentials. This method intelligently removes314 and reapplies authentication where possible to avoid credential loss.315 """316 original_request = response.request317 assert _is_prepared(original_request)318 assert _is_prepared(prepared_request)319 320 headers = prepared_request.headers321 original_url = original_request.url322 url = prepared_request.url323 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 any326 # 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 None331 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 the340 environment variables. If we are redirected to a URL covered by341 NO_PROXY, we strip the proxy configuration. Otherwise, we set missing342 proxy keys for this URL (in case they were stripped by a previous343 redirect).344 345 This method also replaces the Proxy-Authorization header where346 necessary.347 348 :rtype: dict349 """350 assert _is_prepared(prepared_request)351 headers = prepared_request.headers352 scheme = urlparse(prepared_request.url).scheme353 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, None362 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_proxies369 370 def rebuild_method(371 self, prepared_request: PreparedRequest, response: Response372 ) -> None:373 """When being redirected we may want to change the method of the request374 based on certain specs or browser behavior.375 """376 method = prepared_request.method377 378 # https://tools.ietf.org/html/rfc7231#section-6.4.4379 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 = method393 394 395class Session(SessionRedirectMixin):396 """A Requests session.397 398 Provides cookie persistence, connection-pooling, and configuration.399 400 Basic Usage::401 402 >>> import requests403 >>> 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.AuthType416 proxies: dict[str, str]417 hooks: dict[str, list[_t.HookType]]418 params: MutableMapping[str, Any]419 stream: bool420 verify: _t.VerifyType421 cert: _t.CertType422 max_redirects: int423 trust_env: bool424 cookies: RequestsCookieJar425 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 each444 #: :class:`Request <Request>` sent from this445 #: :class:`Session <Session>`.446 self.headers = default_headers()447 448 #: Default Authentication tuple or object to attach to449 #: :class:`Request <Request>`.450 self.auth = None451 452 #: Dictionary mapping protocol or protocol and host to the URL of the proxy453 #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to454 #: 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 each461 #: :class:`Request <Request>`. The dictionary values may be lists for462 #: representing multivalued query parameters.463 self.params = {}464 465 #: Stream response content default.466 self.stream = False467 468 #: SSL Verification default.469 #: Defaults to `True`, requiring requests to verify the TLS certificate at the470 #: remote end.471 #: If verify is set to `False`, requests will accept any TLS certificate472 #: presented by the server, and will ignore hostname mismatches and/or473 #: expired certificates, which will make your application vulnerable to474 #: 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 file477 #: that will be used to verify the TLS certificate.478 self.verify = True479 480 #: SSL client certificate default, if String, path to ssl client481 #: cert file (.pem). If Tuple, ('cert', 'key') pair.482 self.cert = None483 484 #: Maximum number of redirects allowed. If the request exceeds this485 #: limit, a :class:`TooManyRedirects` exception is raised.486 #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is487 #: 30.488 self.max_redirects = DEFAULT_REDIRECT_LIMIT489 490 #: Trust environment settings for proxy configuration, default491 #: authentication and similar.492 self.trust_env = True493 494 #: A CookieJar containing all currently outstanding cookies set on this495 #: session. By default it is a496 #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but497 #: 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 self507 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>` for513 transmission and returns it. The :class:`PreparedRequest` has settings514 merged from the :class:`Request <Request>` instance and those of the515 :class:`Session`.516 517 :param request: :class:`Request` instance to prepare with this518 session's settings.519 :rtype: requests.PreparedRequest520 """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 cookies531 merged_cookies = merge_cookies(532 merge_cookies(RequestsCookieJar(), self.cookies), cookies533 )534 535 # Set environment's basic authentication if not explicitly set.536 auth = request.auth537 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=CaseInsensitiveDict549 ),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 p556 557 def request(558 self,559 method: str,560 url: _t.UriType,561 params: _t.ParamsType = None,562 data: _t.DataType = None,563 headers: Mapping[str, str | bytes] | None = 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 query582 string for the :class:`Request`.583 :param data: (optional) Dictionary, list of tuples, bytes, or file-like584 object to send in the body of the :class:`Request`.585 :param json: (optional) json to send in the body of the586 :class:`Request`.587 :param headers: (optional) Dictionary of HTTP Headers to send with the588 :class:`Request`.589 :param cookies: (optional) Dict or CookieJar object to send with the590 :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 enable594 Basic/Digest/Custom HTTP Auth.595 :param timeout: (optional) How many seconds to wait for the server to send596 data before giving up, as a float, or a :ref:`(connect timeout,597 read timeout) <timeouts>` tuple.598 :type timeout: float or tuple599 :param allow_redirects: (optional) Set to True by default.600 :type allow_redirects: bool601 :param proxies: (optional) Dictionary mapping protocol or protocol and602 hostname to the URL of the proxy.603 :param hooks: (optional) Dictionary mapping hook name to one event or604 list of events, event must be callable.605 :param stream: (optional) whether to immediately download the response606 content. Defaults to ``False``.607 :param verify: (optional) Either a boolean, in which case it controls whether we verify608 the server's TLS certificate, or a string, in which case it must be a path609 to a CA bundle to use. Defaults to ``True``. When set to610 ``False``, requests will accept any TLS certificate presented by611 the server, and will ignore hostname mismatches and/or expired612 certificates, which will make your application vulnerable to613 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.Response618 """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, cert643 )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 resp654 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 send665 in the query string for the :class:`Request`.666 :param \*\*kwargs: Optional arguments that ``request`` takes.667 :rtype: requests.Response668 """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.Response679 """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.Response690 """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-like706 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.Response710 """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-like721 object to send in the body of the :class:`Request`.722 :param \*\*kwargs: Optional arguments that ``request`` takes.723 :rtype: requests.Response724 """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-like735 object to send in the body of the :class:`Request`.736 :param \*\*kwargs: Optional arguments that ``request`` takes.737 :rtype: requests.Response738 """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.Response748 """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.Response756 """757 # Set defaults that the hooks can utilize to ensure they always have758 # 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 hooks773 allow_redirects = kwargs.pop("allow_redirects", True)774 stream = kwargs.get("stream")775 hooks = request.hooks776 777 # Get the appropriate adapter to use778 adapter = self.get_adapter(url=request.url)779 780 # Start time (approximately) of the request781 start = preferred_clock()782 783 # Send the request784 r = adapter.send(request, **kwargs)785 786 # Total elapsed time of the request (approximately)787 elapsed = preferred_clock() - start788 r.elapsed = timedelta(seconds=elapsed)789 790 # Response manipulation hooks791 r = dispatch_hook("response", hooks, r, **kwargs)792 793 # Persist cookies794 if r.history:795 # If the hooks create history then we want those cookies too796 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 start812 history.insert(0, r)813 # Get the last request made814 r = history.pop()815 r.history = history816 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 PreparedRequest821 self.resolve_redirects(r, request, yield_requests=True, **kwargs)822 )823 except StopIteration:824 pass825 826 if not stream:827 r.content828 829 return r830 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: dict843 """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 None848 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 configuration854 # 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 verify860 )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.BaseAdapter875 """876 for prefix, adapter in self.adapters.items():877 if url.lower().startswith(prefix.lower()):878 return adapter879 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] = adapter894 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 state902 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.0913 914 This method has been deprecated since version 1.0.0 and is only kept for915 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: Session919 """920 return Session()921 